0x00 前言

纯VM,朝花夕拾

0x01 解

无壳,直接进main函数

这么清晰?直接解了看看

我就知道.jpg

main下面有一大堆vm

可以发现是非常标准的VM

经过重设变量类型(a1应为_DWORD *)和一些分析,可以得出各变量的含义:

1
2
3
4
//a1 = memory
//a1[0] = eip
//a1[502] = stack bottom
//a1[501] = stack pointer

然后在vm_init里面可以找到opcode

image-20241013170316642

直接写解释器看看各条命令,这里的opcode是以4bytes为单位,其中第一个byte为操作码,后面的是立即数之类的东西

我直接一个拆分,一个字节一个字节读,所以我的eip的加加要加4,某些大的立即数也要从后往前读(小端),同时由于第一个值是eip,我也是自己搞一个来,所以有些地址操作要变,当然里面所有的立即数之类的,特别是放进去的地址后面写的时候也要做变换(后面会有注释)

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
import struct
def vm(opcode):
eip=4
stack = []
while True:
if opcode[eip] == 1:
stack.append((opcode[eip+2]<<8)+opcode[eip+1])
ins = f'push {hex((opcode[eip+2]<<8)+opcode[eip+1])}'
eip+=4
elif opcode[eip] == 2:
tmp = stack.pop()
ins = f'pop {tmp}'
eip+=4
elif opcode[eip] == 3:
tmp = stack.pop()+stack.pop()
stack.append(tmp)
ins = f'add, result = {tmp}'
eip+=4
elif opcode[eip] == 4:
tmp = stack.pop() | stack.pop()
stack.append(tmp)
ins = f'and, result = {tmp}'
eip+=4
elif opcode[eip] == 5:
tmp = stack.pop()
stack.append(tmp << opcode[eip+1])
ins = f'lsh, result = {tmp}'
eip+=4
elif opcode[eip] == 6:
tmp = stack.pop()
stack.append(tmp >> opcode[eip+1])
ins = f'rsh, result = {tmp}'
eip+=4
elif opcode[eip] == 7:
ins = 'read'
eip+=4
elif opcode[eip] == 8:
tmp = stack.pop()
ins = f'print {tmp}'
eip+=4
elif opcode[eip] == 9:
ins = f'jmp'
# eip = stack[-1]
eip+=4
elif opcode[eip] == 10:
ins = f'be'
eip +=4
elif opcode[eip] == 11:
ins = f'bl'
eip+=4
elif opcode[eip] == 12:
ins = 'return'
print(ins)
return
print(ins)

with open('./s.txt','rb') as f:
o = f.read()
opcode = struct.unpack('B'*1568,o)

读出来发现不太对,因为中间有bebl这些条件跳转,也有jmp这种普通跳转

涉及到跳转就比较麻烦了,在跳转之前都要进行一次分析看看跳转实现的条件以及最终的结果

之后就先分析一下跳转条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
_DWORD *__fastcall vm_be(_DWORD *a1)
{
_DWORD *result; // rax

if ( a1[501] <= 2u )
{
puts("be instruction cannot be executed with less than three stack elements!");
fflush(_bss_start);
_exit(-1);
}
result = a1;
if ( a1[a1[501] - 2 + 502] == a1[a1[501] - 3 + 502] )
*a1 = a1[a1[501] - 1 + 502];
else
++*a1;
return result;
}

这边含义就是stack[-2]需要等于stack[-3]才能使eip跳转到stack[-1],否则eip直接加加

而加加之后会进到一个jmp,注意跳转的时候要对应字节数,所以这些跳转我这边是这样写的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
elif opcode[eip] == 9:
ins = f'jmp'
eip = (stack[-1]+1)*4 # 第一个值为eip,不用,最后地址要乘4
elif opcode[eip] == 10:
ins = f'be'
if stack[-2] == stack[-3]:
eip = (stack[-1]+1)*4
else:
eip+=4
elif opcode[eip] == 11:
ins = f'bl'
if stack[-2] >= stack[-3]:
eip+=4
else:
eip = (stack[-1]+1)*4

