buuctf [GKCTF2020]EzMachine

vm的题目欠了好久了~~

首先OD打开,跟进去,发现此处每次CALL的位置都会变化。这应该就是我们要找的。

 跟进这个函数,大概分析下流程,能找到虚拟机的字节码。

 同时,在IDA里对应。可以找到CALL调用的函数的地址。通过X交叉引用,就能找到虚拟机的指令集。

 List里存有各个opcode对应的地址,有了指令集。结合IDA和OD动调,逐个分析其功能。在看各函数功能的时候,同时要分辨出各个参数的作用。

------------------------------------------------------------------------------------

这里是字节码,在调用中用了两个数组。每次调用都取两个操作数,command_0是第一个,_1是第二个。

 这里存放四个寄存器。

剩下的是所总结的其他的参数作用。有些参数实在是看不懂。。。

 ----------------------------------------------------------

然后要分析指令集的功能。没有注释的就不上图了。??是看不懂的,也有很多标注不太标准。

0.eipadd

 1.mov

 

 

 2.VM_push_data

3.vm_push

 4.vm_pop

5.caseprint

6.vm_add

7.vm_sub

8.vm_mul

9.vm_div

10.vm_xor

11.vm_jmp

12.vm_subcmp

 13.vm_je

14.vm_jne

15.vm_big_jmp

16.vm_low_jmp

17.vm_input

18.???

19.vm_LoadStack

20.vm_LoadString

0xff: vm_end

---------------------------------------------------------------------------------------------------------------------------------------------------

然后就是写脚本翻译一下vm的字节码。

 1 code =[  #字节码
 2   0x01, 0x03, 0x03, 0x05, 0x00, 0x00, 0x11, 0x00, 0x00, 0x01,
 3   0x01, 0x11, 0x0C, 0x00, 0x01, 0x0D, 0x0A, 0x00, 0x01, 0x03,
 4   0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x01, 0x02, 0x00,
 5   0x01, 0x00, 0x11, 0x0C, 0x00, 0x02, 0x0D, 0x2B, 0x00, 0x14,
 6   0x00, 0x02, 0x01, 0x01, 0x61, 0x0C, 0x00, 0x01, 0x10, 0x1A,
 7   0x00, 0x01, 0x01, 0x7A, 0x0C, 0x00, 0x01, 0x0F, 0x1A, 0x00,
 8   0x01, 0x01, 0x47, 0x0A, 0x00, 0x01, 0x01, 0x01, 0x01, 0x06,
 9   0x00, 0x01, 0x0B, 0x24, 0x00, 0x01, 0x01, 0x41, 0x0C, 0x00,
10   0x01, 0x10, 0x24, 0x00, 0x01, 0x01, 0x5A, 0x0C, 0x00, 0x01,
11   0x0F, 0x24, 0x00, 0x01, 0x01, 0x4B, 0x0A, 0x00, 0x01, 0x01,
12   0x01, 0x01, 0x07, 0x00, 0x01, 0x01, 0x01, 0x10, 0x09, 0x00,
13   0x01, 0x03, 0x01, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01,
14   0x06, 0x02, 0x01, 0x0B, 0x0B, 0x00, 0x02, 0x07, 0x00, 0x02,
15   0x0D, 0x00, 0x02, 0x00, 0x00, 0x02, 0x05, 0x00, 0x02, 0x01,
16   0x00, 0x02, 0x0C, 0x00, 0x02, 0x01, 0x00, 0x02, 0x00, 0x00,
17   0x02, 0x00, 0x00, 0x02, 0x0D, 0x00, 0x02, 0x05, 0x00, 0x02,
18   0x0F, 0x00, 0x02, 0x00, 0x00, 0x02, 0x09, 0x00, 0x02, 0x05,
19   0x00, 0x02, 0x0F, 0x00, 0x02, 0x03, 0x00, 0x02, 0x00, 0x00,
20   0x02, 0x02, 0x00, 0x02, 0x05, 0x00, 0x02, 0x03, 0x00, 0x02,
21   0x03, 0x00, 0x02, 0x01, 0x00, 0x02, 0x07, 0x00, 0x02, 0x07,
22   0x00, 0x02, 0x0B, 0x00, 0x02, 0x02, 0x00, 0x02, 0x01, 0x00,
23   0x02, 0x02, 0x00, 0x02, 0x07, 0x00, 0x02, 0x02, 0x00, 0x02,
24   0x0C, 0x00, 0x02, 0x02, 0x00, 0x02, 0x02, 0x00, 0x01, 0x02,
25   0x01, 0x13, 0x01, 0x02, 0x04, 0x00, 0x00, 0x0C, 0x00, 0x01,
26   0x0E, 0x5B, 0x00, 0x01, 0x01, 0x22, 0x0C, 0x02, 0x01, 0x0D,
27   0x59, 0x00, 0x01, 0x01, 0x01, 0x06, 0x02, 0x01, 0x0B, 0x4E,
28   0x00, 0x01, 0x03, 0x00, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00,
29   0x01, 0x03, 0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00
30 ]
31 opcodekey = {0:'eipadd',1:'mov',2:'VM_push_data',3:'vm_push',4:'vm_pop',5:'caseprint',6:'vm_add',
32              7:'vm_sub',8:'vm_mul',9:'vm_div',10:'vm_xor',11:'vm_jmp',12:'vm_subcmp',
33              13:'vm_je',14:'vm_jne',15:'vm_big_jmp',16:'vm_low_jmp',17:'vm_input',18:'???',
34              19:'vm_LoadStack',20:'vm_LoadString',0XFF:'vm_end'}
35 num =0
36 line = 1;
37 for i in range(len(code)):
38     if(num==0):
39         print(str(line)+': '+opcodekey[code[i]],end=' ')
40         line = line +1
41     else:
42         print(code[i],end=' ')
43     if(num==2):
44         print()
45     num +=1
46     if(num==3):
47         num=0

