Invisible Ink
Invisible Ink
Platform: CUSTOM | Category: Forensics | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: pdf_polyglot_extraction, pdf_hidden_text_extraction, zip_password_extraction, imagemagick_swirl_reversal, trailing_data_analysis
Summary
Walkthrough of the CUSTOM Forensics Invisible Ink. 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,
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
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}")
Key Takeaways / Lessons
- Mastering pdf_polyglot_extraction, pdf_hidden_text_extraction, zip_password_extraction, imagemagick_swirl_reversal, trailing_data_analysis was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-16 • CUSTOM • Invisible Ink • Difficulty: Easy