re | [GKCTF2020]EzMachine

这是一个win32的VM逆向。

这是第一次做VM逆向,简单的说一下思路:

1.找出主要的虚拟机逻辑片段。

2.找出指令集、堆栈、寄存器。

3.找出要执行的虚拟指令。

4.具体分析。

这次的主函数加了花指令混淆,去除掉以后创建函数然后f5:

re | [GKCTF2020]EzMachine

同时找到了字符串和虚拟寄存器的地址:

re | [GKCTF2020]EzMachine

大概的流程摸清楚了,开始分析指令:

指令分析:
0 : 'nop',
1 : 'mov i(寄存器编号), j',
2 : 'push x',
3 : 'push i(寄存器编号)',
4 : 'pop i(寄存器编号)',
5 : 'print',
6 : 'add i(寄存器编号),j(寄存器编号?)',
7 : 'sub',
8 : 'mul',
9 : 'div',
10 : 'xor',
11 : 'jmp',
12 : 'subcmp',
13 : 'je',
14 : 'jne',
15 : 'jg',
16 : 'jl',
17 : 'input',
18 : 'clr?',
19 : 'loadstack',
20 : 'loadstring',
0xff : 'quit',

此处后半部分参考了这篇文章的分析过程(https://blog.csdn.net/weixin_43876357/article/details/108488762)

下面把command给提出来:

command:
01 03 03 05 00 00 11 00 00 01 01 11 0C 00 01 0D
0A 00 01 03 01 05 00 00 FF 00 00 01 02 00 01 00
11 0C 00 02 0D 2B 00 14 00 02 01 01 61 0C 00 01
10 1A 00 01 01 7A 0C 00 01 0F 1A 00 01 01 47 0A
00 01 01 01 01 06 00 01 0B 24 00 01 01 41 0C 00
01 10 24 00 01 01 5A 0C 00 01 0F 24 00 01 01 4B
0A 00 01 01 01 01 07 00 01 01 01 10 09 00 01 03
01 00 03 00 00 01 01 01 06 02 01 0B 0B 00 02 07
00 02 0D 00 02 00 00 02 05 00 02 01 00 02 0C 00
02 01 00 02 00 00 02 00 00 02 0D 00 02 05 00 02
0F 00 02 00 00 02 09 00 02 05 00 02 0F 00 02 03
00 02 00 00 02 02 00 02 05 00 02 03 00 02 03 00
02 01 00 02 07 00 02 07 00 02 0B 00 02 02 00 02
01 00 02 02 00 02 07 00 02 02 00 02 0C 00 02 02
00 02 02 00 01 02 01 13 01 02 04 00 00 0C 00 01
0E 5B 00 01 01 22 0C 02 01 0D 59 00 01 01 01 06
02 01 0B 4E 00 01 03 00 05 00 00 FF 00 00 01 03
01 05 00 00 FF 00 00 00

然后写个脚本来还原伪汇编代码:

 1 cmd = [0x01,0x03,0x03,0x05,0x00,0x00,0x11,
 2 0x00,0x00,0x01,0x01,0x11,0x0C,0x00,0x01,0x0D,
 3 0x0A,0x00,0x01,0x03,0x01,0x05,0x00,0x00,0xFF,
 4 0x00,0x00,0x01,0x02,0x00,0x01,0x00,0x11,0x0C,
 5 0x00,0x02,0x0D,0x2B,0x00,0x14,0x00,0x02,0x01,
 6 0x01,0x61,0x0C,0x00,0x01,0x10,0x1A,0x00,0x01,
 7 0x01,0x7A,0x0C,0x00,0x01,0x0F,0x1A,0x00,0x01,
 8 0x01,0x47,0x0A,0x00,0x01,0x01,0x01,0x01,0x06,
 9 0x00,0x01,0x0B,0x24,0x00,0x01,0x01,0x41,0x0C,
10 0x00,0x01,0x10,0x24,0x00,0x01,0x01,0x5A,0x0C,
11 0x00,0x01,0x0F,0x24,0x00,0x01,0x01,0x4B,0x0A,
12 0x00,0x01,0x01,0x01,0x01,0x07,0x00,0x01,0x01,
13 0x01,0x10,0x09,0x00,0x01,0x03,0x01,0x00,0x03,
14 0x00,0x00,0x01,0x01,0x01,0x06,0x02,0x01,0x0B,
15 0x0B,0x00,0x02,0x07,0x00,0x02,0x0D,0x00,0x02,
16 0x00,0x00,0x02,0x05,0x00,0x02,0x01,0x00,0x02,
17 0x0C,0x00,0x02,0x01,0x00,0x02,0x00,0x00,0x02,
18 0x00,0x00,0x02,0x0D,0x00,0x02,0x05,0x00,0x02,
19 0x0F,0x00,0x02,0x00,0x00,0x02,0x09,0x00,0x02,
20 0x05,0x00,0x02,0x0F,0x00,0x02,0x03,0x00,0x02,
21 0x00,0x00,0x02,0x02,0x00,0x02,0x05,0x00,0x02,
22 0x03,0x00,0x02,0x03,0x00,0x02,0x01,0x00,0x02,
23 0x07,0x00,0x02,0x07,0x00,0x02,0x0B,0x00,0x02,
24 0x02,0x00,0x02,0x01,0x00,0x02,0x02,0x00,0x02,
25 0x07,0x00,0x02,0x02,0x00,0x02,0x0C,0x00,0x02,
26 0x02,0x00,0x02,0x02,0x00,0x01,0x02,0x01,0x13,
27 0x01,0x02,0x04,0x00,0x00,0x0C,0x00,0x01,0x0E,
28 0x5B,0x00,0x01,0x01,0x22,0x0C,0x02,0x01,0x0D,
29 0x59,0x00,0x01,0x01,0x01,0x06,0x02,0x01,0x0B,
30 0x4E,0x00,0x01,0x03,0x00,0x05,0x00,0x00,0xFF,
31 0x00,0x00,0x01,0x03,0x01,0x05,0x00,0x00,0xFF,
32 0x00,0x00,0x00]
33 
34 opcode = {0 : 'nop',
35 1 : 'mov i(寄存器编号), j',
36 2 : 'push x',
37 3 : 'push i(寄存器编号)',
38 4 : 'pop i(寄存器编号)',
39 5 : 'print',
40 6 : 'add i(寄存器编号),j(寄存器编号?)',
41 7 : 'sub',
42 8 : 'mul',
43 9 : 'div',
44 10 : 'xor',
45 11 : 'jmp',
46 12 : 'subcmp',
47 13 : 'je',
48 14 : 'jne',
49 15 : 'jg',
50 16 : 'jl',
51 17 : 'input',
52 18 : 'clr?',
53 19 : 'loadstack',
54 20 : 'loadstring',
55 0xff : 'quit',}
56 
57 i = 0 #3个byte为一条指令
58 x = 0
59 for ip in cmd:
60     if x % 3 == 0:
61         i += 1
62         print(str(i) + ': ', end='') # 打印行号
63         print(opcode[ip] + ' ', end='')
64     elif x % 3 == 1:
65         print(str(ip)+',', end='')
66     else:
67         print(str(ip))
68     x += 1

运行结果(因为我的标注所以有点难看、将就一下):

1: mov i(寄存器编号), j 3,3
2: print 0,0
3: input 0,0
4: mov i(寄存器编号), j 1,17
5: subcmp 0,1
6: je 10,0
7: mov i(寄存器编号), j 3,1
8: print 0,0
9: quit 0,0
10: mov i(寄存器编号), j 2,0
11: mov i(寄存器编号), j 0,17
12: subcmp 0,2
13: je 43,0
14: loadstring 0,2
15: mov i(寄存器编号), j 1,97
16: subcmp 0,1
17: jl 26,0
18: mov i(寄存器编号), j 1,122
19: subcmp 0,1
20: jg 26,0
21: mov i(寄存器编号), j 1,71
22: xor 0,1
23: mov i(寄存器编号), j 1,1
24: add i(寄存器编号),j(寄存器编号?) 0,1
25: jmp 36,0
26: mov i(寄存器编号), j 1,65
27: subcmp 0,1
28: jl 36,0
29: mov i(寄存器编号), j 1,90
30: subcmp 0,1
31: jg 36,0
32: mov i(寄存器编号), j 1,75
33: xor 0,1
34: mov i(寄存器编号), j 1,1
35: sub 0,1
36: mov i(寄存器编号), j 1,16
37: div 0,1
38: push i(寄存器编号) 1,0
39: push i(寄存器编号) 0,0
40: mov i(寄存器编号), j 1,1
41: add i(寄存器编号),j(寄存器编号?) 2,1
42: jmp 11,0
43: push x 7,0
44: push x 13,0
45: push x 0,0
46: push x 5,0
47: push x 1,0
48: push x 12,0
49: push x 1,0
50: push x 0,0
51: push x 0,0
52: push x 13,0
53: push x 5,0
54: push x 15,0
55: push x 0,0
56: push x 9,0
57: push x 5,0
58: push x 15,0
59: push x 3,0
60: push x 0,0
61: push x 2,0
62: push x 5,0
63: push x 3,0
64: push x 3,0
65: push x 1,0
66: push x 7,0
67: push x 7,0
68: push x 11,0
69: push x 2,0
70: push x 1,0
71: push x 2,0
72: push x 7,0
73: push x 2,0
74: push x 12,0
75: push x 2,0
76: push x 2,0
77: mov i(寄存器编号), j 2,1
78: loadstack 1,2
79: pop i(寄存器编号) 0,0
80: subcmp 0,1
81: jne 91,0
82: mov i(寄存器编号), j 1,34
83: subcmp 2,1
84: je 89,0
85: mov i(寄存器编号), j 1,1
86: add i(寄存器编号),j(寄存器编号?) 2,1
87: jmp 78,0
88: mov i(寄存器编号), j 3,0
89: print 0,0
90: quit 0,0
91: mov i(寄存器编号), j 3,1
92: print 0,0
93: quit 0,0
94: nop

整体逻辑如下:

1.先判断是不是17位

2.大小写字母分别进行操作并压栈

3.将结果压栈

4.将结果pop出来比较

具体的分析过程还是有参考上面的那篇文章。

然后写一个脚本来还原flag:

 1 ans = [7,
 2 13,
 3 0,
 4 5,
 5 1,
 6 12,
 7 1,
 8 0,
 9 0,
10 13,
11 5,
12 15,
13 0,
14 9,
15 5,
16 15,
17 3,
18 0,
19 2,
20 5,
21 3,
22 3,
23 1,
24 7,
25 7,
26 11,
27 2,
28 1,
29 2,
30 7,
31 2,
32 12,
33 2,
34 2,]
35 ans.reverse()
36 flag = ''
37 for i in range(0, len(ans), 2):
38     x = ans[i] + ans[i+1]*16
39     tmp = (x-1) ^ 71
40     if tmp >= ord('a') and tmp <= ord('z'):
41         flag += chr(tmp)
42         continue
43     tmp = (x+1) ^ 75
44     if tmp >= ord('A') and tmp <= ord('Z'):
45         flag += chr(tmp)
46         continue
47     flag += chr(x)
48     
49 print(flag)

跑一下就出来了。

总的来说感觉这种题不是很复杂,但是需要足够细致耐心。

上一篇:利用esp8266进行服务端与客户端之间的无线传输


下一篇:JAVA类文件结构(一)