本人第一次尝试VM类型的逆向CTF题,若有错误,欢迎指出交流QAQ

Hgame 2023 week4 (NSSCTF上下载) VM

第一部分

IDA打开,主程序如图:

我们点击第一个qmemcpy里的sub_140001000函数,跟进sub_140001060(第一个)函数:

这里应该是对虚拟机初始化,初始化几个关键值(指向字符串的索引位置,临时存放变量位置,输入的每个值位置等),但是我们并不知道这几个值对应的是什么,所以先放一边。回到主函数,我们看到有一个for循环,应该是最后比较flag的地方

我们将这个数组记为data[i],然后看下面if语句,很明显是最后的告诉我们是否正确的输出,大致分析了一下,接着我们上主菜:分析这个VM的模拟器(sub_1400010B0):

第二部分

进入这个函数,我们可以看到一个数组,点进去发现有很多的数据,最关键的是它有一个 != 255,

那么我们可以大胆的猜测这个数组是opcode,那么这个a1 + 24大概就是ip了,我们接着点击下面的函数进行分析:

这里很明显是一个cpu的模拟器,也是这个vm题最重要的部分,我们挨个分析:

第一个函数:

发现它是一个将data赋值给a1,还有a1赋值给data的一个操作所以,我们猜测这里是一个mov命令的模拟,但是要注意这里出现了a1[6] + 2/3这个东西,所以猜测这里一个有几个不同的模拟寄存器(模拟rax,rbx等)

第二个函数:

出现++a1的形式,还有将a1数组里的值赋值,可以猜测这里是一个push命令的模拟

第三个函数:

出现a1–与上一个函数相反的操作,那应该是pop命令的模拟:

这里我们看了三个函数,大致确认有一个reg[6]的数组,还有一个ip,一个esp指针,所以我们可以开始构造一个结构题辅助我们解题:

点击IDA上方的WIndow,再点击Local Types(如果没有这个选项尝试shift + F1快捷打开试试),再按下INS按键(键盘上),创造一个结构体(删除是DEL键,如果要重新编辑这个结构体可以右键这个结构体有一个Edit选项)

1
2
3
4
5
6
struct vm
{
_DWORD reg[6];
_DWORD ip;
_DWORD esp;
};

先大概创造成这样,然后我们将之前几个函数类型改一下(选择输入参数前的_DWORD类型按下Y键,再输入我们定义的结构体名称)

一下子我们可以看见这个函数清晰了很多,说明我们大概是改对了,后面每一个函数都先这么改后再分析

第四个函数:

看到很多运算符号,所以我们将这个函数命名为calc

第五个函数:

发现如果直接修改类型为结构体,那么就会出现LOBYTE:

这个时候我们定义的结构体就可能不全或出现问题,我们重新分析一下这里有一个*(_BYTE *)(a1 + 32),那么这里的(a1 + 32)类型就不是我们定义的 _DWORD类型,应该为int类型,修改一下我们的结构体:

1
2
3
4
5
6
7
struct vm
{
_DWORD reg[6];
_DWORD ip;
_DWORD esp;
_BYTE zf;
};

这里的函数很明显是一个cmp命令的模拟,所以我们直接把(a1 + 32)认为是zf寄存器

修改后的函数:

很清晰明了。

第六,七,八个函数:

后面这几个函数都是jmp命令的模拟,只不过第七第八个函数用到了zf,所以可能是jne命令和je命令(可以再后面得到汇编代码时再改哪一个是jne哪一个是je)

第三部分

然后这里我们就已经将这个vm的逻辑基本理清了,现在就是写脚本还原这个汇编指令,然后读汇编解flag:

