代码虚拟机保护学习

虚拟机架构

基于栈的(Stack-based)

基于栈的虚拟机主要通过栈来执行操作,比如两个数字20和7相加可以用如下方式进行:

步骤:

  1. POP 20
  2. POP 7
  3. ADD 20,7,result
  4. PUSH result

基于栈的优点:

  1. 操作数由栈指针隐式寻址,虚拟机不需要显式地知道操作数地址
  2. 平均指令长度更短

基于寄存器的(Register-based)

基于寄存器的虚拟机的指令的操作数是在指令中显式寻址,也就是说指令需要包含操作数的地址(寄存器),比如两个数字5和30相加可以用如下方式进行:

步骤:

  1. ADD R1, R2, R3 ; # R1和R2的值相加,并将结果存到R3 Z中

基于寄存器的优点:

  1. 没有压入和弹出栈的开销,VM指令执行得更快
  2. 可以存储表达式的计算结果到寄存器中,当相同的表达式再次出现时可以直接使用,减少了再次计算的开销

VMProtect 1.81 Demo分析

VMP 1.81 demo的虚拟机架构是基于栈的,未经处理的代码如下

1
2
3
4
5
6
7
.text:00401000 sub_401000      proc near               ; CODE XREF: start↓p
.text:00401000 mov eax, dword_403000
.text:00401005 add eax, 12345678h
.text:0040100A sub eax, 12345678h
.text:0040100F mov dword_403000, eax
.text:00401014 retn
.text:00401014 sub_401000 endp

经VMP虚拟化后的代码如下

1
2
3
4
5
6
7
8
9
10
.text:00401000 sub_401000      proc near               ; CODE XREF: start↓p
.text:00401000 push offset vm_data
.text:00401005 call vm_entry
.text:0040100A
.text:0040100A loc_40100A: ; CODE XREF: vm_entry+2B↓j
.text:0040100A ; DATA XREF: .vmp0:00404294↓o ...
.text:0040100A mov esi, [ebp+0]
.text:0040100D add ebp, 4
.text:00401010 jmp loc_40474E
.text:00401010 sub_401000 endp

VMP结构

  • VM_DATA 虚拟机字节码
  • VM_EIP 指向虚拟机字节码的某个地址,在VMP中为ESI
  • VM_CONTEXT 虚拟机上下文,在VMP中为EDI
  • VM_STACK 虚拟栈,在VMP中EBP就是虚拟栈的栈顶指针

vm_entry

虚拟机的入口,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.vmp0:0040472C                 push    esi
.vmp0:0040472D push edi
.vmp0:0040472E push esp
.vmp0:0040472F push ebx
.vmp0:00404730 push eax
.vmp0:00404731 push edx
.vmp0:00404732 push ebp
.vmp0:00404733 pushf
.vmp0:00404734 push ecx
.vmp0:00404735 push ds:reloc
.vmp0:0040473B push 0
.vmp0:00404740 mov esi, [esp+2Ch+arg_0] ; vm_data
.vmp0:00404744 mov ebp, esp ; 虚拟栈栈顶指针
.vmp0:00404746 sub esp, 0C0h ; 分配虚拟栈和虚拟机上下文空间
.vmp0:0040474C mov edi, esp ; 虚拟机上下文
.vmp0:0040474E
.vmp0:0040474E loc_40474E: ; CODE XREF: sub_401000+10↑j
.vmp0:0040474E add esi, [ebp+0]
.vmp0:00404751
.vmp0:00404751 vm_dispatcher: ; CODE XREF: vm_entry-722↑j
.vmp0:00404751 ; vm_entry-718↑j ...
.vmp0:00404751 mov al, [esi]
.vmp0:00404753 movzx eax, al
.vmp0:00404756 inc esi
.vmp0:00404757 jmp ds:handlers[eax*4]

可以看到虚拟机入口先保存了各种寄存器,并为虚拟栈和虚拟机上下文开辟了空间,然后读取vm_data准备进入vm_handler。

vm_handler

所有的handler入口存储在一张表中,根据vm opcode即可进入对应的handler,下面分析几个handler

立即数压栈

1
2
3
4
5
6
7
.vmp0:0040462B vPushImm4:                              ; CODE XREF: vm_entry+2B↓j
.vmp0:0040462B ; DATA XREF: .vmp0:00404240↑o ...
.vmp0:0040462B mov eax, [esi] ; 读取立即数
.vmp0:0040462D sub ebp, 4
.vmp0:00404630 lea esi, [esi+4] ; vEIP + 4
.vmp0:00404633 mov [ebp+0], eax ; 压入虚拟栈中
.vmp0:00404636 jmp loc_40400F

