For this challenge they give us the encrypted hex to load on the rhme2 board but also an unencrypted binary dump which we can run on an emulator or even on a real Arduino.
Disclaimer Always remember to RTFD!
0x00 -- intro
Furthermore, if we have access to a debugWire compatible board (as I did), like the AVR Dragon we can debug the device using the on chip debugger. Debugging Arduino boards is somewhat tricky as for this specific AVR chip JTAG is not supported, but a proprietary debug protocol called debugWire is instead.
It's perfectly detailed here. Lucky me I did have a fake Arduino UNO debug-enable friendly lying around and the only thing I had to do was just cut the trace.
I used radare2 to statically reverse engineer the code while debugging with Atmel Studio + AVR Dragon + Modified Chinese Arduino UNO:
0x01 -- readSerial() and the USART
The program just asks for input, then checks it and spits 'Better luck next time!' on fail. So trying to put a breakpoint after reading the serial input seems like a good idea, but how?
Well as we are dealing with an embedded system, let's take a look at the general architecture of the microcontroller:
The USART (Universal Synchronous and Asynchronous serial Receiver and Transmitter) is just a peripheral connected to the system data bus and in order to interact with it we just load and retrieve data from I/O registers.
After reading several pages of datasheet fun about how USART does work it seems clear that we need to keep track of at least two things:
- Receive Complete (RXC) Flag on the UCSRnA Register [Offset: 0xC0]: This flag bit is set when there are unread data in the receive buffer and cleared when the receive buffer is empty (i.e., does not contain any unread data).
- USART I/O Data Register 0 [Offset: 0xC6]: Reading the UDR0 Register location will return the contents of the Receive Data Buffer Register (RXB).
That's enough theory! We then need to find a loop-like structure checking against 0xC0 I/O register. Opening the binary file with r2 and after a few moments of crawling, searching and renaming it yields:
We found a function which loads the contents of the 0x00C0 I/O register (using Z as a pointer because ld r24,z does r24 ← (Z)); it sets r24 to 0x00 in case we have data in the buffer. We also may have noticed the XREF to this function from 0x02b2:
And following the XREF from 0x2a8 we end up in another function (I called it getMyPass) which loops until '0x0D' or '0x0A' is received (that is,\r or \n, ending the string).
0x02 debugNow let's go to Atmel Studio and set a BP on 0x0000030c; that's where it will jump when a 0x0d it's received.
The array containing our password starts at a fixed memory location: 0x012E.
From there it goes jumping (surprise!) between functions. Back to r2 then, and after several
Check the comments for clarity; the check_2 function just retrieves the values of password and password (that is, 0x135 = <base>0x12E + <offset>7), adds them together and checks if the result equals 0xD3. If the branch is taken (brne) the runtime flag generation code does not get executed. This pattern gets repeated 12 times more, checking against a different constraint each time.
0x03 - reEvery function matching the latter pattern was renamed as check_<num>, in appearance order from lower to higher addresses:
The real execution order is the following:
check_1 → check_2 → check_13 → check_11 → check_12 → check_6 → check_8 → check_14 → check_4 →check_9 → check_7 → check_3 → check_5 → check_10 → check_15
And a brief description of every check:
- check_1: Computes entered password length.
- check_2: password + password == 0xD3
- check_3: password * len(password) == 0x297
- check_4: password + password == 0x8F
- check_5: password * password == 0x122F
- check_6: password + password == 0x92
- check_7: password * password == 0x2873
- check_8: password * password == 0x2B0C
- check_9: password + password == 0xA7
- check_10: password + password == 0xA0
- check_11: password * password == 0x13B7
- check_12: password * password == 0x1782
- check_13: password * password == 0x15C0
- check_14: password + password == 0xA5
- check_15: Checks if every earlier step was taken properly.
At first I tried to solve it with pen and paper, but my head almost blew up from such hardcore math and ended up writing a python script:
The password turned out to be g1v3_1t_t0_m3 yeah! :D
So that was it! It was really fun and I did learned a lot of AVR internals!