2.2.NAND(与非门)
本文的两节重头戏来了,NAND(与非门)与EFLAGS标志位检测+跳转,理解完了这两节后,对于VM就可以无视了,一切伪指令在你眼里都是正常的指令。跟踪VMP就和跟踪普通程序一样,想看API获取就看API获取,想看看程序的anti方式就看anti方式。一切都回到了正常,你可以看穿VM(虚拟机)这个吓人的外衣。
2.2.1.NAND起源
NAND(与非门)和NOR(或非门)来源于de Morgan's Laws(德·摩根定律),运用于逻辑、数字电路等方面,本节专注于它与and or xor not 之间的联系。
德·摩根定律是属于逻辑学的定律。 德·摩根定律(或称德·摩根定理)是形式逻辑中有关否定所描述的系统方式中的逻辑运算符对偶对的一系列法则。由此引出的关系也就被称为“德·摩根二重性”。
奥古斯都·德·摩根首先发现了在命题逻辑中存在着下面这些关系:
非(P 且 Q)=(非 P)或(非 Q)
非(P 或 Q)=(非 P)且(非 Q)
德·摩根的发现影响了乔治·布尔从事的逻辑问题代数解法的研究,这巩固了德·摩根作为该规律的发现者的地位,尽管亚里士多德也曾注意到类似现象、且这也为古希腊与中世纪的逻辑学家熟知(引自Bocheński《形式逻辑历史》)。(引自维基百科,关键字:德·摩根定律)
我们再来看它在数学逻辑中的表示:
1.jpg下载此附件需要消耗2Kx,下载中会自动扣除。
(引自:MathWorld,关键字:de Morgan's Laws)
由于不是用我们熟悉的计算机方式来表达,上面的两段解说比较抽象,请看2.2.2.
2.2.2.NAND与逻辑运算
在加壳记事本中使用的是NAND,下面部分将专注于NAND。对于NOR,理论都是一样的,只是不用NAND来实现。
NAND(A,B):
NOT(A)
NOT(B)
ADN(A,B)
这就是NAND的操作方式。NAND的价值在于:使用NAND可以实现NOT AND OR XOR这4个逻辑运算。
NOT(A):
NAND(A,A)
AND(A,B):
NAND(NAND(A,A),NAND(B,B))
OR(A,B):
NAND(NAND(A,B),NAND(A,B))
XOR(A,B):
NAND(NAND(NAND(A,A),NAND(B,B)),NAND(A,B))
2.2.3.VMP伪指令执行过程
NOT(4DBE4AD8):
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_PUSHdw_EBP
0013F9A8 0013F9AC .
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_COPYdw_EBPSTACK
0013F9A8 4DBE4AD8 JM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_NANDdw
0013F9A8 00000286 ..
0013F9AC B241B527 'A
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(A,A)
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9AC B241B527 'A
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
NOT(4DBE4AD8)=B241B527
AND(4DBE4AD8,4DFD2FC2):
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_PUSHdw_EBP
0013F9A8 0013F9AC .
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_COPYdw_EBPSTACK
0013F9A8 4DBE4AD8 JM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_NANDdw
0013F9A8 00000286 ..
0013F9AC B241B527 'A
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(NAND(A,A),NAND(B,B))
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9AC B241B527 'A
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_PUSHdw_IMMEDIATEdw
0013F9A8 B202D03D =
0013F9AC B241B527 'A
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(NAND(A,A),NAND(B,B))**B202D03D=NAND(B,B)**
VM_NANDdw
0013F9A8 00000206 ..
0013F9AC 4DBC0AC0 .M
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(NAND(A,A),NAND(B,B))VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9AC 4DBC0AC0 .M
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VMP的B数据是直接传递它的相反数B202D03D给VM,相当于隐藏了一次NAND(B,B)的过程。AND(4DBE4AD8,4DFD2FC2)=4DBC0AC0
OR (00000293,00000100):
0013F780 00000293 ..
0013F784 00000100 ...
VM_NANDdw ;NAND(NAND(A,B),NAND(A,B))
0013F784 FFFFFC6C l
VM_PUSHdw_EBP
VM_COPYdw_EBPSTACK ;复制结果,就相当于NAND(NAND(A,B),NAND(A,B))
0013F780 FFFFFC6C l
0013F784 FFFFFC6C l
VM_NANDdw ;NAND(NAND(A,B),NAND(A,B))
0013F784 00000393 ..
OR (00000293,00000100)=00000393
XOR(4DBE4AD8,4DFD2FC2):
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_PUSHdw_EBP
0013F9A8 0013F9AC .
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_COPYdw_EBPSTACK
0013F9A8 4DBE4AD8 JM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_NANDdw
0013F9A8 00000286 ..
0013F9AC B241B527 'A
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(NAND(NAND(A,A),NAND(B,B)),NAND(A,B))
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9AC B241B527 'A
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_PUSHdw_IMMEDIATEdw
0013F9A8 B202D03D =
0013F9AC B241B527 'A
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(NAND(NAND(A,A),NAND(B,B)),NAND(A,B))**B202D03D=NAND(B,B)**
VM_NANDdw
0013F9A8 00000206 ..
0013F9AC 4DBC0AC0 .M
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(NAND(NAND(A,A),NAND(B,B)),NAND(A,B))
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9AC 4DBC0AC0 .M
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_PUSHdw_EDISTACKdw
0013F9A8 4DBE4AD8 JM
0013F9AC 4DBC0AC0 .M
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_PUSHdw_IMMEDIATEdw
0013F9A4 4DFD2FC2 /M
0013F9A8 4DBE4AD8 JM
0013F9AC 4DBC0AC0 .M
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_NANDdw
0013F9A4 00000282 ..
0013F9A8 B2009025 %.
0013F9AC 4DBC0AC0 .M
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(NAND(NAND(A,A),NAND(B,B)),NAND(A,B))
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9A8 B2009025 %.
0013F9AC 4DBC0AC0 .M
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_NANDdw
0013F9A8 00000202 ..
0013F9AC 0043651A eC.
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(NAND(NAND(A,A),NAND(B,B)),NAND(A,B))
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9AC 0043651A eC.
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
上面这条XOR指令,就是VM在确定跳转地址后的解密指令。加密地址是A数据4DBE4AD8,XOR运算后得到ESI跳转地址0043651A。
在VMP中,减法是采用迂回的方式实现的:
A-B:
NOT(A)
A=A+B
NOT(A)
而NOT运算又要使用NAND来完成
A-B:
NAND(A,A)
A=A+B
NAND(A,A)
2.3.EFLAGS标志位检测+跳转
这一节看完后,就可以畅通无阻的浏览VMP的伪指令了。
2.3.1.判断两个数是否相同
举例数据:
把立即数0000和内存00427D51中的1个word数据比较,检测是否为0。
整个过程分为两个阶段:
第一阶段:执行减法运算
A-B:
NAND(A,A) ;这里的标志位是无用的
A=A+B ;获得标志位A
NAND(A,A) ;获得标志位B
第二阶段:合并两个标志位
A=AND(A,00000815)
B=AND(B,FFFFF7EA)
A=A+B
第三阶段:检测ZF位+跳转
构建跳转地址结构
检测ZF位
获得加密跳转地址
解密跳转地址
调用VM_JMP
在开始这个部分前,把所有VM_MOV_EDISTACK_EBPSTACK伪指令中的AND AL,3C指令的下一条指令处下好断点,我们要记录下各个标志位的走向!000000286-->14(表示EFL存储到偏移量14的[EDI+EAX]位置)
第一阶段:
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_PUSHw_IMMEDIATEb
0013F9AC 0000
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;立即数IMM0000
VM_PUSHdw_IMMEDIATEdw
0013F9A8 7D51
0013F9AC 00000042 B...
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_PUSHw_MEMORYb
0013F9AC 00000000
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;内存数MEM0000。很明显,我们看到两个数是相同的
VM_PUSHdw_EBP
0013F9A8 0013F9AC .
0013F9AC 00000000 ....
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_COPYw_EBPSTACK
0013F9A8 0000
0013F9AC 00000000 ....
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;复制内存数MEM0000
VM_NANDw
0013F9A8 00000286 ..
0013F9AC 000000FF ...
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NOT(MEM0000)=MEM00FF
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9AC 000000FF ...
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;000000286-->14(表示EFL存储到偏移量14的[EDI+EAX]位置)
VM_ADDb_EBPSTACK
0013F9A8 0286
0013F9AC 00FF0000 ...
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;00FF=IMM0000+MEM00FF
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9AC 00FF
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;标志位A 000000286-->04
VM_PUSHdw_EBP
0013F9A8 F9AE
0013F9AC 00FF0013 ..
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_COPYw_EBPSTACK
0013F9AC 00FF00FF ..
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_NANDw
0013F9A8 0246
0013F9AC 00000000 ....
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NOT(00FF)
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9AC 0000
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;标志位B 00000246-->3C
VM_MOVw_EDISTACKb_EBPSTACKw
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;
第一阶段结束。
两个操作数都是0000,很明显这次判断将是两个数相同,减法后的ZF位置1。
运算的结果都是无用的,关键在于它的标志位,继续看标志位ZF的检测+跳转
第二阶段:
VM_PUSHdw_EDISTACKdw
0013F9AC 00000286 ..
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;标志位A 000000286<--04
VM_PUSHdw_EDISTACKdw
0013F9A8 00000286 ..
0013F9AC 00000286 ..
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;再来一次标志位A
VM_NANDdw
0013F9A8 00000282 ..
0013F9AC FFFFFD79 y
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(A,A)=NOT(A)=FFFFFD79
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9AC FFFFFD79 y
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_PUSHdw_IMMEDIATEw
0013F9A8 FFFFF7EA
0013F9AC FFFFFD79 y
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(X,X)=NOT(00000815)=FFFFF7EA 传递相反数,隐藏NOT(00000815)
VM_NANDdw
0013F9A8 00000202 ..
0013F9AC 00000004 ...
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(NAND(A,A),NAND(X,X))=标志位A 00000286 AND 00000815
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9AC 00000004 ...
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_PUSHdw_EDISTACKdw
0013F9A8 00000246 F..
0013F9AC 00000004 ...
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;标志位B 00000246<--3C
VM_PUSHdw_EDISTACKdw
0013F9A4 00000246 F..
0013F9A8 00000246 F..
0013F9AC 00000004 ...
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;再来一次标志位B
VM_NANDdw
0013F9A4 00000282 ..
0013F9A8 FFFFFDB9
0013F9AC 00000004 ...
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(B,B)=NOT(B)=FFFFFDB9
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9A8 FFFFFDB9
0013F9AC 00000004 ...
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_PUSHdw_IMMEDIATEw
0013F9A4 00000815 ..
0013F9A8 FFFFFDB9
0013F9AC 00000004 ...
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(Y,Y)=NOT(FFFFF7EA)=00000815 传递相反数,隐藏NOT(FFFFF7EA)
VM_NANDdw
0013F9A4 00000206 ..
0013F9A8 00000242 B..
0013F9AC 00000004 ...
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(NAND(B,B),NAND(Y,Y))=标志位B 00000246 AND FFFFF7EA
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9A8 00000242 B..
0013F9AC 00000004 ...
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_ADDdw_EBPSTACK
0013F9A8 00000202 ..
0013F9AC 00000246 F..
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;两个AND后的标志位相加
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9AC 00000246 F..
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;00000246-->00 暂存结果
第二阶段结束
现在VMP已经把两个标志位合并成了一个,关于标志位合并的解析结束后再来看。继续看第三阶段:检测ZF+跳转,注意看好堆栈数据的构造,堆栈虚拟机的跳转判断有他独特的地方!同时它巧妙的运用了ZF位在EFLAGS中的位置。
第三阶段:
VM_PUSHdw_IMMEDIATEdw
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;跳转地址1
VM_PUSHdw_IMMEDIATEdw
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;跳转地址2
VM_PUSHdw_EBP
0013F9A4 0013F9A8 .
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;跳转地址指针
VM_PUSHw_IMMEDIATEb
0013F9A0 0004
0013F9A4 0013F9A8 .
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;传递4,看好堆栈的构造,下面的几个操作是独立的
VM_PUSHdw_EDISTACKdw
0013F99C 0246
0013F9A0 00040000 ...
0013F9A4 0013F9A8 .
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;第二阶段结果00000246<--00
VM_PUSHdw_EBP
0013F998 F99E
0013F99C 02460013 .F
0013F9A0 00040000 ...
0013F9A4 0013F9A8 .
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_COPYdw_EBPSTACK
0013F998 0246
0013F99C 02460000 ..F
0013F9A0 00040000 ...
0013F9A4 0013F9A8 .
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;复制标志位
VM_NANDdw
0013F998 0282
0013F99C FDB90000 ..
0013F9A0 0004FFFF .
0013F9A4 0013F9A8 .
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(A,A)=NOT(A)=NOT(00000246)=FFFFFDB9
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F99C FDB9
0013F9A0 0004FFFF .
0013F9A4 0013F9A8 .
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_PUSHdw_IMMEDIATEb
0013F998 FFBF
0013F99C FDB9FFFF
0013F9A0 0004FFFF .
0013F9A4 0013F9A8 .
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(B,B)=NOT(00000040)=FFFFFFBF 传递相反数,隐藏NOT(000000040)
VM_NANDdw
0013F998 0202
0013F99C 00400000 ..@. ; OFFSET NOTEPAD.B
0013F9A0 00040000 ...
0013F9A4 0013F9A8 .
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;NAND(NAND(B,B),NAND(B,B))=标志位 00000246 AND 00000040
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F99C 0040
0013F9A0 00040000 ...
0013F9A4 0013F9A8 .
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;AND结果是00000040,说明ZF位是1,两个数相等;想想如果不相等,结果是00000000
VM_SHRdw_EBPSTACKb
0013F99C 00000202 ..
0013F9A0 00000004 ...
0013F9A4 0013F9A8 .
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;右移4位刚好把00000040移动成00000004;如果不相等,右移后是00000000
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9A0 00000004 ...
0013F9A4 0013F9A8 .
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A
VM_ADDdw_EBPSTACK
0013F9A0 00000206 ..
0013F9A4 0013F9AC .
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;00000004+0013F9A8=0013F9AC;如果不相等,00000000+0013F9A8=0013F9A8
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9A4 0013F9AC .
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;跳转地址指针指向的就是判断后的跳转地址
VM_COPYdw_EBPSTACK
0013F9A4 4DBE4AD8 JM
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;跳转地址指针指向的跳转地址复制出来
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;把最终的跳转地址暂存到EDISTACK,4DBE4AD8-->18
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;扫尾工作,释放EBPSTACK
VM_MOVdw_EDISTACKdw_EBPSTACKdw
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;扫尾工作,释放EBPSTACK
VM_PUSHdw_EDISTACKdw
0013F9AC 4DBE4AD8 JM
0013F9B0 7C92118A | ; RETURN to ntdll.7C92118A ;压入判断的跳转地址4DBE4AD8<--18
第三阶段结束
接下来VM将使用一次XOR运算解密4DBE4AD8数据(详见2.2.3.XOR举例),然后是VM_JMP指令调用的组合(详见2.1.3.举例),全过程结束。
两个操作数都是0000,1个来自内存空间,一个来自ESI的编译数据,同时这段代码是在VM刚刚启动就进行的了,都是定量。但是VM还要进行检测,说明两个数据是不确定的,VM在运行过程中要知道它是不是0,可以把它猜测为VMP内部的一个信号。VM一开始就要知道到底应该走向哪个分支。到后面我们会进行测试,如果这个信号比较结果不为0,VM的走向是怎样的。
下面我们来详解上面的操作过程,从第二阶段合并标志位来看
第一阶段:执行减法运算
IMM0000-MEM0000:
NAND(IMM0000,IMM0000) ;这里的标志位是无用的
00FF=IMM00FF+MEM0000 ;获得标志位A 000000286
NAND(00FF,00FF) ;获得标志位B 000000246
第二阶段:合并两个标志位
00000004=AND(00000286,00000815)
00000242=AND(00000246,FFFFF7EA)
00000246=00000004+00000242
把两个标志位分别AND后相加,AND操作时用于保留想要的标志位,加法把它合并起来。
关于EFLAGS标志位,Intel的资料显示:
3.jpg下载此附件需要消耗2Kx,下载中会自动扣除。
各个标志位的详细说明,请查阅Intel 64 and IA-32 Architectures Software Developer's Mannual(Intel 64位与IA-32体系结构软件开发者指南)中卷1的3.4.3 EFLAGS Register
关于ADD指令,Intel的资料显示:
ADD—Add
Operation
DEST DEST SRC;
Flags Affected
The OF, SF, ZF, AF, CF, and PF flags are set according to the result.
把00000286 AND 00000815使用二进制表示:
0000 0000 0000 0000 0000 0010 1000 0110
AND 0000 0000 0000 0000 0000 1000 0001 0101
我们现在就可以看到,VM要保留的是 OF AF PF CF 位。那么,SF和ZF位为什么不在这里保留呢?我们要想到,由于这里并不是A-B的最后结果,SF 和 ZF位要等到最后的运算完成才能知道。在标志位A中,PF位为1,PF位被保留。
第一个AND数00000815与第二个AND数FFFFF7EA之间是有内在联系的。00000815+FFFFF7EA=FFFFFFFF,也就是说,这两个这两个AND操作时可以把所有的标志位都保留下来的,不会出现遗漏。而把它分开的话,是由于变换了减法的运算方式不进行保留对应的保留。
最后的NAND(A,A):
NOT A ;第一个操作数
NOT A ;第二个操作数
AND A,A ;最终标志位B 00000246是来自这里
关于AND逻辑运算,Intel的资料显示:
AND—Logical AND
Operation
DEST DEST AND SRC;
Flags Affected
The OF and CF flags are cleared; the SF, ZF, and PF flags are set according to the
result. The state of the AF flag is undefined.
把00000246 AND FFFFF7EA使用二进制表示:
0000 0000 0000 0000 0000 0010 0100 0110
AND 1111 1111 1111 1111 1111 0111 1110 1010
VM要把除了上面00000815保留了的 OF AF PF CF 以外的标志位都保留了下来。在标志位B中,IF ZF PF 和第二位是Intel的保留位默认为1 这4个标志位为1,所以IF ZF PF被保留。
两个标志位相加后,最终合并成为两个操作数SUB指令后的标志位00000246
下面我们来看第三阶段:
构建跳转结构:
0013F9A0 0004
0013F9A4 0013F9A8 .
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
把两个跳转地址4DBE49D5与4DBE4AD8压入堆栈。0013F9A8成为跳转地址指针,指向第一个跳转地址。如果0013F9A8指针+4,它就会指向第二个指针。最后还有1个0004,它并不是用于给指针+4的操作数,它要参与到巧妙判断ZF位的运算中。
接下来,VM用NAND执行一次AND操作,操作数是:标志位00000246与00000040 (在NAND操作中,VM不意外的隐藏了一次NAND(B,B)操作,直接传递00000040的相反数FFFFFFBF)
0013F998 FFBF
0013F99C FDB9FFFF
0013F9A0 FFFF .
0013F9A0 0004 ;为了清晰变现,把它分开显示
0013F9A4 0013F9A8 .
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
VM_NANDdw
0013F99C 0040 ;运算结果为00000040
0013F9A0 00040000 ...
0013F9A4 0013F9A8 .
0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM
NAND操作是:
NOT(A)
NOT(B)
AND(A,B)
所以,在这条伪指令的内部,00000246 AND 0000040
把00000246 AND 00000040使用二进制表示:
0000 0000 0000 0000 0000 0010 0100 0110
AND 0000 0000 0000 0000 0000 0000 0100 0000
唯一的检测ZF位,如果ZF位为1,那么结果将是00000040,否则是00000000。由于ZF位刚好是在byte的4的位置,把它和前面的跳转地址指针相加,0013F9A8+0则是不变,指向第一个地址,+4就指向第二个地址,所以刚好可以让AND后的结果与指针0013F9A8进行1次加法运算,如果ZF位是1,0013F9A8+4将指向4DBE4AD8完成判断跳转。由于ZF位的前面还有1个byte的数据0,就是00000040中最后的1个byte0,如果直接和0013F9A8相加,就变成+40,所以要先进行1次4个bit的右移,00000040变为00000004,这样才正确。
ZF位为1,AND 00000040后: ZF位为0,AND 00000040后:
00000040 00000000
SHR(4) 00000004 SHR(4) 00000000
ADD 0013F9A8 ADD 0012F9A8
结果 0013F9AC 结果 0013F9A8
0013F9A4 0013F9AC . 0013F9A4 0013F9A8 .
0013F9A8 4DBE49D5 IM 0013F9A8 4DBE49D5 IM
0013F9AC 4DBE4AD8 JM 0013F9AC 4DBE4AD8 JM
ZF位的不同带来跳转地址的不同,把相应的跳转地址解密后,使用VM_JMP给VM的指令指针ESI赋值,全程结束。
进行ZF位比较的话,只需要比较最后的标志位B就可以了,而且可以进行直接的比较,不需要这样截取+拼接,那么为什么VMP还需要在整个过程中截取了所有的标志位呢?我想,可以这么来考虑,在VMP中标志位的截取+拼接在代码中是属于一个模块,不管VMP要检测哪个标志位,它都是先调用这个模块然后再进行标志位检测。虽然在单纯的ZF位检测中,有了很多的不必要的操作,但是它增加了通用性,只要调用了这个模块,VMP在后面可以接上任意标志位的检测。