Spoiler-Free Guide to ROP Emporium :: Challenges 0 - 4

Introduction

This is part one of the two-part walkthrough series we’re writing for the wonderful challenges on ROP Emporium. As always, we’re offering spoiler-free hints and tips for those of you who just want some help without the entire challenge being spoiled!

There are eight challenges in total. This part covers the first four.

General advice

  • If you’re just starting out, I recommend you read the Beginner’s Guide at least twice before proceeding to the challenges.
  • Fully read each challenge’s page first. It’s full of essential and helpful tips (I needed to hear this too).
  • If you need a good workflow to enumerate the binaries, I recommend the following:
# this extracts strings cleaner than `strings` does
# comes with radare2
rabin2 -z <challenge> 

# in gdb
checksec # to see what protections it has
info function # to see what functions we can use to our advantage

ret2win

This challenge covers the most basic form of ROP. Return to ret2win to win. If you’re feeling lost, I recommend reviewing the calling conventions on all the platforms that you’re doing the challenge on.

Remember that a basic ROP is just an overflow in which we gain control of the EIP and we simulate the stack frame for functions that we want to call.

x86

Hint (how to find overflow offset)

With gdb-peda :

pattern create 200 200.txt creates a pattern file of 200 characters.

r < 200.txt pipes the pattern into the program.

You’ll see a SIGSEGV along with the address that it segfaults on.

pattern offset <segfault address> determines the offset!

Hint (skeleton of a payload)

<offset> <function to return to>

x64

Hint (how to find overflow offset)

Using the same workflow that we use for x86, you’ll find that gdb doesn’t tell us which address it segfaults on anymore on x64. Instead, we can examine $rbp with x $rbp, find the offset for it with pattern offset <address>, and add 8 bytes to this offset to get our true EIP overwriting offset. This is because $rbp+8 is typically where the return address resides (review stack frames for more details).

Hint (skeleton of a payload)

It’s the same as on x86.

split

This challenge covers calling a function with one argument!

So the author tells us that system and /bin/cat flag.txt are already present in the binary. All we need to do is to find them and stitch them together.

Hint (how to find /bin/cat flag.txt)

While running the program in gdb, you can find a string with the command find <string>.

x86

Hint (skeleton of a payload)

<offset> <function #1> <function #2> <arguments to function #1>

The offset is the usual "A" * 44. Function #1 gets executed after the overflow (because we overwrote the original return address at this position) with its arguments, then function #2 gets executed. Function #2 can just be "AAAA" if you don’t care about exiting gracefully.

x64

Hint (skeleton of a payload)

It’s mostly the same with x86, but x64 passes arguments in rdi, rsi, rdx, rcx registers and so on, which means you need to pop or mov your arguments into the right registers first, then you can call the function.

So for calling a function with one argument: <offset> <address of pop rdi> <argument #1> <function #1>. Calling a function with multiple arguments is covered later.

Hint (how to find useful gadgets)

With gdb-peda:

ropsearch "pop rdi"

callme

x86

The challenge in this one is to chain function calls. In a single chain we’d do

"A" * 44  + first_function_address + second_function_address + func_1_arguments

But if we want to chain two functions, after the first one executes and returns, the stack will look like this

second_return_address + func_1_arguments

Ok, the program pops second_return_address and executes it, now the stack is

func_1_arguments

Which obviously will stop the chain because it’s not a valid function.

So the solution is to use pop gadgets to clean up the stack frame. For example, take a look at this two-function chain.

# pop_2 can point to a gadget like pop ecx ; pop edx ; ret
"A" * 40 + first_function + pop_2 + first_func_arg_1 + first_func_arg_2 
+ second_function + junk_return_address + second_func_arg

After first_function executes, the stack becomes

pop_2 + first_func_arg_1 + first_func_arg_2 
+ second_function + junk_return_address + second_func_arg

The pop_2 pops the next two arguments which have already been used by first_function. Now the stack is

second_function + pop_1 + second_func_arg

Which executes second_function just fine! And with this technique, we can construct a ROP chain however long we’d like (as long as there is enough space on the stack for it).

x64

The solution for x64 is surprisingly easier than the x86 one, because on x64 we don’t pass arguments on the stack, we do it via the registers. This means we don’t actually have to clean anything up and we can just chain on forever!

Hint (how to pass arguments on x64)

We pass them in registers rdi rsi rdx rcx r8 r9, in this order.

Hint (how we can find gadgets to help us)

Thankfully, the author put some useful gadgets in the aptly named function usefulGadgets. In gdb, dissassemble the function with disas usefulGadgets.

write4

In the previous challenges, the arguments (“flag.txt”, etc.) have always been conveniently in the executable, ready for us to reuse to our benefit. This time, it’s different. This challenge covers manually writing arbitrary data to memory.

How might we execute this arbitrary write? Say we’d like to write the string “bird” to the start of the data segment. One of the simplest chains could be:

pop eax ; "bird"
pop ebx ; address of the start of the data segment
mov [ebx], eax ; move "bird" into the address

x86

Hint (how do I write a bigger string?)

You append the same chain to the payload again, but this time the address that you’re writing to should be increased by 4 bytes. Repeat this until you’ve written the whole string 4 bytes at a time.

Hint (where should I write the string?)

Check for sections with write privileges using readelf -S ./write4. For example, you could write it to the start of the GOT section in this one.

Hint (skeleton of a payload) (slight spoiler)

<offset> <arbitrary write for "flag"> <arbitrary write for ".txt"> 
<address of print_file> <junk return address> <address of "flag.txt" that you wrote>

x64

Tip: you can write an 8-byte string in one go because we’re on x64.

If you like my approach to these ROP challenges, maybe you’d like my fun & interactive cybersec courses too :)