Промпты на стол! (Prompts on the table!)
Промпты на стол! (Prompts on the table!)
Platform: bug-makers | Category: Steganography | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2025-10-19 | Status: Solved Techniques: docx_xml_run_analysis, font_family_steganography, single_char_run_extraction
Summary
Task: DOCX containing an LLM prompt/response hides a flag via font-based steganography. Solution: word/document.xml splits text into 264 runs; payload characters are single-char runs set in Liberation Serif (vs Times New Roman cover); concatenating them in document order yields the flag.
Промпты на стол! (Prompts on the table!) — bug-makers
Description
На рабочем столе у коллеги лежат файлы, в которых он сохраняет свои запросы к нейросети и ответы на них, вот один из них. Зачем он это делает — непонятно.
English summary: A colleague stores his queries to a neural network and the answers in files; one is provided as prompts.zip. The goal is to find the hidden flag inside the document.
Analysis
The ZIP extracts to a single artifact, prompt JSON to C# class.docx (plus a __MACOSX resource fork to ignore). Opened normally, the DOCX shows an innocent LLM conversation: a request to convert several JSON objects (User, Product, Order, Company, Event) into C# classes, followed by the assistant's generated C# class definitions. Visible text is only ~4 KB.
A DOCX is just a ZIP archive, so the next step is to unzip it and inspect word/document.xml directly. The red flag: word/document.xml was ~102 KB — far larger than the small amount of visible text would justify.
Common document-stego vectors were checked and ruled out:
- No zero-width / invisible Unicode —
0x200b,0x200c,0x200d,0x2060,0xfeff,0xadall absent; visible text was pure ASCII. - No white/colored hidden text — every
w:colorvalue was000000. - No
w:vanish/ hidden-text formatting. - docProps metadata (
core.xml,app.xml) held nothing useful — just timestamps and aMyOfficeapplication string.
The actual trick is in the run structure. The text was fragmented into 264 separate <w:r> runs, using two fonts:
- Most runs used
Times New Roman— the cover text. - Scattered single-character runs used
Liberation Serif— visually identical when rendered, but a different font family.
This is font-family steganography: the font choice forms a hidden channel, and the alternate-font characters spell the payload. The flag content even self-references the technique — "how could you see the difference" — because the two fonts look identical to the eye.
Solution
Unzip the DOCX, parse word/document.xml, and extract (in document order) the text of every run whose w:rFonts w:ascii is Liberation Serif.
python read-only • copy allowed#!/usr/bin/env python3 import re, html data = open('word/document.xml', encoding='utf-8').read() runs = re.findall(r'<w:r>(.*?)</w:r>', data, re.S) msg = '' for r in runs: f = re.search(r'w:ascii="([^"]*)"', r) t = re.search(r'<w:t[^>]*>(.*?)</w:t>', r, re.S) if not t or not f: continue if 'Liberation' in f.group(1): msg += html.unescape(t.group(1)) print(msg) # -> buGctF{REDACTED}
Concatenating the Liberation Serif characters in document order spells the flag.