Flare-On 7 – Task 10

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 fragment of code responsible for tracing syscalls, and retrieving their numbers

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:

checksum syscall name references %eax arg0 (%ebx) arg1 (%ecx) arg2 (%edx) arg3 (%esi) arg4 (%edi) arg5 (%ebp)
b82d3c24 1 exit man/ cs/ 0x01 int error_code
a4f57126 2 fork man/ cs/ 0x02
91bda628 3 read man/ cs/ 0x03 unsigned int fd char *buf size_t count
7e85db2a 4 write man/ cs/ 0x04 unsigned int fd const char *buf size_t count
6b4e102c 5 open man/ cs/ 0x05 const char *filename int flags umode_t mode
5816452e 6 close man/ cs/ 0x06 unsigned int fd
44de7a30 7 waitpid man/ cs/ 0x07 pid_t pid int *stat_addr int options
f7ff4e38 11 execve man/ cs/ 0x0b const char *filename const char *const *argv const char *const *envp
ab202240 15 chmod man/ cs/ 0x0f const char *filename umode_t mode
3dfc1166 34 nice man/ cs/ 0x22 int increment
2499954e 54 ioctl man/ cs/ 0x36 unsigned int fd unsigned int cmd unsigned long arg
4a51739a 92 truncate man/ cs/ 0x5c const char *path long length
9678e7e2 96 getpriority man/ cs/ 0x60 int which int who
83411ce4 97 setpriority man/ cs/ 0x61 int which int who int niceval
9c7a9d6 122 uname man/ cs/ 0x7a struct old_utsname *
c93de012 152 mlockall man/ cs/ 0x98 int flags
e8135594 217 pivot_root man/ cs/ 0xd9 const char *new_root const char *put_old
view raw syscalls.csv hosted with ❤ by GitHub

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.

/*
Compile:
gcc -fPIC -c -m32 memcmp.c -o memcmp.o
gcc -shared -m32 -o memcmp.so memcmp.o
*/
#include <stdio.h>
int memcmp(const void *s1, const void *s2, size_t n)
{
printf("s1: %s\n", (char*)s1);
printf("s2: %s\n", (char*)s2);
return 1;
}
view raw memcmp.c hosted with ❤ by GitHub

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.


Stage 2

About hasherezade

Programmer and researcher, interested in InfoSec.
This entry was posted in CrackMe and tagged , . Bookmark the permalink.

1 Response to Flare-On 7 – Task 10

  1. 0xstan says:

    Great write-up !

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s