寄存器压栈

1
2
3
4
5
6
7
.vmp0:004045AF vPushReg4:                              ; CODE XREF: vm_entry+2B↓j
.vmp0:004045AF ; DATA XREF: .vmp0:handlers↑o ...
.vmp0:004045AF and al, 3Ch ; al & 0x3C = 寄存器在虚拟机上下文中的位置
.vmp0:004045B2 mov edx, [edi+eax] ; 获取虚拟机寄存器的值
.vmp0:004045B5 sub ebp, 4
.vmp0:004045B8 mov [ebp+0], edx ; 压入虚拟栈中
.vmp0:004045BB jmp loc_40400F

寄存器出栈

1
2
3
4
5
6
7
.vmp0:00404058 vPopReg4:                               ; CODE XREF: vm_entry+2B↓j
.vmp0:00404058 ; DATA XREF: .vmp0:004040A4↓o ...
.vmp0:00404058 and al, 3Ch
.vmp0:0040405B mov edx, [ebp+0]
.vmp0:0040405E add ebp, 4
.vmp0:00404061 mov [edi+eax], edx
.vmp0:00404064 jmp vm_dispatcher

读取内存

1
2
3
4
5
6
.vmp0:00404069 vReadMemSs4:                            ; CODE XREF: vm_entry+2B↓j
.vmp0:00404069 ; DATA XREF: .vmp0:00404120↓o ...
.vmp0:00404069 mov eax, [ebp+0] ; 获取栈顶作为读取的地址
.vmp0:0040406C mov eax, ss:[eax]
.vmp0:0040406F mov [ebp+0], eax ; 将读取到的值写回栈顶
.vmp0:00404072 jmp vm_dispatcher

写入内存

1
2
3
4
5
6
7
.vmp0:004045D8 vWriteMemSs4:                           ; CODE XREF: vm_entry+2B↓j
.vmp0:004045D8 ; DATA XREF: .vmp0:004041B0↑o ...
.vmp0:004045D8 mov eax, [ebp+0] ; 获取栈顶为写入的地址
.vmp0:004045DB mov edx, [ebp+4] ; 获取次栈顶为写入的值
.vmp0:004045DE add ebp, 8
.vmp0:004045E1 mov ss:[eax], edx
.vmp0:004045E4 jmp vm_dispatcher

返回(退出虚拟机)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.vmp0:0040408E vRet:                                   ; CODE XREF: vm_entry+2B↓j
.vmp0:0040408E ; DATA XREF: .vmp0:004042A4↓o ...
.vmp0:0040408E mov esp, ebp
.vmp0:00404090 pop edx
.vmp0:00404091 pop ebx
.vmp0:00404092 pop ecx
.vmp0:00404093 popf
.vmp0:00404094 pop ebp
.vmp0:00404095 pop edx
.vmp0:00404096 pop eax
.vmp0:00404097 pop ebx
.vmp0:00404098 pop esi
.vmp0:00404099 pop edi
.vmp0:0040409A pop esi
.vmp0:0040409B retn

从上面可以看出所有需要抬升栈顶的handler结束后都不是直接返回到vm_dispatcher,而是走到了loc_40400F,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.vmp0:0040400F loc_40400F:                             ; CODE XREF: vm_entry-6D9↓j
.vmp0:0040400F ; vm_entry-6A3↓j ...
.vmp0:0040400F lea eax, [edi+50h] ; 虚拟栈的stack limit,vm_context和vm_stack中间还有一部分缓冲区
.vmp0:00404012 cmp ebp, eax ; 判断虚拟栈栈顶指针是否越界,即是否栈溢出
.vmp0:00404014 ja vm_dispatcher ; 若没有超过stack limit则返回vm_dispatcher
.vmp0:0040401A mov edx, esp ; edx = 真实栈栈顶
.vmp0:0040401C lea ecx, [edi+40h]
.vmp0:0040401F sub ecx, edx ; ecx = edi+0x40-edx
.vmp0:00404021 lea eax, [ebp-80h]
.vmp0:00404024 and al, 0FCh
.vmp0:00404027 sub eax, ecx ; eax = ((ebp-0x80)&0xFC)-(edi+0x40-edx) 开辟虚拟栈空间
.vmp0:00404029 mov esp, eax
.vmp0:0040402B pushf ; 保存eflags
.vmp0:0040402C push esi ; 保存vEIP
.vmp0:0040402D mov esi, edx
.vmp0:0040402F lea edi, [eax+ecx-40h]
.vmp0:00404033 push edi ; 保存新的vm_context位置
.vmp0:00404034 mov edi, eax
.vmp0:00404036 cld
.vmp0:00404037 rep movsb ; 将vm_context以及真实栈的数据填充回新的栈中
.vmp0:00404039 pop edi ; 恢复vm_context
.vmp0:0040403A pop esi ; 恢复vEIP
.vmp0:0040403B popf ; 恢复eflags
.vmp0:0040403C jmp vm_dispatcher

