split
Combining ret2win techniques to make multiple jumps.
Last updated
Combining ret2win techniques to make multiple jumps.
Last updated
This challenge will be a step above the first, where we must jump between multiple links before jumping to a final function.
Unlike other binaries, I am going to make a preliminary check first. You can do this across any binary to find interesting information before you start.
I want to check the strings
to see the hardcoded strings in the binary. This can often be a valuable way to see not only the strings in the binary but also the functions that are present. We'll look around for these strings as we navigate the binary to ensure we understand the control flow.
Here are the most important things I found with strings split
:
Here's what we found:
Since function names are just ASCII text and are also hard coded, we can find those.
We see that /bin/cat flag.txt
is called, as well as /bin/ls
. ls
is a new one we haven't expected to see, so we need to carefully watch for what that does.
Let's run the binary and see what happens:
This is performing as expected. It's crashing for large input. We're not seeing the ls
or cat flag.txt
call yet, so let's go hunting.
We'll disassemble these the same way we disassembled rop2win: writing equivalent C code as we go.
Our primary functions are usefulFunction
, pwnme
, and main
. We'll start with main()
:
Then we'll do usefulFunction
:
Well, we found the ls
call. Where's the cat flag.txt
call? Let's check pwnme
to see if it's there:
We don't have cat flag.txt
here. What are we going to do? We need to consider the attack vector.
The attack vector is the method we use to exploit the binary. The goal here is to understand how we need to exploit the binary. Take a minute to think about how you would do this yourself before considering my solution.
The key here is that we don't have to jump to the start of a function. We can choose to jump in the middle of a function if this benefits us more. Therefore, our attack vector looks something like this:
Overflow the buffer to overwrite the return pointer and control execution.
Find a way to control rdi
to pass a parameter into a function.
Load rdi
with the address of /bin/cat flag.txt
.
Jump immediately to the call to system
with rdi
loaded so that system("cat flag.txt")
gets called.
With some good foresight, we can see that this will take 3 jumps to do. We'll call each jump a link inside the chain.
Now, let's gather everything we need for the attack.
We need to find the following:
The size of the padding.
A gadget to write to rdi
.
The address of /bin/cat flag.txt
The address of the system
instruction.
Getting the Padding
Getting the padding is the easiest part. We start writing to rbp-0x20
, so we must write 0x20+0x8
bytes to reach the return pointer.
Getting the Gadget
We only need one major gadget: the ability to write to rdi
. We want to write to rdi
from the stack. This calls for a gadget that does the following:
Let's understand why this works.
pop rdi
takes the first item off the stack and stores it in the register provided (rdi
). This loads the desired value.
ret
continues our control of the return pointer in the same way we controlled it prior.
The lack of further instructions improves our chances of not overwriting data that will cause the program to crash.
How do we find gadgets? There are two popular choices for programs that find gadgets: ropper
and ROPgadget
. Both are equally useful, and I often find myself using them interchangeably. I have provided both program syntax and output; you can choose your personal preference.
To show all gadgets, we can use:
Radare2 also supports finding gadgets. However, I find it to be less useful than the other two. This is more useful when searching for specific functionalities rather than checking for availability. When applicable, I show the Radare2 command as well.
This will show a long list of every gadget that the libraries can find. We can filter this one of a few ways:
The third instruction is ROPgadget
exclusive, and lets you only show binaries with specific instruction sets. Based on all these instructions, we should find the following gadget:
The left is the address of the instruction, and the right is the gadget. This is exactly what we needed!
Finding the String
gdb
makes this process super easy. Since the string is hardcoded, we can just search for it in memory. We can do this with the search-pattern
:
This shows us the string is at 0x601060
.
Finding the system
Instruction
We know this is inside usefulFunction
. We want to use this rather than going directly to better simulate the code truly executing this instruction.
Let's take each step and put it together.
Step 1: Padding
We need to write 0x20+0x8
bytes to reach the return pointer. We can do this with:
Step 2: Gadgets
We need a gadget to call ret
.
We need to write the gadget to the stack. We can do this with:
Step 3: Go to System
We need to write the address of system
to the return pointer. We can do this with:
Now, to put it together.
If we run this, we'll get the flag!
Please make sure that everything we did in this binary makes sense. This is the foundation of ROP and it only gets harder from here.