@FadeTrack
2015-10-21T18:17:37.000000Z
字数 5649
阅读 1926
介绍代码虚拟化[译]
代码虚拟化
介绍代码虚拟化
By Nooby
翻译:FadeTrack
本文描述了如何通过“虚拟机”保护代码 并且 将这种技术运用在流行的虚拟机上。
带着你从入门到精通。 :)
翻译不当之处多多见谅,遇到疑惑或者有问题的地方,请以原文为准。
早期的软件保护,比较成熟的办法都是基于 模糊 和 变化(混淆、膨胀),这种方法将垃圾代码插入到原始代码流,或是改变原来的指令为意义相似的指令,又或是替换一些常量计算。插入有条件的和无条件的分支 以及 在关键的执行代码上随机的插入一些字节(使得这个流程不可逆)。
例子 example_obfuscated.exe 就是这样处理的。
随着时间的推移,程序员的水平上升和调试器的增强。一些成熟的逆向工程工具和手段使得原本被保护的代码变得 可读/可逆。使用膨胀的方法确实能有效阻止逆向,但是会增大软件的体积。于是,人们开始追求一个新的保护代码的方法,这种方法不用增长大小。
这样的循环代码就像一个原始代码的“模拟器”(或者称为解释器)。
接收的数据流 ( 又称为Pesudo-code or P-code),做微操作(处理程序),就像一个“虚拟机”执行指令集那样。最终这个过程演变为了: code virtialization
。(指令虚拟化)
我们知道真实的处理器 拥有 寄存器,翻译解码器 以及 逻辑处理器。虚拟级也是一样的。
虚拟机的入口代码实际上是在收集实体处理器的上下文信息,执行循环将读取 P-Code 并派发到相应的处理程序(handler),当虚拟机退出时,它将通过之前保存的上下文信息来更新真实的处理器的寄存器信息。
这里做一个简单例子,来假设一个函数将通过虚拟机执行。
最初的指令:
add eax, ebx
Retn
通过将其转换为虚拟代码:
push address_of_pcode ==> 将 p-code 的地址入栈
jmp VMEntry
VMEntry:
push all register values ==> 将所有的寄存器信息入栈
jmp VMLoop
VMLoop:
从 VMEIP 处 获取 p-code
调度处理程序 [Add_EAX_EBX_]
VMInit:
pop all register values into VMContext ==> 将所有寄存器的值弹出到 VMContext
pop address_of_pcode into VMEIP ==> 将 p-code 弹出到 VMEIP
jmp VMLoop
Add_EAX_EBX_Hander:
do “add eax, ebx” on VMContext ==> 在 VMContext 里面完成 操作
jmp VMLoop
VMRetn:
restore register values from VMContext ==> 通过VMContext恢复寄存器信息
do “retn” ==> 返回
注意,虚拟机最好不要效仿x86指令,因为如果指令也能被真正的处理器执行的话,可能会在某些特定的地方导致虚拟机退出,之后就会卡死在这里或则出现一些不可预料的情况( Ps: 这一句不太好翻译)。
实际的虚拟机的处理程序通常用更通用的设计思想,而不是上面的示例中的处理程序。
通常 p - code也决定了操作数。
“Add_EAX_EBX_Hander”可以被定义为“Add_Handler”,它需要两个参数,产生一个结果。
也会 加载/存储 寄存器,处理过程/保存参数和结果。
这样会提高处理程序的可重用性,以便跟踪处理程序而不需要去理解虚拟机的原始构造代码。
现在我们来看看 一个基于堆栈(stack-based)的虚拟机是如何工作的:
Add_Hander:
pop REG ; REG = parameter2
add [STACK], REG ; [STACK] points to parameter1
GetREG_Handler:
fetch P-Code for operand
push VMCONTEXT[operand] ; push value of REG on stack
SetREG_Handler:
fetch P-Code for operand
pop VMCONTEXT[operand] ; pop value of REG from stack
The P-Code of above function will be:
Init
GetREG EBX
GetREG EAX
Add
SetREG EAX
Retn
对于虚拟机来说,代码混淆和变形是非常重要的,因为直接将虚拟机的解释器暴露在外的话,逆向者就可以通过一些自动化的工具对虚拟机的底层构架进行分析。
由于处理器上的一些寄存器并没有被使用(VMContext和 虚拟机解释器可能使用的少量寄存器 是 分开存储),所以它们可以被用作额外的混淆。
虚拟机的处理程序可以设计成尽可能少的操作数/上下文依赖性。
此外,真正的处于 VMContext 的 堆栈指针可能被追踪,堆栈可以被抛弃在解释器循环内。
通过以上说明,不难看出代码混淆和变形可以是非常有效的。
混淆虚拟机的例子可以在example_virtualized.exe
中找到。
现在我们知道如何保护虚拟机的执行部分,让我们继续看看将指令转换为p-Code 的方法,这是虚拟化的精彩的一部分代码。
这里有一个增加可用性和复杂性的方法。
逻辑处理可以根据下面的公式分解成类似 NAND/NOR 的操作:
NOT(X) = NAND(X, X) = NOR(X, X)
AND(X, Y) = NOT(NAND(X, Y)) = NOR(NOT(X), NOT(Y))
OR(X, Y) = NAND(NOT(X), NOT(Y)) = NOT(NOR(X, Y))
XOR(X, Y) = NAND(NAND(NOT(X), Y), NAND(X, NOT(Y))) = NOR(AND(X, Y), NOR(X, Y))
减法可被转换成带EFlags进位计算的加法。
SUB(X, Y) = NOT(ADD(NOT(X), Y))
将 EFLAGES 之前的最后一个 NOT 作为 A, EFLAGES 之后的最后一个 NOT 最为B,那么计算如下:
EFLAGS = OR(AND(A, 0x815), AND(B, NOT(0x815))) ; 0x815 masks OF, AF, PF and CF
由于虚拟机可以比一个实际的x86处理器有更多的寄存器,真正的处理器寄存器可以动态地映射到虚拟机寄存器,可以使用额外的寄存器来存储中间值或用来做混淆。使得指令通过下文所述的内容得到进一步的模糊和优化。
由于寄存器的抽象,不同的 p-code可以有不同的寄存器映射,这样就可以不时的去改变设计,使得逆向更加困难。
当下一块 P-Code 具有不同的寄存器映射时,虚拟机仅仅交换处于上下文的值。
当像XCHG这一类的转换指令时,它可以简单地改变寄存器而不产生任何P- code的映射。看下面的例子:
原来的指令:
xchg ebx, ecx
add eax, ecx
当前寄存器映射
Real Registers Virtual Registers
EAX R0
EBX R1
ECX R2
GetREG R2 ; R2 = ECX
GetREG R1 ; R1 = EBX
SetREG R2 ; ECX = value of EBX
SetREG R1 ; EBX = value of ECX
GetREG R2
GetREG R0 ; R0 = EAX
Add
SetREG R0
Before Exchange
Real Registers Virtual Registers
EAX R0
EBX R1
ECX R2
After Exchange
Real Registers Virtual Registers
EAX R0
EBX R2
ECX R1
[Map R1 = ECX, R2 = EBX] ; exchange
GetREG R1 ; R1 = ECX
GetREG R0 ; R0 = EAX
Add
SetREG R0 ; R0 = EAX
这样的轮循也可以应用到最后SetREG操作, 这样的结果还将写入另一个未使用的虚拟机寄存器(即 R3), 舍弃拥有无效数据的R0。这一块 P-Code 的操作将在3个寄存器上,所以它很难被还原。
P-Code With Context Rotation 2:
[Map R1 = ECX, R2 = EBX] ; exchange
GetREG R1 ; R1 = ECX
GetREG R0 ; R0 = EAX
Add
[Map R0 = Unused, R3 = EAX] ; rotation
SetREG R3 ; R3 = EAX
当处理一条指令时,尤其是在寄存器之间赋值时,可能会出现 源寄存器 和 目标寄存器 之间的映射。除非源寄存器将被改变(强制重新映射或GetREG & SetREG操作)。
这种映射可以读访问权目标寄存器,将其重定向到它没有实际执行任务的来源。
采取以下的代码为例:
Original Instructions:
mov eax, ecx
add eax, ebx
mov ecx, eax
mov eax, ebx
P-Code:
Current Register Mappings
Real Registers Virtual Registers
EAX R0
EBX R1
ECX R2
[Make alias R0 = R2]
GetREG R1 ; R1 = EBX
GetREG R2 ; reading of R0 redirects to R2
Add
[R0(EAX) is being changed, since R0 is destination of an alias, just clear its alias]
[Map R0 = Unused, R3 = EAX] ; rotation
SetREG R3 ; R3 = EAX
[Make alias R2 = R3]
GetREG R1
[R3(EAX) is being changed, since R3 is source of an alias, we need to do the assignment]
[Map R3 = ECX, R2 = EAX] ; we can simplify the R2 = R3 assignment by rotation
[Map R0 = EAX, R3 = Unused] ; another rotation
SetREG R0 ; R0 = EAX
给定上下文的一组指令,它可以确定,在某些时候某些寄存器的值改变的而不影响程序逻辑,以及一些EFLAGS计算的开销可以忽略。
例如,在0 x4069A8 example.exe 的一段代码:
PUSH EBP
MOV EBP, ESP ; EAX|ECX|EBP|OF|SF|ZF|PF|CF
SUB ESP, 0x10 ; EAX|ECX|OF|SF|ZF|PF|CF
MOV ECX, DWORD PTR [EBP+0x8] ; EAX|ECX|OF|SF|ZF|PF|CF
MOV EAX, DWORD PTR [ECX+0x10] ; EAX|OF|SF|ZF|PF|CF
PUSH ESI ; OF|SF|ZF|PF|CF
MOV ESI, DWORD PTR [EBP+0xC] ; ESI|OF|SF|ZF|PF|CF
PUSH EDI ; OF|SF|ZF|PF|CF
MOV EDI, ESI ; EDI|OF|SF|ZF|PF|CF
SUB EDI, DWORD PTR [ECX+0xC] ; OF|SF|ZF|PF|CF
ADD ESI, -0x4 ; ECX|OF|SF|ZF|PF|CF
SHR EDI, 0xF ; ECX|OF|SF|ZF|PF|CF
MOV ECX, EDI ; ECX|OF|SF|ZF|PF|CF
IMUL ECX, ECX,0x204 ; OF|SF|ZF|PF|CF
LEA ECX, DWORD PTR [ECX+EAX+0x144] ; OF|SF|ZF|PF|CF
MOV DWORD PTR [EBP-0x10], ECX ; OF|SF|ZF|PF|CF
MOV ECX, DWORD PTR [ESI] ; ECX|OF|SF|ZF|PF|CF
DEC ECX ; OF|SF|ZF|PF|CF
TEST CL, 0x1 ; OF|SF|ZF|PF|CF
MOV DWORD PTR [EBP-0x4], ECX
JNZ 0x406CB8
注释展示了在指令执行之前的违背使用的 寄存器/flag 的状态。 这些被用作生成寄存器轮循。
EFLAGS计算冗长并且夹杂垃圾指令,这使得逆向更加复杂。
将原本指令内的常量给转换成算式计算,这样常量出现在运行时,可以避免直接暴露。
虚拟机可以通过pushing/writing随机值来混淆的堆栈,而真正的ESP可以从VMContext 计算/跟踪得到。
It is possible to use multiple virtual machines to execute one series of P-Code. On certain points, a
special handler leading to another interpreter loop is executed. The P-Code data after such points
are processed in a different virtual machine. These virtual machines need only to share the
intermediate run-time information such as register mappings on switch points. Tracing such P-Code
will need to analyze all virtual machine instances, which is considerably much more work.
VMProtect
http://vmpsoft.com/
Code Virtualizer
http://www.oreans.com/
Safengine
http://www.safengine.com/
ReWolf's x86 Virtualizer
http://rewolf.pl/stuff/x86.virt.pdf
OllyDBG
http://www.ollydbg.de/
VMSweeper
http://forum.tuts4you.com/topic/25077-vmsweeper/