Buffer Overflow Attacks ROM Emporium Challenge Solutions Return Oriented Programming Buffer Overflow Attack

Return Oriented Programming – callme– ROP Emporium

Before I dive into this challenge, I would like to cover some of the core concepts related to this challenge. When I was solving this challenge, I wonder what if a user does not have a fundamental idea about things cover in this challenge. If you’re familiar with the concepts such as how lazy binding, linkers or procedure linkage table works then feel free to skip the first and and look at the Walkthrough.

Little Intro on Linkers

The job of linker is to convert the object file into executables and shared libraries. When you write a program in C/C++, the compiler translate this code into something that machine can understand known as assembly code. Now, assembler turn this code into an object file directly. In an old days, when people used to write assembly code, the assembler generates an executable file which machine can executes directly. Since the development of new languages and shared libraries created, there was a need of running assembler at two different times and combined the output of the shared librarier and program into sinble executable file. This new program become known as linker which links the multiple object file into single executables.

When the program loads into the memory, it would automatically loads the limited number of linkers which would link the program with the shared libraries. So, the linkers that runs when the program starts is known as dynamic linkers.

What does Linker is responsible for?

  • Read the input object file and decided the length and type of the content. Also, Reading the symbols.
  • Preparing the symbol table containing all the symbol.
  • Linking undefined symbol to their definations.
  • Deciding where should all the symbols go in the output executable file which means deciding where they should go in memory when the program runs.

Walkthrough

You must call the callme_one(), callme_two() and callme_three() functions in that order, each with the arguments 0xdeadbeef, 0xcafebabe, 0xd00df00d e.g. callme_one(0xdeadbeef, 0xcafebabe, 0xd00df00d) to print the flag. For the x86_64 binary double up those values, e.g. callme_one(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d).

Okay, so we know that the binary will have at least these three functions present and we have to call each one in the specific order with the specific argument in order to print the flag. Let’s check out the memory protections settings using checksec.

performing checksec on the ROP Emporium binary to verify if the stack execution is enabled or not.

Looking at the output above, other than our usual things, we are seeing “RUNPATH: b’.'”. Which means the run path of the binary is being inspected and it has the list of directories that dynamic linker will use to search for shared libraries when the program runs. So in our case, we have a directory value set to (.) which means to find the shared libraries from the directory from where the binary executes. Let’s look at the list of functions to make sure we have those three functions presents in the binary.

getting the list of functions attach to the binary using gdb

Let’s run the binary and finding the offset. This is one of the common process when dealing with Buffer Overflow exploit and having an accurate offset value is so important in order to make sure your exploit works. I have covered the detail steps on what offset is in my previous blogs.

creating pattern using pattern_create on kali to verify the offset value
Verifying the offset value for buffer overflow exploit using pattern_offset

Attack Plan

Okay so we have our offset of 44. So we have to provide 44 characters of input then padding and EBP following to that we will need to call callme_one(), callme_two() and callme_three() function along with the specific arguments provided in the hints. We know from the hint that the function will take three arguments as 0xdeadbeef, 0xcafebabe, 0xd00df00d. So in order to do it, we will need to use ROPgadgets and locate three pop instruction with a ret instruction. We need an address of all three function, an address of the three POP with RET instruction. First let’s find out our ROPgadget.

Locating the memory address using ROPgadget for memory instructions

Now, let’s find out the memory address of all three function (callme_one, callme_two and callme_three). Usually you can use the ‘p’ command to get the memory address of the function however, in this scenario, we will need to grab the calling address from the Procedure Linkage Table. The calling address is located in the ‘usefulFunction’ so if you simply disassemble the ‘usefulFunction, you will find that address.

disassembling the function using gdb

So we are going to use following address for the functions.

Function NameFunction Address
callme_one0x80484f0
callme_two0x8048550
callme_three0x80484e0
Calling address for all three functions.

Using the offset, padding, function address and arguments we can form up one liner to capture the flag for the 32-bit binary.

One liner exploit for callme challenge of ROP emporium

If the one-liner is too long then we can create exploit for the same binary.

Working exploit of callme challenge ROP Emporium 32 bit binary to capture the flag.

We are using pwn library within python and setting up our addresses. Running the above exploit will also get us the same flag.

callme challenge exploit capturing the flag

x64 Architecture

The offset for the 64 bit binary is 40. So Other than changing the offset and memory addresses, everything remains the same for the exploit.

exploit code for 64 bit binary callme challenge ROP emporium

Running the exploit above will get us the flag as well.

callme 64 bit exploit successfully runs

If you notice that we have a different order in the payload variable. For 32 bit binary we have the following order:

payload = buffer + callme_1 + pop3ret + arguments
payload += callme_2 + pop3ret + arguments
payload += callme_3 + pop3ret + arguments

In the 32-bit exploit, arguments are typically passed on the stack, and the order in which they’re pushed onto the stack matters. So, we construct our payload to first push the arguments onto the stack in the correct order and then call the function. So I have push the arguments onto the stack [arguments], then return to the pop3ret gadget, which pops the arguments off the stack and finally returns control to the callme_1 function.

Where as for 64 bit binary, we have the following order:

payload = buffer + pop3ret + arguments + callme_1
payload += pop3ret + arguments + callme_2
payload += pop3ret + arguments + callme_3

In the 64-bit exploit, the first few function arguments are typically passed in registers (e.g., rdi, rsi, rdx, etc.) rather than on the stack. So, we don’t need to push the arguments onto the stack before calling the function. Instead, we place the arguments in the appropriate registers before returning to the function address. So, we first return to the pop3ret gadget, which will pop the arguments into registers (rdi, rsi, rdx), and then you directly call the callme_1 function.

  • In 32-bit exploitation, arguments are typically passed on the stack, so you push the arguments onto the stack before calling the function.
  • In 64-bit exploitation, the first few arguments are passed in registers, so you prepare the arguments in registers before calling the function.

@Ringbuffer

Some of the latest blogs