Moin!

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:

nbn1

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')[15].split()[2],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()[1]) == 10:
                       print regs
        pc = int(regs.split('\n')[15].split()[2],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()[2]
    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 136.243.194.58:1234
Remote debugging using 136.243.194.58: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:

nbn2 nbn3 nbn4

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 136.243.194.58:1234
Remote debugging using 136.243.194.58: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

Cheers,

nsr