下载:下载

VM虚拟保护——Simple VM

需要知识

  • 数电基础
  • C语言基础
  • x86汇编指令
  • 逻辑分析能力
  • VM保护基本术语
    • Dispatcher: 调度器,用于判断跳转到对应的Handler执行指令
    • Handler: 处理器,用于模拟每个指令

分析

  • 下载后发现有两个文件,分别是
    • p.bin
    • vm_rel
  • 我们直接用IDA打开vm_rel这个文件,发现是一个64位的elf文件。老规矩在左边的Function names中搜索main直接打开main函数查看一下伪代码

- 经过简单的分析知道,首先程序会打开“p.bin”的文件然后在内存中分配一个和文件大小一样大的空间。并把分配 好的内存的地址存放到v5,再经过fread()函数读取文件中的内容存放到内存中

  • 然后调用子程序sub_4008996开始程序的执行
  • 直接双击该子程序进去可以看到由while(1),switch语句构成整个子程序
  • 在虚拟机保护的分析中只要看到类似的结构,就可以判断出该子程序就是整个vm的核心Dispatcher

- 看到这里对于初学者来说可能比较吃力,毕竟笔者也是初学者所以刚开始看的时候着什么玩意啊哈哈哈

  • 不急慢慢来,首先不管代码啥的直接尝试是否能暴力破解出flag的长度。那么怎么暴力破解呢。根据我的了解有些vm会不断的调用getchar或scanf一个个字符的获取。所以只要统计这两个函数的调用次数就能得到flag的长度。
  • 还有就是直接在程序慢慢的输入,有些vm是在字数长度足够长的情况下才会执行。那么直接不断地尝试知道反应后减少字符串的长度。在不断尝试如下图

  • 可以看到前面输入的没反应,当不断加长flag的长度有反应了。经过不断的尝试发现flag的长度为31.But!回车键也算一个实际上是32个字符

  • 上图就是根据在getchar下断点,每断下一次就+1直到getchar没有被调用为止。发现也是调用了32次
  • 知道了flag的长度后,来看一下他是如何获取用户的输入的。直接在switch的地方下一个断运行程序开始调试

  • 获取flag存储:

    • 先获取flag需要的长度给v15
    • dword_6010A4 = 0x101+index,index是flag没获取一个字符自增1用于存放flag数组的下标
    • 调用getchar获取flag的值存放与v14
    • 令c=v14
    • v8 = dword_6010A4 = 0x101 + index,让v8等于flag的下标
    • *(v1 + v8) = c存放flag
  • 读取flag加密:

    • 这里就不详细讲了,首先获取flag中的每个字符然后进行一个ANDN的“量子万用门”实现的对flag加密

ANDN万用门

*   何为万用门?万用门就是只需要一个简单的逻辑门nor或andn就可组成其他的基本逻辑运算 与或非,与非,异或,同或,或非,与,非,或的运算黄色字体部分就是万用门的 常见组合

  • 知道了使用万用门进行加密操作的,那怎么分析他到底做了什么呢?这就不得不用到数电中的基本知识了。不懂数电的只能死记硬背了吧。

复现VM

  • 现在我们要做的就是把每次进行ANDA的运算记录下来对上下文进行分析推出。他到底执行了什么操作
  • 现在把伪代码给拔下来稍加修改加个printf打印每次ANDN的结果,模拟一下整个vm的执行获取Log进行分析
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
// ConsoleApplication2.cpp : Defines the entry point for the console application.
//

#define _CRT_SECURE_NO_WARNINGS 1

//#include stdafx.h
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>

long buf = 0;
void* ptr = 0;

#define _DWORD unsigned int
#define _BYTE unsigned char

#define __int64 unsigned int

DWORD globVar;
DWORD c;

