Last updated
Last updated
Downloading the file given by the platform, we face with this:
Pretty unreadable, but we can get a better readability passing this code to an online .
We can get a slight better readability of the code, which makes more easy to understand what it is doing and how we can do the reverse operation of the program.
Reading it, we can note that it is xoring our input, adding 12 and subtracting 3, we can do the reverse operation with this simple line of code and retrieve the original flag.
The CTF gives a linux ELF binary to us.
Analysing it with Binary Ninja, we can discover some interesting behaviours.
First, the app checks if the current working directory is equal to "Service", if not, it doesn't execute. By second, it have a debugger checking with ptrace. If it detects that we are running the program with a debugger, it stops the application.
Finally, if all the preconditions succeeds, the program calls the constructFlag()
function.
So, in order to run this program with a debugger attached to it we have two options:
Patch the program to ignore the ptrace verification.
Hijack the ptrace function in order to deliver fakes returned values.
I took the first option, but I didn't patched the file. We can see in the assembly code that the verification is made by verifying if the returned value of ptrace (rax
register in x86 calling conventions) is different than 0xffffffffffffffff
. If yes, it executes the constructFlag
function, if not, it aborts the execution.
Executing the program with gdb ./chal_home -x chall.gdb
, we can successfully bypass the ptrace verification.
Opening the constructFlag
function on the disassembler, we can see that it has a lot of if elses that are hard to analyse.
By the very start of the program, we can note that it copying is 176 bytes from the &data_2010
address to the &var_118
(rbx - 0x118
).
Checking for uses of the var_118
variable, we can get couple definitions on what seems an array item definition.
When I saw this, the first thing I imagined was to set a breakpoint on the end of the constructFlag
and analyse the strings that come after the rbx-0x118
, doing that, surprising, we can get the flag.
I had some trouble when trying to reverse engineering those ELFs programs with Binary Ninja, so in the mid of the competition I switched to IDA free.
After disassemble the code and rename some variables, I could get this result:
We can see that the program doesn't do any kind of verification to check how you are running it (implementing anti-debugging or running conditions).
At the firsts lines of the pseudo code, we can note that it is getting 45 bytes from the user input, checking if the length of the data is exactly 45 characters and start to fill the array input_data
with the user input.
On the second loop at the 21st line, we can see that it is setting the indexth indexes
array item with index
and starting a thread pool sending a pointer to that index to the start_routine
function. Let's take a look on the last mentioned method.
This is the core of the challenge. It starts dereferencing that index pointer straight before starting a while loop (also can be interpreted as a for loop).
Within the loop, the program do a module operation with the index_dereferenced
item of the zeros
array (it is just a data array with lot of zeroes) plus the index dereferenced with 3.
After it, the program do different operations depending on the result of the module calculation and stores the value inside the input_data
array. In the end off the loop, it adds 1 in the index_dereferenced
item of the zeros
array and restart the loop, and the loop will not execute if this value got greater than 2.
Each calculation can be simplified as:
module == 0 {((byte ^ 0x7f) + 5) * 3}
module == 1 {((byte * 3) ^ 0x7f) + 5}
module == 2 {((byte + 5 ) * 3) ^ 0x7f}
Going back to the main function, after the program encrypt every character of the user input, the program do a last loop to check if the result of the encrypted user content is equal to the encrypted flag stored in the program.
I don't like using IDA to retrieve data from binaries, so I used Binary Ninja and extracted the contents of the enc_flag
as 4 bit integers little-endian.
Doing the reverse operation of all the mathematics involved on the encryption of the program, we can create this simple python code:
Executing the script, we can retrieve the flag.
As usual, opening the file on IDA we can get a initial overview of the main function.
The screenshot has some renamed variables that didn't come as symbols with the binary.
On the start, we can note that it is performing a for loop until the v3
variable turns equal to the end of the user_input
variable. These variables are pointers and if we divide where user_input
ends (512) with the number that is begin used as incrementer (16), we can get the value 32, which is the size of the user input.
After this, it calls the function sub_1220
which both Binary Ninja and IDA Free couldn't decompile with perfection.
This function is extremely tedious to reverse because it contains a lot of conditions and mathematical operations to encrypt the user input. When I saw this I immediately opened to see how the data that is encrypted looks like.
Going back to the main function to search an interesting offset that the program access those files, I could find the file offsect 0x10e3 which loads the encrypted user input (unk_4E4D) and also loads the encrypted flag from the data segment to the memory.
Setting a breakpoint on this offset with GDB, printing both values and running with a user input of 32 "A", we can get this:
Running the application twice with the same configuration results in the same values.
Knowing this, I could try to replace somes "As" with "Bs" to see the application behaviour. The input I will be sending is BAAAABAABAAAABAAAAAABAAAABAAABAA
.
We can see a side by side comparison of the results. On the left, it is the input begin all with "As" and on the right side it is the mixed input with "B" characters.
We can see that only where we put a letter be is modified. Therefor, the encryption algorithm doesn't seems to mix the values together to build a more stronger ciphertext.
So, as the program doesn't mixes the contents, we can tell that if we send 32 sequences of the same byte, we will get the encrypted version of that byte in each space of the array.
With that in mind, we can automate the GDB to loop through all the printable characters, send it to the program, dump the program and store a database with all those encrypted chars. Finally, we can loop through all the indexes of the encrypted flag and try to search on our database which character in that index would produce the encrypted byte.
Applying this methodology with Python, I made this program:
Executing it with the -x
GDB flag, we get the flag with success.
We can automize the process of setting rax
register to 0 (or any other value) with GDB script files. I use to work with PIE enabled but you can also do this with vanilla GDB.