Detecting VBA stomping and DDE in weaponized documents
What VBA stomping is, and why reconciling p-code against source detects it
VBA stomping is a document technique in which the macro source stored in a file no longer matches the compiled form the application executes, so a reviewer who reads only the source reads a program that will never run. The detection is not a keyword or a signature; it is the reconciliation of the two representations against each other, and the disagreement is the finding. Malwagon compares them on the analysis host before the file is opened anywhere, and declines to repeat what the readable macro claims.
| Key fact | Detail |
|---|---|
| The technique | Readable source and compiled macro form sit side by side, and nothing keeps them in agreement |
| The detection | Compare the two; a disagreement is evidence about the file, not about the readable code |
| Why strings fail | The decoy source is the author's free variable and can be arbitrarily dull |
| Other paths | Field commands, object containers and relationship targets need no macro |
Why a weaponized document is a program and should be reviewed as one
A weaponized document's code belongs to the application, not to the file. The macro, the embedded object, the field command and the attached template all fire because the office application, the reader or the help viewer does what the format asks of it. The features are the attack.
Review it as a program: what runs when the file opens, which execution primitives the code reaches for, which endpoints it names. The interpreter is a large third-party application whose version, patch level and locale are part of that runtime. The framing drives document analysis here.
How stored macro source and the compiled stream come to disagree
A macro project stores two representations of the same code: the compressed source an editor would show you, and the compiled form the engine runs. Both are written at authoring time, the project directory records where one ends and the other begins, and nothing afterwards enforces that they agree. Replace the source with something ordinary, leave the compiled form alone, and the file reads clean while behaving as before.
Detection follows from the mechanism. Do not write a rule for what stomped macro text looks like; the decoy can be anything, including a genuine macro lifted from a real business document. Write the comparison, and weigh it as its own finding.
One implementation detail cost real time here. The comparison in our tooling only runs when the parser is handed a real path on disk; an in-memory parse skips it silently, and that is indistinguishable from agreement. A check that can quietly not run needs a third state, or a pipeline hole reads as clean.
| Reading | What it means | What a report should do |
|---|---|---|
| The two agree | The readable macro fairly describes the program | Weigh the source on its own indicators |
| The two disagree | The source is a decoy; the compiled form is the program | Raise a finding, stop quoting the source, hand the question to a run |
| The comparison never ran | Absent evidence, from an in-memory parse or a killed one | State the absence; never render it as a clean result |
Why field abuse and object containers move execution out of the macro layer
Turning macros off does not close the file. A dynamic data exchange style field asks the host application to hand a command line to another program, with no macro involved. Its two variants are not the same finding: one fires when the document opens, the other waits for someone to update the link. The automatic form is the single document technique we let stand on its own weight, because a file that runs a command line on open has no benign reading.
Anchor that detection on field structure, not on letters. A bare substring search fires on ordinary words ending in the same characters, and narrowing it by also demanding a shell name in the file makes both the false positive and the miss depend on unrelated text.
Object containers are the other route out. Resolve each class identifier against a named table, recover the file name a generic package object carries, and test it against the runnable types. A document embedding a portable document is a person attaching a file; one embedding a shortcut or an installer is a delivery mechanism.
Relationship targets deserve their own pass. A template fetched over http is remote code arriving after the file was scanned. A UNC target makes the application authenticate outward to a machine the author chose: credential exposure costing the attacker no macro at all.
What a static reviewer should extract from a container before anything is opened
Type the file from its bytes, never from its name. An encrypted office document is not a zip but a compound file holding an encrypted package, which a name-driven router sends to the wrong engine and calls unremarkable.
| Container family | Pull this out first | Why it matters |
|---|---|---|
| OOXML package | Part inventory, macro project, relationship targets, object storages | Every execution route is a named part or relationship |
| Legacy compound file | Object pool storages with class ids, macro project, encryption state | An encrypted document has no readable macros and looks clean |
| Rich text | Object classes, payload headers, field structures | No macro project exists, so objects and fields are the program |
| Portable document | Keyword census over raw bytes and inflated streams, opening actions, launch targets | Active content hides in compressed object streams |
| Notebook file | Attachment records carved by marker, with type and size | The attachment is the payload, the notebook a wrapper |
| Compiled help | Shortcut control markers and their command parameters | A weaponized help file runs a command on open |
Bound every one of those passes. A document parser is a decompressor, and decompressors amplify: measured here, a 1,610 byte macro-bearing file expanded into enough macro text to hold a worker for 174 seconds at 900 MB resident. Each parse runs in a forked child under a wall clock and a memory limit, and a killed parse is recorded as a finding, because a resource bomb has that shape and a blank report does not.
What static review and detonation each answer about the same file
Static review answers what a file is able to do, completely over the bytes, and it still answers when the run reaches nothing. Detonation answers what the file did on one image, one office build, one locale, on one attempt. Neither subsumes the other.
A stomped macro is where that difference bites. We report the disagreement and leave the compiled stream compiled; nothing here turns it back into readable source. That finding is a routing decision as much as a verdict. Payloads dropped during the run are carved from the guest's disk and analysed by file analysis, and a macro that assembles a command line is easier to read as a command, which is what command analysis exists for.
Absence of network evidence is not evidence about the sample: free runs have no egress by design, so a template fetch produces nothing to see. That boundary is set out on the security page.
Frequently asked questions
What is VBA stomping in a weaponized document?
VBA stomping leaves a document's readable macro source in place while the compiled form the application executes carries different code, so reading the source describes behaviour the document will not perform. Detection comes from comparing the two stored representations of the same module and treating a disagreement as a finding about the file itself.
Can a document execute code without any macro at all?
Yes. A dynamic data exchange style field hands a command line to another application, and its automatic variant fires when the file is opened. Object containers can drop and launch a packaged file, or instantiate a component with a memory corruption history. An external relationship can fetch a template after the file was scanned.
Does stripping macros make a document safe to open?
Stripping macro parts and rebuilding the container removes one execution route and produces a disarmed copy plus a list of what was removed. Field commands, object containers, external relationship targets and legacy macro sheets survive that rebuild untouched. Treat a disarmed copy as reduced risk, and review the remaining routes separately.
Why does document parsing need a resource budget?
Macro storage is compressed, and a decompressor can be driven to expand a tiny file into hundreds of megabytes. A document small enough to attach to an email held one analysis worker for nearly three minutes in testing here. Bounding each parse in a child process caps that, and a killed parse is itself a finding.