This is a small write-up of the “x-sixty-what” challenge from picoCTF 2022 (Binary Exploitation Category). The challenge is now available in picoGym here !
Description :
Overflow x64 code
Most problems before this are 32-bit x86. Now we’ll consider 64-bit x86 which is a little different! Overflow the buffer and change the return address to the flag
function in this program. (Download Linux binary and source code below).
Hint 1 : Now that we’re in 64-bit, what used to be 4 bytes, now may be 8 bytes.
Hint 2 : Jump to the second instruction (the one after the first push
) in the flag
function, if you’re getting mysterious segmentation faults.
Solution :
First of all, as I never did x64 stack overflow before, I just Googled it and found this article which greatly helped me understanding why RIP is not directly overwritten after the overflow. You should really take a look at it !
When executed, the program asks for a string to get a flag. When a string is entered, the program just exits.
By looking at the provided source code, we quickly understand that we must find a way to call the flag()
function that will open a flag.txt
file and print its output.
First, let’s generate a big enough payload that will overflow the buf
variable of the vuln()
function.
For this we used a script provided in Metasploit to generate a 1000 bytes payload in fuzz.bin : /opt/metasploit-framework/embedded/framework/tools/exploit/pattern_create.rb -l 1000 > fuzz.in
Now, let’s the vuln program under gdb (gdb -q ./vuln
) and providing the previously created payload as input (r < fuzz.in
).
As expected the program crashed. We can now check the value of the RBP register : 0x3363413263413163
Then let’s find how many bytes are needed to manipulate RIP register.
For this we used another script provided by Metasploit :/opt/metasploit-framework/embedded/framework/tools/exploit/pattern_offset.rb -q 0x3363413263413163
and found that the value of the RBP register is at offset 64 of the injected payload.
We can verify this with an hex viewer in the fuzz.in file (64 = 0x40) :
Now we need to find what to put in our payload to get the program jumps to the flag() function after we sent the payload.
For this : objdump -d vuln
will show you the address of the flag() function (0x401236
)
As suggested in the 2nd hint, we should try to jump to the instruction after the push, instead of the first instruction of the function (believe me, if you jump on the first one, you’ll get weird segmentation faults).
The address we want to jump is then 0x40123b
Also remember that in 64bits, registers are 8 bytes, and that we needed 64 bytes + 8 bytes to overwrite RBP.
But we want to overwrite the return address which right above in the stack, so we need : (64 + 8) bytes to fill the reserved space + 8 bytes for the address we want to jump to in little endian (000000003b1240).
The payload can then be generated like this :python -c 'print("A"*72 + "\x3b\x12\x40\x00\x00\x00\x00\x00", end="")' > payload.in
We can now rerun the program with gdb, pass the payload.in as input, and get the content of the flag.txt file :
or we can even directly pipe the payload to the program :python -c 'print("A"*72 + "\x3b\x12\x40\x00\x00\x00\x00\x00", end="")' | ./vuln
Now we have a working local exploit, we need to run it against the online instance to get the real flag.
For this I wrote a small python script using (great) pwn library :
Once executed, we get the flag :
The flag we are looking for is then : picoCTF{b1663r_15_b3773r_011d4bd8}