handler分类的依据是对PCODE.OpcodeDetail数据和VM_Context.flag的使用方式。不同类型的handlerflag各个位有不同的解释。同属1类的handler真正用到的标记位也不同,通过分析各位对执行细节的影响可以推测出其含义。

这部分内容比较繁琐,下面先列出具体代码。

Handler Type 1______________________________________________________

绝大部分指令都属于这一类(包括上文的VM_Load),这类指令的OpcodeDetail2字节。下面以Vm_ADD为例。

 

 

VM_Context.flag被预置为4。其含义为将flag.OperandSize(b1,b2)置为10b=2 ,代表32位操作。后面给出flag的完整定义,会看得更清楚。

 

解码2个字节,称其为byte1,byte2。从现在起把用key1解码的Argument称为OldArgument,

将后面的switch-case压入的dword称为NewArgument

 

先看1557FD3byte2的检测。若byte2b71,调整vm栈内的OldArgument。由于VM_Context内这2dword始终为0,这里的sub/add并没有实际的作用。在手工还原代码的过程中我没碰到b71的情况,也许注册版会不同。

 

byte230h, 设置flag.FS(b6),代表对内存寻址时使用fs段寄存器。

 

byte250h,设置flagb7,这个位的含义不清楚,没有发现哪个handler检测了该位。对byte2的使用就是这些。

 

现在回过头看看byte1byte1被分为2部分,b7b0-b6。称b7为符号位SignBit,b0-b6为类型位TypeBits。下面是根据TypeBits值的switch-case分枝。

 

这种代码前文已经出现过了。switch-case做了2件事,根据SignBit,TypeBits继续设置flag,vm栈内压入NewArgument。现在跟过去,把所有分枝走到J

 

Case 0                                                                             

NewArgument = 0

 

Case 1                                                    

                          

flag.OprandSize = 2(0->8,1->16,2->32)

flag.ByRef = 1 操作数是个指针

NewArgument = VM_Context.register的地址,所谓VM的寄存器只是VM_Context内的1个成员变量。

 

Case 2,3       

                                                                     

flag.ByRef = 1

flag.OprandSize = 2

NewArgument = VM_Context.register的地址

这里检查了SignBit(byte1b7),这个位为1有特殊的含义,此时OldArgument代表以偏移量表示的某个地址,offset表示是因为其真实地址与VM映射基地址相关。

 

下面计算其真实地址,替换掉vm栈内的原OldArgument

 

F0000080h <= OldArgument <= F000008Ah,则减去F0000080h的结果(0-A)代表从VM_Context.segCS开始计算的offset,OldArgument实际代表VM_Context内某个段寄存器地址。

2个取值范围检查。

F0000000h <= OldArgument <= F0000070h,则减F0000000h的结果(0-70)代表VM_Context内通用寄存器闭合环内的offset。后面的几行代码根据当前滚动字节将其调整为指向正确的地址。

 

VM_Context.eax,在环内的offset0,OldArgument的解码值为F0000000h,Vm_Context.ebxoffset8,OldArgument解码值为F0000008h。前文已经提到过寄存器滚动对写解码程序没有影响,解出的OldArgument明确提定了要访问的寄存器。

 

OldArgument不在上述2个范围内,则将其减80000000h,代表该VM保护代码对应pcode数据内的offset,即指向某个pcode数据。

 

Case 4,5,6,7                                                

                       

flag.ByRef = 1

4   -> OperandSize = 0

5   -> OperandSize = 1

6,7 -> OperandSize = 2

NewArgument = VM_Context.register的值

 

Case 8,9,A,B                   

                                                      

这里设置了flagb4,这个标记位的含义不清楚,检测了该标记的handler只有Vm_PUSH,其作用看起来和ByRef相似,表示操作数应作为指针使用。从还原的部分代码看,解码的byte1会导致b41时执行的指令总是:

 

pop [addr_context.esp] (4 bytes)

 

即从vm栈内弹出1dwordVM_Context.esp。这个标记可能代表与VM_Context.esp相关的操作。

8   -> OperandSize = 0

9   -> OperandSize = 1

A,B -> OperandSize = 2

 

SignBit1, OldArgument代表VM_Context内的通用寄存器,NewArgument为计算的实际地址,否则NewArgument等于OldArgument

 

