侧边栏壁纸
  • 累计撰写 38 篇文章
  • 累计创建 23 个标签
  • 累计收到 9 条评论

目 录CONTENT

文章目录

【Reverse】[GKCTF 2020]EzMachine复现(VM逆向)

Fup1p1
2023-01-02 / 0 评论 / 0 点赞 / 632 阅读 / 4,813 字 / 正在检测是否收录...

前言

第一次做VM逆向题,学到很多。
VM逆向框架其实就是虚拟机去模拟一个机器,模拟一个cpu去执行指令的一个过程。这个虚拟机的伪寄存器、伪堆栈、伪字符串会分配在程序的全局变量里。

查壳

32位无壳,IDA打开

去掉花指令

Snipaste_2023-01-02_10-22-36
垃圾数据nop掉就行了

开始分析

Snipaste_2023-01-02_10-19-17
For循环内部其实就是不断地去将虚拟机指令与编写的opcode比较,一旦找到对应的指令就执行。

查看opcode

Snipaste_2023-01-02_10-40-18
这题最重要的就是分析每个opcode指令,分析伪寄存器的地址。
比如,这四个全局变量经常被调用,而且都是储存或者转移操作,我们就可以猜测这四个可能就是伪寄存器。
Snipaste_2023-01-02_10-41-45
905BDB,每条opcode指令就能看到这个值+=3,不免怀疑,他就是eip。
Snipaste_2023-01-02_10-46-10

分析opcode

给一些能分析出来的全局变量重命名,便于分析,我都是以vm_开头。
限于篇幅和能力,我就浅浅分析几个例子。
如果想看具体分析,可以看看别人的博客
博客链接
Snipaste_2023-01-02_10-50-21
这条太简单,只是增加eip,没有其他操作,其实就是nop
Snipaste_2023-01-02_10-50-48
在Register[v0]的地址(也就是伪寄存器的地址)上赋予第二个操作数的值v1。相当于mov Register data,将立即数移动到指定寄存器上,第一个操作数代表第几个寄存器。

编写脚本

cmd=[0x01, 0x03, 0x03, 0x05, 0x00, 0x00, 0x11, 0x00, 0x00, 0x01, 0x01, 0x11, 0x0C, 0x00, 0x01, 0x0D, 0x0A, 0x00, 0x01, 0x03, 0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x01, 0x02, 0x00, 0x01, 0x00, 0x11, 0x0C, 0x00, 0x02, 0x0D, 0x2B, 0x00, 0x14, 0x00, 0x02, 0x01, 0x01, 0x61, 0x0C, 0x00, 0x01, 0x10, 0x1A, 0x00, 0x01, 0x01, 0x7A, 0x0C, 0x00, 0x01, 0x0F, 0x1A, 0x00, 0x01, 0x01, 0x47, 0x0A, 0x00, 0x01, 0x01, 0x01, 0x01, 0x06, 0x00, 0x01, 0x0B, 0x24, 0x00, 0x01, 0x01, 0x41, 0x0C, 0x00, 0x01, 0x10, 0x24, 0x00, 0x01, 0x01, 0x5A, 0x0C, 0x00, 0x01, 0x0F, 0x24, 0x00, 0x01, 0x01, 0x4B, 0x0A, 0x00, 0x01, 0x01, 0x01, 0x01, 0x07, 0x00, 0x01, 0x01, 0x01, 0x10, 0x09, 0x00, 0x01, 0x03, 0x01, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01, 0x06, 0x02, 0x01, 0x0B, 0x0B, 0x00, 0x02, 0x07, 0x00, 0x02, 0x0D, 0x00, 0x02, 0x00, 0x00, 0x02, 0x05, 0x00, 0x02, 0x01, 0x00, 0x02, 0x0C, 0x00, 0x02, 0x01, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x0D, 0x00, 0x02, 0x05, 0x00, 0x02, 0x0F, 0x00, 0x02, 0x00, 0x00, 0x02, 0x09, 0x00, 0x02, 0x05, 0x00, 0x02, 0x0F, 0x00, 0x02, 0x03, 0x00, 0x02, 0x00, 0x00, 0x02, 0x02, 0x00, 0x02, 0x05, 0x00, 0x02, 0x03, 0x00, 0x02, 0x03, 0x00, 0x02, 0x01, 0x00, 0x02, 0x07, 0x00, 0x02, 0x07, 0x00, 0x02, 0x0B, 0x00, 0x02, 0x02, 0x00, 0x02, 0x01, 0x00, 0x02, 0x02, 0x00, 0x02, 0x07, 0x00, 0x02, 0x02, 0x00, 0x02, 0x0C, 0x00, 0x02, 0x02, 0x00, 0x02, 0x02, 0x00, 0x01, 0x02, 0x01, 0x13, 0x01, 0x02, 0x04, 0x00, 0x00, 0x0C, 0x00, 0x01, 0x0E, 0x5B, 0x00, 0x01, 0x01, 0x22, 0x0C, 0x02, 0x01, 0x0D, 0x59, 0x00, 0x01, 0x01, 0x01, 0x06, 0x02, 0x01, 0x0B, 0x4E, 0x00, 0x01, 0x03, 0x00, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x01, 0x03, 0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00,0x00]
opcode={
    0:'nop',
    1:'mov',
    2:'push data',
    3:'push reg',
    4:'pop reg',
    5:'printf',
    6:'add',
    7:'sub',
    8:'imul',
    9:'div',
    0xA:'xor',
    0xB:'jmp',
    0xC:'cmp(sub&cmp)', #两数相减的比较
    0xD:'je',
    0xE:'jne',
    0xF:'jg',
    0x10:'jl',
    0x11:'scanf&strlen',
    0x12:'memset',
    0x13:'load1', #这两个指令没搞懂
    0x14:'load2', 
    0xff:'exit',
    }
