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.
- 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
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.
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.
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.
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 :
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:
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
wait4(-1, NULL, 0, NULL) = 5962
Also if the 4 bytes after an interrupt is
DEADCODE it starts encrypting the code previously decrypted.
PTRACE_POKETEXT are used consecutively for decryption/encryption.
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:
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 :
It uses some bytes from crackme and does simple xor operations with these qwords.
Here is the psuedocode implemented in python :
dec_key_data = [0x0000000000000301,
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.
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.
patch_addr = leetbabe_addr + 4 + (qword & 0xff) + (qword >> 32)
And at the end we replace useless bytes with nops.
The patching function is as follows :
Lets verify if we have successfully patched it.
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.
#empty string => 0
And finally we have the flag! 0xCC~0xCC
You can find the unprotector script along with the idb files on my github here :
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!