VMP在执行的过程中会判断虚拟栈空间是否足够,如果不够会动态开辟栈空间,这也就解释了为什么一开始只分配了0xC0的栈空间。

vm_data

站在巨人的肩膀上,直接贴大佬的分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
vPopReg4        R3           ; R3 = 0   
vPushImm4 0x765da981 ;
vAdd4 ;
vPopReg4 R7 ; R7 = eflag
vPopReg4 R6 ; R6 = 0x765da981
vPopReg4 R2 ; R2 = ECX
vPopReg4 R15 ; R15 = EFLAG
vPopReg4 R11 ; R11 = EBP
vPopReg4 R9 ; R9 = EDX
vPopReg4 R13 ; R13 = EAX
vPopReg4 R14 ; R14 = EBX
vPopReg4 R10 ; R10 = ESP
vPopReg4 R4 ; R4 = EDI
vPopReg4 R0 ; R0 = ESI
vPopReg4 R1 ; push/call 的返回地址 0x40100A
vPopReg4 R8 ; push 的值 即 vm_data 0x404781

// mov eax, dword_403000
vPushImm4 0x403000
vReadMemDs4
vPopReg4 R5 ; R5 = [0x403000]

// add eax, 12345678h
vPushImm4 0x12345678
vPushReg4 R5 ;
vAdd4 ; R5 + 0x12345678
vPopReg4 R7 ; add_flag
vPopReg4 R13 ; R13 = R5 + 0x12345678

// sub eax, 12345678h

// 关键计算公式如下:
// not(a) = nor(a, a)
// and(a, b) = nor(not(a), not(b))
// sub(a, b) = not(not(a) + b) = nor(not(a) + b, not(a) + b) = nor(nor(a, a) + b, nor(a, a) + b)
// 0xfffff7ea = not(0x815)
// not_flag(a) = nor_flag(a, a)
// and_flag(a, b) = nor_flag(a, not(b))
// sub_flag(a, b) = and(FFFFF7EA, not_flag(not(a) + b)) + and(0x815 , add_flag(not(a), b))

vPushImm4 0x12345678 ; 0x12345678 是 b
vPushReg4 R13 ; R13 是 a
vPushEBP
vReadMemSs4 ; 栈顶2个 a
vNor4 ; nor(a, a) = not(a)
vPopReg4 R1 ; R1 = not_flag(a)
vAdd4 ; not(a) + b
vPopReg4 R15 ; R15 = add_flag(not(a) + b)
vPushEBP ;
vReadMemSs4 ; 栈顶2个 not(a) + b
vNor4 ; not(not(a)+b) = sub(a,b)
vPopReg4 R8 ; R8 = not_flag(not(a) + b)
vPopReg4 R1 ; R1 = sub(a,b) = R13 - 0x12345678
vPushReg4 R15 ; R15 = add_flag(not(a) + b)
vPushEBP
vReadMemSs4
vNor4 ; not(R15) = not(add_flag(not(a) + b))
vPopReg4 R5 ; flag - 无用
vPushImmSx2 0xfffff7ea
vNor4 ; nor(0xfffff7ea, R14) = and (0x815, R15) = and(0x815, add_flag(not(a) + b))
vPopReg4 R7 ; R7 = and(0x815, add_flag(not(a) + b)) sub_flag 右半部分
vPushReg4 R8
vPushReg4 R8
vNor4 ; not(R8) = not(not_flag(not(a) + b))
vPopReg4 R12 ; flag - 无用
vPushImmSx2 0x815
vNor4 ; nor(0x815, not(R8)) = and(0xfffff7ea, R8) = and(FFFFF7EA, not_flag(not(a) + b))
vPopReg4 R12 ; flag - 无用
vAdd4 ; add
vPopReg4 R12 ; flag - 无用
vPopReg4 R13 ; R13 = and(FFFFF7EA, not_flag(not(a) + b)) + and(0x815 , add_flag(not(a), b)) = sub_flag(a, b)

