Overview
This project analyses a Formbook campaign from July 2026 that was delivered through malicious Excel attachments — the kind that arrives daily in any invoice@ mailbox. Six samples were obtained from MalwareBazaar and examined in an isolated FLARE-VM environment with no internet access.
The work covers both halves of the problem: static analysis of the VBA dropper and a YARA rule built from it, then controlled detonation with Sysmon telemetry and a Sigma rule for the observed behaviour. The final step was testing my own detection claims against the full public rule corpus — which produced a result I did not expect.
Tools Used
oletools (olevba) oledump.py YARA 4.5.5 Sysmon 15.21 (SwiftOnSecurity config) Hayabusa 4.0.0 MalwareBazaar API PowerShell VirtualBox / FLARE-VM (Windows 10)
Sample Info
| Property | Value |
|---|---|
| File name | NEW ORDER SPECS JU142026.xlsm |
| File type | OOXML, Excel workbook with macro |
| MD5 | ddc8b61db5c6f4d7ab83170cc74f4d03 |
| SHA256 | 0bb875dcb216560f3a1d0b8034d290c10e7c15a25a7d5a805152cd3784b9e7b5 |
| Size | 176,403 bytes (172 KB) |
| Family | Formbook |
| First seen | 2026-07-14 |
| Source | MalwareBazaar (abuse.ch) |
Five further samples from the same campaign were analysed for validation.
The Starting Question
Two samples from the same campaign, uploaded minutes apart, differ in size by a factor of 12.6 — 176 KB against 2.2 MB. My first assumption was file bloating: padding a document with junk to push it past the size limit at which some AV engines stop scanning.
Comparing the OOXML containers showed something else entirely.

| Container part | Sample A | Sample B |
|---|---|---|
| xl/worksheets/sheet2.xml | 122,565 | 1,283,948 |
| xl/sharedStrings.xml | 302,991 | 3,090,337 |
| xl/vbaProject.bin | 20,992 | 20,992 |
Only the worksheet data grew. The VBA module is identical in size — and, as the code comparison confirmed, identical in content. The size difference is not padding; it is the payload itself, stored inside spreadsheet cells.
Static Analysis
The macro runs on Workbook_Open and contains no network calls and no readable indicators. It reads a base64 string from column A of a hidden worksheet, decodes it via MSXML2.DOMDocument, decrypts it with a repeating-key XOR, writes the result to %appdata% through ADODB.Stream, and launches it with wscript.exe.
Three obfuscation techniques stand out:
Strings assembled at runtime. MSXML2.DOMDocument and bin.base64 never appear as complete strings in the file — they are concatenated from fragments held in an array.
Arithmetic instead of constants. 8 * 8 for 64, 8 - 8 for the hidden-window flag. Signature-based rules keyed on those literals miss.
Execution hidden in an error handler. The launch call sits under ErrorHandler:, reached by normal fall-through rather than by an actual error.
Everything campaign-specific — the dropped filename, the XOR key, the payload itself — lives in worksheet cells, not in the code. The macro is a generic decoder.
The first worksheet contains nothing but an instruction designed to make the user click the button Office puts above it:

The warning bar is yellow rather than red because the file carries no Mark-of-the-Web — it was extracted from an archive, and archivers routinely drop that marker. The same happens with zipped email attachments in the wild.
Payload Extraction
Reassembling column A, base64-decoding it and applying the XOR key from cell C1 reproduces the dropped file exactly:
| Sample A | Sample B | |
|---|---|---|
| Dropped filename | CYwLYqxCJm.js | NvEBKfHKwr.js |
| XOR key | FclkuFaGkb | YACysExJ_h |
| Payload size | 210,204 B | 2,145,862 B |
| SHA256 | E8ADBCFB…19D318 | F67F5002…9BA2A6 |
Both payloads are JavaScript obfuscated with obfuscator.io — and they are different files. Any detection anchored on the payload covers exactly one sample.
Detection: YARA
An .xlsm is a ZIP archive, so the VBA is deflate-compressed inside it: none of the macro strings exist as plaintext in the document. They are readable in the extracted xl/vbaProject.bin, because VBA stores the project identifier table separately from the compressed code body. The rule therefore targets the extracted module, and says so in its metadata.
What the rule keys on:
- Custom function names chosen by the author (
HeliPhant,Gorima,getElement,StringToBytes) - The combination of COM objects used for the drop (
ADODB.Stream,wscript.shell) - An OLE file signature as a structural anchor
What it deliberately ignores: file hashes, the hash of the VBA module, the project GUID, file sizes, dropped filenames, XOR keys. All of these change between samples — the project GUID is regenerated on every rebuild, which makes hashing the macro module as useless as hashing the document.
Validation
| Test set | Objects | Matches |
|---|---|---|
| July 2026 campaign | 6 | 6 |
| Formbook XLSM 2022–2024 | 10 | 0 |
| Legitimate VBA (Office add-ins, Didier Stevens tools) | 19 | 0 |
The zero on older samples is the rule’s boundary, documented rather than hidden: it detects one specific builder, not the Formbook family. The false-positive set was deliberately chosen to include legitimate macros that query WMI, the registry and running processes — the kind that a loose rule would flag.
Dynamic Analysis
Static analysis implied an execution chain; detonation confirmed it. The sample was run in an isolated VM (VirtualBox Internal Network, no gateway, clipboard and shared folders disabled, snapshot taken beforehand). Only the Sysmon .evtx was extracted afterwards, through a host-initiated transfer that mounts nothing inside the guest.

