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

Challenge :

subject

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:

webpagepassword

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 :

idastrings

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 :

radare2stringref

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 :

radare2checkcode

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

radare2checkfunction

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 :

radare2password12

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 :

radare2fctmac

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 peace of code :

radare2mac

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 :

requestmac

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 tools various, 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 (@CaptainRicard)

Write-up by Phil

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

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s