我的学习笔记大量都是基于 CTF-WIKI。
UAF,Use After Free 的缩写,是一种常见漏洞。
# 原理
Use After Free 意如其名,是一个内存漏洞,当一个内存块被释放后又被使用,有以下几种情况:
- 内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
- 内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。
- 内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。
# 例题
链接:https://pan.baidu.com/s/1ItYklH0FyT0CNrs7Jjumuw
提取码:F1re
首先纵观全局,调试一下后,还原一下结构体:
1 | struct note { |
分析一下,系统一共有几个函数:
①:addnote: 系统先申请一个 0x10 的结构体,其中前八个字节拿来存放 prev_size 和 size,后八个字节拿来存放两个地址,分别是结构体里函数的地址和 content 所在的地址。接着用户可以申请一个自定义大小的堆块,拿来存放 content 数据。
②:printnote:代码如下,会去 print 出结构体第二个指针指向的地址里的内容,也就是 content 的内容。
1 | return puts(*(const char **)(a1 + 4)); |
③:del_note:漏洞出现的地方,程序在 free 后没有把指针置为 NULL。存在 UAF 漏洞。
④:print_note:调用 printnote 函数输出 content 内容。
以及系统有一个后门函数 magic
1 | return system("cat flag"); |
那思路就很明确了,我们需要控制程序流程到该后门函数。目的也很明确了,只能去改写结构体里函数的地址为后门函数地址。
# 具体思路
①:首先创建两个 note:note0 与 note1,这两个 note 的 size 大小只要不与 0x8 相同就行。这里我申请的 0x10
原因:避免后期申请 note2 的时候取 note0 或者 note1 的 content 堆段,就达不到修改结构体里指针的效果。
效果:
②:先 free 掉 note0,再 free 掉 note1。
原因:注意 note0 与 note1 里都有 0x10 大小的堆块和 0x content size 大小的堆块。具体为什么要先 free 掉 note0 再 free 掉 note1 后面一起说。
效果:
同时现在 fastbin 里出现了相应的空闲堆块。
③:再申请一个 0x8 大小的 note3。
由于创建用户申请的堆块前会申请一个结构体:
1 | *(¬elist + i) = malloc(8u); |
这个堆块会直接从 fastbin 里取一块 0x10 大小的堆块。
且 fastbin 的每个 bin 采取 LIFO 策略,最近释放的 chunk 会更早地被分配
由于 print_note 有检查非空机制:
1 | if ( *(¬elist + v1) ) |
因此我们最终是只能 print note0 的,所以我们的就得修改 note0 结构体堆块里的指针,因此我们之前需要后 delete note1,让 note1 的空闲堆块先被分配,接下来才是把 note0 的空闲堆块分给 content。也就是说,现在我们把 note0 的指针段变成了 note3 的 content,因此可以修改。
# exp:
1 | from pwn import* |