UAF漏洞

概述:

UAF漏洞全称:use after free,翻译过来也就是说当一个指针所指向的指针块被释放掉之后可以再次被使用,但有一定的前置条件:

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

我们一般称被释放后没有被设置为NULL的内存指针为dangling pointer(悬空指针)

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

示例:

我们以ctfshow pwn141为例子

pwn141

分析:

提示:使用已释放的内存

  • 远程环境:Ubuntu 18.04

check

1
2
3
4
5
6
7
8
9
10
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
SHSTK: Enabled
IBT: Enabled
Stripped: No

程序是一个图书管理系统存在打印flag的函数

分析程序

main:

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
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int n4; // eax
char buf[4]; // [esp+0h] [ebp-10h] BYREF
unsigned int v5; // [esp+4h] [ebp-Ch]
int *p_argc; // [esp+8h] [ebp-8h]

p_argc = &argc;
v5 = __readgsdword(0x14u);
init();
logo();
while ( 1 )
{
menu();
read(0, buf, 4u);
n4 = atoi(buf);
if ( n4 == 4 )
exit(0);
if ( n4 > 4 )
{
LABEL_12:
puts("Invalid choice!");
}
else
{
switch ( n4 )
{
case 3:
print_note();
break;
case 1:
add_note();
break;
case 2:
del_note();
break;
default:
goto LABEL_12;
}
}
}
}

存在3个选项,添加条目,删除条目,打印条目

add函数:

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
unsigned int add_note()
{
int v0; // esi
int i; // [esp+Ch] [ebp-1Ch]
int size; // [esp+10h] [ebp-18h]
char buf[8]; // [esp+14h] [ebp-14h] BYREF
unsigned int v5; // [esp+1Ch] [ebp-Ch]

v5 = __readgsdword(0x14u);
if ( count <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !*(&notelist + i) )
{
*(&notelist + i) = malloc(8u);
if ( !*(&notelist + i) )
{
puts("Alloca Error");
exit(-1);
}
**(&notelist + i) = print_note_content;
printf("Note size :");
read(0, buf, 8u);
size = atoi(buf);
v0 = *(&notelist + i);
*(v0 + 4) = malloc(size);
if ( !*(*(&notelist + i) + 4) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *(*(&notelist + i) + 4), size);
puts("Success !");
++count;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full!");
}
return __readgsdword(0x14u) ^ v5;
}

两个malloc申请了两个chunk,chunk1大小为8被赋值为一个打印函数,chunk2的大小取决于输入,用于存储读入的content信息

打印函数

1
2
3
4
int __cdecl print_note_content(int a1)
{
return puts(*(a1 + 4));
}

delete函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned int del_note()
{
int count; // [esp+4h] [ebp-14h]
char buf[4]; // [esp+8h] [ebp-10h] BYREF
unsigned int v3; // [esp+Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("Index :");
read(0, buf, 4u);
count = atoi(buf);
if ( count < 0 || count >= count )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&notelist + count) )
{
free(*(*(&notelist + count) + 4));
free(*(&notelist + count));
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}

第一个if用于判断index是否超过chunk数量的范围

第二个if如果所选择的chunk不为空则free掉add中申请的两个chunk但是free后并没有将指针置零,存在uaf漏洞

如何修复 uaf 漏洞只需free后将指针置零即可

1
2
free(*(&notelist + v1));
*(&notelist + v1) = NULL;

print函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned int print_note()
{
int count; // [esp+4h] [ebp-14h]
char buf[4]; // [esp+8h] [ebp-10h] BYREF
unsigned int v3; // [esp+Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("Index :");
read(0, buf, 4u);
count = atoi(buf);
if ( count < 0 || count >= count )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&notelist + count) )
(**(&notelist + count))(*(&notelist + count));
return __readgsdword(0x14u) ^ v3;
}

第一个if和del中的一样

第二个if就是我们漏洞利用的关键,程序会以index选择的组的chunk1当作函数执行,chunk2当作参数输出

正常情况下chunk1存储的是print_onte_content函数

我们要通过uaf漏洞将chunk1改为后门函数地址,再一次调用print_note即可

那么要如何利用呢?我们动调一步一步判断

动调: