Insomni’Hack 2017 write-up : Internet Of Fail – 400

Challenge :

The « This » link give you a file named: iof-07439bc6dfe424ad52e70c05684e3b1b87badf69.elf (392Kb)

(you can download the binary HERE)

http://iof.teaser.insomnihack.ch/ display this:

Have a look to the ELF file, what kind of hardware did Balda choose ? …

phil@maxi:~/inso2017$ file iof-07439bc6dfe424ad52e70c05684e3b1b87b adf69.elf iof-07439bc6dfe424ad52e70c05684e3b1b87badf69.elf: ELF 32-bit LSB executable, Tensilica Xtensa, version 1 (SYSV), statically linked, stripped

What -the fuck- is this target ? First guess helped with Google was a ESP8266 board.

Playing with « strings » command give us a little more :

phil@maxi:~/inso2017$ strings iof-07439bc6dfe424ad52e70c05684e3b1 b87badf69.elf ... <html><head><title>IoF</title></head><body><h1>Congrats !</h1> Your flag is <b>INS{<i>sha1(password)</i>}</b></body></html> ... <html><head><title>IoF</title></head><body><h1>Intranet of Fail</h1> <form><input type=text name=q placeholder=password...></form></body> </html> ... /tmp/challenge/esp-idf/components/esp32/./crosscore_int.c

The folder with the name esp32 give us the target : ESP32, the little brother of the ESP8266.

2 others strings are the 2 web pages : it clearly give the game, you need to submit the good password, then the flag is a sha1() of the founded password.

The pain to get THE tools

Search for « reversing esp32 code » and you get *0* reply. Easy. About reversing the ESP8266, you are more lucky but nothing will do the job straight away.

The first idea is to send the file to https://www.onlinedisassembler.com/. It looks like it failed, but we’ll find later it was a not so bad disassembling. Only the entry point was bad, and we’ve think it didn’t manage to disassemble all the op-codes, we were wrong.

Digging again with « strings » give us that the ELF was generated with crosstool-NG. We download the official development dev-kit : https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-61-gab8375a-5.2.0.tar.gz . Objdump is always your friend and objdump was inside.

phil@maxi:~/inso2017$ xtensa-esp32-elf-objdump -Dslx iof-07439b c6dfe424ad52e70c05684e3b1b87badf69.elf > fulldissas.s phil@maxi:~/inso2017/work$ ls -lh fulldissas.s -rw-rw-r-- 1 phil phil 7,3M janv. 21 17:55 fulldissas.s

Opening the file give us something clean and neat. But a 175000+ lines in a single flat file opened with vi doesn’t help you that much to understand something.

Looking for a modern disassembling tool is needed. IDA-PRO doesn’t help, the target is unknown. Even adding a Xtensa plug-in doesn’t work at all.

Google point us Radare2 ! But the Debian packaged version was not able to handle the Xtensa assembler. It is mandatory to build Radare2 from the source code.

Intermediate point

We can’t run and debug live the device, we don’t have the good board.

We’ve not found any simulator that can run the firmware.

We can at least hope for a static analysis but …

We don’t know the Xtensa op-code

We’ve never used Radare2

OK, everything is under control.

Find an entry point

Even if IDA doesn’t display Xtensa op-code it handle nice the ELF file regarding the sections, offset, strings etc. Interesting strings and their memory locations :

The string containing the winning page is at address 0x3f406834. If if we locate a reference to this string, we should not be too far from the password checking code.

Let’s introduce you Radare2 !

It’s never too late to do the good things, but we need to learn Radare2 in a few minutes. This page from Thanat0s help us a lot (the whole blog is full of nice stuff) !

Here is what a 5 minutes tutorial gives on the binary firmware :

With Radare2 we’ve found an address using the pointer to the winning string. Address 0x400d0ad4. But, all the op-code in this sector doesn’t seems to look some good. When you look binary, you see a table of pointer. Unfortunately no more reference to 0x400d0ad4 in the code …

