VMProtect分析-IAT修复

一、IAT保护方式

原函数调用代码:

1
004018EE FF 15 0C 30 40 00                       call    ds:__imp__GetSystemTimeAsFileTime@4

经过IAT保护后:

1
2
3
00D118FD | 52           	| push edx                           	|
00D118FE | E8 66A33000 | call vmpfuck.vmp.101BC69 |
00D11903 | 3145 FC | xor dword ptr ss:[ebp-4],eax |

由于原字节大小为6,vmp保护后使用了call vmpSeg使得字节为5,但由于要统一,因此添加了一个push reg指令。

跟进函数后,使用调试器的跟踪功能进行记录,得到代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0101BC69 | 90           	| nop                                	|
0101BC6A | 99 | cdq |
0101BC6B | BA 49578563 | mov edx,63855749 |
0101BC70 | 0FCA | bswap edx |
0101BC72 | 5A | pop edx | ;弹出堆栈,抵消外层的push edx,同时获取返回到地址,即edx=0x00D11903
0101BC73 | E9 33B9EBFF | jmp vmpfuck.vmp.ED75AB |
00ED75AB | 871424 | xchg dword ptr ss:[esp],edx | ;重新讲返回到地址写入到栈顶
00ED75AE | E9 7C020F00 | jmp vmpfuck.vmp.FC782F |
00FC782F | 52 | push edx |
00FC7830 | BA 1715D100 | mov edx,vmpfuck.vmp.D11517 |
00FC7835 | 8B92 9FAE2B00 | mov edx,dword ptr ds:[edx+2BAE9F] |
00FC783B | 8D92 48604129 | lea edx,dword ptr ds:[edx+29416048] | ;GetSystemTimeAsFileTime函数地址
00FC7841 | E9 2657F1FF | jmp vmpfuck.vmp.EDCF6C |
00EDCF6C | 871424 | xchg dword ptr ss:[esp],edx | ;写入到栈顶
00EDCF6F | E9 172FE5FF | jmp vmpfuck.vmp.D2FE8B |
00D2FE8B | C3 | ret | ;调用api函数

二、修复方法

经过多次尝试发现,IAT函数的调用全部指向VMP0,且函数头部都为nop指令。

image-20230117161756094

image-20230117161815125

因此可以直接暴力搜索.text段的E8 ?? ?? ?? ??,然后计算出call的地址是否属于vmp0段,再判断函数头部是否为nop指令来进行修复。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//搜素call vmp0的地址
for (int n = 0; n < detail->x86.op_count; n++) {
cs_x86_op* op = &(detail->x86.operands[n]);
switch (op->type)
{
case X86_OP_IMM:
{
ULONG_PTR callAddr = op->imm;
if (callAddr > segs[3].base &&
callAddr < segs[3].base + segs[3].size) //是否在VMP0段
{
unsigned char nop=NULL;
uc_mem_read(uc, callAddr,&nop,1);
if (nop == 0x90)
vec_iatList.push_back(insn->address);//添加

}
}
default:
break;
}
}

将符合条件的添加在vec数组中,然后进行unicorn模拟执行到ret。

由于模拟环境中没有api模块,因此会产生异常,通过检测异常前的指令是否为ret来进行修复。

image-20230117182421318

此时堆栈的值如下表:

addr value
esp api函数地址
esp+4 正常返回到地址

因此读取栈顶值即可获取到api地址,然后再遍历模块中的IAT进行函数名获取。

image-20230117182609319

然后修复espeip来使模拟器继续正常运行。

image-20230117182732236

修复效果如下:

image-20230117182747410

github:https://github.com/PlaneJun/vmp_fixiat