分析一下所得到的伪代码。得到程序的基本流程。

1: mov 3 3                 
2: caseprint 0 0 
3: vm_input 0 0         输入flag
4: mov 1 17             将17放到v1
5: vm_subcmp 0 1         v1-v0,比较长度
6: vm_je 10 0             相等则跳10
7: mov 3 1 
8: caseprint 0 0 
9: vm_end 0 0             结束
10: mov 2 0             0给v2

11: mov 0 17             17给v0
12: vm_subcmp 0 2         比较v0和v2
13: vm_je 43 0             相等则到43
============================================
14: vm_LoadString 0 2     
15: mov 1 97             97给v1
16: vm_subcmp 0 1         v0和97比较    
17: vm_low_jmp 26 0     小于则跳到26
18: mov 1 122             122给v1
19: vm_subcmp 0 1         比较
20: vm_big_jmp 26 0     大于则跳26
===========若是小写字母======================
21: mov 1 71             71给1
22: vm_xor 0 1             v0与v1异或,放在v0
23: mov 1 1             1给1
24: vm_add 0 1             v0++
25: vm_jmp 36 0         跳到36
=============================================
26: mov 1 65             65给v1
27: vm_subcmp 0 1         比较
28: vm_low_jmp 36 0     小于则跳36
29: mov 1 90             90给v1
30: vm_subcmp 0 1         比较
31: vm_big_jmp 36 0     大于则跳36
===========若是大写字母==========================
32: mov 1 75             75给1
33: vm_xor 0 1             异或,放在v0
34: mov 1 1             
35: vm_sub 0 1            v0-- 
==================================================
36: mov 1 16             16给1
37: vm_div 0 1             v0/v1。就是加密后flag/16.结果在v0,余数在v1
38: vm_push 1 0         v1压栈,余数
39: vm_push 0 0         v0压栈,商
40: mov 1 1             1给v1
41: vm_add 2 1             v2+1,表示已经完成加密的Flag的个数
42: vm_jmp 11 0         跳到11

43: VM_push_data 7 0     数据压栈,第二位
44: VM_push_data 13 0 
45: VM_push_data 0 0 
46: VM_push_data 5 0 
47: VM_push_data 1 0 
48: VM_push_data 12 0 
49: VM_push_data 1 0 
50: VM_push_data 0 0 
51: VM_push_data 0 0 
52: VM_push_data 13 0 
53: VM_push_data 5 0 
54: VM_push_data 15 0 
55: VM_push_data 0 0 
56: VM_push_data 9 0 
57: VM_push_data 5 0 
58: VM_push_data 15 0 
59: VM_push_data 3 0 
60: VM_push_data 0 0 
61: VM_push_data 2 0 
62: VM_push_data 5 0 
63: VM_push_data 3 0 
64: VM_push_data 3 0 
65: VM_push_data 1 0 
66: VM_push_data 7 0 
67: VM_push_data 7 0 
68: VM_push_data 11 0 
69: VM_push_data 2 0 
70: VM_push_data 1 0 
71: VM_push_data 2 0 
72: VM_push_data 7 0 
73: VM_push_data 2 0 
74: VM_push_data 12 0 
75: VM_push_data 2 0 
76: VM_push_data 2 0 

77: mov 2 1             1给v2(这里v2是数量)
78: vm_LoadStack 1 2     
79: vm_pop 0 0             出栈,放到v0
80: vm_subcmp 0 1         v0和v1比较
81: vm_jne 91 0         不相等就到91
82: mov 1 34             34给v1
83: vm_subcmp 2 1         v2和v1比较
84: vm_je 89 0             相等就到89
85: mov 1 1             1给v1
86: vm_add 2 1             v2++
87: vm_jmp 78 0         到78
================成功==================
88: mov 3 0 
89: caseprint 0 0 
90: vm_end 0 0 
================结束==================
91: mov 3 1 
92: caseprint 0 0 
93: vm_end 0 0 
94: eipadd 

大体的流程是,输入17位Flag。对每一位,如果是小写,则(flag+1)^71,如果是大写,则(flag-1)^75。然后对每一位,除以16.商和余数分别压栈。再与已经在另一个栈中的数据分别比较,相同则正确。

编写解密脚本。

 1 a=[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 b=[0]*17
36 j=0
37 for i in range(0,len(a),2):
38     b[j]=a[i]*16+a[i+1]
39     j += 1
40 print(b)
41 t = 0
42 flag = ''
43 for i in range(len(b)):
44     t = (b[i]+1)^75
45     if t>=ord('A') and t<=ord('Z'):
46         flag += chr(t)
47         continue
48     t = (b[i]-1)^71
49     if t>=ord('a') and t<=ord('z'):
50         flag += chr(t)
51         continue
52     flag += chr(b[i])
53 flag=flag[::-1]
54 print(flag)

flag{Such_A_EZVM}

 

posted @ 2020-10-06 17:21  Evening_Breeze  阅读(224)  评论(0编辑  收藏  举报