注意每一个模拟汇编指令后ip+的数都不一样,所以我们要一一对应。

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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
opcode = [0x00, 0x03, 0x02, 0x00, 0x03, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x03, 0x02, 0x32, 0x03, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x02, 0x64, 0x03, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x01, 0x00, 0x00, 0x03, 0x00, 0x08, 0x00, 0x02, 0x02, 0x01, 0x03, 0x04, 0x01, 0x00, 0x03, 0x05, 0x02, 0x00, 0x03, 0x00, 0x01, 0x02, 0x00, 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0x03, 0x00, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x03, 0x01, 0x28, 0x04, 0x06, 0x5F, 0x05, 0x00, 0x00, 0x03, 0x03, 0x00, 0x02, 0x01, 0x00, 0x03, 0x02, 0x96, 0x03, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x04, 0x07, 0x88, 0x00, 0x03, 0x00, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x03, 0x01, 0x28, 0x04, 0x07, 0x63, 0xFF, 0xFF]
data= [0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0000009B, 0x000000A8, 0x00000002, 0x000000BC, 0x000000AC, 0x0000009C, 0x000000CE, 0x000000FA, 0x00000002, 0x000000B9, 0x000000FF, 0x0000003A, 0x00000074, 0x00000048, 0x00000019, 0x00000069, 0x000000E8, 0x00000003, 0x000000CB, 0x000000C9, 0x000000FF, 0x000000FC, 0x00000080, 0x000000D6, 0x0000008D, 0x000000D7, 0x00000072, 0x00000000, 0x000000A7, 0x0000001D, 0x0000003D, 0x00000099, 0x00000088, 0x00000099, 0x000000BF, 0x000000E8, 0x00000096, 0x0000002E, 0x0000005D, 0x00000057, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000000C9, 0x000000A9, 0x000000BD, 0x0000008B, 0x00000017, 0x000000C2, 0x0000006E, 0x000000F8, 0x000000F5, 0x0000006E, 0x00000063, 0x00000063, 0x000000D5, 0x00000046, 0x0000005D, 0x00000016, 0x00000098, 0x00000038, 0x00000030, 0x00000073, 0x00000038, 0x000000C1, 0x0000005E, 0x000000ED, 0x000000B0, 0x00000029, 0x0000005A, 0x00000018, 0x00000040, 0x000000A7, 0x000000FD, 0x0000000A, 0x0000001E, 0x00000078, 0x0000008B, 0x00000062, 0x000000DB, 0x0000000F, 0x0000008F, 0x0000009C, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00004800, 0x0000F100, 0x00004000, 0x00002100, 0x00003501, 0x00006400, 0x00007801, 0x0000F900, 0x00001801, 0x00005200, 0x00002500, 0x00005D01, 0x00004700, 0x0000FD00, 0x00006901, 0x00005C00, 0x0000AF01, 0x0000B200, 0x0000EC01, 0x00005201, 0x00004F01, 0x00001A01, 0x00005000, 0x00008501, 0x0000CD00, 0x00002300, 0x0000F800, 0x00000C00, 0x0000CF00, 0x00003D01, 0x00004501, 0x00008200, 0x0000D201, 0x00002901, 0x0000D501, 0x00000601, 0x0000A201, 0x0000DE00, 0x0000A601, 0x0000CA01, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000]
ip = 0


def mov():
global ip,opcode
if(opcode[ip + 1] == 0):
print("mov reg[0],data[reg[2]]")
ip += 4
elif(opcode[ip + 1] == 1):
print("mov data[reg[2]],reg[0]")
ip += 4
elif(opcode[ip + 1] == 2):
print(f"mov reg[{opcode[ip + 2]}],reg[{opcode[ip + 3]}]")
ip += 4
elif(opcode[ip + 1] == 3):
print(f"mov reg[{opcode[ip + 2]}],{opcode[ip + 3]}")
ip += 4


def push():
global ip,opcode
if(opcode[ip + 1] == 0):
print("push reg[0]")
ip += 2
elif(opcode[ip + 1] == 1):
print("push reg[0]")
ip += 2
elif(opcode[ip + 1] == 2):
print("push reg[2]")
ip += 2
elif(opcode[ip + 1] == 3):
print("push reg[3]")
ip += 2

