VMP分析笔记(cmp命令在VM中的表达)
VMP的破解
http://www.52pojie.cn/thread-205189-1-1.html
(出处: 吾爱破解论坛)
看了这个帖子,自己重新理解了帖子的话,就是下面这句。
在vm里a-b就是
temp= add(a, nor(b,b))
nor (temp, temp)
所以调用VM_Handle的顺序应该是
Nor(X1,X2)à add(Y1,Y2)à nor(Z1,Z2)àseteip
其中X1==X2 , Z1==Z2
这个笔记可以看作是cmp指令在VM中的表达过程
自己写了一个demo
易语言代码如下
.版本 2
.子程序 _按钮1_被单击
.局部变量 a, 整数型
置入代码 ({ 235, 16, 86, 77, 80, 114, 111, 116, 101, 99, 116, 32, 98, 101, 103, 105, 110, 0 })
a = 到整数 (编辑框1.内容)
.如果真 (a = 123)
信息框 (“success”, 0, , )
返回 ()
.如果真结束
信息框 (“fail”, 0, , )
置入代码 ({ 235, 14, 86, 77, 80, 114, 111, 116, 101, 99, 116, 32, 101, 110, 100, 0 })
然后分别找到Nor32 handler 和add32 handler 这个可以用FKvmp插件找到
得出 00499BDE 0049B565 这2个地址
现在我要的数据是
0049B565 0145 04 add dword ptr ss:[ebp+0x4],eax 这句的eax 和[ebp+4]
还有就是 nor32 中的
Not eax
Not edx
And eax,edx
详细看脚本代码
脚本代码
loop:
run
cmp esi,00499BDE
jnz nor32
nor32:
var a
log "nor32"
log eax
log edx
mov a,eax
not a
log a
cmp esi,0049B565
jnz test_start
test_start:
var tmp
mov tmp,[ebp+4]
log "add32"
log eax
log tmp
jmp loop
好了,运行脚本然后下00499BDE 0049B565 这2个地址的断点,点击按钮 让程序自己跑起来,这时候脚本开始记录下我想要的数据。
点击OD中的L 就可以看到所有输出的数据。
我是输入123 得出以下数据
00499BDE [PhantOm_iNFO] > Breakpoint [sti]
00499BDE Breakpoint at VMadd_vm.00499BDE
nor32
eax: 0000007B
edx: 0000007B
a: FFFFFF84
add32
eax: 0000007B
tmp: 0000007B
0049B565 [PhantOm_iNFO] > Breakpoint [sti]
0049B565 Breakpoint at VMadd_vm.0049B565
nor32
eax: FFFFFF84
edx: 00000286
a: 0000007B
add32
eax: FFFFFF84
tmp: 0000007B
00499BDE [PhantOm_iNFO] > Breakpoint [sti]
00499BDE Breakpoint at VMadd_vm.00499BDE
nor32
eax: FFFFFFFF
edx: FFFFFFFF
a: 00000000
add32
eax: FFFFFFFF
tmp: FFFFFFFF
可以看到7BH=123D
这时a=not 7B = FFFFFF84
Add FFFFFF84,7B = FFFFFFFF
Not FFFFFFFF=0
然后我又自己思考了一下,为什么最后是等于0呢,因为上面已经说了,cmp命令相当于a-b 比如我这个程序对比输入的数据是否等于123 即16进制7B 。只要将输入的数和7B相减等于0的话,说明输入对了。
然而在VM中,先not 后再and 再not 最后结果等于0 最后还是等于0
以上就是正确的流程,最后是等于0的 所以为什么吾爱的那个帖子,走到and eax,edx 清0 就是这个原因
错误的流程:
00499BDE [PhantOm_iNFO] > Breakpoint [sti]
00499BDE Breakpoint at VMadd_vm.00499BDE
nor32
eax: 000001C8
edx: 000001C8
not eax
temp: FFFFFE37
not edx
temp1: FFFFFE37
add32
eax: 000001C8
tmp: 000001C8
0049B565 [PhantOm_iNFO] > Breakpoint [sti]
0049B565 Breakpoint at VMadd_vm.0049B565
nor32
eax: FFFFFE37
edx: 00000282
not eax
temp: 000001C8
not edx
temp1: FFFFFD7D
add32
eax: FFFFFE37
tmp: 0000007B
00499BDE [PhantOm_iNFO] > Breakpoint [sti]
00499BDE Breakpoint at VMadd_vm.00499BDE
nor32
eax: FFFFFEB2
edx: FFFFFEB2
not eax
temp: 0000014D
not edx
temp1: 0000014D
add32
eax: FFFFFEB2
tmp: FFFFFEB2
上面就是下面这句话的解释
temp= add(a, nor(b,b))
nor (temp, temp)
总结:
如果遇到2个数进行对比的话,可以找到add32 add dword ptr ss:[ebp+0x4],eax 关注eax和[ebp+0x4]的值 可以找到是和哪个数据对比
Demo and pdf download:
https://yunpan.cn/c6i72JaATqvBR 访问密码 b0b7
附录:
在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操作时用于保留想要的标志位,加法把它合并起来。
By 星空之上
2016-8-14 15:02:55