堆漏洞挖掘中last remainder的深度剖析

一、什么是last remainder

  • 如果在bins链中存在freechunk时,当我们去malloc的时候,malloc的请求大小比freechunk的大小小,那么arena就会切割这个freechunk给malloc使用,那么切割之后剩余的chunk就被称为“last remainder”。
  • 当产生last remainder之后,表示arena的malloc_state结构体中的last_remainder成员指针就会被初始化,并且指向这个last remainder。

二、last remainder产生的情景(重点)

  • malloc的时候,不论malloc的大小,首先会去检查每个bins链(出去fastbins链)是否有与malloc相等大小的freechunk,如果没有就去检查bins链中是否有大的freechunk可以切割,如果切割,那么就切割大的freechunk,那么切割之后的chunk成为last remainder,并且last remainder会被放入到unsortedbin中。
  • 切割unsortedbin其实就是_int_malloc函数的for大循环的第一步,切割smallbins、largebins其实就是_int_malloc函数的for大循环的第三步(见_int_malloc函数文章:https://blog.csdn.net/qq_41453285/article/details/99005759)。

切割unsortedbin中的大chunk时:

  • 当unsortedbin有对应的freechunk可以给malloc切割使用时,unsortedbin会发生以下步骤:
    • ①先将这些freechunk放置到对应大小的bins链上(放入smallbin或largebin)。
    • ②放置到对应的bins链上之后,切割此freechunk。
    • ③切割之后会产生last remainder,再将last remainder放到unsortedbin上。
  • 例如:
    • 此时,unsortedbin有两个0x800的freechunk。
    • 此时申请一个0x600的chunk。
    • 那么unsortedbin会先consolidate,把两个0x800的freechunk先移动到largebin中。
    • 然后再切割largebin的freechunk,将切割后余留下的0x200放入unsortedbin。

切割smallbins、largebins中的大chunk时

  • ①切割smallbins或者largebins中的大freechunk,产生last remainder。
  • ②将last remainder放入到unsortedbin中。
  • 注意(重点):malloc永远不会去检测切割fastbins(详情见_int_malloc函数的执行顺序)。

三、last remainder的consolidate

  • 概念:当我们切割一个bins链中的大chunk时产生last reminader时,会发生consolidate,但是此种consolidate不会去整理fastbins中的freechunk(重点)。

四、演示案例

#include <stdio.h>
#include<malloc.h>
#include<unistd.h>
#include<string.h>
int main(){
    int size = 0x120;
    void *p = malloc(size);
    void *junk = malloc(size);  //放置释放p和q之后,p和q发生合并
    void *q = malloc(size);
    void *r = malloc(size);   //放置释放q之后,q和topchunk发生合并
	
    printf("p:0x%x\n",p);
    printf("q:0x%x\n",q);
    printf("r:0x%x\n",r);
	
    strcpy(p,"aaaaaaaabbbbbbbb");
    strcpy(q,"ccccccccdddddddd");
    strcpy(r,"eeeeeeeeffffffff");
    
    sleep(0);  //只为了打断点使用,无其他作用
    free(p);
	
    sleep(0);
    free(q);
	
    sleep(0);
    malloc(0x90);
    sleep(0);

    return 0;
}
  • 第一步:打断点到第一个sleep,并运行程序。

  • 第二步:c一下,来到第二个sleep,此时p已经被释放。

  • 第三步:c一下,来到第三个sleep,此时q也被释放。

  • 第四步:c一下,来到第四个sleep,此时申请了一块0x90的chunk,在glibc中其实是申请了0xa0的chunk。
  • 因为p是先被放入unsortedbin的,所以malloc(0x90)时切割的就是p所指的chunk,可以看到原先的0x130的堆块前0xa0给malloc使用了,剩下的0x30就是我们的last remainder
  • 并且通过bins可以看到,此次切割还触发了一次unsortedbin的consolidate,使q所指的chunk被整理到了smallbin中,但是切割之后的last remainder还留在unsortedbin中

  • 第五步:查看一下表示arnea的struct malloc_state结构体的last_remainder指针此时指向了我们的last remainder。

五、演示案例

#include <unistd.h>
#include <malloc.h>

int main()
{
    int size=0x30;
    int size2=0x200;

    int *p1=malloc(size);
    int *p2=malloc(size2);
    int *temp=malloc(size); //防止p2和p3都被释放之后,p2和p3发生合并
    int *p3=malloc(size2);
    int *temp2=malloc(size); //防止p3被释放之后与topchunk合并

    sleep(0);  //只为打断点使用
    free(p1);

    sleep(0);
    free(p2);
    free(p3);

    sleep(0);
    malloc(0x100);

    sleep(0);
    return 0;
}
  • 第一步:打端点到sleep并运行起来。

  • 第二步:c一下释放p1,可以看到其被加到fastbin中。

  • 第三步:c一下,释放p2和p3,可以看到p2和p3都被加到unsortedbin中。

  • 第四步:c一下,又申请了一个0x100的堆块,此时会去切割unsortedbin中的freechunk,并且触发consolidate,但是这个consolidate没有去整理fastbins,fastbins中的chunk没有变动。

六、演示案例

#include <unistd.h>
#include <malloc.h>

int main()
{
    int size=0x300;
    
    int *p1=malloc(size);
    int *p2=malloc(size);  //防止p1被释放之后与topchunk合并
    
    sleep(0);  //为了程序打断点使用,无其他作用
    free(p1);

    sleep(0);
    malloc(0x700);

    sleep(0);
    malloc(0x200);

    return 0;
}
  • 第一步:打断点到sleep函数,运行程序,查看一下当前的堆块。

  • 第二步:n几下,释放p1,可以看到p1被放入unsortedbin中。

  • 第三步:n几下,malloc(0x700)的堆块,此时unsortedbin中的chunk(p1)被整理到smallbins中。

  • 第四步:n几下,然后申请一个0x200的堆块,申请的时候malloc发现smallbins中有满足的chunk可以切割,于是就去切割smallbins中的堆块,并产生last remainder,将last remainder放入unsortedbin中

七、演示案例

#include <unistd.h>
#include <malloc.h>

int main()
{
    int size=0x300;
    
    int *temp=malloc(0x30);
    int *p1=malloc(size);

    //防止p1释放并consolidate到smallbins中之后,再释放p2导致p1和p2合并,合并之后又被放入unsortedbin中
    int *temp2=malloc(0x30);

    int *p2=malloc(0x100); 
    int *p3=malloc(size);//防止p2被释放之后与topchunk合并
    
    sleep(0);  //为了程序打断点使用,无其他作用
    free(p1);

    sleep(0);
    malloc(0x700);
    
    sleep(0);
    free(temp);
    free(p2);

    sleep(0);
    malloc(0x200);

    return 0;
}
  • 第一步:打断点到sleep,运行程序,并查看当前堆块(由于屏幕原因,topchunk没有截图下来)。

  • 第二步:c一下,释放p1。

  • 第三步:c一下,malloc(0x700),将unsortedbin中的p1整理到smallbin中。

  • 第四步:c一下,释放temp和p2,此时temp被放入fastbins中,p2被放入unsortedbin中(注意:如果没有temp2这个堆块,此处释放p2之后,由于p1和p2是物理相邻的chunk,那么p1和p2就会合并然后一起放入到unsortedbin中了)。

  • 第五步:n几下,malloc(0x200),此时malloc会切割smallbins中的p1,并产生last remainder(0x602250),并把last remainder放入到unsortedbin中。由于切割chunk并产生last remainder会发生consolidate,所以unsortedbin中的p2从unsortedbin中整理到smallbins中(由于此种情况的consolidate不会整理fastbins中的chunk,所以fastbins中的chunk仍然在fastbins中)。


  • 我是小董,V公众点击"笔记白嫖"解锁更多【堆漏洞挖掘】资料内容。

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董哥的黑板报

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值