Points: 50
Category: re


I found this onion in my kitchen, may I ask you to dissect it?




The file we downloaded is an ELF 64-bit executable. First, let’s try running it:

So our task will be getting the flag out of binary. Next step is running it in gdb. The binary is not stripped, so we can start by looking at the list of functions.

First thing to notice is that ptrace is on the list of functions. Binaries in CTF tasks commonly use this function to detect if they’re being run in debugger. Calling ptrace(PTRACE_TRACEME, …) will return -1 if the process is being run in debugger, so it’s a very simple check to implement. Indeed if we run the binary in gdb it will just print “:(“, without even asking us for password. Fortunately the check is also very easy to work around it – just put a breakpoint in ptrace, step out of it and swap the return value to 0 (by overriding register).

Now that we have taken care of that let’s take a look at main function:

Not much going on in here – it asks for password, reads user input allocates some memory and copies a whole bunch of something (about 150KB) into it. Finally it jumps into whatever it is that it copied. Let’s set a breakpoint at jump location and see what’s going on in there.

We can see that the code takes the first character of our input (rax+0x0), then takes a single bit out of it and checks it value. If the 7th bit is 0 the result of ‘and’ operation will be 0 and the conditional jump in 8th line will take us to a short piece of code that print the sad face (“:(“) and terminates the program. That’s not what we want to happen, so let’s examine the other branch. We can easily pass the check by setting up a breakpoint in the line with and al, 0x40 and just changing the value of al register.
After checking the single bit of our input it runs some loop. Afterwards it performs a jump into total garbage. It seems that the loop must be decrypting the code before we jump into it. Let’s see what’s going on in there. The loop translated into pseudocode looks roughly like this:

So basically it reads number of bytes to decrypt and a key and then performs a simple xor using that key. Ok, let’s set a breakpoint in the jump location and see how the code looks after decrypting.

Turns out this chunk looks pretty much the same as the previous one. Except it checks a different bit. We can repeat the whole process and get yet another chunk of similar code, checking yet another bit. And so on, probably for at least a few hundred times. Theoretically we could get the whole password that way, but it would take ages to do that manually. Instead I decided to automate the process by writing a python script.

First I dumped the memory from gdb to a file (dump binary memory packed.dump 0x7ffff7fad000 0x7ffff7fad000+0x24c8d). This dump can be deassembled using objdump (objdump -D -b binary -mi386 -Mintel,x86-64 packed.dump > packed.objdump). Of course only the already decrypted code will make sense. However, the code is very repetitive and has a clear pattern. For example we just need to look for mov  al,BYTE PTR [rax+0x{offset}] to see which character is being examined by a given chunk of a code. And we can easily parse the following line to get specific bit.

So I wrote a python script that runs a loop, parsing a chunk, using the collected data to decrypt the next chunk and calling objdump to deassemble it.

After ~1600 loops the script produced the flag: hxp{1_h0p3_y0u_d1dnt_p33l_th3_0ni0n_by_h4nd}. Very appropriate 🙂
This was a very cool task, big thanks to organisers for preparing it.

Tagged with:

Leave a Reply