Let’s go back to the xtensa-esp32-elf-objdump output. We where more lucky with this one (remember we never used Radare2 before …).

401040a7: 2a9c beqz.n a10, 0x401040bd 401040a9: 02ad mov.n a10, a2 401040ab: 328ab1 l32r a11, 0x400d0ad4

The upper op-codes looks like nice, register, pointers etc. The new interesting address become 0x401040ab.

After doing our homework on Radare2, here is the interesting zone :

All the decision rely on the beqz.n a10, 0x401040bd . This means that our new interesting address is the function 0x40103fe8 :

According to the two last screenshots we know the path in the code to display. More interesting, we know where the result of the password checking is stored : 0x400d0a98. This result must be equal to 0xffff at the end of those 22 functions or it’ll never reach the display of the winning page.

Xtensa op-code, or how to learn a new target in 5 minutes

Now, we must start the hard job : reverting the 22 functions and compute the password to submit for displaying the winning page. A major problem is the Xtensa op-codes : a sort of RISC unaligned CPU, with 2 ou 3 (!!!) bytes instructions set. Little endian. Damn !

You can download the 662 pages reference guide HERE (xtensa Instruction Set Architecture (ISA) Reference Manual.pdf) .

Some funny instructions, wont give the sense, just guess :

slli a8, a6, 4

extui a9, a7, 4, 4

entry a1, 48

memw

quou a2, a10, a8

Now the big deal, looking inside the 22 functions

21 on 22 functions have the same pattern, exemple :

21 functions do this pseudo-code (1 function is a simple xor) :

if ( checkSomething(password[]) ) result ^= currentXorValue return

The difficulty is that NOT ALL the 21 functions must be called …

This is a good thing, because not all the sub-function have sense : some are impossible compute, some are non-printable chars etc.

To be more accurate in witch function we need to reverse, we’ve extracted all the xored values at the end of each function. Then we’ve tried to find a path to obtain 0xffff as result. Here is the table « function / xored values » :

function xored value call8 fcn.40103b40 8001 call8 fcn.40103b90 4000 call8 fcn.40103bbc 0012 call8 fcn.40103bdc 0102 call8 fcn.40103c00 2002 call8 fcn.40103c24 1008 call8 fcn.40103c44 0804 call8 fcn.40103c98 4008 call8 fcn.40103ce0 0020 call8 fcn.40103d60 0900 call8 fcn.40103d88 0100 call8 fcn.40103dac 2040 call8 fcn.40103dd8 0008 call8 fcn.40103e04 0002 call8 fcn.40103e64 0080 call8 fcn.40103ec0 0088 call8 fcn.40103ee0 0200 call8 fcn.40103f00 0420 call8 fcn.40103f3c 0004 call8 fcn.40103f5c 0400 call8 fcn.40103f84 0040 call8 fcn.40103fb4 0010

Then, the possible paths to obtain 0xffff (each bit to 1) as result are :

bit 15: 8001 bit 14: 4000 or 4008 bit 13: 2002 or 2040 bit 12: 1008 bit 11: 0804 or 0900 bit 10: 0420 or 0400 bit 09: 0200 bit 08: 0102 (mandatory) bit 07: 0080 or 0088 bit 06: 2040 or 0040 bit 05: 0020 or 0420 bit 04: 0012 or 0010 bit 03: 1008 or 4008 or 0008 or 0088 bit 02: 0804 or 0004 bit 01: 0012 or 0102 or 2002 or 0002 bit 00: 8001

Witch give this simple path :

bit 0 : 8001 bit 1 : 0102 bit 2 : 0804 bit 3 : 1008 bit 4 : 0010 bit 5 : 0420 or 0020 bit 6 : 2040 bit 7 : 0080 bit 8 : 0102 bit 9 : 0200 bit 10 : 0420 or 0400 bit 11 : 0804 bit 12 : 1008 bit 13 : 2040 bit 14 : 4000 bit 15 : 8001

But, that’s the theory … It never works as is because we for sure made a mistake somewhere :). Anyway, it has help a little to select the function to reverse.

