最近学习脱The Enigma Protector壳发现IAT处理的关键代码都在虚拟机里面,让我郁闷了好一段时间,几次想写引篇文章,怕理解上有误而误导大家,想了想又放弃了,这次终于鼓起勇气为大家献上此篇文章,如果有错误欢迎指正,我会第一时间将错误更正。
此文献给和我一样不懂虚拟机的朋友,由于我的语言表达能力有限希望通过此文让大家了解虚拟机是怎么一回事,如果雷同,纯属抄袭。^_^
下面的分析以The Enigma Protector2.5虚拟机。
一、虚拟机入口
总之来说The Enigma Protecto从低版本到现在的2.5版本虚拟机结构变化不大。虚拟机的入口主要作用是保存当前的寄存器、标志寄存器、SHE链、TEB.StackLimit结构成员的值、TEB.StackBase结构成员的值
1.压入堆栈的加密索引
在进入虚拟机以前,会将一个加密的索引压入堆栈,该索引是虚拟机代码执行的开始位置,紧接着JMP到VMEntry虚拟机入口。
2.保存当前寄存器环境
进入VMEntry后压将8个常用寄存器和EFLAG寄存器压入堆栈中。
下面是pusha、pushf操作后的寄存器压入堆栈时的分布图:
3.紧接着保存寄存器环境到VM_CONTEXT结构中,该结构保存了进入虚拟机进的寄存器及堆栈环境。
我将虚拟机上下文内包含了一个结构,由VM_CONTEXT和包含的结构VM_RegCONTEXT寄存上下文两个结构组成
VM_CONTEX及里面的VM_RegCONTEXT大小为54H字节,总结构大小为64H字节, 其他的字段我想看名字就明白什么含义了,结构中没有引用的偏移量目前暂未定义名字,也不明白什么含义请大家原谅,呵呵。
VMStarus:这个字段暂时定义为虚拟机状态。
NewESP:当前虚拟机运行的堆栈
StackBase:为当前虚拟机栈的起始地址
StackLimit:为当前虚拟机栈的结束地址
OriginalExceptionList:该字段为进入虚拟机以前,将保存原始SHE结构链地址
OriginalStackLimit:该字段为进入虚拟机以前,将保存原始堆栈上限地址
OriginalStackBase:该字段为进入虚拟机以前,将保存原始起始地址
4.保存当前的堆栈起始地址、堆栈上限地址并设置新的虚拟机堆栈起始地址、堆栈上限。
5.调用虚拟机解释引擎完毕后,会恢复原始堆栈的值及保存返回地址、VM_CONTEXT的寄存器值到原始堆栈中。
二、虚拟机的解释引擎分析
现在来到了本文分析的重点了,在分析之前先让大家复习一下汇编知识。
一条完成的汇编指令分为:
无操作数类如:CMC、CDQ等
单操作数类:PUSH、POP、JMP、JE、JNE等
双操作数类: MOV、LODS、STOS、LOOP等
三操作数类: IMUL、SHLD等
如下图:
关于寻址方式我只列出比较复杂点的,其他的寻址方式就不一一列出了。
寻址方式在偏移量进行寻址时,内存地址的偏移量可分为三部分:一个32位基址寄存器,一个可乘1、2、4或8的32位变址寄存器,一个8位/32位的偏移常量,并且这三部分还可进行任意组合,省去其中之一或之二。
1.VMDispatch的参数
在VMDispatch里一共传进来了两个参数,一个是加密的索引相当于VM_EIP吧,另一个是虚拟机上下文结构的地址。在各个Header里主要工作任务是改变原始寄存器及内存的值及数据的各种运算。
2.VM_DiapatchTable
VM_DiapatchTable是虚拟机的一个派遣表,是虚拟机的调度中心,VMStart会读取当前虚拟机指令后,决定调用哪一个Handle去解释执行。
3.Opcode结构
由上图可以看到通过简单的乘法运算LEA ESI,[BBX+EBX*8],MOV EAX,[EAX + ESI*8]得到真的要读取虚拟机指令的位置。
虚拟机指令Opcode的结构大小为48H,该结构由vmMainOpcode及子结构vmParam结构组成,它包含了主要虚拟机指令及操作数的操作方式、操作数的寻址方式、常量、全局变量、内存操作数大小等信息。
4.Handle的分析
在此之前先讲讲Handle一般工作流程:
下面的一般工作流程跟据不同的指令可多可少。
(1)取操作数的值
首先会读取vmParam结构,取得三个操作数的值。如果当前指令只有二个操作数那么第三个操作为0,同样如果只有一个操作数,第二、三个操作数取回的值为0。
跟据寻址方式,其中三个操作数的值可能是来自寄存器或内存
(2)计算操作数的值
(3)保存操作数
操作数的值运算之后,就该把值写回到VM_RegContext的寄存器或内存当中了。一般来说将保存到目标操作数里如:MOV、ADD、SUB等。还有IMUL、BSWAP、XCHG会把值保存到第一个操作数和第二个操作数。
(4)影响的标志位
如ADD、SUB、MUL、DIV、OR、NOT、AND、SHL、SHR等等指令的操作会影响到标志位。VM_EFLAG标志位可能将会被设置。
(5)返回VM_DiapatchTable指令下一条指令
有了上面的知识,现在开始着手分析Handle了。
VM_ADD分析:
取操作数:
GetOperand分析:
GetOperand用三重循环分别取三个操作数的值
(1) 初始化一个值
首先Mov [ebp+OperandValue],eax初始化一值,三个操作数的临进的值都会入此操作数
(2)
确定操作数大小
确定操作数是BYTE PTR、WORD PTR、DOWRD PTR
(2)寻址方式
跟据vmParam结构确定寻址试并计算出表达式的值即操作数的值, 如: [esi]、[esi+ 28]、[esi+edx*4]、[esi+edx*4+8]等
(3)
保存操作数的值
最后将值保存到操作数,内存操作数将会计算寻址方式并取出内存中的值放入相应的”操作数”
现在返回到VM_ADD
取回操作数就要开始进行简单的加法运算了:
将值保存到第一个操作数:
加算运算之后当然要把值放入回操作数了
最后由于加法运算会影响标志位
将加法算后的Value和未相加之前第一个操作数值进行比较,来确定标志位是否OF、SF、CF、ZF等
全文分析完毕,其实IDB文件中的注释会更详细,希望帮助大家对虚拟机的理解有所帮助。
因篇幅问题不能全部显示,请点此查看更多更全内容