House of pig

文章发布时间:

最后更新时间:

文章总字数:
2k

预计阅读时间:
8 分钟

前言

针对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函数

image-20230512125333840

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

image-20230512125536275

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

image-20230512125720119

先进行了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
2
3
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结构体了 尝试是否能够打通 下面是我自己一直在用的调试源码 可以自行编译 里面包含了大部分常见的漏洞 可以给复现很大帮助

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#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链表上的过程这里就不过多赘述了 还需要根据题目情况来完成

直接来看如何伪造

image-20230512132557309

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

image-20230512133542732

可以发现和rdi寄存器有关系 而rdi寄存器经过动调发现 rdi的值是用来伪造file结构体的堆块的chunk头的prev_size域 使其最后一位不为1即可

接下来 顺利来到malloc函数 申请多大的chunk和rdi寄存器有关系 索引一下发现和r12寄存器有关系

image-20230512133833992

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

image-20230512134020160

image-20230512134335350

同时rbx的值和rdi是一样的 所以这里r14的值和r12也可以被我们赋值 那么这里r12的值就要为

*malloc_size = (r12-r14) 2 + 0x68

由于free_hook-0x10放入的tcachebin链表范围很大 所以这里的size其实不用太担心 接着往下看

memcpy在gdb中的形式是这个函数 我也不知道啥原因

总之si进去看看怎么个赋值法

image-20230512135113532

image-20230512135201186

由这四行汇编决定 其中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需要注意

image-20230512141633604

rsi的值需要大于rcx才行 setz这条汇编指令把ZF标志位的值传给了cl寄存器 这里我试了下貌似都是1 所以rcx最后的值为r12-r14+1

也就是0x19 那么rsi的值只要大于这一个就可以了 而rsi的值有rbx+0x20处决定

那么还有最后一步了 就是调用free函数时的rdi寄存器

image-20230512142039411

这个地址相当于chunk的size域 覆盖的时候替换为/bin/sh即可

那么到这里覆盖就结束了 下面是模板 一些地址偏移自己注意一下

1
2
3
4
5
6
payload = cyclic(0x10)+ p64(2) +b'/bin/sh\x00'  #从低地址处的一个chunk堆溢出覆盖 所以cyclic可以忽略掉 从p64(2)开始覆盖fake file的prev_size域
fake_file = p64(system_addr)*2
fake_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