justCTF 2020 [debug_me_if_you_can-RE]
I had some time over the weekend and decided to try some reversing challenges from justCTF[*] 2020. Although I didn’t participate in the ctf seriously but solved an interesting challenge which deserves a writeup for sure.
Challenge
I bet you can’t crack this binary protected with my custom bl33d1ng edg3 pr0t3c70r!!!111oneoneone
Download : supervisor crackme.enc flag.png.enc
TL;DR
- Protector uses Nanomite technique
- Using strace to analyse execution
- Code is decrypted on the fly and then again encrypted
- Rebuilding a clean unprotected binary with IdaPython
- Keygenning
Role of Father Process
The crackme.enc binary is encrypted. I tried forcing some of it to code but that didn’t help me enough. So it looks that it is only encrypted at some marked positions in the function. Also that int3 looks suspicious.
So I shifted my focus to another binary from the provided files ie. supervisor.
1 | └──╼ $./supervisor |
supervisor begins by initializing some data. Later it forks a child process and executes crackme.enc with ptrace(PTRACE_TRACEME)
which means that crackme.enc is being traced by the supervisor.
The child logic starts with continuing the child process until it encounters an interrupt.. ahh ok!. So simply after an interrupt is generated it transfers control to the father and father handles its execution from that point.
This is a typical case of nanomite.
https://www.apriorit.com/white-papers/293-nanomite-technology
Later it gets the rip from the child’s interrupt context and peeks 4 bytes from it. These can be either 13 37 BA BE
or DE AD C0 DE
and exits if it is neither of them.
If 13 37 BA BE
, then it also peeks the other 4 bytes in succession.
Also if you aren’t much familiar with ptrace flags I recommend you to read this :
https://www.linuxjournal.com/article/6100
Then it notes the address of first occurences of bytes FE ED C0 DE
and DE AD C0 DE
.
It is often useful to display opcodes bytes along with the disassembly in IDA, especially in cases like these.
Options -> General -> Disassembly -> Number of opcode bytes (non-graph)
Now it decrypts some specific bytes after the noted FE ED C0 DE
address, sets rip to it and continues execution.
So we arrive at the following code block structure:
Tracing Execution with strace
The execution flow can simply be traced with the help of strace. Since it does all of the work with ptrace we see a lot of calls in strace output.
As we found out through static analysis, it starts off by comparing 4 bytes after an interrupt with 1337BABE
, and looks out for FEEDC0DE
and DEADC0DE
afterwards.
1 | wait4(-1, NULL, 0, NULL) = 5962 |
Also if the 4 bytes after an interrupt is DEADCODE
it starts encrypting the code previously decrypted. PTRACE_PEEKTEXT
and PTRACE_POKETEXT
are used consecutively for decryption/encryption.
1 | ptrace(PTRACE_GETREGS, 5962, NULL, 0x7ffdd357e660) = 0 |
Also as you can observe the patched addresses are not in any order and overlap too.
So the final code flow can be depicted as follows:
Analysing Decryption Algorithm
The decryption algorithm is simple indeed!
It uses the qwords initialised earlier and references those according to the two specific bytes after 13 37 BA BE
.
Each decryption cycle uses 5 specific qwords at a time.
The layout is as follows :
1 | 0x5607d9b68d60 0x0000000000007777 |
It uses some bytes from crackme and does simple xor operations with these qwords.
Here is the psuedocode implemented in python :
1 | dec_key_data = [0x0000000000000301, |
Writing an Unprotector
I thought of several ways to approach it such as :
- Patching encryption function call in supervisor and use strace
- Use a regex to filter out the good bytes from the strace log
- Use IDAPython to patch some bytes and unprotect it
- Use emulation to only decrypt code
For the first one, its not that easy there’s a catch, simply patching the encryption call will not work as the decryption key is also generated from the encrypted bytes afterwards.
The second one is a faster approach among the others and will surely give us the flag easily but its not fun and not the best way to solve it imo.
The last two would require us to understand the decryption process as well but will help us in the long run if we are given another binary protected with the same fancy bl33d1ng edg3 pr0t3c70r!!!
I’ll use the third one here.
We begin by finding all the 1337babes in the text section.
1 | def find_1337babes(): |
Those 1337babes addresses are used for identifying a code block as encrypted and then we look for first occurences of feedc0de and deadc0de.
I also wrote a short script to dump the qwords data from the supervisor binary.
Then we mark the addresses to be patched by our decrypt function.
1 | patch_addr = leetbabe_addr + 4 + (qword[1] & 0xff) + (qword[1] >> 32) |
And at the end we replace useless bytes with nops.
The patching function is as follows :
1 | def start_patch(leetbabes): |
Analysing Unprotected Crackme
Lets verify if we have successfully patched it.
1 | └──╼ $./crackme.enc |
Clean! We can now start working on the crackme.
The crackme looks for a file named secret_key
, checks it and then later decryptd the flag.png.enc
file with AES.
If there are some errors in file operations we get an error.
The key checking algorithm is not too complex either. It looks like it is somewhat similar to a custom binary to decimal conversion algo. It looks for a ?
that indicates a decimal has been processed. The result is then used as an index to some predefined bytes which it later compares with another index(1-127).
I dumped those bytes and wrote a short keygen script.
1 | #empty string => 0 |
1 |
|
1 | └──╼ $./crackme.enc |
And finally we have the flag! 0xCC~0xCC
Solution Files
You can find the unprotector script along with the idb files on my github here :
mrT4ntr4/Challenge-Solution-Files/justCTF_2020_debugme
This was an awesome crackme with a clever design and can prove a good resource for practicing tooling too..
I hope we get to see bl33d1ng edg3 pr0t3c70r v2, the next year!