// mov dword_403000, eax
vPushReg4 R1
vPushImm4 0x403000 ; [0x403000] = R1
vWriteMemDs4

// ret
vPushReg4 R0 ; ESI
vPushReg4 R4 ; EDI
vPushReg4 R8 ; flag(无用)
vPushReg4 R14 ; EBX
vPushReg4 R1 ; RAX
vPushReg4 R9 ; EDX
vPushReg4 R11 ; EBP
vPushReg4 R13 ; sub_flag
vPushReg4 R2 ; ECX
vPushReg4 R7 ; flag(无用)
vPushReg4 R0 ; ESI(无用)
vRet

vm_data一开始先将vm_entry压入栈中的寄存器全部存到vm_context,由此也能确定vm_context与原始寄存器的对应关系,vm_data的结束部分也与vRet可以对应得上。

VMProtect 1.8 分析

由于没有找到VMP 1.81 Demo,只找到了由其加壳过的程序,指令的丰富程度不够,于是找到了VMP 1.8正式版进行分析,未经处理的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
.text:00401000 sub_401000      proc near               ; CODE XREF: start↓p
.text:00401000 mov eax, dword_403000
.text:00401005 add eax, 12345678h
.text:0040100A sub eax, 12345678h
.text:0040100F mov dword_403000, eax
.text:00401014 cmp eax, 12345678h
.text:00401019 jz short locret_401020
.text:0040101B mov eax, dword_403000
.text:00401020
.text:00401020 locret_401020: ; CODE XREF: sub_401000+19↑j
.text:00401020 retn
.text:00401020 sub_401000 endp

经VMP虚拟化后的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
.text:00401000 sub_401000      proc near               ; CODE XREF: start↓p
.text:00401000
.text:00401000 var_8 = dword ptr -8
.text:00401000 var_4 = dword ptr -4
.text:00401000
.text:00401000 ; FUNCTION CHUNK AT .UPX1:00406174 SIZE 0000001F BYTES
.text:00401000
.text:00401000 push 80E461h
.text:00401005 call sub_406BF9
.text:0040100A pushf
.text:0040100B jmp loc_406174
.text:0040100B sub_401000 endp ; sp-analysis failed
.text:0040100B
.text:00401010
.text:00401010 ; =============== S U B R O U T I N E =======================================
.text:00401010
.text:00401010
.text:00401010 sub_401010 proc near ; CODE XREF: sub_406E3D-9D4↓p
.text:00401010
.text:00401010 arg_4C = dword ptr 50h
.text:00401010
.text:00401010 ; FUNCTION CHUNK AT .UPX1:00405995 SIZE 00000005 BYTES
.text:00401010
.text:00401010 mov [esp+arg_4C], eax
.text:00401014 jmp loc_405995
.text:00401014 sub_401010 endp
.text:00401014
.text:00401019
.text:00401019 ; =============== S U B R O U T I N E =======================================
.text:00401019
.text:00401019
.text:00401019 sub_401019 proc near ; CODE XREF: .UPX1:00406DEC↓p
.text:00401019 call sub_4055CD
.text:0040101E fdivr qword ptr [ebp-0Fh]
.text:0040101E sub_401019 endp

可以看到比demo版本抽象了很多

vm_dispatcher

