虚拟机的概念与VMP
平常所用的安卓模拟器,VMware,VirtualBox等就是虚拟机,它们的工作流程很简单,即,读取一条指令,执行这条指令,读取下一条指令,执行这条指令…
写成伪代码:
while(true) // The opcode dispatcher
{
opcode = opcodes[ip++]
handler = GetHandler(opcode)
handler();
}
这些虚拟机如安卓模拟器,它是模拟的真实CPU指令(Arm指令)。而VMP则设计了一套CPU,将x86指令编译到此CPU的字节码,虚拟执行。
在上文伪代码中出现的变量如opcode,handler与ip,都是提供虚拟机运行,而非虚拟机虚拟的内容。
被模拟的内容仅会在handler中被操作。
VMP虚拟机架构
VMP的CPU与x86CPU类似,存在栈,与寄存器。
当VMP的虚拟机启动时,VMP会把x86下的寄存器转移到VMP的寄存器中(虚拟寄存器,以下称VRegs)
当VMP的虚拟机退出时,VMP会把VRegs转移到x86的寄存器中
VMP的虚拟机(以下称VM)启动后,即开始像其他虚拟机一样,不断增加虚拟指令指针(Virtual Instruction Pointer,以下称VIP),通过VIP确定handler,执行handler
VM的计算过程在虚拟栈(Virtual Stack,以下称VStack)中完成,如果你熟悉C#的IL指令,VM不断地进行VPUSH与VPOP,这将是一个熟悉的过程
VMP(3.X)将以上虚拟机的“opcode dispatcher” unroll,脱离while不断执行handler
VM的启动
(VMP 3.4 x64 使用x64dbg trace得到 )
005 | push r8
006 | push rax
007 | mov r8w,71F4
008 | movsxd r8,r10d
009 | push rsi
00A | mov r8b,r15b
00B | movzx r8w,dil
00C | push r10
00D | mov r10b,BF
00E | push rdx
00F | cmovs r10w,r9w
010 | cwd
011 | push rdi
012 | pushfq
013 | shr r8b,cl
014 | sar dh,6F
015 | rol r10b,AD
016 | push r9
017 | adc r10b,43
018 | push rbp
019 | movzx r8w,r10b
01A | ror r8w,cl
01B | cmovns dx,r15w
01C | push r13
01D | push r14
01E | ror di,91
01F | push r11
020 | push rcx
021 | adc dil,3A
022 | cmc
023 | jmp 1400AE88C
024 | push r15
025 | rcl r11d,cl
026 | shr dil,cl
027 | cdq
028 | push rbx
029 | push r12
VMP启动时首先将x86寄存器压入x86栈中
在这里,x86寄存器对于VM的作用仅仅是为了被送入栈中,所以类似mov r8w,71F4
这样的指令都是花指令,无需关注
047 | mov rdi,rsp
随后VM将x86栈指针赋值给rdi,此时,rdi变为VM的VStack
(!!注意,并非每次VStack都为rdi,VMP每次编译虚拟机时都会产生变化)
(!!注意,此时尚未进入handler,VStack是给虚拟机运行用的而不是被虚拟的内容)
056 | mov r10d,dword ptr ss:[rbp]
057 | cmp r10d,r8d
058 | test dx,FDC
059 | add rbp,4
05A | jmp 140081FC0
05B | xor r10d,r11d
05C | stc
05D | not r10d
05E | add r10d,2BFF5AA0
05F | not r10d
060 | sub r10d,67D85431
061 | push r11
062 | movzx r11,cx
063 | xor r11b,r10b
064 | mov r11b,CB
065 | xor dword ptr ss:[rsp],r10d
066 | rol r11,cl
067 | add r11b,A3
068 | sbb r11w,44EE
069 | pop r11
06A | movsxd r10,r10d
06B | add rbx,r10
06C | jmp 1400ABB4B
06D | jmp rbx
随后VM安排rbp作为VIP
第56行VM利用VIP读取opcode
第6B行VM利用opcode计算得到handler
第6D行VM开始执行handler
进入handler后就是真正的虚拟化世界
06E | mov r8,qword ptr ds:[rdi]
06F | test r9b,E9
070 | ror r10b,C1
071 | add rdi,8
072 | movzx r10d,byte ptr ss:[rbp]
073 | stc
074 | add rbp,1
075 | clc
076 | test r9w,1E3D
077 | xor r10b,r11b
078 | jmp 14000CF51
079 | not r10b
07A | jmp 140098145
07B | dec r10b
07C | clc
07D | cmc
07E | stc
07F | rol r10b,1
080 | neg r10b
081 | add r10b,6C
082 | clc
083 | cmc
084 | cmp r15b,r13b
085 | xor r11b,r10b
086 | test r14w,16D0
087 | mov qword ptr ss:[rsp+r10],r8
088 | mov r8d,dword ptr ss:[rbp]
089 | jmp 140086333
08A | add rbp,4
08B | xor r8d,r11d
08C | jmp 1400D0AA9
08D | inc r8d
08E | stc
08F | cmc
090 | ror r8d,1
091 | stc
092 | xor r8d,31B317C7
093 | cmc
094 | clc
095 | jmp 14005C21D
096 | ror r8d,1
097 | test r9b,bl
098 | test r13w,4998
099 | push r11
09A | xadd r11d,r11d
09B | xor dword ptr ss:[rsp],r8d
09C | cmp r13w,di
09D | pop r11
09E | movsxd r8,r8d
09F | stc
0A0 | clc
0A1 | add rbx,r8
0A2 | jmp 140094398
0A3 | push rbx
0A4 | ret
在这个handler中做了很多事
- 从rdi(VStack)中读取值到r8,并将r8存入[rsp+r10]
- 第88行利用VIP读取OPCODE,第8A行VIP+=4,第A1行得到handler,随后利用push ip,ret的方式跳转到handler
翻译成人话
- VM将VStack中暂存的x86寄存器转移至VRegs中,这里rsp+r10就是VReg的指针
- 由于VM(3.X)unroll了opcode的分发器,在handler执行后,会执行dispatcher应该干的事情,即读取下一个opcode并跳转至下一个handler
以上操作将执行“寄存器个数”次
此时VM启动结束
VM的模拟执行
我这里仅对一行汇编进行VMP虚拟化
add rax, 1
摘抄handler关键部分如下
731 | mov rax,qword ptr ss:[rbp]
748 | sub rdi,8
74B | mov qword ptr ds:[rdi],rax
78B | mov r9,qword ptr ss:[rsp+rdx]
78E | sub rdi,8
78F | mov qword ptr ds:[rdi],r9
7B1 | mov rsi,qword ptr ds:[rdi]
7B2 | or ax,r15w
7B3 | mov r8,qword ptr ds:[rdi+8]
7B4 | bswap rax
7B5 | shl ah,6A
7B6 | add rsi,r8
7B7 | mov qword ptr ds:[rdi+8],rsi
7DE | add rdi,8
81B | mov r8,qword ptr ds:[rdi]
834 | mov qword ptr ss:[rsp+r10],r8
翻译一下,这四段代码执行了五个不同的handler
VPUSH [VIP]
VPUSH VRegs[VRAX]
VADD
VPOP
VRegs[VRAX] = [VStack]
翻译成伪代码
RAX = RAX + *VIP ; *VIP 为 1
VM的退出
其流程与VM的启动类似
VM清空VStack后不断地将VRegs 使用VPUSH hander压入VStack
通过
mov rsp,rdi
将x86栈指针指向VStack,此后VStack销毁
并不断使用POP指令将栈上的数值还原到x86真实寄存器
摘抄代码如下
C8F | mov rsp,rdi
C90 | pop r12
C91 | xchg esi,r15d
C92 | pop rbx
C93 | pop r15
C94 | shld esi,edi,6C
C95 | test sil,25
C96 | pop rcx
C97 | cdq
C98 | pop r11
C99 | lahf
C9A | shl rdx,cl
C9B | pop r14
C9C | or esi,738A3B0F
C9D | pop r13
C9E | pop rbp
C9F | test r9b,r12b
CA0 | sbb r8d,429264F6
CA1 | or di,7FAC
CA2 | pop r9
CA3 | not sil
CA4 | popfq
CA5 | movzx ax,r15b
CA6 | cdqe
CA7 | pop rdi
CA8 | cwd
CA9 | bswap dx
CAA | pop rdx
CAB | pop r10
CAC | bswap rax
CAD | cwde
CAE | mov r8w,r9w
CAF | pop rsi
CB0 | cbw
CB1 | cmovg eax,edx
CB2 | pop rax
CB3 | not r8b
CB4 | movsx r8w,r14b
CB5 | pop r8
CB6 | ret
至此一个VM过程结束
包括VM的启动与结束,它只执行了
ADD RAX, 1
而已
总结
不要分析VMP
啊 是dalao QWQ 作为信息安全专业刚进去的准大一看完之后 收获很多QWQ 谢谢dalaoQWQ