好好说话之Fastbin Attack(1):Fastbin Double Free

好像拖更了好久。。。实在是抱歉。。。。主要是fastbin attack包含了四个部分,后面的例题不知道都对应着哪个方法,所以做完了例题才回来写博客。fastbin attack应该也会分四篇文章分开写。学了这么长时间pwn给我最大的感受就是量变确实可以引起质变,现在做题或者学习新的技术的时候可以隐约的进行以点看面的思考,通过某一处问题会在心里想象可能会引发什么什么样的后果。总的来说不后悔入坑pwn,精确到字节之间的环环相扣让我为之着迷!

编写不易,如果能够帮助到你,希望能够点赞收藏加关注哦Thanks♪(・ω・)ノ

往期回顾:
好好说话之Use After Free
好好说话之unlink
好好说话之Chunk Extend/Overlapping

Fastbin Attack

简单的介绍一下,我们可以根据名字看出,这一类漏洞利用的方法主要基于fastbin机制的缺陷,其实fastbin attack并不是指某一种利用方法,而是一些。这类利用的前提是:

  • 存在堆溢出、use-after-free等能控制chunk内容的漏洞
  • 漏洞发生于fastbin类型的chunk中

如果细分的话,可以做如下的分类:

  • Fastbin Double Free
  • House of Spirit
  • Alloc to Stack
  • Arbitrary Alloc

前两种主要漏洞侧重于利用free函数释放的真的 chunk或伪造的chunk,然后再次申请chunk进行攻击,后两种侧重于故意修改fd指针,直接利用malloc申请指定位置chunk进行攻击。这篇文章主要讲解的是Fastbin Double Free,后几种会在之后文章中继续讲解

原理

fastbin attack存在的原因在于fastbin时使用单向链表来维护释放的堆块的,并且由fastbin管理的chunk即使被释放,其next_chunk的prev_inuse位也不会被清空。我们用一个例子来看一下fastbin是怎么管理空闲chunk的,并演示这两处问题:

  1 //gcc -g hollk1.c -o hollk1
  2 #include<stdio.h>
  3 int main(void)
  4 {   
  5     void *chunk1,*chunk2,*chunk3;
  6     chunk1 = malloc(0x30);
  7     chunk2 = malloc(0x30);
  8     chunk3 = malloc(0x30);
  9     
 10     free(chunk1);
 11     free(chunk2);
 12     free(chunk3);
 13     return 0;
 14 }

这是一个非常简单的程序,创建了3个0x30大小的chunk,并且依次释放。因为在编译阶段使用-g参数,所以我们在第10行下断点,我们看一下在内存中三个chunk的部署情况:

在这里插入图片描述

接下来我们在第13行下断点,看一下释放之后内存以及fastbin中的情况:
在这里插入图片描述
可以看到,由于创建的三个chunk的大小为0x30,那么自然而然的在释放后进入fastbin。由于fastbin是以单向链表的形式管理释放chunk的,所以可以在图1中看到chunk只有fd位置具有指针,并且指向前一个chunk的prev_size

可以看一下图2的最后,会有一个白色的<-- 0x0的标识,这证明chunk1前面已经没有被释放的chunk了。那么就会存在一个疑问:在fastbin中后一个被释放的chunk的fd指向前一个被释放的chunk的prev_size,那么谁指向最后一个被释放的chunk3的prev_size呢?其实是main_arena指向了chunk3的prev_size

另外有一点需要注意,我们往前翻,看一下在你为释放阶段chunk的prev_inuse标志位为1,回来在看释放后的内存情况,chunk的prev_inuse位依然还是1不变

Fastbin Double Free

可以从名字上直译可以解读为fastbin中的chunk被释放两次,事实上并不仅限于两次,也可以多次,因此被释放的chunk可以在fastbin链表中存在多次。这样导致的后果是多次分配可从fastbin链表中取出同一个堆块,结合堆块的数据内容可以实现类似于类型混淆(type confused)的效果

