file challenge.pdf
$ pdfdumper challenge.pdf --all -d output_dir/
$ pdfdumper challenge.pdf --all -d output_dir/
$ pdfdumper challenge.pdf --all -d output_dir/
output_dir/
output_dir/
├── obj1.txt # catalog
├── obj2.txt # page tree
├── obj3.txt # page object
├── obj4.txt # font resource
...
├── obj22.js # ← this one
├── obj23.txt # metadata
└── obj24.txt # cross-reference stream
output_dir/
├── obj1.txt # catalog
├── obj2.txt # page tree
├── obj3.txt # page object
├── obj4.txt # font resource
...
├── obj22.js # ← this one
├── obj23.txt # metadata
└── obj24.txt # cross-reference stream
output_dir/
├── obj1.txt # catalog
├── obj2.txt # page tree
├── obj3.txt # page object
├── obj4.txt # font resource
...
├── obj22.js # ← this one
├── obj23.txt # metadata
└── obj24.txt # cross-reference stream
$ cat output_dir/obj22.js
"ZmxhZ3tQREZfanNfc3RyZWFtX2hpZGRlbl9kYXRhfQ=="
$ cat output_dir/obj22.js
"ZmxhZ3tQREZfanNfc3RyZWFtX2hpZGRlbl9kYXRhfQ=="
$ cat output_dir/obj22.js
"ZmxhZ3tQREZfanNfc3RyZWFtX2hpZGRlbl9kYXRhfQ=="
$ echo "ZmxhZ3tQREZfanNfc3RyZWFtX2hpZGRlbl9kYXRhfQ==" | base64 -d
flag{PDF_js_stream_hidden_data}
$ echo "ZmxhZ3tQREZfanNfc3RyZWFtX2hpZGRlbl9kYXRhfQ==" | base64 -d
flag{PDF_js_stream_hidden_data}
$ echo "ZmxhZ3tQREZfanNfc3RyZWFtX2hpZGRlbl9kYXRhfQ==" | base64 -d
flag{PDF_js_stream_hidden_data}
pdf-parser.py
--type /JavaScript
$ strings challenge.pdf | grep -i "flag\|ctf\|base64\|javascript"
$ strings challenge.pdf | grep -i "flag\|ctf\|base64\|javascript"
$ strings challenge.pdf | grep -i "flag\|ctf\|base64\|javascript"
$ mkdir output_dir
$ pdfdumper challenge.pdf --all -d output_dir/
$ ls output_dir/
$ mkdir output_dir
$ pdfdumper challenge.pdf --all -d output_dir/
$ ls output_dir/
$ mkdir output_dir
$ pdfdumper challenge.pdf --all -d output_dir/
$ ls output_dir/
# Check all JS objects
$ cat output_dir/*.js # Or if pdfdumper didn't separate by extension, check all stream objects
$ for f in output_dir/*; do echo "=== $f ==="; cat "$f"; echo; done | head -200
# Check all JS objects
$ cat output_dir/*.js # Or if pdfdumper didn't separate by extension, check all stream objects
$ for f in output_dir/*; do echo "=== $f ==="; cat "$f"; echo; done | head -200
# Check all JS objects
$ cat output_dir/*.js # Or if pdfdumper didn't separate by extension, check all stream objects
$ for f in output_dir/*; do echo "=== $f ==="; cat "$f"; echo; done | head -200
# Base64
$ echo "ENCODED_STRING" | base64 -d # Hex
$ echo "HEXSTRING" | xxd -r -p # Zlib-compressed stream (if pdfdumper didn't auto-decompress)
$ python3 -c "import zlib, sys; print(zlib.decompress(sys.stdin.buffer.read()))" < stream_file
# Base64
$ echo "ENCODED_STRING" | base64 -d # Hex
$ echo "HEXSTRING" | xxd -r -p # Zlib-compressed stream (if pdfdumper didn't auto-decompress)
$ python3 -c "import zlib, sys; print(zlib.decompress(sys.stdin.buffer.read()))" < stream_file
# Base64
$ echo "ENCODED_STRING" | base64 -d # Hex
$ echo "HEXSTRING" | xxd -r -p # Zlib-compressed stream (if pdfdumper didn't auto-decompress)
$ python3 -c "import zlib, sys; print(zlib.decompress(sys.stdin.buffer.read()))" < stream_file
"ZmxhZ3tQREZfanNfc3RyZWFtX2hpZGRlbl9kYXRhfQ=="
$ echo "ZmxhZ3tQREZfanNfc3RyZWFtX2hpZGRlbl9kYXRhfQ==" | base64 -d
flag{PDF_js_stream_hidden_data}
$ echo "ZmxhZ3tQREZfanNfc3RyZWFtX2hpZGRlbl9kYXRhfQ==" | base64 -d
flag{PDF_js_stream_hidden_data}
$ echo "ZmxhZ3tQREZfanNfc3RyZWFtX2hpZGRlbl9kYXRhfQ==" | base64 -d
flag{PDF_js_stream_hidden_data}
file challenge.pdf
binwalk -e challenge.pdf
pdfdumper challenge.pdf --all -d out/
cat out/obj22.js
pdfdumper file.pdf --all -d out/
pdf-parser.py --type /JavaScript file.pdf
pdf-parser.py --object 22 file.pdf
pip install pdfminer.six
pip install pdfminer.six
pip install pdfminer.six
pdfminer.six
pdfminer.six
# If pdfdumper fails on a malformed PDF, try:
$ pdf-parser.py challenge.pdf # Or use qpdf to repair first:
$ qpdf --qdf challenge.pdf repaired.pdf
$ pdfdumper repaired.pdf --all -d out/
# If pdfdumper fails on a malformed PDF, try:
$ pdf-parser.py challenge.pdf # Or use qpdf to repair first:
$ qpdf --qdf challenge.pdf repaired.pdf
$ pdfdumper repaired.pdf --all -d out/
# If pdfdumper fails on a malformed PDF, try:
$ pdf-parser.py challenge.pdf # Or use qpdf to repair first:
$ qpdf --qdf challenge.pdf repaired.pdf
$ pdfdumper repaired.pdf --all -d out/
pdf-parser.py
# 1. Quick string check (10 seconds)
strings challenge.pdf | grep -i "flag\|ctf\|base64" # 2. Dump everything (30 seconds)
pdfdumper challenge.pdf --all -d out/ && ls -la out/ # 3. Check non-standard extensions first
cat out/*.js 2>/dev/null || echo "no JS objects" # 4. Scan all stream content for Base64 patterns
grep -l "[A-Za-z0-9+/]\{40,\}=*" out/*
# 1. Quick string check (10 seconds)
strings challenge.pdf | grep -i "flag\|ctf\|base64" # 2. Dump everything (30 seconds)
pdfdumper challenge.pdf --all -d out/ && ls -la out/ # 3. Check non-standard extensions first
cat out/*.js 2>/dev/null || echo "no JS objects" # 4. Scan all stream content for Base64 patterns
grep -l "[A-Za-z0-9+/]\{40,\}=*" out/*
# 1. Quick string check (10 seconds)
strings challenge.pdf | grep -i "flag\|ctf\|base64" # 2. Dump everything (30 seconds)
pdfdumper challenge.pdf --all -d out/ && ls -la out/ # 3. Check non-standard extensions first
cat out/*.js 2>/dev/null || echo "no JS objects" # 4. Scan all stream content for Base64 patterns
grep -l "[A-Za-z0-9+/]\{40,\}=*" out/* - Any .js files — JavaScript in a PDF with no visible content is always suspicious
- Any stream objects that aren't fonts — obj*.txt files with unusual size
- Metadata objects — sometimes flags are hidden in XMP metadata or document properties
- Form objects — interactive PDF forms can contain hidden fields