好好说话之Tcache Attack(3):tcache stashing unlink attack

tcache stashing unlink attack这种攻击利用有一个稍微绕的点,就是small bin中的空闲块挂进tcache bin这块。弯不大,仔细想想就好了

往期回顾:
好好说话之Tcache Attack(2):tcache dup与tcache house of spirit
好好说话之Tcache Attack(1):tcache基础与tcache poisoning
好好说话之Large Bin Attack
好好说话之Unsorted Bin Attack
好好说话之Fastbin Attack(4):Arbitrary Alloc
(补题)2015 9447 CTF : Search Engine
好好说话之Fastbin Attack(3):Alloc to Stack
好好说话之Fastbin Attack(2):House Of Spirit
好好说话之Fastbin Attack(1):Fastbin Double Free
好好说话之Use After Free
好好说话之unlink

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

tcache stashing unlink attack

首先从名字就可以看出这种方法与unlink有关,这种攻击利用的是tcache bin中有剩余(数量小于TCACHE_MAX_BINS)时,同大小的small bin会放进tcache中,这种情况可以使用calloc分配同大小堆块触发,因为calloc分配堆块时不从tcache bin中选取。在获取到一个smallbin中的一个chunk后,如果tcache任由足够空闲位置,会将剩余的smallbin挂进tcache中,在这个过程中只对第一个bin进行了完整性检查,后面的堆块的检查缺失。当攻击者可以修改一个small bin的bk时,就可以实现在任意地址上写一个libc地址。构造得当的情况下也可以分配fake_chunk到任意地址

例题:how2heap中的tcache stashing unlink attack

例题源码如下,稍作改动,去掉了一些不影响执行流程的输出代码:

  1 //gcc -g -no-pie hollk.c -o hollk
  2 //patchelf --set-rpath 路径/2.27-3ubuntu1_amd64/ hollk
  3 //patchelf --set-interpreter 路径/2.27-3ubuntu1_amd64/ld-linux-x86-64.so.2 hollk
  4 #include <stdio.h>
  5 #include <stdlib.h>
  6 #include <assert.h>
  7 
  8 int main(){
  9     unsigned long stack_var[0x10] = {0};
 10     unsigned long *chunk_lis[0x10] = {0};
 11     unsigned long *target;
 12 
 13     setbuf(stdout, NULL);
 14     
 15     printf("stack_var addr is:%p\n",&stack_var[0]);
 16     printf("chunk_lis addr is:%p\n",&chunk_lis[0]);
 17     printf("target addr is:%p\n",(void*)target);
 18 
 19     stack_var[3] = (unsigned long)(&stack_var[2]);
 20 
 21     for(int i = 0;i < 9;i++){
 22         chunk_lis[i] = (unsigned long*)malloc(0x90);
 23     }
 24 
 25     for(int i = 3;i < 9;i++){
 26         free(chunk_lis[i]);
 27     }
 28     
 29     free(chunk_lis[1]);
 30     free(chunk_lis[0]);
 31     free(chunk_lis[2]);
 32     
 33     malloc(0xa0);
 34     malloc(0x90);
 35     malloc(0x90);
 36     
 37     chunk_lis[2][1] = (unsigned long)stack_var;
 38     calloc(1,0x90);
 39 
 40     target = malloc(0x90);
 41 
 42     printf("target now: %p\n",(void*)target);
 43 
 44     assert(target == &stack_var[2]);
 45     return 0;
 46 }

简单的描述一下这个程序的执行流程:首先创建了一个数组stack_var[0x10],一个指针数组chunk_lis[0x10],一个指针target。接下来调用setbuf()函数进行初始化。接着调用printf()函数打印stack_var、chunk_lis首地址及target的地址。接下来将stack_var[2]所在地址放在stack_var[3]中。接着循环创建8个size为0xa0大小的chunk,并将八个chunk的malloc指针依序放进chunk_lis[]中。然后根据chunk_lis[]中的堆块malloc指针循环释放6个已创建的chunk。接下来依序释放chunk_lis[1]、chunk_lis[0]、chunk_lis[2]中malloc指针指向的chunk。然后连续创建三个chunk,第一个size为0xb0,第二个size为0xa0,三个size为0xa0。接下来将chunk_lis\[2]\[1]位置中的内容修改成stack_var的起始地址,接着调用calloc()函数申请一个size为0xa0大小的chunk。最后申请一个size为0xa0大小的chunk,并将其malloc指针赋给target变量,并打印target

由于我们在编译的时候使用了-g参数,所以在使用gdb进行调试的时候可以在代码行下断点。首先我们在第19行下断点,查看一下打印出来的各个变量的地址:

在这里插入图片描述