Case C,D,E,F           

                                                            

解码的OldArgument为指针,取其所指的dword替换保存在vm栈内的原OldArgument(SignBit1,OldArgument代表VM_Context内通用寄存器,应先计算出实际地址再取值)

 

ByRef = 1

C   -> OperandSize = 0

D   -> OperandSize = 1

E,F  -> OperandSize = 2

NewArgument = VM_Context.register地址

 

Case 10,11            

                                                             

 

SignBit1,OldArgument代表VM_Context内通用寄存器地址或pcode数据地址,用计算的实际地址替换vm栈内的OldArgument

 

NewArgument = OldArgument(SignBit1,用的是替换后的值)

 

Case 18,19,1A                                                                       

flag.Stack = 1 操作数来自vm

18  -> OperandSize = 0

19  -> OperandSize = 1

1A  -> OperandSize = 2

NewArgument = 0

 

Case 1E               

                                                               

flag.EFlag = 1

NewArgument = VM_Context.eflag

 

Case 20,21,22,23                                             

                      

20    -> OperandSize = 0

21    -> OperandSize = 1

22,23 -> OperandSize = 2

NewArgument = OldArgument

 

Default                  

                                                            

NewArgument = 0

 

 

执行完switch-case,vm栈内[esp]NewArgument,[esp+4]OldArgument(可能调整过),flag相应标记已置位。这实际是个操作数准备过程。

 

上面的代码对所有Type1类型的handler是相同的(是程序生成的J)。具体使用哪些操作数和标记,与具体的handler相关。

 

下面看看Vm_ADD的执行过程。

flag.Stack若为1,表示ADD2operand都取自vm栈。计算结果写回vm栈内dst所在位置。先看operand在栈内的情况。

 

检测flag.OperandSize,上面代码处理的是8位操作。注意在执行add,VM_Context.eflag赋给eflag寄存器,执行完后再将当前eflag保存回VM_Context.eflag变量。

 

16位操作。

 

32位操作。

 

flag.Stack0的代码。

所以Vm_ADD的操作可用伪码表示为:

文本框: if(flag.Stack)
{
    add [esp+4],[esp] ;OperandSize = 8/16/32位
}
else if(flag.ByRef)
{
VM_Context.register = add([NewArgument],OldArgument) ; 32位
}

 

 

 

 

 

 

下面列出Type1 handler使用的flag。在分析完全部4handler后会给出VM的指令集,可以看到各handler是如何使用flag的。

 

b7

b6

b5

b4

b3

b2  b1

b0

?

 

FS

寻址内存时是否使用fs

EFlag

是否操作VM_Context.Eflag(比如模仿popf)

?

操作VM_Context.esp?

Stack

operand来自vm(由前面执行的handler压入)

OperandSize

0 -> 8

1 -> 16

2 -> 32

ByRef

是否将数据解释为地址(而不是立即值)

 

 

下表为TypeBits(byte1b0-b6)NewArgumentflag的影响。为空表示无需修改原值

 

Type

Bits

flag

Operand

Size

OldArgument

NewArgument

0

 

32b

 

0

1

ByRef

32b

 

VM_Context.register地址

2

ByRef

32b

if(SignBit),OldArgumentVMContext内段寄存器,通用寄存器或某项PCode数据offset,替换为对应的实际地址

VM_Context.register地址

3

4

ByRef

8b

 

VM_Context.register

5

16b

6

32b

7

32b

8

OperateEsp

?

8b

 

if(SignBit),OldArgumentVM_Context内通用寄存器offset, NewArgument为对应的实际地址,否则NewArgument=OldArgument

9

16b

A

32b

B

32b

C

ByRef

8b

OldArgument为指针,将其替换为所指的dword

(if(SignBit),代表VM_Context内通用寄存器offset,需先计算实际地址地址)

VM_Context.register地址

D

16b

E

32b

F

32b

10

 

32b

if(SignBit),OldArgumentVM_Context内通用寄存器或PCode数据offset,替换为实际地址

OldArgument(if(SignBit),用计算的真实地址值)

11

18

Stack

8b

 

0

19

16b

1A

32b

1E

EFlag

32b

 

VM_Context.eflag

20

 

8b

 

OldArgument

21

16b

22

32b

23

32b