def pop():
global ip,opcode
if(opcode[ip + 1] == 0):
print("pop reg[0]")
ip += 2
elif(opcode[ip + 1] == 1):
print("pop reg[0]")
ip += 2
elif(opcode[ip + 1] == 2):
print("pop reg[2]")
ip += 2
elif(opcode[ip + 1] == 3):
print("pop reg[3]")
ip += 2

def calc():
global ip,opcode
if(opcode[ip + 1] == 0):
print(f"add reg[{opcode[ip + 2]}], reg[{opcode[ip + 3]}]")
ip += 4
elif(opcode[ip + 1] == 1):
print(f"sub reg[{opcode[ip + 2]}], reg[{opcode[ip + 3]}]")
ip += 4
elif(opcode[ip + 1] == 2):
print(f"mul reg[{opcode[ip + 2]}], reg[{opcode[ip + 3]}]")
ip += 4
elif(opcode[ip + 1] == 3):
print(f"xor reg[{opcode[ip + 2]}], reg[{opcode[ip + 3]}]")
ip += 4
elif(opcode[ip + 1] == 4):
print(f"shl reg[{opcode[ip + 2]}], reg[{opcode[ip + 3]}]")
print(f"reg[{opcode[ip + 2]}] & 0xFF00")
ip += 4
elif(opcode[ip + 1] == 5):
print(f"shr reg[{opcode[ip + 2]}], reg[{opcode[ip + 3]}]")
ip += 4

def cmp():
global ip,opcode
print("cmp reg[0],reg[1]")
ip += 1

def jmp():
global ip,opcode
print(f"jmp {opcode[ip + 1]}")
ip += 2

def jne():
global ip,opcode
print(f"jne {opcode[ip + 1]}")
ip += 2

def je():
global ip,opcode
print(f"je {opcode[ip + 1]}")
ip += 2


while opcode[ip] != 255:
if(opcode[ip] == 0):
print("%d " % (ip), end = '')
mov()
elif(opcode[ip] == 1):
print("%d " % (ip), end = '')
push()
elif(opcode[ip] == 2):
print("%d " % (ip), end = '')
pop()
elif(opcode[ip] == 3):
print("%d " % (ip), end = '')
calc()
elif(opcode[ip] == 4):
print("%d " % (ip), end = '')
cmp()
elif(opcode[ip] == 5):
print("%d " % (ip), end = '')
jmp()
elif(opcode[ip] == 6):
print("%d " % (ip), end = '')
jne()
elif(opcode[ip] == 7):
print("%d " % (ip), end = '')
je()

小技巧:可以在最后print时,在前面print出此时的ip数,这样我们在看汇编时才知道jmp到了哪里,当然也可以让他print横线找到jmp的落点,下面为汇编代码:

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
0 mov reg[2],0
4 add reg[2], reg[3]
8 mov reg[0],data[reg[2]]
12 mov reg[1],reg[0]
16 mov reg[2],50
20 add reg[2], reg[3]
24 mov reg[0],data[reg[2]]
28 add reg[1], reg[0]
32 mov reg[2],100
36 add reg[2], reg[3]
40 mov reg[0],data[reg[2]]
44 xor reg[1], reg[0]
48 mov reg[0],8
52 mov reg[2],reg[1]
56 shl reg[1], reg[0]
reg[1] & 0xFF00
60 shr reg[2], reg[0]
64 add reg[1], reg[2]
68 mov reg[0],reg[1]
72 push reg[0]
74 mov reg[0],1
78 add reg[3], reg[0]
82 mov reg[0],reg[3]
86 mov reg[1],40
90 cmp reg[0],reg[1]
91 jne 95
93 jmp 0
95 mov reg[3],0
99 pop reg[0]
101 mov reg[2],150
105 add reg[2], reg[3]
109 mov reg[0],data[reg[2]]
113 cmp reg[0],reg[1]
114 je 136
116 mov reg[0],1
120 add reg[3], reg[0]
124 mov reg[0],reg[3]
128 mov reg[1],40
132 cmp reg[0],reg[1]
133 je 99

