Invisible Ink
Invisible Ink
Platform: Tjctf | Category: Forensics | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | Date: 2026-05-16 | Status: Solved Techniques: pdf_polyglot_extraction, pdf_hidden_text_extraction, zip_password_extraction, imagemagick_swirl_reversal, trailing_data_analysis
Summary
Task: 638KB PDF polyglot file with hidden text, appended ZIP archive, and swirl-distorted PNG image. Solution: extract hidden password via pdftotext, carve ZIP from PDF trailing data, decrypt archive, reverse ImageMagick swirl distortion to read flag.
Recon
Port scan
bash read-only • copy allowednmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
tjctf| ID:20260516_tjctf_invisible_ink - Tags: imagemagick, zip, hidden_text, pdf, polyglot, pdftotext, password_protected_zip, swirl_distortion
- Indicators: PDF file size much larger than expected for text-only content, data after %%EOF marker in PDF, PK magic bytes in trailing data, swirl-distorted text in PNG image, hidden text layer in PDF not visible in viewer
- Source:
20260516_tjctf_invisible_ink.md
Foothold
Vulnerability / Misconfiguration
- pdf_polyglot_extraction
- pdf_hidden_text_extraction
- zip_password_extraction
- imagemagick_swirl_reversal
- trailing_data_analysis
bash read-only • copy allowed<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
bash read-only • copy allowedsudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
bash read-only • copy allowed<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- pdf_polyglot_extraction
- pdf_hidden_text_extraction
- zip_password_extraction
- imagemagick_swirl_reversal
- trailing_data_analysis
- Tags: imagemagick, zip, hidden_text, pdf, polyglot, pdftotext, password_protected_zip, swirl_distortion
Original Writeup
<details><summary>Click to expand original content</summary>Description
Check out this cool PDF I found... I wonder if there's anything hidden inside!
A 638KB PDF file (chall.pdf) is provided. The goal is to find a hidden flag. The file is suspiciously large for a 2-page text-only document, hinting at embedded data.
Analysis
Stage 1: PDF Reconnaissance
The PDF has 2 pages, created with "Skia/PDF m149 Google Docs Renderer". Key observations:
- File size: 638KB — far too large for a text-only PDF (actual PDF structure is only ~31KB)
- No embedded images:
pdfimages -listreturns nothing - No attachments:
pdfdetach -listshows 0 embedded files - Hidden text:
pdftotextextracts text not visible in the PDF viewer, including a poem about CTFs and crucially a password
bash read-only • copy allowedpdftotext chall.pdf -
The extracted text includes:
- A decoy message: "There's nothing here lol why are you looking"
- A poem about CTF challenges
- A password string:
DBf8nEBgwRhZ
Stage 2: Polyglot Detection — ZIP After PDF EOF
The PDF structure ends at ~31KB with the %%EOF marker, but the file continues for another ~607KB. Examining the trailing bytes reveals a PK (ZIP) magic header immediately after EOF:
python read-only • copy allowedwith open('chall.pdf', 'rb') as f: data = f.read() # Find the last %%EOF marker eof_pos = data.rfind(b'%%EOF') trailing = data[eof_pos + 6:] # skip %%EOF\n # Write the trailing ZIP data with open('hidden.zip', 'wb') as f: f.write(trailing)
The carved ZIP archive is 606KB and contains a single password-protected file: original_distorted.png.
Stage 3: ZIP Extraction
Using the password discovered in the PDF hidden text:
bash read-only • copy allowedunzip -P "DBf8nEBgwRhZ" hidden.zip
This extracts original_distorted.png — a 1920×1080 RGB PNG image.
Stage 4: Image Analysis
The PNG shows red handwritten text on a white background that has been distorted with a swirl effect (rotation that varies with distance from center). Pixel analysis reveals:
- Red channel: only 36 unique values (220–255) — the text is painted in shades of red
- Green and Blue channels: all 256 values present — standard white background
- Some text is partially readable at the edges: "tjctf{..." and fragments
Solution
The swirl distortion was applied using ImageMagick's -swirl option. To reverse it, apply the same operation with a negative angle. Testing various angles:
bash read-only • copy allowedconvert original_distorted.png -swirl -240 unswirled.png
At -240 degrees, the text becomes clearly readable across three lines:
- Line 1:
tjctf{p0lygl0t_ - Line 2:
REDACTED - Line 3:
c00l}
Full Solve Script
</details>python read-only • copy allowed#!/usr/bin/env python3 """Solve script for Invisible Ink - TJCTF 2026""" import subprocess # Step 1: Extract hidden text from PDF to find password result = subprocess.run(['pdftotext', 'chall.pdf', '-'], capture_output=True, text=True) # Password found in output: DBf8nEBgwRhZ # Step 2: Carve ZIP from PDF trailing data with open('chall.pdf', 'rb') as f: data = f.read() eof_pos = data.rfind(b'%%EOF') trailing = data[eof_pos + 6:] with open('hidden.zip', 'wb') as f: f.write(trailing) # Step 3: Extract password-protected ZIP subprocess.run(['unzip', '-o', '-P', 'DBf8nEBgwRhZ', 'hidden.zip']) # Step 4: Reverse swirl distortion subprocess.run(['convert', 'original_distorted.png', '-swirl', '-240', 'unswirled.png']) print("Flag visible in unswirled.png: tjctf{REDACTED}")
signed by XESXOR