[3DS CTF] [FOR 200 – What the Hex] Write up


The NSA trainee was fired and left a very strange file on his desktop.
Without any success, the director asked you to take a look at this strange file. Can you figure something out?

Solved by 29 Teams


We have a huge file (267MB) named massa.raw let’s take a look at it.

ctf@VM: file massa.raw
messa.raw: ASCII text, with very long lines, with no line terminators
ctf@VM: head -c 50 massa.raw

The file is a (very) long hexadecimal line. We convert it into binary to understand what it is:

ctf@VM: cat massa.raw | xxd -r -p > hex-to-raw
ctf@VM: head -c 30 hex-to-raw
FIF    �� C 

OK, we can clearly see a ‘FIF‘  header.  A few google search teach us that the FIF format is a photoshop format and is very undocumented.  But the ‘FIF’ string in the header seems very familiar doesn’t it ?

A standard jpg header is:

FF D8 FF E0 ?? ?? 4A 46 49 46 00                ; ÿØÿà??JFIF.

and the file have to finish with the trailer:

FF D9      ; ÿÙ

Source: https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format#File_format_structure

Let’s search if the trailer is on the file.

ctf@VM: grep -ci 'ffd9' massa.raw

OK there’s the trailer we are looking for. Now let’s add a valid jpg header to our raw file:

ctf@VM:printf "\xff\xd8\xff\xe0\x00\x10\x4a" | cat - hex-to-raw > itIsA.jpg

Awesome! We have a valid jpg file and a flag!

But that’s not the expected flag format as atcasanova (an admin) help me find out. The expected flag start with ‘3DS{

So we dug, a LOT.
The file (once in jpg format) is still 137Mo large, for a 1149×81 image it’s enormous. Binwalk can’t find hidden file in it, stegcrack neither. And what if there was other images hidden but with the same truncated header?

We search for jpg headers:

ctf@VM:  grep -o '46494600'  massa.raw  | wc -l

Okay, that’s a lot of jfif files. We script the extraction of the files:

def gethiddensjpg(directory,originalFile):
    # Defines:
    goodHeader='ffd8ffe000104a464946' # Correct jpg header
    if not os.path.exists(directory):
    c = 0 # File name counter
    currentImg = '' # Buffer for current extracted jpg

    with open(originalFile, 'rb') as f:
        for ch in iter(lambda: f.read(2), ''):
            currentImg += ch
            if ch == '46' and currentImg[-10:-2 ] == 'ffd94649': # Trunked header to search for 
                newImg = goodHeader + currentImg + ch
                filename = str(directory)+'/'+str(c)+'.jpg'
                fflag = open(filename,'w')
                currentImg = '' # Clean buffer
                c+=1            # Iterate file name

We obtain 2999 jpg files:

ctf@VM: ls flags/ | wc -l

We have 2999 jpg files with a flag on each one. Now we read each file to find what’s the next step:

def flagOCR(directory,destFile):
    # The files are : 3DSCTF{xxxx_xxxx_xxxx_xxxx}
    # We search for 3DS{xxx-xxx-xxx-xxx}
    for file in os.listdir(directory):
        listeString.append(str(subprocess.check_output("gocr "+ directory + "/" + file +" -C 'a-z0-9A-Z{}_-' -u ''", shell=True)).strip().replace(" ", ""))
    f = open(destFile, 'w')

And miracle! The line 2364 is on the good format:

But we can’t flag with 3DS{u_5hOuldv3_7ried_tesseract}.
The challenge creator help us to figure this out:

ctf@VM: tesseract 2364.jpg stdout

Tesseract miss-readed the ‘l’ (lower L) on a ‘I’ (upper i) and gave us the right flag.

Flag was 3DS{u_5hOuIdv3_7ried_tesseract}

Leave a Reply

Your email address will not be published. Required fields are marked *