Handler的基本执行流程

下面介绍handler的执行过程。不同类型的handlerVM_Context.flag的位有不同的解释,对于从PCODE.OpcodeDetail解码数据的使用也不同。这里只涉及handler共同的部分,handler类型相关的细节在分析handler类别的时候再讲。

 

Vm_Load指令为例,这个指令将1imm32装入VM_Context.register(VM的寄存器),后续代码并未使用,实际上等同于垃圾指令。该指令不使用VM_Context.flag,可以避免过多的细节。

 

进入handler,esi置为pcode数据地址,Init_Keys字节,b71则将Key10。取InitKey1_and_NumOfRotate(名取得很拙劣,但总得有个名字J)字节的低2,进一步变换Key1

 

cl=1。这些imm8是加壳时生成的随机数。

 

cl = 2

 

cl = 3

 

InitKey1_and_NumOfRotateb2-b4 3,这个值若为0,jz跳走。否则将VM_Context内的通用寄存器环滚动指定的字节数。这段代码看起来比较啰嗦,对于写解码器也没有影响,可以直接到jzdst继续。

 

如果你想看看数据是怎么滚动的,可以用OllyDbg,在进入VM前将各寄存器设为特定值。

进入VM,dump窗口显示VM_Context

设置停止条件,然后Ctrl-F11跟踪。

 

很直观J。注意不是每次都会滚动,寄存器的值也会因执行的操作而变化。

 

继续。下面解码PCODE.Argument

解码handler操作需要的细节数据,即上文中的OpcodeDetailVm_Load需要2byte

 

这里将flag的值预置为4。解码2个字节,根据解码结果设置VM_Context.flag。具体的细节在分析特定类型的handler时再讲。

 

 

测试OpcodeDetail1个字节的低7,继续设置VM_Context.flag。这是个switch-case,无论走哪个分枝,都会在VM栈内压入1dword。细节后面再讲。

 

下面有很多caller的代码就是switch-case的出口。到这里,VM栈内(与进入该handler时相比)压入了2dword,[esp+4]为解码的Argument,[esp]switch-case压入的dword

 

 

解码下1条指令的opcode2

 

 

接下来这不起眼的2,就是在执行Vm_Load了。将switch-case压入栈的dword送到VM_Context.register

 

 

解码下1条指令的opcode1

 

 

解码下1条指令的pcode数据地址。

 

到这里结束当前handler,执行下1条指令。

 

对于当前handler的执行,有几项数据是在执行实质的操作前必须解码的,即寄存器滚动字节数,Argument,OpcodeDetail。跳到下条指令需要的数据(Next_pcode,Next_opcode1,Next_opcode2)只要在jmp esi前解码即可,实际上Themida正是这样做的,3项数据的解码操作位置是可变的。

 

在下面的部分,我们将详细分析4handler的细节。