Ох уж этот кладмен (Oh, this stash guy)
Ох уж этот кладмен (Oh, this stash guy)
Platform: cyberareactf | Category: Forensics | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-07-15 | Status: Solved Techniques: arithmetic_decoding, docx_structure_analysis, label_length_key, ooxml_bookmark_steganography
Summary
Task: a DOCX access-control migration report hides a flag with no standard stego vector (no macros, embeddings, or hidden text). Solution: a customXml part reveals an arithmetic rule M = B + L + K; the hidden channel is OOXML bookmark names NB_<seq>_<M> around English labels, decoded as B = M - len(label) - key_digit(Request ID 23063).
Ох уж этот кладмен (Oh, this stash guy) — cyberareactf
Description
В ходе внутренней проверки был обнаружен DOCX-файл с отчётом по миграции реестра управления доступом. Один из бывших сотрудников оставил странную пометку: «Внутри этого документа спрятан клад:)». Формат флага: CYBERAREA{}.
English summary: An internal review found a DOCX report about migrating an access-control registry. A former employee left an odd note: "Inside this document a treasure is hidden :)". The title puns on клад (treasure/stash) and кладмен (drug-stash courier). The flag is hidden inside the OOXML package via a non-standard steganographic channel and must be recovered with a custom arithmetic decoding rule.
Analysis
1. Unwrap the containers
The file is delivered as report.docx.zip, which is a ZIP wrapping the real report.docx plus
a macOS resource-fork artifact:
report.docx.zip
├── report.docx (the real OOXML package)
└── __MACOSX/._report.docx (resource fork, ignore)
Unzipping yields outer/report.docx, a valid Microsoft Word 2007+ (OOXML) package with 21
standard parts. file confirms the type; there is no trailing/appended data, no ZIP
comment, and no archive comment.
2. Rule out standard DOCX stego vectors
Systematically eliminating the usual DOCX hiding places — none of them carry the payload:
- No
word/embeddings/, no media, no macros (vbaProject.binabsent). - No external relationships → no Remote Template Injection.
- No OLE embedded objects.
- No
<w:vanish/>, no tracked revisions, no hidden-text payload. (White text exists but is ordinary dashboard/table-header styling, not a channel.) - No font/color/size character channel.
- Nothing in metadata, footer, empty footnotes/endnotes, or styles.
3. customXml holds the instructions, not the ciphertext
word/customXml/item1.xml and item2.xml are identical hint parts. They tell the analyst to:
-
Inspect the English labels in the document.
-
Follow the entries in order.
-
Apply the "legacy rule":
M = B + L + Kwhere
B= the original ASCII byte (the value we want),L= the UTF-8 byte length of the associated English label,K= a key digit taken by repeating the Request ID digits23063.
4. The hidden channel = OOXML bookmark names
word/document.xml contains invisible bookmarks wrapped around 38 selected English control
labels (e.g. "Access governance", "Account lifecycle", ...). Each relevant bookmark is named:
NB_<sequence>_<M>
<sequence>= ordering index1..38,<M>= the encoded number from the ruleM = B + L + K.
Examples span NB_01_086 … NB_38_136. AUX_* bookmarks are decoys, and REF_* bookmarks
are unrelated navigation markers — both are ignored.
Solution
For each NB_* bookmark:
- Read its
<w:name>and parsesequenceandM. - Take the English label text from the paragraph the bookmark sits in;
L = len(label.encode())(UTF-8 byte length). - Derive the key digit by cycling the Request ID:
K = int("23063"[(sequence-1) % 5]). - Recover the byte:
B = M - L - K, which is a printable ASCII character.
Sorting by sequence and concatenating the recovered bytes yields the flag.
python read-only • copy allowed#!/usr/bin/env python3 import re, zipfile from xml.etree import ElementTree as ET DOCX = "outer/report.docx" W = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}" REQUEST_ID_DIGITS = "23063" root = ET.fromstring(zipfile.ZipFile(DOCX).read("word/document.xml")) entries = [] for p in root.iter(W + "p"): label = "".join(n.text or "" for n in p.iter(W + "t")) for m in p.iter(W + "bookmarkStart"): mm = re.fullmatch(r"NB_(\d+)_(\d+)", m.get(W + "name", "")) if mm: seq, M = map(int, mm.groups()) entries.append((seq, M, label)) entries.sort() flag = "" for seq, M, label in entries: L = len(label.encode()) # UTF-8 byte length of the English label K = int(REQUEST_ID_DIGITS[(seq - 1) % 5]) flag += chr(M - L - K) # B = M - L - K print(flag) # CYBERAREA{REDACTED}
The recovered plaintext is leetspeak Russian: "кладмен больше так не будет" ("the stash guy won't do this again"), tying back to the challenge's клад/кладмен wordplay.