现代代码虚拟化简介

Posted python与软件

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了现代代码虚拟化简介相关的知识,希望对你有一定的参考价值。

本文描述了如何通过“虚拟机”和使用的技术完成保护代码

流行的虚拟机,读者可以从文章对此类虚拟机有相当程度的了解。             

为何选择虚拟机?

    在软件保护的早期阶段,开发了混淆和变异等技术,这样的方法将垃圾代码插入到原始代码流中,将原始指令更改为它们同义词,用一些计算替换常量,插入条件和无条件分支并在执行代码之间的配置单元中写入随机字节(使顺序反汇编失败),此类处理的示例包括在example_obfuscated.exe中。


    随着时间的推移,这种方法引入的复杂程度变得不能跟上 开发调试工具(许多现在具有运行时跟踪功能)和平均人工技能的提升。 熟练的逆向工程师可以手动或半自动清 代码使其可读/可更改。  较重的混淆会增加受保护代码的大小 但复杂

性几乎没有增加。  人们开始寻求一种代码方法 防止这种蛮力。  通过使用

set将指令分解为执行循环 在可重用的微操作中,执行的代码长度可以指数

增加   这样的循环代码就像原始代码的“模拟器”(又名解释器),利用数据流(又名Pesudo代码或P代码),做微操作(又名  处理程序),就像“虚拟机”一样,在自己的指令集上执行。  这成长为

术语:代码虚拟化。


虚拟机如何工作?

    我们知道真正的处理器有寄存器,指令解码器和执行逻辑。  虚拟

机器大致相同。  虚拟机入口代码将从真实处理器收集上下文信息并存储在其自己的上下文中循环执行将读取P-Code并调度到相应的处理程序。当虚拟机退出时,它将从其存储的上下文更新实际处理器寄存器。


举一个简单的例子,有一个函数通过伪虚拟机执行:

原始:

    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将需要分析所有虚拟机实例,这是相当多的工作

以上是关于现代代码虚拟化简介的主要内容,如果未能解决你的问题,请参考以下文章

现代虚拟机如何处理内存分配?

VXLAN简介(摘抄)

每天3分钟操作系统修炼秘籍:虚拟内存简介

虚拟内存 内存分页 LargePage

虚拟内存 内存分页 LargePage

虚拟内存 内存分页 LargePage