$ cat writeup.md…
$ cat writeup.md…
tjctf
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.
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.
The PDF has 2 pages, created with "Skia/PDF m149 Google Docs Renderer". Key observations:
pdfimages -list returns nothingpdfdetach -list shows 0 embedded filespdftotext extracts text not visible in the PDF viewer, including a poem about CTFs and crucially a passwordpdftotext chall.pdf -
The extracted text includes:
DBf8nEBgwRhZThe 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:
with 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.
Using the password discovered in the PDF hidden text:
unzip -P "DBf8nEBgwRhZ" hidden.zip
This extracts original_distorted.png — a 1920×1080 RGB PNG image.
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:
The swirl distortion was applied using ImageMagick's -swirl option. To reverse it, apply the same operation with a negative angle. Testing various angles:
convert original_distorted.png -swirl -240 unswirled.png
At -240 degrees, the text becomes clearly readable across three lines:
tjctf{p0lygl0t_REDACTEDc00l}#!/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}")
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar