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}