Description
What could go wrong if we let Word documents run programs? Download weird.docm and find out.
Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Identify the file typeObservationI noticed the file extension was .docm rather than .docx, which suggested it was a macro-enabled Word document and warranted running the file command to confirm the container format before choosing the right extraction tool.Run the file command on weird.docm to confirm it is an OLE2 Compound Document - a macro-enabled Word document. The 'm' suffix in .docm signals that the file can contain VBA or script macros that execute when the document is opened. This is the entry point for the challenge.bashfile weird.docmWhat didn't work first
Tried: Rename the file to .zip and unzip it expecting to find VBA source files directly.
Like .docx, a .docm file is a ZIP-based OOXML container - unzipping it reveals the standard directory structure (word/, _rels/, [Content_Types].xml). However, the macro source is not stored as readable XML: it lives inside word/vbaProject.bin, which is itself an OLE2 binary stream. Manually digging through that binary is impractical, so olevba from the oletools suite is the right tool: it opens the ZIP, locates vbaProject.bin, decompresses the VBA bytecode, and prints readable source.
Tried: Run strings weird.docm and grep for picoCTF to find the flag without any other tool.
The flag is base64-encoded inside the macro, so strings output will not contain the literal prefix 'picoCTF'. You may see fragments of VBA bytecode but not readable source, because the OLE2 stream stores VBA in a compressed binary form. olevba decompresses and decompiles the VBA source to make it human-readable.
Learn more
.docm is Microsoft's macro-enabled Word format. Like .docx, it is a ZIP-based OOXML container (ECMA-376). Unzipping it reveals the standard directory structure, but the macros are not stored as readable XML. Instead, they live inside
word/vbaProject.bin, which is an OLE2 (Object Linking and Embedding) binary stream embedded within the ZIP. That stream holds compiled VBA bytecode and source.Macros in Office documents can execute arbitrary code - read files, spawn processes, download payloads. This is why modern Office installations block macros from internet-downloaded files by default and why .docm files are treated as high-risk by email gateways.
Step 2
Extract the macro code with olevbaObservationI noticed the file command confirmed an OLE2 binary format, which meant standard unzip would fail and I needed an OLE2-aware tool; olevba from oletools is the standard choice for decompressing and printing VBA macro source from that container format.Use olevba from the python-oletools suite to dump all macro source code embedded in the document. olevba parses the OLE2 container, decompresses the VBA source stored inside vbaProject.bin, and prints each macro module to stdout. Look through the output for anything that looks encoded - a long base64 string is the clue.bashpip install oletools # install if not already presentbasholevba weird.docmIn the output you will see a
Sub runpython()that calls a Python one-liner. The Python command prints the stringcGljb0NURnttNGNyMHNfcl9kNG5nM3IwdXN9. That is a base64-encoded flag.What didn't work first
Tried: Use oleid instead of olevba to read the macro source.
oleid is also part of the oletools suite but it only reports metadata and risk indicators (e.g. 'VBA Macros: Yes'), not the actual macro source code. It will confirm that macros are present but will not print the Sub runpython() code or the encoded string inside it. olevba is the correct tool for dumping readable VBA source.
Tried: Run oledump.py on the file and try to extract the macro stream by index number without knowing which stream holds the VBA.
oledump.py lists all OLE streams by index and marks the VBA-containing stream with an 'M' flag. If you extract a stream by a wrong index you get binary content or document text, not VBA source. olevba automates the correct stream selection and decompression, making it faster for straightforward macro extraction.
Learn more
olevba is part of the oletools suite by Philippe Lagadec. It decompresses VBA source from the
vbaProject.binOLE stream and prints each Sub, Function, and property, along with a risk summary flagging suspicious keywords likeShell,CreateObject, orWScript.Run.The macro in this challenge uses a
Shellcall to invoke Python and print an encoded string - a pattern seen in real macro malware that uses scripting engines to execute payloads without triggering keyword-based AV signatures tuned to VBA itself.Alternative - LibreOffice macro editor: Open the file in LibreOffice Writer, then go to Tools > Macros > Edit Macros. Navigate to weird.docm > Project > Document Objects > ThisDocument to read the same macro source interactively.
Step 3
Decode the base64 string to reveal the flagObservationI noticed the olevba output contained the string cGljb0NURnttNGNyMHNfcl9kNG5nM3IwdXN9, a long run of alphanumeric characters with no picoCTF prefix visible, which are the hallmarks of base64 encoding and suggested piping it through base64 -d to recover the plaintext flag.Pipe the base64 string into base64 -d (or base64 --decode) to recover the plaintext flag. The decoded output is the flag in the standard picoCTF format.bashecho "cGljb0NURnttNGNyMHNfcl9kNG5nM3IwdXN9" | base64 -dExpected output
picoCTF{m4cr0s_r_d4ng3r0us}What didn't work first
Tried: Use base64 -D (capital D) instead of base64 -d to decode the string.
On Linux, the correct flag is lowercase -d (--decode). Capital -D is not a valid option for GNU coreutils base64 and will print an error. On macOS the BSD base64 binary does accept -D, so scripts that work on a Mac may silently fail when run on a Linux CTF box. Always use lowercase -d for cross-platform compatibility.
Tried: Pipe the base64 string through CyberChef 'From Base64' using the URL-safe alphabet.
The encoded string cGljb0NURnttNGNyMHNfcl9kNG5nM3IwdXN9 uses standard base64 (A-Z, a-z, 0-9, +, /), not the URL-safe variant that replaces + with - and / with _. CyberChef defaults to standard alphabet, so it decodes correctly, but if you manually select URL-safe mode you will get corrupted output for any character that maps to + or /.
Learn more
Base64 encodes binary data (or ASCII text) using 64 printable characters (A-Z, a-z, 0-9, +, /). Each 3 bytes of input become 4 base64 characters, so the output is about 33% longer than the input.
base64 -dreverses this exactly.Hiding a flag or payload as base64 inside a macro is a simple obfuscation layer. It defeats naive string searches for
picoCTFin the raw file bytes, since the encoded form looks like random alphanumeric noise. Analysts always check for base64 when strings look like long runs of A-Z/a-z/0-9 characters.
Interactive tools
- File Magic IdentifierIdentify file types from magic numbers. Paste hex bytes or drop a file to detect PNG, JPEG, ZIP, PDF, ELF, PCAP, SQLite, and dozens of other formats.
Flag
Reveal flag
picoCTF{m4cr0s_r_d4ng3r0us}
weird.docm is an OLE2 macro-enabled Word document. olevba extracts a Sub runpython() macro that prints a base64 string; decoding it with base64 -d reveals the flag.