Asprotect 中的 X86 虚拟机代码分析
作 者: 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];
[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!