EXCEL.EXE → SysWOW64\wscript.exe "%appdata%\CYwLYqxCJm.js"
The dropped filename and size matched what static analysis had predicted from cell A1. Two details the code alone did not reveal: the child process runs from SysWOW64 rather than System32 (Office is 32-bit), and it runs at Medium integrity — no privilege escalation, nothing anomalous about the permissions.
100 milliseconds after launch, a DNS query to xevzora.cfd from the same PID. Resolution failed — the lab has no network — so no second stage was retrieved and no C2 infrastructure was confirmed beyond that domain.
Two observations worth recording:
No FileCreate event for the dropped script. The SwiftOnSecurity Sysmon config filters file creation selectively, and a .js in AppData\Roaming falls outside its rules. A detection built on file creation would not have worked with this configuration.
Excel added the document to Trusted Documents the moment macros were enabled. Reopening it will not prompt again. In an organisation where users routinely click “Enable Content” on invoices, an accumulation of these registry entries is itself a measurable signal.
Detection: Sigma
A Sigma rule for the observed chain: an Office application spawning a Windows Script Host process that executes a script from a user-writable directory.
Tested against the captured telemetry: 1 hit across 2,059 events of normal VM activity — including a legitimate EXCEL.EXE → splwow64.exe print-driver spawn two minutes earlier.
Testing My Own Claim
I had argued that common detection rules miss this chain. Rather than leave that as an assumption, I ran the same EVTX through Hayabusa’s full corpus — 4,659 rules, 2,294 applicable after channel filtering.

Two high-severity alerts came back. Mine, and Suspicious Microsoft Office Child Process — an official SigmaHQ rule maintained since 2018 that covers exactly this scenario.
So the claim was too broad, and the write-up says so. What holds up is narrower: rules hardcoded to WINWORD.EXE as the parent miss this, because the parent is Excel; rules hardcoded to powershell.exe as the child miss it, because the child is WSH. Those patterns are common in the field, but they are not a shortcoming of the public corpus.
The two rules also differ in how they check paths. SigmaHQ checks the path of the executable, which here sits in SysWOW64 and never triggered their filter — the alert fired on the process name alone. Mine checks the path of the script passed to it, which must sit in a user-writable directory. Narrower coverage, cleaner signal: evading it would require dropping the payload somewhere that needs admin rights.
The realistic takeaway. The gap is not a lack of rules in the industry. It is the distance between a rule existing in a public repository and an alert firing in a real SOC — which needs Sysmon deployed, configured to log process creation, and the corpus actually loaded and tuned. If any link is missing, this chain runs unobserved.
Less dramatic than “nobody detects this”, but it is what the data shows.
MITRE ATT&CK
| Technique | ID |
|---|---|
| Phishing: Spearphishing Attachment | T1566.001 |
| User Execution: Malicious File | T1204.002 |
| Command and Scripting Interpreter: Visual Basic | T1059.005 |
| Command and Scripting Interpreter: JavaScript | T1059.007 |
| Obfuscated Files or Information | T1027 |
| Deobfuscate/Decode Files or Information | T1140 |
| Application Layer Protocol: DNS | T1071.004 |
Indicators
Droppers (SHA256)
0bb875dcb216560f3a1d0b8034d290c10e7c15a25a7d5a805152cd3784b9e7b5
2cdedb9e87b19f3da82e49563e2f3ca9b1e9f4e195a89b2e60e24e380261e2ef
9b53181baa1988e0bb078c899a1abb149bc796347badabd9a7b687f4d1905aa1
55ca939df21d1486628a2801ea28d0b8e1eb63e624218dbb42348e36cbadfbfd
42255ff1914882e20ddb9116b521287dccd2bee944e056bc8f1bd1c2970299a6
415a2251f27ed7f9856775f551415f6ded1034ab37f71ddb5f2829bb6ef091ec
Network
xevzora.cfd — DNS query from wscript.exe, 100 ms after launch. Not confirmed as live infrastructure (resolution failed in the isolated lab).
Host
%appdata%\<random>.js executed by wscript.exe with an Office process as parent. Filenames are randomised per sample — the pattern is the durable indicator, not the name.
Full Write-Up
Complete analysis, both detection rules, all validation output and the full IOC set:
No malware samples or extracted payloads are published — hashes only.