32c3 ctf is over and I finally found some time to do a writeup again - it has been quite some time since I wrote the last. The challenge I’m going to talk about is embedded 400 - NordicByNature which we solved during the first night.
On a highlevel view, we were given a schematic of a PCB, a remote-gdb connection to a microcontroller (according to the challenge description established with a JTAG connection) and a VNC connection to the devices display, which is automatically started when connecting to the gdb server. Oh, and of course a link to the song “Nordisch By Nature” by Fettes Brot, a hiphop song from 1995 in “Plattdeutsch”, a language deployed in northern Germany.
The schematic tells us that there is a microcontroller, 12 pushbuttons (0-9, ESC, ENTER) and a display. The most important information is which kind of microcontroller is used: it is a lm3s811 - short recon tells us that it uses an ARM cortex-m3 CPU. So, let’s get arm-none-eabi-gdb ready and poke around at the target! Once connecting to the gdb-server, continuing the execution and opening a VNC connection, we get the following image:
Normally, we could now go ahead and just dump the binary firmware code via gdb. However, it turns out that trying to read memory directly returns just zerobytes. Nevertheless, we can control the execution, read from and write to registers. I immediately recalled the content of a recent blogpost from includesecurity, although I wasn’t able to find it again during the ctf.
In short, a protection technique which prevents us from reading memory is in place, which probably exist with the intention to prevent people from reversing/dumping vendors firmware. Nevertheless, we can set $pc to a value of our choice, which means we can just execute any bit of the firmware. Additionally, the ARM instruction set contains insturctions like POP or LDR, which basically can load contents of memory referenced by a register into a register. This is exactly the read-primitive we want to have! :)
The only problem is finding such an instruction “blindly”. Thus, I wrote a small gdb-python script to ease this process:
regs = gdb.execute('i r',to_string=True) print regs pc = int(regs.split('\n').split(),16) last_pc = pc for i in range(0,100): gdb.execute('set $r0 = %d' % pc) gdb.execute('set $r1 = %d' % pc) [...] # set all registers to $pc gdb.execute('si') regs = gdb.execute('i r',to_string=True) r = regs.split('\n') for i in range(0,13): if len(r[i].split()) == 10: print regs pc = int(regs.split('\n').split(),16) if last_pc == pc: pc += 4 last_pc = pc
In a nutshell, it sets all registers to the same value as $pc, single steps and parses the new register state. As soon one register contains a full word, this register state is printed. Of course, this is no guarantee that we have one of the desired read instructions, but we can manually evaluate if the shown bytes are meaningful opcodes.
Eventually, I found a LDR R1, [R2] instruction at 0xecc and was able to dump the firmware with another small gdb-python script:
import struct start = 0x0000 end = 0x1700 gadget = 0xecc f = open('a.bin','w+') gdb.execute('set height 0') for i in range(start,end,4): gdb.execute('set $pc = %d' % gadget) gdb.execute('set $r2 = %d' % i) gdb.execute('si',to_string=True) word = gdb.execute('p/x $r1',to_string=True).split() f.write(struct.pack("<I",int(word,16))) f.flush() print '[*] Dumped 0x%x: %s' % (i,word) f.close()
Now, we can finally start reversing the firmware. In case you want to have a look on your own, you can find a copy of our dumped bits and bytes here.
Examining the strings, we already see that there is somekind of a secret menu. This is referenced in a function which also appears to print the flag. Since we have full control $pc, we can just directly execute the according code and don’t have to bother with details whether and how the code would be reached normally.
(gdb) target remote 18.104.22.168:1234 Remote debugging using 22.214.171.124:1234 0x00000820 in ?? () (gdb) c Continuing. ^C Program received signal SIGINT, Interrupt. 0x0000101e in ?? () (gdb) set $pc=0x106 (gdb) c
This results in the following output on the display:
Hmm, okay, that wasn’t as easy as hoped. Thus, a little bit more reversing is needed to find the real interesting pieces of code:
ROM:00000116 MOVS R2, #0x14 ROM:00000118 MOVS R1, #0 ROM:0000011A ADD R0, SP, #0xC ROM:0000011C BL memset ROM:00000120 MOVS R2, #10 ROM:00000122 ADD R1, SP, #0xC ROM:00000124 MOVS R0, #0 ROM:00000126 BL generate_flag ROM:0000012A MOVS R2, #1 ROM:0000012C MOVS R1, #0x24 ; '$' ROM:0000012E ADD R0, SP, #0xC ROM:00000130 BL update_display
Thus, instead of trying to read the flag from the display, we can combine our code-exec and read-memory primitives to directly get the flag. The only pitfall left is that generate_flag takes the amount of generated characters as argument in r2, which we have to fix manually. After small adjustments in the dumpscript, we can solve the challenge in the following way:
(gdb) target remote 126.96.36.199:1234 Remote debugging using 188.8.131.52:1234 Program received signal SIGTRAP, Trace/breakpoint trap. 0x00000820 in ?? () (gdb) d breakpoints Delete all breakpoints? (y or n) y (gdb) b *0x126 Breakpoint 3 at 0x126 (gdb) b *0x12a Breakpoint 4 at 0x12a (gdb) c Continuing. ^C Program received signal SIGINT, Interrupt. 0x0000101e in ?? () (gdb) set $pc=0x116 (gdb) c Continuing. Breakpoint 3, 0x00000126 in ?? () (gdb) set $r2=0x100 (gdb) c Continuing. Breakpoint 4, 0x0000012a in ?? () (gdb) source ctf/2015/32c3/nordicbynature/dumpstuff.py [*] Dumped 0x200010a8: 0x20000fb4 [*] Dumped 0x200010ac: 0x12b [*] Dumped 0x200010b0: 0x0 [*] Dumped 0x200010b4: 0x853 [*] Dumped 0x200010b8: 0x200010c0 [*] Dumped 0x200010bc: 0x33633233 [*] Dumped 0x200010c0: 0x6d34445f [*] Dumped 0x200010c4: 0x31445f6e [*] Dumped 0x200010c8: 0x346c7073 [*] Dumped 0x200010cc: 0x345f7379 [*] Dumped 0x200010d0: 0x415f3372 [*] Dumped 0x200010d4: 0x5c2f5c31 [*] Dumped 0x200010d8: 0x7379612f [*] Dumped 0x200010dc: 0x3030745f [*] Dumped 0x200010e0: 0x346d735f [*] Dumped 0x200010e4: 0xffff3131 (gdb) shell strings a.bin 32c3_D4mn_D1spl4ys_4r3_A1\/\/ays_t00_sm411