作 者: blackeyes
1. 起因:
最近跟踪一 Asprotect 保护的程序, 发现 stolen code 都是在 Asprotect 自己的虚拟机中执行,
非常不利于跟踪与分析, 于是把 Asprotect 的虚拟机代码进行了分析.
2. 代码处理概述
还是用例子来说明吧, 原始的一段 CODE 如下:
00D6FC1C 55 PUSH EBP
00D6FC1D 8BEC MOV EBP,ESP
00D6FC1F 83C4 E0 ADD ESP,-20
...
00D6FD48 8BE5 MOV ESP,EBP
00D6FD4A 5D POP EBP
00D6FD4B C2 0C00 RETN 0C
Asprotect 将上面的每一行机器代码分析处理, 然后每一行保存到一个固定大小的结构中,
运行的时候这段代码就只需要下面四行:
00D6FC1C 68 00000000 PUSH 0
00D6FC21 68 1CFCD600 PUSH 0D6FC1C
00D6FC26 68 B432E600 PUSH 0E632B4
00D6FC2B E8 18960000 CALL 00D79248
其中:
00D6FC1C ----- 代码起始地址
00E632B4 ----- 一结构起始地址, 包含处理后的代码信息
00D79248 ----- X86 虚拟机 Function 地址
3. 机器代码分析
每一行机器代码被分析处理后, 会分解成 10 项 保存到结构中, 如下:
1 - 第 1 个 机器码的内存起始地址;
2 - 机器码的第 1 个 BYTE, 如果不是前缀机器码, 就是真正的机器码的第 1 个 BYTE;
3 - 机器码的第 2 个 BYTE, 并且前面是前缀机器码, 它是真正的机器码的第 1 个 BYTE;
4 - 机器码中的立即数是否要调整, 相当于重定位, 例如;
00400000 68 34124000 PUSH 00401234
如果希望这行代码在 00500000 是这样工作的:
00500000 68 34125000 PUSH 00501234
即表示机器码中的立即数是随段起始地址而调整的.
5 - 机器码中的 第 1 个 立即数;
6 - 机器码中的 第 2 个 立即数;
7 - 机器码中的算术/逻辑操作;
0:ADD, 1:OR, 2:ADC, 3:SBB, 4:TEST, 5:SUB, 6:XOR, 7:CMP
8 - 机器码中的 ModRM 操作码;
9 - 机器码中的 SIM 操作码;
10 - 机器码中的 Displacement 操作码;
每一项都由一 Function 读出, 其中一些还要做一些变换.
并不是每一项都存在于每一行机器码.
4. 机器代码数据结构
每一行机器代码对应的结构如下:
typedef struct {
BYTE FirstOpcode_0;
BYTE Unknown1;
BYTE SecondImmediateData;
BYTE Unknown2[5];
BYTE SIMOpcode;
BYTE Unknown3[2];
DWORD DisplacementOpcode;
BYTE Unknown4;
BYTE FirstOpcode_1; // if FirstOpCode is a prefix
BYTE Unknown5[3];
DWORD ImmediateDataOpcode;
DWORD Unknown6;
BOOL bAdjustValueFlag;
BYTE Unknown7;
DWORD EncryptedEIPAddress; //+1E , EncryptedEIPAddress + baseAddress + randxx ==>EIPAddress
BYTE Unknown8[2];
BYTE MathType; // Mathtype or an additional opcode
BYTE Unknown9[3];
BYTE ModRMOpcode;
DWORD Unknown10;
} ENC_LINE;
每一段代码由 n 行代码构成, 对应如下的结构:
typedef struct {
DWORD Unknown;
DWORD pFirstItem;
DWORD ItemNum;
BYTE FuncIndex[0x0A]; // 00E632C0 01 06 05 00 08 04 03 07 02 09
BYTE FuncIndex2[0x0A];
DWORD Funcs[0x0A];
/*
00E632D4 011F0000 // return __0014 Func3
00E632D8 011E0000 // return __10 Func0
00E632DC 01200000 // return __1C Func8
00E632E0 011C0000 // return __08 Func6
00E632E4 01230000 // return __28 Func5
00E632E8 01220000 // return __24 Func2
00E632EC 011A0000 // return __00 Func1
00E632F0 011D0000 // return __000B Func7
00E632F4 011B0000 // return __02 Func4
00E632F8 01210000 // return __001E Func9
*/
DWORD ItemSize;
DWORD BaseAddr;
DWORD RandomXX; // +50
DWORD procID;
DWORD Size;
ENC_LINE Lines[0];
} ENC_INFO;
其中 FuncIndex[], FuncIndex2[], Funcs[], 每次运行都会随机重新排序, 但是
for(i=0;i<0x0A;i++) {
j = FuncIndex[i];
Funcxx = Funcs[j]; // Funxx 跟 i 是一一对应的
}
这是Funcxx的返回值 与 i 的 对应图
00E63310 A1 88 00 4A 7B A2 B0 2F 00 0F 18 00 00 00 00 C9 1?4?????6??7777?
00E63320 00 54 F7 21 00 00 00 00 7D 05 54 89 00 9C 68 FD 0???3333????8?99
00E63330 EC A1 7B BC 00 25 45 BF 00 61 08 4A F5 99??2???5????
