This binary exploitation challenge is, at the moment of writing this write-up, rated as EASY with a difficulty score of 2.16 out of 5.0. Its description states the following:
Can you control this applications flow to gain access to the hidden flag function?
As per usual with PWN challenges, we are given a binary to reverse and analyze. You can use whatever reversing tool or framework better suits your needs. There are fantastic tools like Radare2, Cutter, Ghidra, Rizin, GDB (with its many flavors like gdp-peda or pwngdb) or the all-mighty IDA, each and every one with their own advantages and drawbacks.
Upon downloading the binary, it’s time to analyze it and find out what it really is. Using the
file command, we find out it’s a
That’s cool! Being a not stripped binary actually makes our lives as reverse engineers easier since the binary will have some debug information. If you wonder what a stripped or not stripped binary is you can find more information here (Google it, there are plenty of results). Now that we know what architecture we’re dealing with (32-bit) we can start thinking about our exploit and its main differences with 64-bit (4-bytes addresses, arguments are passed via stack, etc).
rabin2 -zzzq commands, we can clearly see there are references to flag. As we will later find out, there is a function called
flag, a flag message and a flag.txt file that somebody will try to open. It is always recommended to use these commands to get a first impressions of what the binary’s behavior will be.
Before starting the actual reverse engineering process, it’s always recommended to execute the binary.
It requests for input and if it’s long enough we can actually break the program. It means we can actually overwrite some stack addresses. Let’s find out!
The next step was finding out what protection was the binary compiled with. In order to do so, you can use what tool you find most convenient. One widespread tool for such purpose is checksec.
I will be using Cutter to reverse engineer the binary. Using Cutter we can inspect its protections:
We find out it’s indeed a 32 bit ELF binary and it has no canaries, it’s not a PIC/PIE binary but NX bit is enabled. NX bit (from no-execute bit) basically doesn’t allow the execution of code on certain regions of memory like the stack. That is, we cannot execute our shellcode if there is a buffer overflow. In case someone is wondering about ASLR: when dealing with PWN challenged you must ALWAYS assume ASLR is enabled on the remote side.
Continuing with the binary, it’s time to explore its declared functions:
There are two functions that easily draw the attention:
flag. Now let’s start from the beginning: exploring the
There is nothing special about it, just a message being printed with
puts and a call to
chall. Notice how
flag function isn’t called.
In case you are not familiar with scanf, I recommend you to read its man page. It basically reads input, tries to parse it according to a given format specified and stores the result in the specified address. In fact, these are the two arguments the function takes. Remember that we are working with a 32-bit architectures. Arguments are passed vya the stack and they are pushed in reverse order. That is, last argument is pushed first.
In the images below you can see the instructions within the
scanf is vulnerable to buffer overflow, we must take note where does the function store the data (or the user input). In other words, where in memory (what address) does the function start writing. It starts at
ebp-0x48. We don’t need to know what specific address that is, it is enough for us to know it’s 0x48 bytes below
ebp because we also know that right above
ebp (that is
ebp+0x48 we can
flag function before assuming it’s the right place to jump to.
It definitely is. It opens a file (we can assume it’s named flag.txt at this point, but the correct move would be to actually check it), gets its contents and prints them.
Our objective is to make it return to
flag (hijacked flow) instead of
main (legitimate execution). Now, how do we do that? REMEMBER that
scanf function will start writing at address
ebp-0x48 and we will have to input
To sum it up, this is the scenario we’re dealing with:
One important thing is getting the address of
flag function. Since the binary is not PIC/PIE (position independent), the .text section addresses won’t change between executions. That is, we can hardcode the address
In order to exploit the binary I will be using pwntools.
# RazviOverflow # Python3 from pwn import * # Getting flag address static = ELF("./hidden_flag_function") flag_address = p32(static.symbols['flag']) payload = b"A" * 0x48 + b"B" * 4 + flag_address # Exploiting the binary binary = process("./hidden_flag_function") #binary = remote("xxx", 0000) print(binary.recv()) binary.sendline(payload) print(binary.recvall())
When you try to execute the exploit for the first time you may find it appear not to be working:
That’s because you may find yourself (as I was) executing the exploit locally and you don’t have a file called
flag.txt. Remember that’s what the function
flag was looking for. Create the file using a fake flag and you’ll see the exploit actually works :)
Now it’s just a matter of executing it remotely, replacing
remote(address, port) in the python exploit.
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.