堆块chunk介绍&堆溢出漏洞的unlink利用原理 您所在的位置:网站首页 溢出漏洞原理 堆块chunk介绍&堆溢出漏洞的unlink利用原理

堆块chunk介绍&堆溢出漏洞的unlink利用原理

2024-03-24 15:35| 来源: 网络整理| 查看: 265

堆块chunk介绍&堆溢出漏洞的unlink利用原理 chunk结构

当进程动态分配内存时,系统会在堆中创建一个chunk(堆块)。chunk包含chunk头和chunk体两部分

chunk头中有两个字段:

prev_size:前一个chunk的size,前指的之前分配的内存,也就是低地址相邻的chunk size:当前chunk的size,size字段的低3位A,M,P不用于计算size,其中末位的PREV_INUSE字段表示前一堆块是否正在使用(1为使用,0为空闲)

chunk体分为两种情况

对于非空闲/正在使用的堆块,chunk体就是当前堆块存放的数据。 空闲的堆块会被一个双向链表(空闲链表)连接。chunk体的前两个size_t的空间存放两个指针fd和bk,fd指向链表中的前一个chunk(低地址),bk指向链表中的后一个chunk(高地址)。之后的空间是空闲的,即全0

image

chunk结构代码如下

struct chunk{ size_t prev_size; size_t size;//低3位为A,M,P union{ struct{ chunk* fd; chunk* bk; }; char userdata[n]; } } chunk注意事项 在64位系统下,size_t为8字节 使用size_t *p = (size_t*)malloc(0x80)分配堆空间后返回的指针p指向的不是chunk的基址&chunk,而是&chunk+2*sizeof(size_t),即跳过了prev_size和size。所以实际上chunk的大小还要加上chunk头,为0x80+0x8*2=0x90 在编写代码时,往往无法获得chunk结构的对象,要想修改chunk的fd和bk的值,需要使用*(&chunk+2*sizeof(size_t)) = value和*(&chunk+3*sizeof(size_t)) = value,或者使用chunk[2]和chunk[3]来修改 当PREV_INUSE=1时,prev_size无意义,一般填0即可 chunk的free过程

如果我们要free一个chunk,设其指针为p,free(p)会进行如下操作:

检查物理上与p前后相邻的chunk是否也是空闲的 如果是,则将空闲的chunk与p进行合并,将空闲的chunk从空闲链表中删除(unlink),将合并后的chunk加入空闲链表

如果free的chunk大小小于0x80,则会放入fastbin,不会进行合并操作

unlink(p)时,首先会进行一个检查

// fd bk if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ malloc_printerr (check_action, "corrupted double-linked list", P, AV); \

然后执行四句代码完成删除

FD=p->fd; BK=p->bk; FD->bk=BK; BK->fd=FD; 堆溢出漏洞的unlink利用原理

由上面的介绍可知,free一个堆块时会在链表中unlink原来空闲的chunk,而unlink的四句代码可以被利用来进行非法操作,方法如下

首先需要堆中有连续的两个chunk_p和chunk_f(假设size均为0x90个字节),还需要可以控制指针p。然后在chunk_p中构造一个略小的fake_chunk,其基址=p,和chunk_p只差一个chunk头,fake_chunk大小为0x80。现在要将fake_chunk伪装成一个空闲的chunk,进行如下操作

通过堆溢出(0x80/8=16,所以是修改p[16]和p[17])设置f->prev_size=sizeof(fake_chunk), f->PREV_INUSE=0,这让系统认为f的前一个chunk是fake_chunk,并且是空闲的 设置fake_chunk->prev_size=0,fake_chunk->PREV_INUSE=1,这让系统认为fake_chunk前面的chunk不是空闲的。prev_size=0的原因参见注意事项(3) 设置fake_chunk->fd=&p-3*sizeof(size_t),fake_chunk->bk=&p-2*sizeof(size_t),这是利用unlink的关键。这里&p是存放p这个指针的内存地址,也就是存放fake_chunk基址的地址

此时堆结构如下 image

现在,执行free(f)。系统先检查与f相邻的fake_chunk,发现是空闲的,所以要从链表中unlink(fake_chunk),四行代码会执行为:

FD=fake_chunk->fd;//FD=&p-3*sizeof(size_t) BK=fake_chunk->bk;//BK=&p-2*sizeof(size_t) FD->bk=BK;//*(&p-3*sizeof(size_t)+3*sizeof(size_t))=BK BK->fd=FD;//*(&p-2*sizeof(size_t)+2*sizeof(size_t))=FD

最后一行执行后会使得*(&p)=&p-3*sizeof(size_t),也就是说unlink使得p指针指向了&p-3*sizeof(size_t)这个地址,一般来说这个地方是处于bss节的。这时候如果再向fake_chunk写入数据,程序就会访问到&p-3*sizeof(size_t),并向其中写入数据。

另外,之所以要在chunk_p中偏移两个size_t构造fake_chunk而不是直接将chunk_p改造为fake_chunk,是因为要绕过unlink(fake_chunk)(也就是unlink(&p))时的检查

// fd bk if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ malloc_printerr (check_action, "corrupted double-linked list", P, AV); \

必须使得FD->bk==BK->fd==&p,我们构造的fake_chunk会使得FD->bk=&p-3*sizeof(size_t)+3*sizeof(size_t)=&p,成功绕过这个检查。

实验

该程序来自https://bbs.kanxue.com/thread-273402.htm

#include size_t* a = NULL; size_t* b = NULL; size_t* c = NULL; size_t* p = NULL; size_t* f = NULL; int main() { p = malloc(0x80); f = malloc(0x80); malloc(0x10); //set f->PREV_INUSE = 0 p[17] = 0x90;//*(f-1) = 0x90; //set f->prev_size = 0x80(fakechunk size) p[16] = 0x80;//*(f-2) = 0x80; //fakechunk p[0] = 0; p[1] = 0x81; p[2] = &a; p[3] = &b; //unlink printf("&a = %p\n", &a); printf("&b = %p\n", &b); printf("&c = %p\n", &c); printf("&p = %p\n", &p); printf("&f = %p\n", &f); printf("p = %p\n", p); printf("f = %p\n", f); free(f); printf("p = %p\n", p); if(&a == p) { printf("hack!!!!\n"); p[0] = 0x11111111; p[1] = 0x22222222; p[2] = 0x33333333; p[3] = 0x44444444; printf("a = %p\n", a); printf("b = %p\n", b); printf("c = %p\n", c); printf("p = %p\n", p); } return 0; }

编译:gcc -g test.c -o test

IDA打开elf可以看到全局变量a,b,c,p,f的地址按顺序存放在ELF的.bss节,两两间隔sizeof(size_t)=8B。这样在设置fake_chunk的fd和bk时,直接赋值&a和&b即可。

image

使用gdb看一下构造完fake_chunk后的堆结构,f的PREV_INUSE已经置为0,说明fake_chunk空闲

image

fake_chunk和f修改如下 image

运行程序,可以看到free(f)后p指向的位置确实发生了变化,改变为了&a,之后成功通过p[0]~p[3]来修改该处的内存数据。

&a = 0x5561b7fde018 &b = 0x5561b7fde020 &c = 0x5561b7fde028 &p = 0x5561b7fde030 &f = 0x5561b7fde038 p = 0x5561b85ed010 f = 0x5561b85ed0a0 p = 0x5561b7fde018 hack!!!! a = 0x11111111 b = 0x22222222 c = 0x33333333 p = 0x44444444

最后要注意的是,该程序需要通过2.27以下的glibc运行(我用的是2.23),2.27以后的glibc会在free(f)时将fake_chunk放入bins中,导致无法与f合并,指针p也就没法改变了。glibc版本切换的方法参考我的文章https://www.cnblogs.com/nemuzuki/p/17290156.html image



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有