对handler分类的依据是对PCODE.OpcodeDetail数据和VM_Context.flag的使用方式。不同类型的handler对flag各个位有不同的解释。同属1类的handler真正用到的标记位也不同,通过分析各位对执行细节的影响可以推测出其含义。
这部分内容比较繁琐,下面先列出具体代码。
Handler
Type 1______________________________________________________
绝大部分指令都属于这一类(包括上文的VM_Load),这类指令的OpcodeDetail为2字节。下面以Vm_ADD为例。
VM_Context.flag被预置为4。其含义为将flag.OperandSize域(b1,b2)置为10b=2 ,代表32位操作。后面给出flag的完整定义,会看得更清楚。
解码2个字节,称其为byte1,byte2。从现在起把用key1解码的Argument称为OldArgument,
将后面的switch-case压入的dword称为NewArgument。
先看1557FD3对byte2的检测。若byte2的b7为1,调整vm栈内的OldArgument。由于VM_Context内这2个dword始终为0,这里的sub/add并没有实际的作用。在手工还原代码的过程中我没碰到b7为1的情况,也许注册版会不同。
若byte2为30h, 设置flag.FS域(b6),代表对内存寻址时使用fs段寄存器。
若byte2为50h,设置flag的b7,这个位的含义不清楚,没有发现哪个handler检测了该位。对byte2的使用就是这些。
现在回过头看看byte1。byte1被分为2部分,b7和b0-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(即byte1的b7),这个位为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,在环内的offset为0,OldArgument的解码值为F0000000h,Vm_Context.ebx的offset为8,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
这里设置了flag的b4,这个标记位的含义不清楚,检测了该标记的handler只有Vm_PUSH,其作用看起来和ByRef相似,表示操作数应作为指针使用。从还原的部分代码看,解码的byte1会导致b4置1时执行的指令总是:
pop [addr_context.esp] (4 bytes)
即从vm栈内弹出1个dword到VM_Context.esp。这个标记可能代表与VM_Context.esp相关的操作。
8 -> OperandSize = 0
9 -> OperandSize = 1
A,B -> OperandSize = 2
若SignBit为1, OldArgument代表VM_Context内的通用寄存器,NewArgument为计算的实际地址,否则NewArgument等于OldArgument
Case C,D,E,F
解码的OldArgument为指针,取其所指的dword替换保存在vm栈内的原OldArgument(若SignBit为1,原OldArgument代表VM_Context内通用寄存器,应先计算出实际地址再取值)。
ByRef = 1
C -> OperandSize = 0
D -> OperandSize = 1
E,F -> OperandSize = 2
NewArgument
= VM_Context.register地址
Case 10,11
若SignBit为1,OldArgument代表VM_Context内通用寄存器地址或pcode数据地址,用计算的实际地址替换vm栈内的OldArgument。
NewArgument
= OldArgument(若SignBit为1,用的是替换后的值)
Case 18,19,1A
flag.Stack
= 1 操作数来自vm栈
18 -> OperandSize = 0
19 -> OperandSize = 1
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,表示ADD的2个operand都取自vm栈。计算结果写回vm栈内dst所在位置。先看operand在栈内的情况。
检测flag.OperandSize,上面代码处理的是8位操作。注意在执行add前,将VM_Context.eflag赋给eflag寄存器,执行完后再将当前eflag保存回VM_Context.eflag变量。
16位操作。
32位操作。
flag.Stack为0的代码。
所以Vm_ADD的操作可用伪码表示为:
下面列出Type1 handler使用的flag。在分析完全部4类handler后会给出VM的指令集,可以看到各handler是如何使用flag的。
b7 |
b6 |
b5 |
b4 |
b3 |
b2 b1 |
b0 |
? |
FS寻址内存时是否使用fs段 |
EFlag是否操作VM_Context.Eflag(比如模仿popf) |
?操作VM_Context.esp? |
Stackoperand来自vm栈(由前面执行的handler压入) |
OperandSize
0 -> 8位 1 -> 16位 2 -> 32位 |
ByRef是否将数据解释为地址(而不是立即值) |
下表为TypeBits(byte1的b0-b6)对NewArgument及flag的影响。为空表示无需修改原值
TypeBits |
flag |
Operand
Size |
OldArgument |
NewArgument |
0 |
|
32b |
|
0 |
1 |
ByRef |
32b |
|
VM_Context.register地址 |
2 |
ByRef |
32b |
if(SignBit),OldArgument为VMContext内段寄存器,通用寄存器或某项PCode数据offset,替换为对应的实际地址 |
VM_Context.register地址 |
3 |
||||
4 |
ByRef |
8b |
|
VM_Context.register值 |
5 |
16b |
|||
6 |
32b |
|||
7 |
32b |
|||
8 |
OperateEsp ? |
8b |
|
if(SignBit),OldArgument为VM_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),OldArgument为VM_Context内通用寄存器或PCode数据offset,替换为实际地址 |
OldArgument(if(SignBit),用计算的真实地址值) |
11 |
||||
18 |
Stack |
8b |
|
0 |
19 |
16b |
|||
|
32b |
|||
1E |
EFlag |
32b |
|
VM_Context.eflag |
20 |
|
8b |
|
OldArgument |
21 |
16b |
|||
22 |
32b |
|||
23 |
32b |