现代代码虚拟化简介
Posted python与软件
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了现代代码虚拟化简介相关的知识,希望对你有一定的参考价值。
本文描述了如何通过“虚拟机”和使用的技术完成保护代码
流行的虚拟机,读者可以从文章对此类虚拟机有相当程度的了解。
为何选择虚拟机?
add eax, ebx
retn
通过将其转换为虚拟化代码
push address_of_pcode
jmp VMEntry
VMEntry:
push all register values(推送所有寄存器值)
jmp VMLoop
VMLoop:
fetch p-code from VMEIP (从VMEIP获取p-code)
dispatch to handler(调度到handler)
VMInit:
//将所有寄存器值弹出到VMContext中
pop all register values into VMContext()
//将address_of_pcode弹出到VMEIP中
pop address_of_pcode into VMEIP
jmp VMLoop
Add_EAX_EBX_Hander:
do “add eax, ebx” on VMContext
jmp VMLoop
VMRetn:
/从VMContext恢复寄存器值
restore register values from VMContext
do “retn”
请注意,虚拟机不要也不需要模拟所有x86指令,有些可以由真实处理器按原样执行,这使虚拟机在某一点退出,将EIP指向原始指令然后重新进入VM。
与上面的示例处理程序相反,实际的虚拟机处理程序通常设计得更通用。 通常,P-code 也确定操作数。 “Add_EAX_EBX_Hander”可以
___被定义为“Add_Handler”,它接受2个参数并产生结果。 还将有_load / store寄存器处理程序,其中包含生成/保存参数和结果。 通过这样做可以使处理程序更加可重用,以便在不了解虚拟机体系结构的情况下跟踪这些处理程序不能很好地理解原始代码。 现在我们看看它如何在基于堆栈的虚拟机上运行
AddHander:
pop REG ;REG = parameter2
add [STACK], REG ; ;[STACK] 指向 parameter1
GetREG_Handler:
fetch P-Code for operand
push VMCONTEXT[operand] ; 在栈上push value of REG
SetREG_Handler:
fetch P-Code for operand
pop VMCONTEXT[operand] ; 在栈中 pop value of REG
The P-Code of above function will be:
Init
GetREG EBX
GetREG EAX
Add
SetREG EAX
Retn
现代虚拟机对逆向工程的抵抗是什么?
代码混淆和变异对代码虚拟化很重要,因为虚拟机解释器直接暴露,它们可以帮助保护虚拟机免受自动分析工具的影响。 在不知道其底层虚拟机架构的情况下,严重混淆的虚拟机处理程序可能需要一段时间才能进行 去混淆。 由于未使用某些处理器寄存器(VMContext是单独存储的,而虚拟机解释器可以设计为仅使用少量寄存器),因此可以将它们用作额外的混淆。 可以将虚拟机处理程序设计为具有尽可能少的操作数/上下文依赖性。 此外,可以在VMContext中跟踪实际堆栈指针,堆栈可以在解释器循环期间被丢弃。在这些方面,代码混淆和变异可以非常有效。
现在我们知道如何保护虚拟机的执行部分,让我们继续看看在将指令转换为P代码时使用了哪些技术,这是P-Codes的一部分。
指令分解
逻辑指令
在增加复杂性和可重复性的方法中,逻辑运算可以分解为像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
算术指令
减法可以用加法代替 :
SUB(X, Y) = NOT(ADD(NOT(X), Y
在最终NOT之前将EFLAGS作为A,将最终NOT之后的EFLAGS作为B,计算如下
EFLAGS = OR(AND(A, 0x815), AND(B, NOT(0x815)))
; 0x815 masks OF, AF, PF and CF
注册抽象
由于虚拟机可以拥有比实际x86处理器更多的寄存器,因此实际处理器寄存器可以动态映射到虚拟机寄存器,额外的寄存器可以用于存储中间值或仅仅是混淆。 这还允许进一步混淆/优化指令,如下所述。
上下文轮换
由于寄存器抽象,不同的P-Code片段可以具有不同的寄存器映射,并且这种对应关系可以被设计为不时地改变,使得逆向工程更加困难。 当下一段P代码具有不同的寄存器映射时,虚拟机仅在其上下文上交换值。 在转换像XCHG这样的指令时,它可以简单地改变寄存器的映射而不产生任何P代码。 请参阅以下示例
原始:
xchg ebx, ecx
add eax, ecx
没有上下文轮换的P代码
当前Register Mappings
GetREG R2 ; R2 = ECX
GetREG R1 ; R1 = EBX
SetREG R2 ; ECX = value of EBX
SetREG R1 ; EBX = value of ECX
GetREG R2GetREG R0 ; R0 = EAX
Add
SetREG R0
具有上下文轮换的P代码(在P代码生成期间进行交换)
交换之前
交换之后
[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个虚拟机寄存器上,这使得逆向工程师很难找到它的x86等效值.
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操作),否则此映射可以将对目标寄存器的读取访问重定向到其源,而不实际执行赋值。以下面的代码作为示例。
原始:
mov eax, ecx
add eax, ebx
mov ecx, eax
mov eax, ebx
P-代码:
当前 Register Mappings
[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] ; 回转
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] ; 我们可以通过回转简化R2 = R3分配
[Map R0 = EAX, R3 = Unused] ; 另一个回转
SetREG R0 ; R0 = EAX
寄存器用法分析
给定一组指令的上下文,可以确定某些寄存器的值在某些时候是可变的而不会影响程序逻辑,并且可以省略EFLAGS计算的一些开销。 例如,下面代码
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
注释中的分析显示指令之前未使用的寄存器/标志状态。 该信息用于生成寄存器旋转,EFLAGS计算省略和垃圾操作 ,使生成的P-Code更难以分析
其他P-code混淆和优化
Const加密
原始指令的中间值和常量可以在运行时转换为计算结果,从而降低常量直接暴露在P-Code中的可能性
堆栈混淆
通过推送/写入随机值可以对虚拟机的堆栈进行模糊处理,因为可以从VMContex计算/跟踪真实的ESP
多个虚拟机解释器
可以使用多个虚拟机来执行一系列P-Code。 在某些点上,执行一个导致另一个解释器循环的特殊处理程序。 在这些点之后的P代码数据在不同的虚拟机中处理。 这些虚拟机只需要共享中间运行时信息,例如交换点上的寄存器映射。 跟踪这样的P-Code将需要分析所有虚拟机实例,这是相当多的工作
以上是关于现代代码虚拟化简介的主要内容,如果未能解决你的问题,请参考以下文章