Noprelo crackme writeup
The crackme itself can be found here: crackmes.one
This crackme is proposed by kellek. It was uploaded on 09/15/2018. It is writed in C/C++ and should runs on Unix/Linux. The assigned level is 2, level 2 is consider as easy. The goal is to show the Good boy message.
$ file noprelo
noprelo: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=04f602ad96dae79a8e86c1a4ee3899f74fbb0c76, stripped
Noprelo is a ELF 64-bit executable.
Let’s try to run:
It prints the “Bad!” message without more informations. Let’s try with ltrace to see there is a call strcmp(3):
$ ltrace ./noprelo
ptrace(0, 0, 1, 0) = -1
puts("Bad!"Bad!) = 5
exit(1 <no return ...>
hmm! There is a call to ptrace(2), here ptrace(2) is used to prevent dynamic analysis.
Okay, the first thing to do is to load the executable into IDA to see what’s going on. A good start is to search for “Bad!” string and see where it is referenced.
It is referenced from sub_117. Now, let’s follow the reference and see what is going on there.
This function seem to compare two strings using strncmp(3), if those two strings are equals, it prints the “Good!” message, otherwise the “Bad!” message is printed.
By looking closely to the strncmp(3) call, the given arguments are strncmp(RDX, RSI, RDI). The value of rdx is moved from eax which is the value returned by sub_1169 function. The value of RSI is the string “__gmon_start__” loaded from the .rodata section.
Finaly, the value of RDI come from RAX which is loaded with the value of [RBP+s1] which is probably the input given as a parameter of sub_1177 function.
Let’s try to given the string “__gmon_start__” as an argument of the executable.
$ ./noprelo __gnom_start__
The “Bad!” message still appear. To find why the “Good!” is not printed, let’s find from where the sub_1177 is called.
The only a reference to sub_1177 from main. It’s time to take a look to the main function
The big picture of main: it call ptrace(2) and compare the EAX register to a variable (var_24). The EAX register probably contains the value returned by ptrace(2) at this time. If EAX do not match var_24, it prints “Bad!”, otherwise it compare the value returned by sub_1169 to another variable (var_34), this is probably used to check if the flag is given in argument of the executable. If it is not the case it prints “Bad!”, otherwise it calls sub_1177 which is the comparison function.
In order to comfirms this guess-work, let’s dive into more prise reverse.
Deep static analysis
From the begin. The main function start at 0x11e1 and save the base pointer (RBP) the move the top of stack (RSP) to RBP.
The three following lines (from 0x11e5 to 0x11e9) push R13, R12 and RBX onto the stack (side note: those registers have added for 64-bit mode, R8 to R15). The main reason for saving those registers is they will be used for futur works.
At 0x11ea, it makes room for local variables. By starting from the begin. The main function start at 0x11e1 and save the base pointer (RBP) the move the top of stack (RSP) to RBP.
The three next lines, stores value from register to local variable. The calling convention used is cdecl. So, EDI must contains the argc parameter, RSI register must contains the argv parameter and RDX should contains the env parameter.
The two next lines (0x11f9 and 0x11fe) are insteresting. The first one, move 0xfffffdb1(4294966705 in decimal) to EDI. The following line, call a function named sub_1169.
Let’s find what’s going on in this function:
This function, simply do a binary not of the given value and returns the result. It is a funny way to obfuscate hardcoded values.
After the call to sub_1169 at 0x11fe, the returned value will be 0x24d. Then, the value is saved in local variable at 0x1203. From 0x1206 to 0x1248, it is the call to ptrace(2) using sub_1169 function to obfuscate the values passed as parameters. The return value of ptrace(2) is saved in local variable. At 0x1250 a call to sub_1169 using the value 0xfffffac6 as paramter is made.
The value returned by this call is used in camparison with the value returned by ptrace(2) made earlier.
So, the value returned by ptrace(2) should be equals to 0x539 (1337 in decimal). After reading the man page of ptrace(2), the expected value will never be returned by ptrace(2)
On success, the PTRACE_PEEK* requests return the requested data (but see NOTES), the PTRACE_SECCOMP_GET_FILTER request returns the number of instructions in the BPF program, and other requests return zero.
On error, all requests return -1, and errno is set appropriately. Since the value returned by a successful PTRACE_PEEK* request may be -1, the caller must clear errno before the call, and then check it afterward to determine whether or not an error occurred.
This executable will always print “Bad!”, it has to be patched.
Patching the executable
In order to make to print to “Good!” message, the local vairable must contains 0x0 before the comparison.
At 0x1250, the value given as argument of sub_1169 must be 0xffffffff instead of 0xfffffac6, this will produce 0x00 as result of sub_1169.
By using the patch assemble function of IDA:
Now, let’s retry the “__gnom_start__” found earlier:
$ ./noprelo __gmon_start__