This time I’m tackling this beautiful crackme, the third of the series. To solve this we will use radare2’s macros and unicorn emulation. Let’s jump right in!
Some Introductory Analysis
The crackme asks for a 4 digit password that the user needs to input one digit at the time. Similar to a PIN.
Looking at main we can see the “check_code_int” function is called near the end of its largest block.
A quick look at the “check_code_int” function reveals a chain of “if” blocks ending on checks between eax and ebx, one for every digit.
The blocks are almost identical, they all do some calculation, then compare the value of ebx against the expected value stored in eax. If the condition is met there's a jump to the next block, if not they return an error code.
Solution 1: Using radare2 macros
In order to get the correct password, we can check for the value of eax at compare time to get each digit. Here are the commands to extract the code using radare2:
# set up relative breakpoints. one per cmp instruction db sym.check_code_int+0x00001289-0x00001265 db sym.check_code_int+0x000012b7-0x00001265 db sym.check_code_int+0x000012e2-0x00001265 db sym.check_code_int+0x0000130d-0x00001265 # execute program dc # input four digits (doesn't matter which ones) 1 1 1 1 # define a macro that replaces the value # of ebx with the content of eax and stores it into a file !rm ./crack_code (eax_replace, dr ebx=`dr eax` | tee -a crack_code, dc) # use the macro .(eax_replace )@@=0 1 2 3 # show the results !cat ./crack_code
Here is the beautiful macro in action
Now we can go ahead and open the chest!
Solution 2: Bruteforce powered by GEF and unicorn-engine
Another way to solve this is to brute-force the password. Since there’s a reasonable amount of valid PINs (10000, to be specific) this wouldn't take too long.
We can emulate the function that checks each digit testing every combination until we get the expected state at the end.
This can be done with ease using the GEF functionality for emulation that generates a unicorn python script we can then modify and adapt for our purposes.
# 1 - step through the code in gdb # 2 - reach the start of the check function # in my case the function starts at 0x555555555269 # and ends at 0x555555555328 # 3 - use the GEF emu command: emu -t 0x0000555555555328 -s -o emu.py
The command above will generate an emulation template for us. We will need to modify it slightly in order to make the emulation run for each possible PIN code until it finds the correct answer.
We can use python’s itertools, to generate the pins and repeat the process until we get rax=1 at the end of an emulation run.
Here we can see the whole thing took around 7 minutes to complete. It’s not the most efficient method in the world but it gets the job done. There are certainly a lot of tweaks we could do to make it more performant if needed.
You can get the full emulation script from my Github and try it yourself.
- Radare2 macros are awesome! We could go a step further and generate an r2script to automate the task using the macros we wrote earlier.
- Emulation is super useful for dealing with obfuscated code and doing brute-force.
- Unicorn-engine is a great tool and GEF command makes our lives a lot easier.
- Another alternative to emulation would be to try dynamic instrumentation. But I’ll leave that for a future post.
Hope you enjoyed this one!