Fastbin Double Free能够成功利用主要有两部分的原因:

  • fastbin的堆块被释放后next_chunk的prev_inuse位不会被清空
  • fastbin在执行free的时候仅验证了main_arena直接指向的块,即链表指针头部的块。对于链表后面的块并没有进行验证

演示说明

  1 //gcc -g hollk2.c -o hollk2
  2 #include<stdio.h>
  3 int main(void)
  4 {
  5     void *chunk1,*chunk2,*chunk3;
  6     chunk1=malloc(0x10);
  7     chunk2=malloc(0x10);
  8 
  9     free(chunk1);
 10     free(chunk1);
 11     return 0;
 12 }

一个简单的小程序,创建了两个0x10大小的chunk,但是在释放的时候释放了两次chunk1,我们执行一下看结果:

在这里插入图片描述
可以看到在执行过程中程序会检测到SIGABRT信号,紧接着进入核心转储,程序中断。这是因为_int_free函数好检测到了fastbin的double free。如何检测的呢?我们前面说过fastbin在执行free的时候仅验证了main_arena直接指向的块,即链表指针头部的块。也就是说在释放chunk1后main_arena直接指向的就是chunk1,这个时候再去释放chunk1,无异于顶枪口搞事情,被抓理所应当

那么如果我们在释放两次chunk1中间添加一个释放chunk2呢?

  1 //gcc - g hollk3.c -o hollk3
  2 #include<stdio.h>
  3 int main(void)
  4 {
  5     void *chunk1,*chunk2,*chunk3;
  6     chunk1=malloc(0x10);
  7     chunk2=malloc(0x10);
  8 
  9     free(chunk1);
 10     free(chunk2);
 11     free(chunk1);
 12     return 0;
 13 }

编译好后,使用gdb调试,首先在第11行下断点,我们一起看一下fastbin中的情况:

在这里插入图片描述

可以看到在释放chunk2之后main_arena直接指向的是chunk2而不是chunk1,但是由于_int_free只检查main_arena前面的chunk是否被释放,即chunk2是否是释放状态,所以我们再一次释放chunk1的时候_int_free就会放行,我们在12行下断点,使程序再一次释放chunk1,一起看一下会有什么样的结果

在这里插入图片描述
可以看到,chunk1再一次被释放,并且又重新被挂进fastbin链表中。我们在前面讲过gdb中fastbin链表中最后白色的标识证明chunk2前面还是存在一个释放后被挂进fastbin中的堆块的

在这里插入图片描述

需要注意的是chunk1再一次被释放,此时你可以将chunk1看作一个新的块,也就意味着chunk1作为chunk2的后一个块被释放,那么此时chunk1的fd的值并不是0,而是指向chunk2。那么这个时候如果我们可以控制chunk1的内容,就可以修改fd指针,从而实现在任意地址分配fastbin块。

为了更好的看到上面描述的样子,我们再看一个例子

  1 //gcc -g hollk4.c -o hollk4
  2 #include<stdio.h>
  3 
  4 typedef struct _chunk
  5 {
  6     long long pre_size;
  7     long long size;
  8     long long fd;
  9     long long bk;
 10 } CHUNK,*PCHUNK;
 11 
 12 CHUNK bss_chunk;
 13 
 14 int main(void)
 15 {
 16     void *chunk1,*chunk2,*chunk3;
 17     void *chunk_a,*chunk_b;
 18 
 19     bss_chunk.size=0x21;
 20     chunk1=malloc(0x10);
 21     chunk2=malloc(0x10);
 22 
 23     free(chunk1);
 24     free(chunk2);
 25     free(chunk1);
 26 
 27     chunk_a=malloc(0x10);
 28     *(long long *)chunk_a=&bss_chunk;
 29     malloc(0x10);
 30     malloc(0x10);
 31     chunk_b=malloc(0x10);
 32     printf("%p",chunk_b);
 33     return 0;
 34 }

