Stage 1
When we open the crackme under a decompiler (i.e. IDA HexRays), this is what we see:

Inside the function comparing the buffer we can even see a string following the casual flag format!

Of course it is too beautiful to be true – when we run the challenge and input this flag, we can see that it’s invalid…

Hmm, the output says that the password that was checked was not the one that we inserted, but instead “sorry i stole your input :)”…
At this point we can start to suspect that the code what we saw under the decompiler was not really the code that was executed… so, what is really happening here? Maybe some code is overwritten before the “main” is called? Let’s see…
Self-modification and the cascading processes
The entry point of the application is in the “start” function. We have offsets of the functions pushed on the stack, to be called by the “__libc_start_main”. One of those functions is “init”.

The function “init” calls further an array of functions:

This array consists of two functions: the first one does nothing but returns 0. The second one (that I labeled as “init_main”) is more elaborated.

Inside “init_main” we can see something interesting…

The fork() function is called – so, the execution splits into two processes: the parent and the child. Then the function prctl is called, with the value PR_SET_PTRACER. As the manual explains:
"prctl() manipulates various aspects of the behavior of the calling thread or process."
And regarding the used option PR_SET_PTRACER:
When a "ptracer process ID" is passed in arg2, the caller is declaring that the ptracer process can ptrace(2) the calling process as if it were a direct process ancestor.
So, the ptracer will be allowed to trace the parent. After that, the parent goes into a sleep mode.
Inside the “child_logic” function we can see ptrace being called multiple times, with different parameters. Fragment of the code:

So, basically, the child instance of the process will be set as a debugger of the parent, and it will be modifying its execution. This technique of obfuscation it is implemented in protectors such as Armadillo.
Yet, if we run the application, we can see that there are not two, but three processes of the same executable deployed:
$ ps -A | grep break 3771 pts/2 00:00:00 break 3772 pts/2 00:00:00 break 3773 pts/2 00:00:00 break
An attempt of attaching GDB to the processes gives the following result:

So, there are three processes, tracing each other in a cascading way:
#1 traced by -> #2 traced by -> #3
Indeed, having a deeper look in the code, we can see the second fork:

This process (referred as “grandchild” is dedicated to make another layer of modifications, this time in the child process.

Similarly as the child, the “grandchild_logic” function also contains some list of handlers that are executed on some events:

The details of this part will be explained in the further part of this post.
What strings are really compared?
The function executed as the child is long and obfuscated. It is easy to guess that here lies the logic of our challenge. As we already found out, the child will be tracing and modifying the parent process.
By observing the execution we could already suspect that the function reading and/or comparing the input is overwritten. Indeed, reading the code of the child we can find out that the function strcmp is being replaced:

Yet, it is not that simple as just replacing one function with another. It is replaced with 0x0B0F (0F 0B
) – which is a bytecode for UD2 (Undefined Instruction). So it seemingly does not make much sense… Until we realize that it was done on purpose – the goal is to generate a SIGILL (Illegal Instruction).
So, once the strcmp gets executed in the parent, the SIGILL will be generated. The child (debugger) will handle it by a dedicated handler. So, instead of going to the function that compares strings, we go to the following piece of code:

This function reads the input that we supplied. Then it sets EIP register to redirect the execution to another function, that looks in the following way:

Deleting the root directory? This looks quite scary! Fortunately… it is just trolling us. This function (and some others seen here) are never executed. How is it possible?
It turns out not only signals can trigger the code replacement, but also syscalls.

The functions that we have seen in the previous part of the code: execve, nice… Are here just to trigger syscalls that will be handled in the main child logic.
The syscalls
At this point we have to find out how the syscalls are handled. In contrast to signals, for which finding handlers was pretty straight-forward, syscalls are obfuscated:

The formula used to obfuscate them…
DWORD case_val = 0x1337CAFE * (syscall_id ^ 0xDEADBEEF);
…cannot be just reverted, because it is lossy (the DWORD is overflowed during the multiplication). So, we have no other choice but brutforce the values. I made the following small brutforcer:
#include <iostream> | |
bool syscalls_brute(int syscall_id) | |
{ | |
DWORD case_val = 0x1337CAFE * (syscall_id ^ 0xDEADBEEF); | |
static DWORD val_arr[] = { 0xE8135594, 0x2499954E, 0x4A51739A, 0x7E85DB2A, 0x3DFC1166, | |
0xF7FF4E38, 0x9C7A9D6, 0x9678E7E2, 0xB82D3C24, 0xC93DE012, 0xAB202240, | |
0x83411CE4, 0x91BDA628, | |
0x44DE7A30, 0x6B4E102C, 0x7E85DB2A, 0x5816452E, 0xB82D3C24, 0x91BDA628, 0xA4F57126 }; | |
for (size_t i = 0; i < sizeof(val_arr); i++) { | |
if (case_val == val_arr[i]) { | |
std::cout << std::hex << val_arr[i] << " , " << std::dec << syscall_id << "\n"; | |
return true; | |
} | |
} | |
return false; | |
} | |
int main() | |
{ | |
for (size_t i = 0; i < 1000; i++) { | |
syscalls_brute(i); | |
} | |
return 0; | |
} |
And I was able to map all the checksums to their corresponding syscalls:
We must keep in mind that some of the functions generate more than just one syscall. Fortunately, we can check the exact implementation in the source code. For example function nice generates: “getpriority/setpriority/getpriority” (source).
Now we are able to fill the real content of the function that is executed. Instead of execve, the following handler will be called:

The execve function is responsible for copying the input buffer.
We can also see that the nice function in reality returns the buffer that will be used as the AES key.
So, the flag part is decrypted by AES, and then compared with the input buffer with the help of memcmp. Knowing this, things are getting easy. We can simply hook the memcmp with the help of LD_PRELOAD, and force it to display the flag.
And we can execute the crackme with our library:
LD_PRELOAD=./memcmp.so ./break

That’s how we’ve got the first part of the flag, which is:
w3lc0mE_t0_Th3_l
This was not that tough – the next part is gonna be harder.
Great write-up !