Exploit code, not people.

Root page Twitter View on GitHub

247CTF - Hidden Flag Function

Posted on 11 Feb 2021.
247ctf assembly ctf tutorial walkthrough debug reverse engineering exploiting pwn binary exploitation hidden flag function buffer overflow


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 32-bit binary and it is not stripped.

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).

After executing strings and 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: chall and flag. Now let’s start from the beginning: exploring the main function.

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.

Inspecting chall function:

The input is read with scanf. In this context, it is mandatory for us to know that scanf is a vulnerable function. Another scanf vulnerability example.

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 teal lines correspond to the buffer and those within red lines correspond to the format specifier.

Since 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+x04 in 32-bit) is stored the return address of the function. Overflowing the buffer and overwriting ebp+0x48 we can hijack the program’s execution flow and control where chall returns.

Let’s inspect 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 writing in memory happens from lower addresses towards higher ones. That is, our vulnerable scanf function will start writing at address ebp-0x48 and we will have to input 0x48 (72 in decimal) bytes to reach ebp, 4 more to overwrite it and reach the return address and, finally, the address (4 bytes) to hijack the execution flow and thus jumping wherever we want. In case you are not familiar with how stack behaves, I recommend you reading some beginner 32-bit exploiting tutorials like the legendary Aleph One’s Smashing The Stack For Fun and Profit published in Phrack, and many others like 1, 2, 3. I also recommend LiveOverflow’s exploiting videos.

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 0x08048576.

In order to exploit the binary I will be using pwntools. PLEASE BEAR IN MIND that the data you want to overwrite and place on the stack must be LITTLE ENDIAN. Pwntools’ packing (p32() or p64()) functions already convert the numbers for us.

# 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)

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 process with 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 :)

More challenges