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
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
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
Notice how, in the image above,
ebp is assigned from the stack right before
chall function finises (instruction at address
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-0x4we must preserve the expected
- The return address of
challmust be overwritten with the return address of
- When executing
flag, the following must be true:
ebp+0x08 = 0x1337,
ebp+0xc = 0x247and
ebp+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
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
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
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 :)
- Click here to see 247CTF index.