示例代码
示例代码如下:
#include "stdafx.h"
#include <Windows.h>
int Calc(int a,int b)
{
return a + b;
}
int main(int argc, char* argv[])
{
Calc(2,3);
getchar();
return 0;
}
同样用VMP1.09对程序进行加壳,只对两句代码进行VM处理。
这次的目的是为了分析虚拟机,选择最小保护。
VM完整流程分析
来到Calc函数当前堆栈值为+0的位置的返回地址,+4和+8的位置是Calc函数的参数
VM解码循环
接着VM开始部分的代码和之前分析过的一样,取指令流然后跳转到handler。
区别于VM最大保护的版本,最小保护的VM代码少了解密指令流的部分,解密Key中保存的就是真实的VM指令流。
保存寄存器环境
首先handler执行代码保存当前的寄存器环境,保存的寄存器环境和保存后的VM_Context如下:
当前堆栈
$ ==> > 00000000--->第二个密钥
$+4 > 0019FED0--->EDI
$+8 > 00401520--->ESI
$+C > 0019FED0--->EBP
$+10 > 0019FE7C--->ESP
$+14 > 00309000--->EBX
$+18 > 021E0FC0--->EDX
$+1C > 00000000--->ECX
$+20 > CCCCCCCC--->EAX
$+24 > 00000206--->EFlags
VM_Context
$ ==> >CCCCCCCC--->EAX
$+4 >00000206--->EFlags
$+8 >0019FED0--->EDI
$+C >00000000--->第二个密钥
$+10 >00000000--->ECX
$+14 >02210FC0--->EDX
$+18 >000B8FA5--->
$+1C >F609851D--->
$+20 >E654F2EE--->
$+24 >0019FED0--->EBP
$+28 >003CA000--->EBX
$+2C >00401520--->ESI
压入EBP和偏移
VM代码保存完了寄存器之后,就要开始执行程序原本的功能了
执行完三个handler之后,VM压入了三个值,分别是
$ ==> > 00000008
$+4 > 0019FED0
$+8 > 0000000C
其中8和C是偏移,而0019FED0是EBP的地址,再来看一下EBP的值
0019FED0 0019FF30
0019FED4 00401126 返回到 VMTest_v.main+26 来自 VMTest_v.0040100A
0019FED8 00000002
0019FEDC 00000003
EBP+8和+C的位置正好是被VM函数的两个参数,VM识图通过这种方式来取到参数,进行运算
执行函数
接着handler将参数一保存到VM_Context+0x20的位置
接着handler执行函数代码,将两个参数相加,结果保存在堆栈中
接着将计算结果保存在VM_Context+0x1C的位置。当前VM寄存器环境如下:
VM_Context
$ ==> >CCCCCCCC--->EAX
$+4 >00000206--->EFlags
$+8 >0019FED0--->EDI
$+C >00000000--->第二个密钥
$+10 >00000000--->ECX
$+14 >02210FC0--->EDX
$+18 >000B8FA5--->
$+1C >F609851D--->计算结果
$+20 >E654F2EE--->参数
$+24 >0019FED0--->EBP
$+28 >003CA000--->EBX
$+2C >00401520--->ESI
恢复寄存器环境
接着将VM_Context中的寄存器恢复到堆栈中,然后再从堆栈将数据恢复到寄存器
函数返回时,功能执行完成。至此,走完了整个VM的虚拟机流程
总结
VM代码流程总结如下:
- 保存寄存器环境到堆栈
- 将堆栈的寄存器环境保存到VM_Context
- 执行真正的函数代码
- 恢复VM_Context到堆栈
- 恢复寄存器