简单的描述一下这个程序,首先创建了一个CHUNK结构体,每个结构体成员都是以long long类型创建的,分别是prev_sizesizefdbk。接着定义了一个size大小为0x21大小的结构体指针bss_chunk,然后申请了两个0x10大小的堆块chunk1、chunk2。接着依次释放了chunk1和chunk2,并再一次释放chunk1。这个时候重新申请一个0x10大小的堆块chunk_a,并且将chunk_a的fd位置设置为bss_chunk结构体指针。接着连续申请三个0x10大小的chunk,第三个chunk指针付给chunk_b,接着打印出chunk_b的地址

在编译好这个程序后,使用GDB打开,我们在23行下断点查看一下内存中的部署:

在这里插入图片描述

可以看到内存中存在chunk10x555555758000和chunk20x555555758020两个堆块,这里需要注意的是bss_chunk这个结构体其实并不是在内存中的,由于结构体指针是以全局变量的形式被创建的,所以结构体指针会被放置在bss段。我们用用ida打开这个程序看一下:

在这里插入图片描述
可以看到结构体指针bss_chunk所在位置为bss段,并且偏移为0x201040,这个偏移的基地址为代码段起始,所以我们在gdb中输入命令vmmap查看一下代码段的起始位置:

在这里插入图片描述
可以看到最上面绿色框中就是代码段的起始地址,那么:

0x201040 + 0x555555554000 = 0x555555755040

我们在gdb中查看一下0x555555755040这个地址:

在这里插入图片描述
可以看到结构体bss_chunk确实是在这个位置的

接下来,我们在第27行下断点查看一下在释放后fastbin中的情况:

在这里插入图片描述
由于在chunk1被二次释放了,所以chunk1在第二次释放后再一次被挂在了fastbin中,并且此时chunk1的fd指向的是chunk20x555555758020。接下来将断点下在28行:

在这里插入图片描述
可以看到在重新申请一个0x10大小的chunk_a的时候,被重新启用的是挂在fastbin最后面的chunk1而不是chunk2。记下来我们在29行下断点,这一步会将刚刚创建的chunk_a的fd位置改为bss_chunk结构体指针:

在这里插入图片描述
接下来连续的申请了三个0x10的chunk,首先申请的第一个0x10就应该是此时fastbin中最后main_arena指向的chunk2,第二个0x10就应该是此时fastbin中最后main_arena指向的chunk1。关键点在第三个0x10的chunk,我们将断点下载地址第31行:

在这里插入图片描述
可以看到此时是fastbin中还会有一个0x10大小的chunk,这个就是我们在bss段自定义的bss_chunk结构体,为什么会这样呢?

在这里插入图片描述

我们一起来看上面这张图,重新捋一遍这个流程:

  • 首先经过double free之后fastbin中的单向链表为chunk1_double --> chunk2 --> chunk1在经过一次malloc申请后main_arena指向的chunk1_double被重新启用,即chunk1倍重新启用,main_arena指向chunk2,并且将chunk1的fd从原来的指向chunk2修改为指向结构体指针chunk1 --> bss_chunk,也就是说在fastbin单向链表中bss_chunk已经作为chunk1前一个被释放的块的存在了
  • 接下来第二次malloc申请后main_arena指向的chunk2被启用,main_arena重新指向chunk1
  • 第三次malloc申请后chunk1再一次被启用,main_arena指向chunk的fd指向的bss_chunk
  • 那么在第四次malloc申请的时候此时main_arena指向的bss_chunk就会被启用

虽然bss_chunk并不是在内存中正常申请的chunk,但是由于我们可以修改chunk1的fd,使他指向bss_chunk,那么即使bss_chunk位于bss段,也会被当作一个chunk来被启用

那么在程序第31行代码中chunk_b其实被赋予的就是bss_chunk的结构体指针,所以在第32行输出的时候实际输出的是bss_chunk的chunk地址:

在这里插入图片描述

在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

hollk

要不赏点?

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

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

打赏作者

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

抵扣说明:

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

余额充值