前言#
针对calloc函数不能申请tcachebin的一种利用链 通过伪造file结构体 调用io_str_overflow函数来获取shell
总体的伪造难度不是很大 强烈建议先只看io链的调用 不看我的解析 然后尝试自己伪造file结构体 可以很大程度提高能力
调用链分析#
这条链的触发形式 还是跟高版本下的链差不多 通过覆盖IO_list_all 来在堆上伪造file结构体 从而覆盖到vtable
不过本链的vtable并不需要我们伪造 原本的vtable是 IO_file_jumps指针 如果触发IO_flush_all_lockp函数 那么就会调用到IO_file_jumps对应偏移处的IO_file_overflow函数

与之对应的 如果我们将vtable修改为IO_str_jumps呢 那么索引到的就会是 IO_str_jumps函数

这个函数就是我们今天所利用的关键 ida打开2.31的libc文件 来看一下函数内部进行了什么样的操作

先进行了malloc 再利用memcpy往申请到的堆空间写入数据 再次调用free函数
那么这里就存在一个致命的漏洞 我们刚才说到 这条链是使用于没有malloc函数 只有calloc函数 无法调用到tcachebin中的chunk 但是我们可以通过Tcache stashing unlink attack 来把hook函数写入到tcachebin中 接着利用这里面的malloc申请出来 同时利用memcpy来覆盖hook函数 最后 如果我们覆盖的是free_hook 再伪造v4参数为/bin/sh 就可以触发system(“/bin/sh”)
那么总结一下 我们为了伪造链 并触发其 需要做到:
1.覆盖IO_list_all为堆地址2.往tcachebin链表中写入free_hook-0x10(之所以要减去0x10 后面会提及)3.构造fake_file 覆盖vtable为IO_str_jumps 伪造IO_str_overflow需要的参数 触发system同时第二步需要注意 我们在Tcache stashing unlink attack中提到 要想把fake chunk放到tcachebin上 还需要满足fake chunk+0x8处为一个可写地址 所以实际上我们要利用largebin attack任意写两次
看到这里 你就可以试着自己去伪造一下file结构体了 尝试是否能够打通 下面是我自己一直在用的调试源码 可以自行编译 里面包含了大部分常见的漏洞 可以给复现很大帮助
#include<stdio.h>#include<stdlib.h>int chunk_time =0;int chunk_size[50];char *chunk_ptr[50];void init(){ setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 2, 0);}void gift(){ size_t puts_addr = (size_t)&puts; printf("%p",(void *)puts_addr);}void menu(){ puts("Life is fucking movie"); puts("Life is always full of unhappiness, like this question"); puts("Anyway, what's your answer"); puts(">");}void add(){ int index; char size[20]; puts("What do you really want?"); if(chunk_time<=32&&chunk_time>=0){ if(!chunk_ptr[chunk_time]){ printf("This is the %dth choice in your life\n",chunk_time); puts("You can customize the size of chunk, but what about your life"); read(0,size,0x8); chunk_size[chunk_time] = atoi(size); chunk_ptr[chunk_time] = malloc(chunk_size[chunk_time]); chunk_time++; }else{ puts("error"); exit(0); } }else{ exit(0); puts(""); }}void add2(){ int index; char size[20]; puts("What do you really want?"); if(chunk_time<=32&&chunk_time>=0){ if(!chunk_ptr[chunk_time]){ printf("This is the %dth choice in your life\n",chunk_time); puts("You can customize the size of chunk, but what about your life"); read(0,size,0x8); chunk_size[chunk_time] = atoi(size); chunk_ptr[chunk_time] = calloc(1,chunk_size[chunk_time]); chunk_time++; }else{ puts("error"); exit(0); } }else{ exit(0); puts(""); }}void delete(){ char data[100]; int index; puts("I didn't set the pointer to zero, just like some things can't be repeated"); scanf("%d",&index); free(chunk_ptr[index]);}void edit(){ int index; int size; puts("It's never too late to start again. What do you regret?"); scanf("%d",&index); puts("You all know that there can be overflows here, so why do you set limits on your life?"); scanf("%d",&size); puts("Come back!"); read(0,chunk_ptr[index],size);}void show(){ puts("You can't live a perfect life without making any effort"); int index; scanf("%d",&index); puts(chunk_ptr[index]);}int main(){ int choice; init(); puts("This program is used to debug heap vulnerabilities"); puts("write by chen"); while(1){ menu(); scanf("%d",&choice); switch(choice){ case 1: add(); break; case 2: delete(); break; case 3: edit(); break; case 4: show(); break; case 5: gift(); break; case 6: add2(); break; case 7: puts("The fog of that morning cleared, not only in the morning, but also in the fog"); puts("You will be stronger next time I see you"); exit(0); break; } }}接下来的详细伪造 我也会以此程序举例
伪造过程分析#
如何覆盖IO_list_all和把free_hook-0x10放到tcachebin链表上的过程这里就不过多赘述了 还需要根据题目情况来完成
直接来看如何伪造