正式版的虚拟机部分做了比较多的混淆,通过调试器的trace功能找到指令执行次数最多的序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
; base = 0x1E0000
0x1e6c7e sub al, ch
0x1e6c80 adc al, 0xab
0x1e6c82 sar al, cl
0x1e6c84 mov al, byte ptr [esi] ;取vm_data
0x1e6c86 rcr dh, 7
0x1e6c89 shld dx, si, 0xb
0x1e6c8e jmp 0x1e66b7
0x1e66b7 shld dx, bx, cl
0x1e66bb sub al, bl ;al=al-bl
0x1e66bd dec dh
0x1e66bf rcl dl, 2
0x1e66c2 stc
0x1e66c3 cmc
0x1e66c4 lea esi, [esi + 1]
0x1e66c7 and dx, dx
0x1e66ca rol dx, 0xd
0x1e66ce add al, 0xbc ;al=al-bl+0xbc
0x1e66d0 rcl dh, 1
0x1e66d2 btc edx, 0xf
0x1e66d6 not al
0x1e66d8 stc
0x1e66d9 clc
0x1e66da push 0xec0c3952
0x1e66df ror al, 2 ;al=ror(al-bl+0xbc,2)
0x1e66e2 not dx
0x1e66e5 stc
0x1e66e6 sub bl, al ;bl=bl-ror(al-bl+0xbc,2)
0x1e66e8 bswap dx
0x1e66eb movzx eax, al
0x1e66ee movsx dx, bl
0x1e66f2 movzx edx, bl
0x1e66f5 movsx dx, bl
0x1e66f9 rol dx, cl
0x1e66fc mov edx, dword ptr [eax*4 + 0x1e67ae] ;<------经典dispatcher
0x1e6703 pushal
0x1e6704 cmc
0x1e6705 pushal
0x1e6706 ror edx, 8 ;<------
0x1e6709 jmp 0x1e664e
0x1e664e jmp 0x1e5883
0x1e5883 bt ax, si
0x1e5887 test bl, 0xfd
0x1e588a add edx, 0xffde0000 ;<------重定位,可在重定位表中找到
0x1e5890 jmp 0x1e638c
0x1e638c mov word ptr [esp + 4], 0xbd6d
0x1e6393 mov dword ptr [esp + 0x40], edx ;<------
0x1e6397 push 0xe6e87eb3
0x1e639c mov dword ptr [esp], 0x129a39d1
0x1e63a3 pushfd
0x1e63a4 push dword ptr [esp + 0x48] ;<------
0x1e63a8 ret 0x4c ;<------

可以看出这是一个比较典型的dispatcher,并且在箭头部分对handler的地址进行了解密和进入,写个脚本处理一下handler table方便分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from idaapi import *
from idc import *
from ctypes import c_int

def ror(int_value,k,bit = 32):
bit_string = '{:0%db}' % bit
bin_value = bit_string.format(int_value)
bin_value = bin_value[-k:] + bin_value[:-k]
int_value = int(bin_value,2)
return int_value

addr = 0x4067AE
end = addr + 4*256

while addr < end:
handler = get_wide_dword(addr)
handler = ror(handler,8)
patch_dword(addr,handler)
addr += 4

patch_bytes(0x00406706,'\x90'*3) #解密部分nop
print 'decrypt handler addr'

vm_handler

由于加了混淆,静态分析会变得比较痛苦,通过trace记录可以减轻人工分析的复杂程度,举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0x1e5f20     cmc
0x1e5f21 mov eax, dword ptr [ebp] ;取栈顶值
0x1e5f24 call 0x1e5950
0x1e5950 call 0x1e67a4
0x1e67a4 clc
0x1e67a5 pushal
0x1e67a6 add dword ptr [ebp + 4], eax ;与次栈顶相加
0x1e67a9 call 0x1e5f15
0x1e5f15 call 0x1e6528
0x1e6528 mov byte ptr [esp + 0xc], al
0x1e652c push 0x68702aba
0x1e6531 pushfd ;保存eflags
0x1e6532 pop dword ptr [esp + 0x30]
0x1e6536 call 0x1e5e8e
0x1e5e8e push dword ptr [esp + 0x34]
0x1e5e92 pop dword ptr [ebp] ;存储eflags
0x1e5e95 pushfd
0x1e5e96 lea esp, [esp + 0x3c]
0x1e5e9a jmp 0x1e6c7e

将注意力集中在ebp有关的部分就能很快确定关键指令,通过分析可知该handler实现的是相加功能。接下来挑几个demo中没看到的讲

跳转(设置vEIP)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0x1e6d28     adc si, 0xc8c6
0x1e6d2d bsf si, ax
0x1e6d31 shrd si, di, cl
0x1e6d35 mov esi, dword ptr [ebp] ;设置vEIP,该值是未经重定位的值
0x1e6d38 test ax, 0xab8e
0x1e6d3c bt eax, 1
0x1e6d40 add ebp, 4 ;vm_stack+4
0x1e6d43 pushfd
0x1e6d44 push dword ptr [esp]
0x1e6d47 lea esp, [esp + 8]
0x1e6d4b jmp 0x1e6c65
0x1e6c65 rcr bl, cl
0x1e6c67 rcr ebx, 7
0x1e6c6a sub al, 0x40
0x1e6c6c movzx dx, dl
0x1e6c70 mov ebx, esi
0x1e6c72 xadd al, dh
0x1e6c75 clc
0x1e6c76 inc al
0x1e6c78 sub dx, ax
0x1e6c7b add esi, dword ptr [ebp] ;通过调试可知该操作是对vEIP进行重定位