可以看到stack_var的起始地址为0x7fffffffdde0,chunk_lis的起始地址为0x7fffffffde60,target的起始地址为0x7ffff7de01ef。这里建议拿个小本本记一下这三处地址,后面在查看内存变化的时候会反复查看,也可以现在就看一下三个地址内部的情况。接下来我们将断点下在第21行,使程序执行stack_var[3] = (unsigned long)(&stack_var[2]);这段代码:

在这里插入图片描述

这里我们看一下stack_var数组内部的情况,在stack_var[3]位置中的内容被修改成了stack_var[2]的地址,接下来我们将断点下在第29行,执行两个for循环:

在这里插入图片描述

这里强调一下,第二个for循环中起始释放的chunk的下标为3,所以释放是从第4个chunk开始的。上图是释放之后bin中的情况,由于我们使用patchelf将程序执行glibc的版本修改为2.27,所以存在tcache机制。被释放的chunk会进入tcache bin中,由于在第一个for循环中我们创建的都是size为0xa0大小的chunk,所以释放之后都会进入tcache bin中0xa0这条单项链表中。这里注意看链表中其实只有6个被释放块,但是tcache链表存放被释放块数量的最大值为7,所以此时tcache并不是满状态

接下来将断点下在第33行,依序释放chunk_lis[1]、chunk_lis[0]、chunk_lis[2],我们再来看一下bin中的情况:

在这里插入图片描述

可以看到在释放chunk_lis[1]的时候chunk2作为最后一个进入tcache的chunk填满了整条链表,接下来再继续释放size为0xa0的堆块的话就不会在进入此条单向链表了。由于chunk_lis[0]、chunk_lis[2]中malloc指向的chunk的size都为0xa0,所以超过fastbin max size,所以会进入unsorted bin中,上图可以看到此时chunk1与chunk3已经进入了unsorted bin中。接下来我们将断点下在第34行,申请一块size为0xb0大小的chunk,我们在看一下bin中的情况:在这里插入图片描述

由于unsorted bin存取机制的原因,如果此时申请一个size为0xb0大小的chunk,unsorted bin中如果没有符合chunk size的空闲块(chunk3、chunk1的size小于0xb0),那么unsorted bin中的空闲块chunk3和chunk1会按照size落在small bin的0xa0链表中。接下来我们将断点下在第37行,完成两次申请size为0xa0大小的chunk

在这里插入图片描述

这样一来由于tcache bin中又满足size为0xa0的空闲块,所以chunk2和chunk4就被重新启用了。那么此时bin中就形成了tcache bin中存在5个空闲块,small bin中存在2个空闲块的情况了,后面去讲为什么这样去部署。接下来我们将断点下在第38行,执行chunk_lis[2][1] = (unsigned long)stack_var;这条语句:在这里插入图片描述

chunk_lis[2][1] = (unsigned long)stack_var;这条语句是这样执行的,首先chunk_lis[2]的位置就是存放chunk3的malloc指针的位置,那么chunk_lis[2][1]指的就是以chunk3头指针为起始位置,向后第二个地址位宽的位置,即chunk3的bk位置。chunk3_bk中的内容就被修改成了stack_var的头指针,这个时候我们可以看一下bin中的状况:

在这里插入图片描述

由于chunk3是unsorted bin中最后一个chunk,且chunk3的bk被修改成了stack_var的头指针,所以,stack_var会被认为是紧跟着chunk3之后释放的一个chunk:

在这里插入图片描述

那么接下来我们将断点下在第40行,调用calloc函数申请一个size为0xa0大小的chunk:

在这里插入图片描述

这里说明一下为什么要使用calloc进行申请chunk,这是因为calloc在申请chunk的时候不会从tcache bin中摘取空闲块,如果这里使用malloc的话就会直接从tcache bin中获得空闲块了。那么在calloc申请size为0xa0大小的chunk的时候就会直接从small bin中获取,那么由于small bin是FIFO先进先出机制,所以这里被重新启用的是chunk1

这个时候就到了前面理论部分描述的内容了:在获取到一个smallbin中的一个 chunk 后会如果 tcache 仍有足够空闲位置(tcache中有两个位置,chunk3和stack_var刚好够落在这两个位置),剩下的 smallbin 从最后一个 stack_var(0x7ffffffddf0)开始顺着bk链接到 tcachebin 中 ,在这个过程中只对第一个 chunk3进行了完整性检查,后面的stack_var的检查缺失。这样一来就造成上图的效果,stack_var就被挂进了tcache bin的链表中

接下来我们将断点下在第44行,最后申请一个size为0xa0大小的chunk,并将其malloc指针赋给target变量,并打印target:

在这里插入图片描述
由于stack_var处于tcache链表的最后一个,所以在申请size为0xa0大小的chunk的时候,stack_var就会被重新启用

例题后补~

在这里插入图片描述

  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

hollk

要不赏点?

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

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

打赏作者

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

抵扣说明:

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

余额充值