__int64 vm()
{
__int64 currentInst; // rax
_BYTE* base; // rbp
int nextInst; // ebx
__int64 v4; // rdx
__int64 v5; // rax
__int64 v6; // rax
__int64 v7; // rax
__int64 v8; // rax
__int64 v9; // rax
int v10; // eax
__int64 v11; // rax
char v12; // dl
int v13; // eax
int v14; // eax
_BYTE* v15; // rax
__int64 v16; // rax
__int64 v17; // rax
__int64 v18; // rax
unsigned int tmp; // rax

currentInst = 0LL;
base = (_BYTE*)ptr;
while (1)
{
nextInst = currentInst + 1;
switch (base[currentInst])
{
case 0:
printf(exit);
return *(unsigned int*)&base[nextInst];
case 1:
tmp = *(_DWORD*)&base[nextInst];
printf(\t#1:0x%x GOTO 0x%x\n, currentInst, tmp);
goto LABEL_35;
case 2:

v4 = nextInst;
nextInst = currentInst + 9;
base[*(signed int*)&base[v4]] = *(_DWORD*)&base[(signed int)currentInst + 5];
printf(\t#2:0x%000x [0x%x] = 0x%x [0x00x]\n, currentInst, v4, *(_DWORD *)&base[(signed int)currentInst + 5], (signed int)currentInst + 5);
break;
case 3:

v5 = nextInst;
nextInst += 4;
v6 = *(signed int*)&base[v5];
printf(\t#3:0x%000x C (0x%x) = DB [0x%x] (0x%x) \n, currentInst, c, v6, base[v6]);
goto LABEL_27;
case 4:

v7 = nextInst;
nextInst += 4;
v8 = *(signed int*)&base[v7];
printf(\t#4:0x%000x [0x%x] = C (0x%x) \n, currentInst, v8, c);
goto LABEL_31;
case 5:
v9 = nextInst;
nextInst += 4;
v10 = (char)base[*(signed int*)&base[v9]];
printf(\t#5:0x%x G (0x%x) = [0x%x] (0x%x)\n, currentInst, globVar, *(signed int *)&base[v9], v10);
goto LABEL_21;
case 6:
v11 = nextInst;
v12 = globVar;
nextInst += 4;
v8 = *(signed int*)&base[v11];
printf(\t#6:0x%x [0x%x] (0x%x) = G (0x%x)\n, currentInst, v8, base[v8], globVar);

/// base[v8] = v12;
goto LABEL_9;
case 7:
printf(\t#7:0x%x \n, currentInst);
v13 = globVar;
goto LABEL_23;
case 8:

v14 = ~(globVar & c);
printf(\t#8:0x%x C = ~(storedG 0x%x & C 0x%x) RES = 0x%x \n, currentInst, globVar, c, v14);
goto LABEL_12;
case 0xA:
printf(\t#0xA:0x%x C = GETCHAR() \n, currentInst);
v14 = getchar();

goto LABEL_12;
case 0xB:
printf(\t#0xB: PUTCHAR(C) \n);
putchar(c);
break;
case 0xC:

v15 = &base[*(signed int*)&base[nextInst]];
printf(\t#0xC:0x%x IF [0x%x]-- (0x%x) GOTO 0x%x ----------- \n, currentInst, nextInst, *v15, *(_DWORD *)&base[nextInst + 4]);
if (*v15)
{
nextInst = *(_DWORD*)&base[nextInst + 4];
--* v15;
}
else
{
printf()))) endloopp\n);
nextInst += 8;
}

break;
case 0xD:
printf(\t#0xD:0x%x C++ (0x%x)\n, currentInst, c);
++c;
break;
case 0xE:
printf(\t#0xE:0x%x G++ (0x%x)\n, currentInst, globVar);
++globVar;
break;
case 0xF:
printf(\t#0xF:0x%x \n, currentInst);
v14 = globVar;
goto LABEL_12;
case 0x10:
printf(\t#0x10:0x%x G (0x%x) = C (0x%x) \n, currentInst, globVar, c);
v10 = c;
goto LABEL_21;
case 0x11:

v16 = nextInst;
nextInst += 4;
v13 = *(_DWORD*)&base[v16];
printf(\t#0x11:0x%x C (0x%x) += DD (0x%x) \n, currentInst, c, v13);
LABEL_23:
c += v13;
break;
case 0x12:
printf(\t#0x12:0x%x C = G (0x%x)\n, currentInst, globVar);
v6 = globVar;
goto LABEL_27;
case 0x13:
printf(\t#0x13:0x%x C = [C] (0x%x)\n, currentInst, c);
v6 = c;
LABEL_27:
v14 = (char)base[v6];
goto LABEL_12;
case 0x14:
printf(\t#0x14:0x%x \n, currentInst);
v17 = nextInst;
nextInst += 4;
v14 = *(_DWORD*)&base[v17];
goto LABEL_12;
case 0x15:
v18 = nextInst;
nextInst += 4;
v10 = *(_DWORD*)&base[v18];
printf(\t#0x15:0x%000x G (0x%x) = DD [0x%x] (0x%x) \n, currentInst, globVar, v18, v10);
LABEL_21:
globVar = v10;
break;
case 0x16:
printf(\t#0x16:0x%x [G(0x%x)] = C (0x%x)\n, currentInst, globVar, c);
v8 = globVar;
LABEL_31:
v12 = c;
LABEL_9:
base[v8] = v12;
break;
case 0x17:
printf(\t#0x17:0x%x C (0x%x) -= G (0x%x) \n, currentInst, c, globVar);
v14 = c - globVar;
LABEL_12:
c = v14;
break;
case 0x18:
printf(\t#18:0x%x if C (%0x %d) goto 0x%04x <<<<<<<<<<<<<<< \n, currentInst, c, c, *(_DWORD *)&base[nextInst]);

if (c) {
LABEL_35:
nextInst = *(_DWORD*)&base[nextInst];
}
else
nextInst = currentInst + 5;
break;

default:
break;
}
if (nextInst >= buf)
return 0LL;
currentInst = nextInst;
}
}

int main()
{
FILE* v3; // rax
const char* v4; // rdi
FILE* v5; // rbx
size_t v6; // rbp
void* v8; // rax

v3 = fopen(p.bin, rb);
v4 = err 0;
if (!v3)
goto LABEL_4;
v5 = v3;
fseek(v3, 0LL, 2);
buf = ftell(v5);
fseek(v5, 0LL, 0);
v6 = buf;
if (buf <= 0)
{
v4 = err 1;
LABEL_4:
puts(v4);
return 0xFFFFFFFFLL;
}
v8 = malloc(buf);
ptr = v8;
v4 = err 3;
if (!v8)
goto LABEL_4;
v4 = err 4;
if (buf != fread(v8, 1uLL, v6, v5))
goto LABEL_4;
fclose(v5);
v4 = err 5;
if ((unsigned int)vm())
goto LABEL_4;
free(ptr);

return 0LL;
}
  • 重新编译打包运行输入31位flag回车即可看到日志,可以看到确实调用了32次getchar()
  • 首先在这里获取第一个字符存入内存0x111中。搜索0x111发现有对该内存数据进行操作的地方

  • 从图中不难看出,这里由几行ANDN。对所输入的flag进行加密。首先输入的flag中第一个是q对应的编码是0x71经过运算后就得到0x51,第二位是 w 对应的编码是0x77经过运算后是0x56。以此类推现在就要用到数电知识了。使用反演律和还原律等逻辑代数公式将其化简。也可以用卡诺图化简
  • 这里我是用逻辑代数公式化简,这里为了方便表达把前面的0xFFFFF都省略了只保留后两位

A=0x20,B=0x71DF=ABFF=DFAAE=DFBRES=AEFF将其使用公式化简得RES=AB+BARES=0x710x20=0x51令A=0x20, B=0x71\\\\ DF = \overline{A · B}\\\\ FF = \overline{DF · A}\\\\ AE = \overline{DF · B}\\\\ RES = \overline{AE · FF}\\\\ 将其使用公式化简得\\\\ RES = \overline{A}· B + \overline{B}· A\\\\ RES = 0x71 ^ 0x20 = 0x51

  • 化简后得表达式在数电中表示的就是异或所以,其实就是把输入得flag异或0x20,0x21,0x22…
  • 0x20=32就是flag的长度然后加1 进行异或,最后比较即可
  • 往下可以看到在这里用flag的最后一位和硬编码数组里的最后一位进行相减,结果不为0则两个数据不一样则跳转。

  • 从图中也能发现9这个数值的地址是0x24,直接去IDA和Hex-Dump窗口跳转到rbp+0x24即可看到09前面的直到0为止都是flag的硬编码数据,那么就可以直接用硬编码数据对它进行异或还原的到Flag
1
2
3
4
5
import binascii

data = binascii.a2b_hex('1018431415474017101D4B121F49481853540157515305565A08585F0A0C5809')
for i, v in enumerate(data):
print(chr(v ^ (0x20 + i)),end='')
  • 得到Flag “09a71bf084a93df7ce3def3ab1bd61f6”

参考文献

https://expend20.github.io/2018/05/24/RCTF-simple-vm.html

https://zhuanlan.kanxue.com/article-14172.htm