vm_data

vm_handler中对vm_data做了一些的加解密处理,如果静态分析vm_data有一定的工作量,这里只需要分析trace就好了,execution-trace-viewer脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
from yapsy.IPlugin import IPlugin
from core.api import Api

class PluginVmpTraceHelper(IPlugin):

def execute(self, api: Api):

trace = api.get_visible_trace()
if not trace:
return

api.print('')

handler_map = {
0x1e5372 : 'vPopReg4',
0x1e5315 : 'vPushImm4',
0x1e5f20 : 'vAdd4',
0x1e5b98 : 'vPushReg4',
0x1e6413 : 'vReadMemDs4',
0x1e6e70 : 'vNor4',
0x1e5cba : 'vPushEBP',
0x1e5f34 : 'vReadMemSs4',
0x1e50d0 : 'vPushImm2ext4',
0x1e527e : 'vWriteMemDs4',
0x1e6be9 : 'vPushImm1ext2',
0x1e5381 : 'vPushImm1ext4',
0x1e5a27 : 'vShr4',
0x1e6d28 : 'vJmp',
0x1e579c : 'vRet'
}
trace_data = api.get_trace_data()
ip_name = trace_data.get_instruction_pointer_name()
if ip_name not in trace_data.regs:
api.print('Error. Unknown instruction pointer name.')
return
ip_index = trace_data.regs[ip_name]

def find_trace(trace, i, eip):
while i < len(trace):
if trace[i]['regs'][trace_data.regs['eip']] == eip:
return trace[i]
i += 1
return -1
handler = []
i = 0
vm_dispatcher_end = 0x1e63a8
api.print('handler execution order')
while i < len(trace):
t = trace[i]
if t['regs'][ip_index] == vm_dispatcher_end:
addr = trace[i+1]['regs'][ip_index]
pcode = handler_map[addr]
if pcode == 'vPopReg4':
api.print('%s\tR%d\t= %#x' % (pcode, find_trace(trace, i, 0x1e654e)['regs'][trace_data.regs['eax']] / 4, find_trace(trace, i, 0x1e6551)['regs'][trace_data.regs['edx']]))
elif pcode == 'vPushImm4':
api.print('%s\t%#x' % (pcode, find_trace(trace, i, 0x1e5cdd)['regs'][trace_data.regs['eax']]))
elif pcode == 'vPushReg4':
api.print('%s\tR%d\t= %#x' % (pcode, find_trace(trace, i, 0x1e6436)['regs'][trace_data.regs['eax']] / 4, find_trace(trace, i, 0x1e6439)['regs'][trace_data.regs['edx']]))
elif pcode == 'vPushImm2ext4':
api.print('%s\t%#x' % (pcode, find_trace(trace, i, 0x1e53d0)['regs'][trace_data.regs['eax']]))
elif pcode == 'vPushImm1ext2':
api.print('%s\t%#x' % (pcode, find_trace(trace, i, 0x1e6026)['regs'][trace_data.regs['eax']] & 0xffff))
elif pcode == 'vPushImm1ext4':
api.print('%s\t%#x' % (pcode, find_trace(trace, i, 0x1e5e3f)['regs'][trace_data.regs['eax']]))
else:
api.print(pcode)
if addr not in handler:
handler.append(addr)
i+=1
api.print('')
api.print('unique handler')
for addr in handler:
api.print(hex(addr))

分析出来的流程如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
vPopReg4	R1	= 0xffde0000
vPushImm4 0x29b3f9de
vAdd4
vPopReg4 R7 = 0x206
vPopReg4 R0 = 0x29b3f9de
vPopReg4 R3 = 0x1e1021
vPopReg4 R10 = 0x1e1021
vPopReg4 R12 = 0x1e1021
vPopReg4 R6 = 0x1e1021
vPopReg4 R7 = 0xb8fd08
vPopReg4 R11 = 0xb8fcbc
vPopReg4 R15 = 0x246
vPopReg4 R14 = 0x1e1021
vPopReg4 R13 = 0x9ed000
vPopReg4 R4 = 0x1e100a
vPopReg4 R2 = 0x80e461

;mov eax, dword_403000
vPushReg4 R1 = 0xffde0000
vPushImm4 0x403000
vAdd4
vPopReg4 R2 = 0x207
vReadMemDs4
vPopReg4 R8 = 0xdeadbeef

