Balda's place

Tags

Insomni'hack 15 - 1-2-3-4

Published on 24 March 2015


This year, a new hardware challenge has been provided, which consists of a small safe with a rotary encoder. To be honest, I ragequitted the challenge after discovering that if you had a newer IDA Pro, everything was far more easy. I jumped back in after some days to see if there was an alternate solution.

Basics

The organizers provided an ELF binary for Atmel AVR, and some schematics to better understand the inner working of the safe.

1-2-3-4

Disassembly

This first part has been made with Phil from mushd00m team, he did a great job and he didn't stop until he got the answer. You can see his writeup (in french) here.

Opening the binary in IDA showed that it mixed RAM and flash memory addresses, resulting in a total mess in the functions and variables. Pretty useless to begin with. Phil got the idea to use the Online disassembler to analyze the file, and it was really helpful to understand how the binary works.

We however managed to locate the functions used to open and close the door, respectively u() and r(), because they are the only ones using the Servo::write() function which drives the door. This is where I left.

After some days thinking about the challenge, I gave it an other shot but this time with less reverse engineering and more bruteforcing.

Input encoding

We know that the input from the rotary encoder is made on pins 7, 8 and 10. Looking for digitalRead() calls with this value as a parameter points to the rot() function :

The values are then stored in the memory at address 0x0126, 0x0128 and 0x012A for the button. Later on, the button value is checked again and an other register is checked for values ranging from 0 to 3 :

Knowing that the PIN code requires four digits, we can assume that the value at address 0x0129 is the index of the current digit entered. Each branch is referring to a value at address 0x0139. It is probably the encoded rotary encoder value.

To sum this up :

  • rot() runs when the user clicks on the switch. Its value is at 0x012A.
  • The value is converted from whatever is used by the rotary switch to a decimal value at 0x0139.
  • The switch is checked for release, then the converted value is messed up with.
  • All of this is made four times. The index value is at 0x0129.

Time to be creative

Quick calculation : given 4 digits of 16 different possibilities, it gives us 65536 different combinations, which is small enough be tested by bruteforce. Let's open simavr and avr-gdb. The goal here is to simulate the arduino and trap the right code using GDB.

Start by loading the binary in the simulator :

$ simavr -g -v -m atmega328p /tmp/1-2-3-4
Loaded 4356 .text
Loaded 34 .data
avr_interrupt_resetav
r_gdb_init listening on port 1234

Then, use GDB to connect to simavr :

$ avr-gdb
GNU gdb (GDB) 7.9
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-unknown-linux-gnu --target=avr".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
> (gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x00000dac in ?? ()
> (gdb) file /tmp/1-2-3-4
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from /tmp/1-2-3-4...done.
> (gdb)

Now we need to simulate the inputs on the PINs, encode them correctly and...

Boring.

Let's just break before the second check of the input switch (which is made after the input -> value conversion) and :

  • Set memory at address 0x0129 to 0 (simulate the release of the switch)
  • Set memory at address 0x0139 to a value between 0 and 15 (conversion from the rotary encoder input)
  • Continue execution

Awesome, but how to do that using GDB ? By using Python with GDB !

I started by defining two breakpoints :

  • 0x04a6 just before the switch state is read
  • on the u() function, so I'll stop once the correct value is found

in GDB, once the breakpoints are placed, fire up a python interpreter using :

> (gdb) info breakpoints
Num     Type           Disp Enb Address    What
2       breakpoint     keep y   0x000004a6 in rot() at /tmp/build5286059455954586736.tmp/cube.cpp:89
3       breakpoint     keep y   0x000001d4 in u() at /tmp/build5286059455954586736.tmp/cube.cpp:208
> (gdb) python-interactive

Then, a bit of (no-brain) Python magic :

>>> a=0                                                          # Define all digits
>>> b=0
>>> c=0
>>> d=0
>>> inferior = gdb.inferiors()[0]                                # process instance
>>> gdb.execute('set pagination off')                            # remove GDB pagination
>>>
>>> def plop(event):                                             # Callback function
...   global inferior
...   global a
...   global b
...   global c
...   global d
...   val = [a,b,c,d]                                            # Set the combination
...   if event.breakpoint.number==2:                             # Is breakpoint reached ?
...     inferior.write_memory(0x800129, '\x00', 1)               # Write switch release
...     index=ord(inferior.read_memory(0x800137,1)[0])           # Get digit index
...     inferior.write_memory(0x800139, chr(val[index]), 1)      # Write computed value
...     if index==3:                                             # Finished entering the combination ? increment
...       if d<15:
...         d=d+1
...       elif c<15:
...         d=0
...         c=c+1
...       elif b<15:
...         d=0
...         c=0
...         b=b+1
...       elif a<15:
...         d=0
...         c=0
...         b=0
...         a=a+1
...     gdb.execute('c')                                         # Step to the next breakpoint
...   if event.breakpoint.number==3:                             # Solution found ?
...     while 1:                                                 # Print it
...       print val
...
>>> gdb.events.stop.connect(plop)                                # attach the function to all breakpoint events

Note that avr-gcc adds 0x8000000 to specify addresses in RAM, so 0x0129 becomes 0x800129.

Running it works quite well, except that every time a wrong combination is entered, the f() function is called. It was used on site to beep and tell the user the code was wrong. As this function uses delay() functions, it slows down our bruteforce attempt. Not cool.

To correct this, I simply patched the binary to change the function by a ret instruction :

becomes

Now the scripts works quite faster and find a solution in about 3 minutes :

15 - 6 - 10 - 7

Conclusions

All in all, it was a nice challenge and the hardware device was absolutely amazing (didn't take pictures, sorry). Looking at the writeups and after discussing with people that solved it, reversing the last digit was quite challenging and some people even tried to bruteforce it live.

This solution might be less subtle, but it has the advantage to present some cool stuff you can do with GDB, even on microcontrollers and the usage of the simulator.

Anyways, thanks to the organizers, and especially to Azox for this task.