
This binary exploitation challenge is, by the time I’m writing this tutorial, rated as EASY with a difficulty score of 2.25 out of 5.0. The description states the following:
Can you control this applications flow to gain access to the hidden flag function with the correct parameters?
There is a binary to download and exploit. Upon downloading it, I used file command to identify, at least based on its header bytes, what kind of binary it is. You could, of course, use any other tool you find appropriate.
As you can see in the image above, the binary is a
The next step is to find out what protections has been the binary compiled with. In the following image I’m using Cutter.
The binary has no stack canaries and it’s not PIC. That is, we can overwrite the stack without worrying about canaries and the addresses of .text section (and other sections as well) will not change between executions. However, the binary was compiled with NX bit, i.e. we cannot execute code from the stack (like inputting a shellcode and jumping to it).
Let us reverse the binary and discover its inner workings :). This time I’ll be using IDA.
Inspecting the declared functions, there are two of them that quickly stand out. These two functions are flag and chall.
Before inspecting them, let us take a look at main. It prints the strings and then calls chall. Pretty simple, nothing relevant here.
Now, when taking a look at chall we can easily spot the vulnerability. The function performs a scanf read at address
The attack is now clear, we can overwrite chall’s return address thus
Now the question is: where do we want to redirect the execution flow? The answer is: to flag function. Inspecting flag one can see there are several consecutive conditional jumps. If the correct conditions are met, there is a final block that actually opens a file called
In order to reach (execute) the code block that prints the flag, we must meet the following requirements from flag’s perspective:
ebp + 0x8 = 0x1337
ebp + 0xc = 0x247
ebp + 0x10 = 0x12345678
In other words, when we manage to hijack the execution flow and reach flag function, its parameters/arguments must be the aforementioned ones. Please bear in mind we are exploiting a binary compiled for a ebp register, since ebp is the base pointer (also known as frame pointer) of that particular stack frame.
In other words, from flag’s point of view, it expects the stack to be aligned like the following image where:
- 1st argument corresponds to 0x1337
- 2nd argument to 0x247
- 3rd argument to 0x12345678
Notice how the return address of the function is stored at ebp+0x4 and right above it, at ebp+0x8, lives the very first parameter.
So, the exploit consists in overwriting the return address of chall function with the address of flag and continue writing data in the stack in such a way that the parameters of flag are set according to the previous image.
call instruction. CALL instruction modifies the stack pointer (ESP register) given it pushes the address to return to. Since we’re hacking our way to call flag, there will be no call instruction. Additionally, functions are preceded by the prologue and succeeded by the epilogue. The prologues and epilogues will still happen, but the absence of call slightly modifies the stack template/setting we must achieve.
Before reasoning about the exploit and the payload, ebx is crucial. It is used throughout the whole program to reference parameters that are constant (hardcoded) and passed to other functions like fopen.
Notice how, in the image above, ebp is assigned from the stack right before chall function finises (instruction at address 084862D). Since chall is the function whose stack frame we will overflow, it is important to keep the correct value at ebp-0x04 because it is later used in flag (right column of the image below) to reference all the parameters needed to open
Now, what is that value? It is fairly simple to find it out with IDA by resolving the offsets used in lea instructions (e.g., address 080485A9). However, you can also debug it using your debugger of preference. You can do so by debugging a legit execution. Place a breakpoint after chall’s prologue and take a look at the contents of the stack. Right below ebp you will see the expected value of ebx. The value in question is 08048708, which corresponds to the beginning of the .rodata (read-only data) section.
Now, with this in mind we know so far that our exploit should meet, at least, the following requirements:
- Writing bytes (scanf’s buffer) starts at address
ebp-0x88. - At
ebp-0x4we must preserve the expectedebxvalue:0x08048708. - The return address of
challmust be overwritten with the return address offlag:0x08048576. - When executing
flag, the following must be true:ebp+0x08 = 0x1337,ebp+0xc = 0x247andebp+0x10 = 0x12345678.
With all this information in mind, the scheme of our exploit is the one I drew in the next image.
Notice I depicted the state of the stack and the registers when chall is about to finish (execute ret) and right after flag epilogue. If there is something you don’t understand (like how the registers work or why do they move) do not hesitate to reach me out!
The logic behind the previous scheme is: in the left side of the image you can see the stack’s composition when chall is about to finish (calling ret). In order to reach flag we must overwrite chall’s return address. We must write 0x84 padding bytes to reach ebp-0x4, write ebx’s expected value (remember we are working with 32-bit little endian), another 0x4 bytes of padding to reach the return address and, finally, the address of flag. We must continue writing data into memory so we accordingly set the parameters of flag. And here is where is most useful the drawing of the stack from flag’s perspective (right side of the image).
Please be aware of the cell separation that exists between ebp and flag’s first argument (even though this memory cell corresponds to what would have been flag’s return address, we couldn’t care less about it at this point). This is how flag expects the stack to be set-up and we must comply with it. This cell separation must be considered when overflowing chall’s buffer.
After all this reasoning, the script that I used and successfully exploits the binary is the following one. BEAR IN MIND you can test your exploit locally by creating a spurious flag.txt.
# RazviOverflow
# Python3
from pwn import *
flag_address = 0x08048576
ebx_original_value = 0x0804a000
arg1 = 0x1337
arg2 = 0x247
arg3 = 0x12345678
payload = b"A"*0x84 + p32(ebx_original_value) + b"B"*0x4 + p32(flag_address) + b"C"*0x4 + p32(arg1) + p32(arg2) + p32(arg3)
binary = process("./hidden_flag_function_with_args")
#binary = remote("XXX", 00000)
print(binary.recv())
binary.sendline(payload)
binary.interactive()
Executing the exploit successfully leaks the flag. The execution ends with errors, but we do not care about a clean execution. We have the flag :)
Now, executing it remotely should leak the actual flag to validate the challenge.
I hope you enjoyed my write-up. I’d be delighted to know whether it helped you progress and learn new things. Do not hesitate to reach me out via Twitter. I’m always eager to learn new things and help others out :)
More challenges
- Click here to see 247CTF index.