Challenge Exploration
The challenge gives us a corrupted PDF file. I tried opening it in several browsers and even some PDF viewer apps but none worked.
Then I shoved the PDF into a hex editor and found that this PDF is actually encrypted. Also, I noticed that this PDF has been made corrupted intentionally. It is missing several endobjs for the 4 0 obj and 7 0 obj, as well as the %%EOF at the end of the file!
Let’s decrypt the PDF to analyze it further!
Finding The Flag
Decrypting “The Devilish File”
A quick Google search shows that tools like qpdf can be used to do this.
Okay so decrypting this PDF file definitely requires a password, but I don’t have one. So I’m going to try with an empty one first.
$ qpdf --decrypt pretty_devilish_file.pdf decrypted.pdf
WARNING: pretty_devilish_file.pdf: file is damaged
WARNING: pretty_devilish_file.pdf: can't find startxref
WARNING: pretty_devilish_file.pdf: Attempting to reconstruct cross-reference table
WARNING: pretty_devilish_file.pdf (trailer, offset 1412): dictionary has duplicated key /Root; last occurrence overrides earlier ones
WARNING: pretty_devilish_file.pdf (object 7 0, offset 1402): expected endobj
WARNING: pretty_devilish_file.pdf (trailer, offset 1410): invalid /ID in trailer dictionary
WARNING: pretty_devilish_file.pdf (object 4 0, offset 915): expected endobj
qpdf: operation succeeded with warnings; resulting file may have some problemsLuckily, qpdf outputs a usable PDF file without a password (wait, is this PDF really encrypted?). Opening the file just says “Flare-On!”, so there’s nothing interesting here.
I think maybe the flag is hidden somewhere in the PDF’s stream.
Analyzing The PDF
Once again, dumping the decrypted PDF into a hex editor didn’t yield anything interesting sadly.
I had no idea what to do next at that point. Then I came across this pdf-parser.py tool by Didier Stevens which can be used to analyze the objects of the PDF.
$ ./pdf-parser.py -f decrypted.pdf
PDF Comment '%PDF-2.0\n'
PDF Comment '%\xbf\xf7\xa2\xfe\n'
obj 1 0
Type: /Catalog
...
obj 4 0
Type:
Referencing:
Contains stream
<<
/Filter /FlateDecode
/Length 290
>>
b"q 612 0 0 10 0 -10 cm\nBI /W 37/H 1/CS/G/BPC 8/L 458/F[\n/AHx\n/DCT\n]ID\nffd8ffe000104a46494600010100000100010000ffdb00430001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101ffc0000b080001002501011100ffc40017000100030000000000000000000000000006040708ffc400241000000209050100000000000000000000000702050608353776b6b7030436747577ffda0008010100003f00c54d3401dcbbfb9c38db8a7dd265a2159e9d945a086407383aabd52e5034c274e57179ef3bcdfca50f0af80aff00e986c64568c7ffd9\nEI Q \n\nq\nBT\n/ 140 Tf\n10 10 Td\n(Flare-On!)'\nET\nQ\n"
obj 5 0
Type:
...
startxref 742
PDF Comment '%%EOF\n'The objects of this PDF was printed out, and I spotted the “Flare-On!” text that we’ve seen earlier. But before that, there is a long hexadecimal byte string of 229 bytes. Previously, I only saw the “Flare-On!” text in the PDF, and nothing else. So what could that be?
ffd8ffe000104a46494600010100000100010000ffdb00430001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101ffc0000b080001002501011100ffc40017000100030000000000000000000000000006040708ffc400241000000209050100000000000000000000000702050608353776b6b7030436747577ffda0008010100003f00c54d3401dcbbfb9c38db8a7dd265a2159e9d945a086407383aabd52e5034c274e57179ef3bcdfca50f0af80aff00e986c64568c7ffd9A bit of decrypting back and forth, I ended up tossing the entire string into Grok and asked it to analyze the string. It turned out that the string was actually a 37x1 pixels JPEG image, which is known by looking at the first bytes — FF D8 FF — as the JPEG signature, and ends with FF D9 confirming that it’s a valid JPEG file!
Extracting The JPEG (And The Flag)
With a simple command, I finally got the JPEG image:
$ echo "ffd8ffe000104a46494600010100000100010000ffdb00430001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101ffc0000b080001002501011100ffc40017000100030000000000000000000000000006040708ffc400241000000209050100000000000000000000000702050608353776b6b7030436747577ffda0008010100003f00c54d3401dcbbfb9c38db8a7dd265a2159e9d945a086407383aabd52e5034c274e57179ef3bcdfca50f0af80aff00e986c64568c7ffd9" | xxd -r -p > image.jpgAnd… okay, it’s a 37x1 grayscale image, is the flag hidden somewhere here?
Given this tiny size, it’s likely that the flag is encoded in the pixel intensities. With 37 pixels, maybe this is a 37-character flag, with each pixel's grayscale value corresponds to the ASCII value of a character. A simple Python script can help to do that!
import io
from PIL import Image
hex_data = "ffd8ffe000104a46494600010100000100010000ffdb00430001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101ffc0000b080001002501011100ffc40017000100030000000000000000000000000006040708ffc400241000000209050100000000000000000000000702050608353776b6b7030436747577ffda0008010100003f00c54d3401dcbbfb9c38db8a7dd265a2159e9d945a086407383aabd52e5034c274e57179ef3bcdfca50f0af80aff00e986c64568c7ffd9"
binary_data = bytes.fromhex(hex_data)
img = Image.open(io.BytesIO(binary_data)).convert("L")
pixel_values = list(img.getdata())
chars = "".join(chr(v) for v in pixel_values)
print(chars)And here it is!
Puzzl1ng-D3vilish-F0rmat@flare-on.com Thoughts
In this challenge, I wasted a lot of time trying to get the flag from the image. A bit of searching on Google pointed me to the direction of image steganography like replacing the LSB, etc. I’ve spent so much time doing research about this and tried various tools to extract any hidden information, but no luck.
In the end, I came across a write-up for another CTF challenge on the internet, where the pixel intensities were used to hide the flag. I then did the same for this challenge, thinking I should’ve thought about this earlier!
By the way if anyone happens to read this, don’t take the hex string to Grok and also don’t struggle with writing a Python script to read the image like me LOL. Just use CyberChef.