如果al寄存器的二进制形式最后一位为1 就会跳转 显然这里不能让他跳转 所以要控制一下rax寄存器的值 往上朔源一下

可以发现和rdi寄存器有关系 而rdi寄存器经过动调发现 rdi的值是用来伪造file结构体的堆块的chunk头的prev_size域 使其最后一位不为1即可
接下来 顺利来到malloc函数 申请多大的chunk和rdi寄存器有关系 索引一下发现和r12寄存器有关系

r12的初始值由rbx+0x40处的地址决定 随后减去了r14的值 最后malloc申请的大小等于r12*2 + 0x64


同时rbx的值和rdi是一样的 所以这里r14的值和r12也可以被我们赋值 那么这里r12的值就要为
*malloc_size = (r12-r14) 2 + 0x68
由于free_hook-0x10放入的tcachebin链表范围很大 所以这里的size其实不用太担心 接着往下看
memcpy在gdb中的形式是这个函数 我也不知道啥原因
总之si进去看看怎么个赋值法


由这四行汇编决定 其中vmovdqu可以操作128位的数据 也就是0x10字节 至于是采用xmm0寄存器来赋值 还是xmm1寄存器 我们来看一下决定地址的rdi寄存器,rsi寄存器,rdx寄存器
回顾一下进入memcpy函数前的三行汇编 rdi的值和rax有关系 rdx的值和r12有关系 而影响xmm寄存器的rsi和r14有关系
那我们来屡一下思路 首先 rdi+rdx-0x10 一定要能覆盖到free_hook 结合上面所说的 可以操作128位的数据 所以最后的地址为free_hook-0x8是最后的底线了
rdi的值没有办法更改 和rax挂钩 而rax是用来伪造的堆的chunk头起始地址 所以rdx为0x18是一个比较合适的值
那么还有一个rsi寄存器的值 和r14挂钩 我们之前不是说过 r12还要减去r14 然后再计算成为malloc的参数嘛 所以r14这里要想给xmm寄存器赋值system函数的真实地址(用来覆盖free_hook) 就需要r14是一个存放system地址的地址 那么这里自然是一个堆地址合适 可以供我们控制
那么r12的值就是r14+0x18 那么这里各个寄存器的值都确定下来了以后 还有一个cmp需要注意

rsi的值需要大于rcx才行 setz这条汇编指令把ZF标志位的值传给了cl寄存器 这里我试了下貌似都是1 所以rcx最后的值为r12-r14+1
也就是0x19 那么rsi的值只要大于这一个就可以了 而rsi的值有rbx+0x20处决定
那么还有最后一步了 就是调用free函数时的rdi寄存器

这个地址相当于chunk的size域 覆盖的时候替换为/bin/sh即可
那么到这里覆盖就结束了 下面是模板 一些地址偏移自己注意一下
payload = cyclic(0x10)+ p64(2) +b'/bin/sh\x00' #从低地址处的一个chunk堆溢出覆盖 所以cyclic可以忽略掉 从p64(2)开始覆盖fake file的prev_size域fake_file = p64(system_addr)*2fake_file += p64(0)+p64(0x200)+p64(0)+p64(heap_addr+0xb30-0x8)+p64(heap_addr+0xb30-0x8+0x18)fake_file = fake_file.ljust(0xc8,b'\x00')fake_file += p64(io_str_jump)payload += fake_file