;add eax, 12345678h
vPushReg4 R8 = 0xdeadbeef
vPushImm4 0x12345678
vAdd4
vPopReg4 R7 = 0x292
vPopReg4 R7 = 0xf0e21567

;sub eax, 12345678h
vPushImm4 0x12345678
vPushReg4 R7 = 0xf0e21567
vPushReg4 R7 = 0xf0e21567
vNor4
vPopReg4 R5 = 0x202
vAdd4
vPopReg4 R9 = 0x212
vPushEBP
vReadMemSs4
vNor4
vPopReg4 R2 = 0x282
vPopReg4 R2 = 0xdeadbeef

;mov dword_403000, eax
;cmp eax, 12345678h
;jz short locret_401020
vPushImm4 0x95c6b72e
vPushImm4 0x95c6b745
vPushReg4 R2 = 0xdeadbeef
vPushReg4 R1 = 0xffde0000
vPushImm4 0x403000
vAdd4
vPopReg4 R7 = 0x207
vPushImm4 0x12345678
vPushReg4 R2 = 0xdeadbeef
vPushReg4 R2 = 0xdeadbeef
vNor4 ;not(0xdeadbeef)
vPopReg4 R4 = 0x202
vAdd4 ;not(0xdeadbeef)+0x12345678
vPopReg4 R8 = 0x206
vPushEBP
vReadMemSs4 ;[ebp]=not(0xdeadbeef)+0x12345678
vNor4 ;not(not(0xdeadbeef)+0x12345678) = 0xdeadbeef - 0x12345678
vPopReg4 R5 = 0x286
vPopReg4 R7 = 0xcc796877
vPushReg4 R8 = 0x206
vPushReg4 R8 = 0x206
vNor4
vPopReg4 R6 = 0x286
vPushImm2ext4 0xfffff7ea
vNor4
vPopReg4 R6 = 0x202
vPushReg4 R5 = 0x286
vPushEBP
vReadMemSs4
vNor4
vPopReg4 R9 = 0x282
vPushImm2ext4 0x815
vNor4
vPopReg4 R4 = 0x206
vAdd4
vPopReg4 R6 = 0x202
vPopReg4 R7 = 0x286
vWriteMemDs4

