ROP Emporium’s ret2win (x64) with Radare2
Updated 2021: I’ve made some minor tweaks to match updates made to the challenges.
This series is going to focus basic x64 return-orientated programming (ROP), using the fantastic buffer overflow challenges from ROP Emporium. These challenges get progressively harder as they introduce new concepts and force you to be creative. We’ll use Radare2 for all debugging, and in my case, everything will be done in an Ubuntu 18.08 container, though this should work on most *nix systems.
If you have no idea how buffer overflows work, try instead going through Sam Bowne’s series covering the basics. If you’re new to x64 assembly but have a foundation in x32, reading this cheat sheet is a strongly recommended: x64 Cheat Sheet — CS Brown. Finally I’m also going to assume you’re familiar with the basics of Radare2, but if not, first check out my series focusing on teaching it by attacking the CMU Bomb Lab challenge.
CMU Bomb Lab with Radare2 — Phase 1
Hello world. In the interests of putting more Radare2 content out there, here’s a noob friendly intro to r2, for those…
The first challenge is basically a warm-up, so we’ll use it as an excuse to cover some basics of overflows with r2. You can download the ret2win challenge here: https://ropemporium.com/binary/ret2win.zip
First, we should check which stack protections have been set on this binary. Use
rabin2 -I to show binary info
$ rabin2 -I ret2win<SNIP>
nx set to
true, we know shellcode cannot be executed off the stack. With
pic set to
partial, we know the binary has ASLR disabled, but not for the OS. You can check system-wide ASLR by checking the value of
$ cat /proc/sys/kernel/randomize_va_space
2 indicates full ASLR is enabled system-wide, so this confirms return-orientated programming is the best strategy. This protection can be disabled by setting the value to a 0, but ASLR is not an issue for us, so leave it on.
Analyzing the Binary
Run r2 with ret2win as an argument, then use
aaa to analyze the binary. Disassembling the main function with
pdf @ main shows the only interesting call it makes is to
sym.pwnme. Disassemble that function with
pdf @ sym.pwnme.
Picking out the important bits, the function allocates 32 (0x20) bytes of space on the stack with
memset, then copies 56 (0x38) bytes into it from
fgets. It’s worth noting that since the function uses
stdin is terminated only by newlines (0xa) or if it reaches the maximum expected characters. This allows many characters that other vulnerable functions would not, such as null bytes. Read more about fgets here.
Finding offsets with r2
Since we’re dealing with x64 assembly, we want to target
rsp when overflowing the buffer. We will use ragg2 to generate a debruijn pattern (a non-repeating pattern), which can be easily identified in memory, and used to calculate offsets. We know
fgets will only read 58 bytes, so that will also be the length of our pattern.
$ ragg2 -P 58 -r
Copy the output to clipboard, then open ret2win with r2 in Debug mode, seek to
sym.pwnme, then set a breakpoint after the call to
fgets. Let’s choose the return instruction at the end for our breakpoint with
db @ 0x00400755. Use
dc to continue execution and paste in the offset pattern in clipboard.
After hitting the next breakpoint, use
drr to to show the registers, and their references (telescoping). You can also use
pxq 8 @ rsp to print the chars that have overwritten the saved
Copy the hex at
rsp to your clipboard, then type
wopO and paste the hex after to calculate the offset for
We now know how
rsp will be overwritten after sending 40 bytes. Now what?
Analyze the binary again with
aaa, then use
afl to list functions. The obvious choice is
Disassemble it and you’ll see it calls
cat flag.txt as an argument. ret2win itself doesn’t require any arguments, so calling it should be as simple as overwriting
rsp with the address of
sym.ret2win. The payload will be very simple:
[40 chars of junk] + [address of ret2win]
Let’s do this with…perl. Why? Because I don’t like how Python3 handles byte printing, while perl is consistent and ubiquitous. Don’t let Python2 be a crutch, because it’s going away.
perl -e ‘print “\xcc”x40 . “\x56\x07\x40\x00”’| ./ret2win
Printing bytes with perl is very similar to Python, with a few minor differences.
- Instead of
-c command, perl uses
- Instead of
*for multiplication, perl uses
- Instead of
+to concatenate strings, perl uses
Hopefully most of that was review, because next we’ll actually see what return-oriented programming can do in split!
Note to Ubuntu 18 users: You have an additional ‘quirk’ that you have to work around. As ROP Emporium explains:
The version of GLIBC packaged with Ubuntu 18.04 uses movaps instructions to move data onto the stack in some functions. The 64 bit calling convention requires the stack to be 16 byte aligned before a call instruction but this is easily violated during ROP chain execution, causing all further calls from that function to be made with a misaligned stack. movaps triggers a general protection fault when operating on unaligned data, so try padding your ROP chain with an extra ret before returning into a function or return further into a function to skip a push instruction.
Our stack is not 16 byte aligned, so this will crash when it hits certain instructions, many of them being in library function calls like system. You could add an extra return gadget, but in this case by skipping the first instruction (push rbp) the stack aligns fine. Simply add 1 to the target address.
Your payload will be:
perl -e ‘print “\xcc”x40 . "\x57\x07\x40\x00"’| ./ret2win
Annoying, yes but get used to it — each of these challenges has quirks with Ubuntu 18, and they’ll soon be in the other distros.