Recover the 0x400d0aa0 empty buffer …

As we where quiet sure of our kung fu, we’ve got another problem :

During the reverse of the 21 functions, some use the content of address 0x400d0aa0. This address is in the RAM zone, and in static analysis, you’re stuck.

While seeking on the whole assembly we found this piece of code :

This function looks like a regular sprintf to construct the HTTP reply from the device. The code look like this :

char MAC[6]; char *format="%02X:%02X:%02X:%02X:%02X:%02X"; sprintf(0x400d0ad8,format,MAC[0],MAC[1],MAC[2], \ MAC[3],MAC[4],MAC[5]);

The interesting thing is that now we can guess what is the value, a MAC address and Balda was kind enough to insert it in the HTTP reply. Let’s dump a request to the server :

The empty 6 bytes buffer is now known : *0x400d0aa0 = 18fe346a95fc

Finish him !!

Now we have everything to compute all 21 functions, here is a summary of the whole reverse process :

call8 fcn.40103b40 passwd[0]='G' call8 fcn.40103b90 passwd[14]=(c-MAC[0]=9)='!' call8 fcn.40103bbc passwd[1]=0x99 NOT GOOD call8 fcn.40103bdc nothing call8 fcn.40103c00 passwd[13]^passwd[1]=4 call8 fcn.40103c24 passwd[12]='s' call8 fcn.40103c44 passwd[2]= c+72 /16 & 0x0F = 108 impossible call8 fcn.40103c98 passwd[3]= impossible call8 fcn.40103ce0 passwd[5]='o' call8 fcn.40103d60 passwd[8]-passwd[11]=10 passwd[11]='n' call8 fcn.40103d88 passwd[8]='x' call8 fcn.40103dac passwd[13]='A' NOT GOOD call8 fcn.40103dd8 passwd[3]='_' call8 fcn.40103e04 passwd[1]='0' call8 fcn.40103e64 passwd[7]=255 or impossible call8 fcn.40103ec0 passwd[7]=passwd[3]='_' call8 fcn.40103ee0 passwd[9]='t' call8 fcn.40103f00 passwd[10]='E' NOT GOOD call8 fcn.40103f3c passwd[2]='t' call8 fcn.40103f5c passwd[10]='3' call8 fcn.40103f84 passwd[6]='U' call8 fcn.40103fb4 passwd[4]='y'

We can deduct the password is G0t_yoU_xt3ns4!

The flag is INS{0c89d3e733a40a02a3dffbe0a0a902b78590d225}.

What we’ve done the wrong way

When you are in CTF context only one thing matter : the FLAG. Quick and dirt, no rules. But here is a list of things we should have tried and for sure have a better (faster) success :

The firmware use FreeRTOS. Trying to reverse regarding the library included should give some good results and probably a good mapping of each part of the firmware.

Try to compile with the same crosscompil tools and compare your own « Hello world ! » served the same way. It should provide us valuable information under GDB with the symbols. As we used objdump from the official crosscompil tools, the dev-env was ready.

The ESP32 have a rom. On github we found this file: esp32.rom.ld . We didn’t exploit this but we bet that’s a way to understand better all the code.

Hey men, it’s time to go now

The CTF last a WE. We start looking this task Saturday AM 10h and we flag it Sunday PM 9h40, 20 minutes before the end of the game. OK, it was not a 36H run, but it takes a lot of time to get ride of all the Balda’s traps. This write-up only give the path to get the solution, but forget a lot of invalids try ;). q3k from Dragon Sector solved it in 9H.

Those kind of hardware reverse engineering are very interesting : you learn a lot of trix using various tools, some new CPU / instructions sets and you overhaul various skills.

For the tenth anniversary of Insomni’Hack CTF, well done Baldanos, a nice challenge ! And a big thanks to SCRT team for the CTF & Insomni’Hack since the beginning.

.

Credits

Challenge solved by Azox (azox @8008135_) & Phil (@PagetPhil)

Write-up by Phil

Challenge by Balda ( @Baldanos or https://balda.ch/ )