cnt=1
index=0
for i in cmd:
    if index%3==0:
        print(str(cnt)+":",end="")
        print(opcode[i],end=" ")
        cnt+=1
    elif index%3==1:
        print(str(i)+',',end="")
    elif index%3==2:
        print(str(i))
    index+=1

分析汇编

1:mov 3,3
2:printf 0,0
3:scanf&strlen 0,0
4:mov 1,17
5:cmp(sub&cmp) 0,1  判断长度是否为17
6:je 10,0            跳转至10
——————————————————————————
7:mov 3,1
8:printf 0,0         退出
9:exit 0,0
——————————————————————————
10:mov 2,0			reg2=0 
11:mov 0,17			 reg0=17
12:cmp(sub&cmp) 0,2		比较reg2与reg0 联系最后的jmp 11 ,应该就是个for循环
13:je 43,0
——————————————————————————
14:load2 0,2
15:mov 1,97 
16:cmp(sub&cmp) 0,1  
17:jl 26,0    小于‘a‘就跳转到26

18:mov 1,122 
19:cmp(sub&cmp) 0,1
20:jg 26,0     大于'z'就跳转到26
21:mov 1,71
22:xor 0,1     异或71			‘a’-‘z’的加密
23:mov 1,1  
24:add 0,1	异或后+1
25:jmp 36,0    跳转到36
——————————————————————————
26:mov 1,65
27:cmp(sub&cmp) 0,1
28:jl 36,0    小于'A'跳转到36

29:mov 1,90    
30:cmp(sub&cmp) 0,1
31:jg 36,0	大于‘Z’跳转到36

32:mov 1,75   
33:xor 0,1	异或75
34:mov 1,1					‘A’-'Z'的加密
35:sub 0,1     异或后-1
——————————————————————————
36:mov 1,16
37:div 0,1	除以16
38:push reg 1,0	将reg1入栈,其实就是余数,十六进制的个位
39:push reg 0,0    将reg0入栈,其实就是商    十六进制的十位
40:mov 1,1		
41:add 2,1		reg2+1
42:jmp 11,0		跳转到11
——————————————————————————
43:push data 7,0
44:push data 13,0
45:push data 0,0
46:push data 5,0
47:push data 1,0
48:push data 12,0
49:push data 1,0
50:push data 0,0
51:push data 0,0
52:push data 13,0
53:push data 5,0
54:push data 15,0
55:push data 0,0
56:push data 9,0
57:push data 5,0
58:push data 15,0
59:push data 3,0
60:push data 0,0
61:push data 2,0
62:push data 5,0
63:push data 3,0
64:push data 3,0
65:push data 1,0
66:push data 7,0
67:push data 7,0
68:push data 11,0
69:push data 2,0
70:push data 1,0
71:push data 2,0
72:push data 7,0
73:push data 2,0
74:push data 12,0
75:push data 2,0
76:push data 2,0
——————————————————————————
77:mov 2,1   reg2=1
78:load1 1,2  这个函数没搞懂,反正肯定是把加密并且拆成十六进制的个位十位的值拿出来放到寄存器里,便于后面的比较。
79:pop reg 0,0  出栈给到reg0
80:cmp(sub&cmp) 0,1		不相同就exit
81:jne 91,0 
——————————————————————————
82:mov 1,34
83:cmp(sub&cmp) 2,1  检查是不是弹出34个值
84:je 89,0
——————————————————————————
85:mov 1,1
86:add 2,1
87:jmp 78,0

88:mov 3,0
89:printf 0,0
90:exit 0,0

91:mov 3,1
92:printf 0,0
93:exit 0,0
94:nop

加密逻辑不算特别复杂(我不会说我看了挺久,但这是事实doge)。

先判断输入是不是17位。是,继续。不是,exit。
然后对每一个输入进行加密。
如果小写 异或71后+1 然后除以16,取商和余数(相当于十六进制的十位和个位)
如果大写 异或75后-1。
如果既不是大写没有不是小写,那么直接除以16。

搞懂加密逻辑后,编写exp

data=[7,13,0,5,1,12,1,0,0,13,5,15,0,9,5,15,3,0,2,5,3,3,1,7,7,11,2,1,2,7,2,12,2,2]
data=data[::-1] #上面是个栈,得倒一下取。
for i in range(0,len(data),2):
    x=data[i]+data[i+1]*0x10
    y=(x-1)^71
    z=(x+1)^75
    if(y>=97 and y<=122 ):
        print(chr(y),end="")
        continue
    elif(z>=65 and z<=90):
        print(chr(z),end="")
        continue
    else:
        print(chr(x),end="")
#ouput:flag{Such_A_EZVM}
0

评论区