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

Description

[EN]
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

Resolution

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
46494600010100000100010000ffdb00430003020202020203

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
1

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
3000

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):
        os.makedirs(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')
                fflag.write(newImg.decode('hex'))
                fflag.close()
                currentImg = '' # Clean buffer
                c+=1            # Iterate file name
    f.close()

We obtain 2999 jpg files:

ctf@VM: ls flags/ | wc -l
2999

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}
    i=0
    listeString=[]
    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')
    f.write("\r\n".join(listeString))
    f.close()

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
3DS{u_5hOuIdv3_7ried_tesseract}

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 *