读代码大概是进行如下的运算:

1
((((data[0 + i] + data[50 + i] )^ data[100 + i]) << 8) & 0xff00 ) + (((data[0 + i] + data[50 + i] )^ data[100 + i]) >> 8) 

这个相当于是将异或后的二进制数据的前八位和后八位调换顺序

1
2
3
4
5
6
# 95 mov reg[3],0
# 99 pop reg[0]
# 101 mov reg[2],150
# 105 add reg[2], reg[3]
# 109 mov reg[0],data[reg[2]]
# 113 cmp reg[0],reg[1] 先进后出的出栈顺序注意 相当于倒序比较

这里相当于是将改变了顺序的数据与data[150 + i]进行比较,不过要注意的是出栈的顺序,先进后出,所以这里是一个倒序比较,exp如下:

1
2
3
4
5
6
7
8
data = [0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0000009B, 0x000000A8, 0x00000002, 0x000000BC, 0x000000AC, 0x0000009C, 0x000000CE, 0x000000FA, 0x00000002, 0x000000B9, 0x000000FF, 0x0000003A, 0x00000074, 0x00000048, 0x00000019, 0x00000069, 0x000000E8, 0x00000003, 0x000000CB, 0x000000C9, 0x000000FF, 0x000000FC, 0x00000080, 0x000000D6, 0x0000008D, 0x000000D7, 0x00000072, 0x00000000, 0x000000A7, 0x0000001D, 0x0000003D, 0x00000099, 0x00000088, 0x00000099, 0x000000BF, 0x000000E8, 0x00000096, 0x0000002E, 0x0000005D, 0x00000057, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000000C9, 0x000000A9, 0x000000BD, 0x0000008B, 0x00000017, 0x000000C2, 0x0000006E, 0x000000F8, 0x000000F5, 0x0000006E, 0x00000063, 0x00000063, 0x000000D5, 0x00000046, 0x0000005D, 0x00000016, 0x00000098, 0x00000038, 0x00000030, 0x00000073, 0x00000038, 0x000000C1, 0x0000005E, 0x000000ED, 0x000000B0, 0x00000029, 0x0000005A, 0x00000018, 0x00000040, 0x000000A7, 0x000000FD, 0x0000000A, 0x0000001E, 0x00000078, 0x0000008B, 0x00000062, 0x000000DB, 0x0000000F, 0x0000008F, 0x0000009C, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00004800, 0x0000F100, 0x00004000, 0x00002100, 0x00003501, 0x00006400, 0x00007801, 0x0000F900, 0x00001801, 0x00005200, 0x00002500, 0x00005D01, 0x00004700, 0x0000FD00, 0x00006901, 0x00005C00, 0x0000AF01, 0x0000B200, 0x0000EC01, 0x00005201, 0x00004F01, 0x00001A01, 0x00005000, 0x00008501, 0x0000CD00, 0x00002300, 0x0000F800, 0x00000C00, 0x0000CF00, 0x00003D01, 0x00004501, 0x00008200, 0x0000D201, 0x00002901, 0x0000D501, 0x00000601, 0x0000A201, 0x0000DE00, 0x0000A601, 0x0000CA01, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000]

for i in range(40):
num = data[150 + 39 - i]
num = (((num << 8) & 0xff00) + ((num >> 8) & 0xff)) & 0xffff
num ^= data[100 + i]
num -= data[50 + i]
print(chr(num),end= '')

flag:

1
hgame{y0ur_rever5e_sk1ll_i5_very_g0od!!}

总结

第一次做vm的题还是有很多的不足,菜就多练.jpg QWQ