vPushEBP
vPushImm1ext2 0x4
vPushReg4 R7 = 0x286
vPushReg4 R7 = 0x286
vNor4
vPopReg4 R8 = 0x282
vPushImm1ext4 0xffffffbf
vNor4
vPopReg4 R15 = 0x246
vShr4
vPopReg4 R4 = 0x246
vAdd4
vPopReg4 R8 = 0x202
vReadMemSs4
vPopReg4 R8 = 0x95c6b745
vPopReg4 R15 = 0x95c6b745
vPopReg4 R15 = 0x95c6b72e
vPushReg4 R8 = 0x95c6b745
vPopReg4 R4 = 0x95c6b745
vPushReg4 R4 = 0x95c6b745
vPushReg4 R4 = 0x95c6b745
vNor4
vPopReg4 R6 = 0x202
vPushImm4 0x6a793929
vNor4
vPopReg4 R9 = 0x286
vPushReg4 R4 = 0x95c6b745
vPushImm4 0x9586c6d6
vNor4
vPopReg4 R15 = 0x206
vNor4
vPopReg4 R9 = 0x206
vPopReg4 R6 = 0x407193 ;vJmp地址
vPushReg4 R12 = 0x1e1021
vPushReg4 R9 = 0x206
vPushReg4 R11 = 0xb8fcbc
vPushReg4 R8 = 0x95c6b745
vPushReg4 R10 = 0x1e1021
vPushReg4 R14 = 0x1e1021
vPushReg4 R7 = 0x286
vPushReg4 R13 = 0x9ed000
vPushReg4 R3 = 0x1e1021
vPushReg4 R2 = 0xdeadbeef
vPushReg4 R12 = 0x1e1021
vPushReg4 R0 = 0x29b3f9de
vPushImm4 0xd64c0622
vAdd4
vPopReg4 R8 = 0x257
vPushReg4 R1 = 0xffde0000
vPushReg4 R6 = 0x407193
vJmp
vPopReg4 R13 = 0xffde0000
vPushImm4 0x29b3f9de
vAdd4
vPopReg4 R12 = 0x206
vPopReg4 R10 = 0x29b3f9de
vPopReg4 R1 = 0x1e1021
vPopReg4 R2 = 0xdeadbeef
vPopReg4 R4 = 0x1e1021
vPopReg4 R7 = 0x9ed000
vPopReg4 R3 = 0x286
vPopReg4 R15 = 0x1e1021
vPopReg4 R14 = 0x1e1021
vPopReg4 R6 = 0x95c6b745
vPushReg4 R6 = 0x95c6b745
vPushEBP
vReadMemSs4
vNor4
vPopReg4 R0 = 0x202
vPushImm4 0x6a793929
vNor4
vPopReg4 R11 = 0x286
vPushReg4 R6 = 0x95c6b745
vPushImm4 0x9586c6d6
vNor4
vPopReg4 R8 = 0x206
vNor4
vPopReg4 R0 = 0x206
vPopReg4 R11 = 0x407193
vPopReg4 R9 = 0xb8fcbc
vPopReg4 R0 = 0x206
vPopReg4 R8 = 0x1e1021
vPushReg4 R13 = 0xffde0000
vPushImm4 0x403000
vAdd4
vPopReg4 R8 = 0x207
vReadMemDs4
vPopReg4 R12 = 0xdeadbeef
vPushImm4 0xffbfcfc3
vPushReg4 R10 = 0x29b3f9de
vPushReg4 R9 = 0xb8fcbc
vPushReg4 R11 = 0x407193
vPushEBP
vReadMemSs4
vPopReg4 R8 = 0x407193
vPushEBP
vReadMemSs4
vNor4
vPopReg4 R5 = 0x286
vPushImm4 0x6a793929
vNor4
vPopReg4 R6 = 0x202
vPushReg4 R8 = 0x407193
vPushImm4 0x9586c6d6
vNor4
vPopReg4 R2 = 0x206
vNor4
vPopReg4 R2 = 0x282
vPushReg4 R14 = 0x1e1021
vPushReg4 R15 = 0x1e1021
vPushReg4 R3 = 0x286
vPushReg4 R7 = 0x9ed000
vPushReg4 R4 = 0x1e1021
vPushReg4 R12 = 0xdeadbeef
vPushReg4 R1 = 0x1e1021
vPushReg4 R10 = 0x29b3f9de
vPushImm4 0xd64c0622
vAdd4
vPopReg4 R2 = 0x257
vPushReg4 R13 = 0xffde0000
vPopReg4 R5 = 0xffde0000
vPushImm4 0x29b3f9de
vAdd4
vPopReg4 R6 = 0x206
vPopReg4 R6 = 0x29b3f9de
vPopReg4 R2 = 0x1e1021
vPopReg4 R0 = 0xdeadbeef
vPopReg4 R1 = 0x1e1021
vPopReg4 R4 = 0x9ed000
vPopReg4 R12 = 0x286
vPopReg4 R13 = 0x1e1021
vPopReg4 R3 = 0x1e1021
vPopReg4 R7 = 0x95c6b745
vPushReg4 R7 = 0x95c6b745
vPushEBP
vReadMemSs4
vNor4
vPopReg4 R10 = 0x202
vPushImm4 0x6a793929
vNor4
vPopReg4 R15 = 0x286
vPushReg4 R7 = 0x95c6b745
vPushImm4 0x9586c6d6
vNor4
vPopReg4 R10 = 0x206
vNor4
vPopReg4 R10 = 0x206
vPopReg4 R15 = 0x407193
vPopReg4 R8 = 0xb8fcbc
vPopReg4 R10 = 0x29b3f9de
vPopReg4 R11 = 0xffbfcfc3
vPushReg4 R4 = 0x9ed000
vPushReg4 R13 = 0x1e1021
vPushReg4 R12 = 0x286
vPushReg4 R8 = 0xb8fcbc
vPushReg4 R0 = 0xdeadbeef
vPushReg4 R15 = 0x407193
vPushReg4 R2 = 0x1e1021
vPushReg4 R3 = 0x1e1021
vPushReg4 R1 = 0x1e1021
vPushReg4 R0 = 0xdeadbeef
vPushReg4 R1 = 0x1e1021
vRet

jcc的目标地址直接通过eflags和一个数算出来了,懵逼,之后再看看。

参考

STACK BASED VS REGISTER BASED VIRTUAL MACHINE ARCHITECTURE, AND THE DALVIK VM

如何分析虚拟机系列(1):新手篇VMProtect 1.81 Demo

如何分析虚拟机(2):进阶篇 VMProtect 2.13.8

execution-trace-viewer x64dbg trace分析工具