题目来源
2018强网杯线上赛-pwn,下载。
漏洞类型
gets()函数导致栈溢出,覆盖指向堆块的指针,可以实现任意地址读和任意地址写。
分析过程
create_role函数当中有两处使用了gets函数,第一反应就是栈溢出。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
35int (__fastcall **create_role())(__int64 a1)
{
_QWORD *v0; // rbx@1
_QWORD *v1; // rbx@1
size_t v2; // rax@1
__int64 v3; // rbx@1
int (__fastcall **result)(__int64); // rax@1
__int64 v5; // rcx@1
char s; // [sp+0h] [bp-1A0h]@1
_QWORD *v7; // [sp+80h] [bp-120h]@1
char *name; // [sp+100h] [bp-A0h]@1
__int64 v9; // [sp+188h] [bp-18h]@1
v9 = *MK_FP(__FS__, 40LL);
v0 = (_QWORD *)operator new(0x20uLL);
clear_chunk((__int64)v0);
v7 = v0;
*v0 = role_information;
puts("Your name:");
gets(&s);
v1 = v7;
v1[2] = strlen(&s);
v2 = strlen(&s);
name = (char *)malloc(v2);
strcpy(name, &s);
v7[1] = name;
puts("N punch?");
gets(&s);
v3 = (__int64)v7;
*(_DWORD *)(v3 + 24) = atoi(&s); // 任意地址写
role_information((__int64)v7);
result = (int (__fastcall **)(__int64))v7;
v5 = *MK_FP(__FS__, 40LL) ^ v9;
return result;
}
这个函数会创建一个role结构体,包含一个role_information函数指针,name字符串的指针,字符串的长度和punch大小:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
240x557538398c10 FASTBIN {
prev_size = 0,
size = 49,
fd = 0x55753627db30,
bk = 0x557538398c50,
fd_nextsize = 0x8,
bk_nextsize = 0x6f
}//role结构体
0x557538398c40 FASTBIN {
prev_size = 0,
size = 33,
fd = 0x4141414141414141,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x203a1
}//name字符串
pwndbg> x/8xg 0x557538398c20
0x557538398c20: 0x000055753627db30//role_information函数指针
0x0000557538398c50//name字符串地址
0x557538398c30: 0x0000000000000008//字符串的长度
0x000000000000006f//punch大小
0x557538398c40: 0x0000000000000000 0x0000000000000021
0x557538398c50: 0x4141414141414141 0x0000000000000000
create_role函数的工作过程:
1: 首先创建一个0x30大小的role结构体chunk,role chunk的地址存于栈上;
2: 在结构体的第一个8字节空间写入role_information函数指针;
3: 利用gets函数读入name字符串,存于栈上;计算name的长度,将长度写入role结构体的第三个8字节空间;
4: 创建name字符串的chunk,将name chunk的地址写入role结构体的第二个8字节空间;
5: 利用gets函数读入punch的大小,先保存在栈上,和name字符串在栈上的位置相同;将punch转换为整型,存入role结构体的第四个8字节空间
role chunk的地址和name(punch)存储在栈上的位置:1
2
3
4
5
6
7
8
9
10
11
12pwndbg> x/18xg $rbp-0x1a0
0x7ffee2bbd660: 0x00007ffee2bbd7b0 //name punch开始写入的位置
0x00007f3a1ce78640
0x7ffee2bbd670: 0x0000000000000000 0x000055753647f020
0x7ffee2bbd680: 0x000055753627da00 0x000055753647f028
0x7ffee2bbd690: 0x000055753627da00 0x00007ffee2bbd920
0x7ffee2bbd6a0: 0x0000000000000000 0x0000000000000000
0x7ffee2bbd6b0: 0x00007ffee2bbd840 0x00007f3a1cc7bac6
0x7ffee2bbd6c0: 0x0000000000000001 0x0000000000000000
0x7ffee2bbd6d0: 0x00007ffee2bbd7e0 0x00007f3a1c529108
0x7ffee2bbd6e0: 0x0000557538398c20 //role chunk的地址
0x00007f3a1cc83eef
从上面可以看出,如果在第二步通过栈溢出将role chunk的地址改写为其他的地址,便可以将后续的name字符串地址和字符串的大小以及punch的大小都写入修改后的位置当中;
结合role_information函数,便可以实现地址泄漏:1
2
3
4int __fastcall role_information(__int64 a1)
{
return printf("<%s> said he can kill the boss with %lx punches\n", *(_QWORD *)(a1 + 8), *(_DWORD *)(a1 + 24));
}
如果在第五步通过栈溢出将role chunk的地址改为其他的地址,便可以将punch的值写入修改后的地位偏移0x18字节处,实现任意地址写。
利用过程
在进入create_role函数之后,heap已经被使用了一部分,使得开始创建chunk的开始地址后两个字节刚好为0x10,所以,需要先创建一个role,其name大小为0x70。
这样,第二次创建role时,name字符串的地址后两个字节刚好为0x00,同时通过栈溢出部分覆盖role chunk的地址使得name字符串的地址存放于地址结尾为0x00xx处。
第三次创建role时,需要进行两次栈溢出,第一次刚好将role chunk地址的后两个字节覆盖为0x00,这样后续便是在第二次创建的name chunk上进行写入操作;
第二次栈溢出将role chunk的地址修改为第二次创建role时栈溢出修改的role chunk地址,结尾是0x00xx,在通过role_information函数进行输出时,会将写入第二个name chunk的第三个name chunk的地址打印出来,实现地址泄漏。
后续的操作很常规,覆盖got,调用system(“/bin/sh”)。
1 | from pwn import * |