所以最后的解释器长这样

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
import struct
def vm(opcode,entered):
eip=4
stack = []
while True:
if opcode[eip] == 1:
stack.append((opcode[eip+2]<<8)+opcode[eip+1])
ins = f'push {hex((opcode[eip+2]<<8)+opcode[eip+1])}'
eip+=4
elif opcode[eip] == 2:
tmp = stack.pop()
ins = f'pop {tmp}'
eip+=4
elif opcode[eip] == 3:
tmp = stack.pop()+stack.pop()
stack.append(tmp)
ins = f'add, result = {tmp}'
eip+=4
elif opcode[eip] == 4:
tmp = stack.pop() | stack.pop()
stack.append(tmp)
ins = f'and, result = {tmp}'
eip+=4
elif opcode[eip] == 5:
tmp = stack.pop()
stack.append(tmp << opcode[eip+1])
ins = f'lsh, result = {tmp}'
eip+=4
elif opcode[eip] == 6:
tmp = stack.pop()
stack.append(tmp >> opcode[eip+1])
ins = f'rsh, result = {tmp}'
eip+=4
elif opcode[eip] == 7:
ins = 'read'
for i in range(len(entered)):
stack.append(ord(entered[i]))
eip+=4
elif opcode[eip] == 8:
tmp = stack.pop()
ins = f'print {chr(tmp)}'
eip+=4
elif opcode[eip] == 9:
ins = f'jmp'
eip = (stack[-1]+1)*4 # 第一个值为eip,不用,最后地址要乘4
elif opcode[eip] == 10:
ins = f'be'
if stack[-2] == stack[-3]:
eip = (stack[-1]+1)*4
else:
eip+=4
elif opcode[eip] == 11:
ins = f'bl'
if stack[-2] >= stack[-3]:
eip+=4
else:
eip = (stack[-1]+1)*4
elif opcode[eip] == 12:
ins = 'return'
print(ins)
return
print(ins)


with open('./s.txt','rb') as f:
o = f.read()
opcode = struct.unpack('B'*1568,o)
vm(opcode,r'DASCTF{fake_flag_!!}')

运行可以知道整个vm就是read某些值然后丢进去做运算,成功即bebl跳转(实际上通过查找opcode会发现bl压根没用到,实锤抄代码了),否则进jmp然后输出错误信息

一个个分析就太麻烦了,这里学个z3结合hook的方法,在read操作码的地方这么写

1
2
3
4
5
6
elif opcode[eip] == 7:
ins = 'read'
s=Solver()
x = BitVec('x',16) # 定义变量
stack.append(x)
eip+=4

然后在be判断的地方这样写

1
2
3
4
5
6
7
8
9
10
11
elif opcode[eip] == 10:
ins = f'be'
s.add(stack[-2] == stack[-3]) # 添加约束
s.add(x<128) # 添加约束
s.add(x>32) # 添加约束
if(s.check() == sat): # 求解
m = s.model()
print('[+]',end='')
eip = (stack[-1]+1)*4
out += chr(m[x].as_long())
print(out)

之后运行起来就可以在后面得到flag了

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
import struct
from z3 import *
def vm(opcode):
eip=4
stack = []
out = ''
while True:
if opcode[eip] == 1:
stack.append(((opcode[eip+2]<<8)+opcode[eip+1]))
ins = f'push {hex(((opcode[eip+2]<<8)|opcode[eip+1]))}'
eip+=4
elif opcode[eip] == 2:
tmp = stack.pop()
ins = f'pop {tmp}'
eip+=4
elif opcode[eip] == 3:
tmp = stack.pop()+stack.pop()
stack.append(tmp)
ins = f'add, result = {tmp}'
eip+=4
elif opcode[eip] == 4:
t1 = stack.pop()
tmp = (t1 | stack.pop())
stack.append(tmp)
ins = f'and, result = {tmp}'
eip+=4
elif opcode[eip] == 5:
tmp = stack.pop()
stack.append(tmp << opcode[eip+1])
ins = f'lsh, result = {tmp}'
eip+=4
elif opcode[eip] == 6:
tmp = stack.pop()
stack.append(tmp >> opcode[eip+1])
ins = f'rsh, result = {tmp}'
eip+=4
elif opcode[eip] == 7:
ins = 'read'
s=Solver()
x = BitVec('x',16)
stack.append(x)
eip+=4
elif opcode[eip] == 8:
tmp = stack.pop()
ins = f'print {chr(tmp)}'
eip+=4
elif opcode[eip] == 9:
ins = f'jmp'
eip = (stack[-1]+1)*4 # 第一个值为eip,不用,最后地址要乘4
elif opcode[eip] == 10:
ins = f'be'
s.add(stack[-2] == stack[-3])
s.add(x<128)
s.add(x>32)
if(s.check() == sat):
m = s.model()
print('[+]',end='')
eip = (stack[-1]+1)*4
out += chr(m[x].as_long())
print(out)
else:
print(hex(stack[-2]))
print(hex(stack[-3]))
return
if stack[-2] == stack[-3]:
eip = (stack[-1]+1)*4
elif opcode[eip] == 11:
ins = f'bl'
if stack[-2] >= stack[-3]:
eip+=4
else:
eip = (stack[-1]+1)*4
elif opcode[eip] == 12:
ins = 'return'
print(ins)
return
print(ins)


with open('./s.txt','rb') as f:
o = f.read()
opcode = struct.unpack('B'*1568,o)
vm(opcode)

# DASCTF{h1Den_Vm_I3_Soo0_Fun_!}