zoukankan      html  css  js  c++  java
  • VTIL & NoVmp 源码简要分析

    项目简介

    VTIL 项目,代表 Virtual-machine Translation Intermediate Language,是一组围绕优化编译器设计的工具,用于二进制去混淆和去虚拟化。

    VTIL 与其他优化编译器(如 LLVM)之间的主要区别在于,它具有极其通用的 IL,可以轻松地从包括堆栈机器在内的任何架构中进行转换。

    由于它是为翻译而构建的,因此 VTIL 不会抽象出本机 ISA,而是按原样保留通用 CPU 的堆栈、物理寄存器和非 SSA 架构的概念。

    本机指令可以在 IL 流的中间插入,物理寄存器可以从 VTIL 指令自由寻址。

    VTIL 还使得在任何请求的虚拟地址处插入本机指令变得轻而易举,而不受特定文件格式的限制。

    项目地址:https://github.com/vtil-project

    架构转换

    下面以 NoVmp 项目为例分析 VMP 指令和 VTIL 指令之间的转换

    Native 指令到 VMP 指令的映射关系定义在 architecture.cpp

    VMP 指令到 VTIL 指令的映射关系定义在 il2vtil.cpp

    VTIL 指令到 Native 指令的映射关系定义在 demo_compiler.hpp

    转换代码定义在 lift_il.cpp

    转换程序通过特征匹配将 Native 指令转换为 VMP 指令,并通过 VMENTERVMEXITVJMP 指令对控制流进行跟踪

    使用递归下降的方法,以 Block 指令块为基本单位进行转换

    VMP 到 VTIL 转换流程

    • 首先转换程序会跟踪原生程序的控制流,若跳转目标地址在 VMP 段内,标志着到达 VMProtectBegin 虚拟机总入口点,开始 VMP 保护

    • VMP 虚拟机中存在若干不同的入口点和出口点,对应 VMENTER 指令和 VMEXIT 指令,除了总入口点和总出口点以外,其他都用于调用外部函数或 Native 指令

    • 当需要调用外部函数或 Native 指令时,程序会先执行 VMEXIT 指令暂时离开虚拟机,随后调用外部函数或 Native 指令,最后再执行 VMENTER 指令重新回到虚拟机

    • 转换程序建立 Block 存放转换后的 VTIL 指令,并使用滚动密钥解密当前 VMP 指令的参数

    • 遇到 VJMP 指令时,程序会在 Block 中插入 JMP 指令,并使用符号执行的方法,求出所有可能的跳转目标,再对这些可能的分支进行递归处理

    • 遇到 VMEXIT 指令时,会对跳转的目标地址进行求解

      • 若目标地址在 VMP 段内,则进一步判断,若SP 偏移小于 0,且跳转目标处的指令是 VMENTER,则代表这里是 External Call,否则是 VM Exit

        • 对于 External Call,标志着控制流暂时离开 VMP 虚拟机调用外部函数,程序会在 Block 中插入 VXCALL 指令调用外部函数,再回到 VMP 虚拟机继续处理

        • 对于 VM Exit,标志着控制流暂时离开 VMP 虚拟机执行 Native 指令块,程序会在 Block 中通过 VEMIT 指令插入 Native 指令块,再回到 VMP 虚拟机继续处理

      • 若目标地址在 VMP 段外,标志着到达 VMProtectEnd 虚拟机总出口点,结束 VMP 保护,程序会在 Block 中插入 VEXIT 指令

    • 遇到其他 VMP 指令时,程序会先将该指令转换为 VTIL 指令,再把转换后的结果插入到 Block

    VMP 执行流程

    图片来源:https://back.engineering/17/05/2021/

    注:在 VMP 3.x 中 CPUID 指令由 VCPUID 指令执行,不会进入 Native Execution,但其他特殊指令的处理和上图是类似的

    指令优化

    VTIL 内置的 11 个 Pass 优化器定义在 VTIL-CoreVTIL-Compileroptimizer

    下面将对这 11 个优化器进行分析

    bblock_extension_pass

    若一个 Block 由且仅由另一个 Block 进行调用,该优化器会尝试将这两个 Block 合并为一个 Block

    branch_correction_pass

    遇到跳转指令时,该优化器会尝试通过符号执行的方法,去除冗余的跳转目标,并将分支指令从 JMP 优化为 JS

    dead_code_elimination_pass

    该优化器会尝试分析无效的读写操作,去除冗余的指令

    fast_dead_code_elimination_pass

    该优化器会尝试分析无效的读写操作,去除冗余的指令,功能同上

    fast_propagation_pass

    该优化器会尝试分析数据的传播过程,去除中间过程冗余的指令

    istack_ref_substitution_pass

    该优化器会尝试将栈上数据的引用全部替换为 SP 加偏移的形式

    mov_propagation_pass

    该优化器会尝试分析数据通过 MOV 指令的传播过程,去除中间过程冗余的指令

    register_renaming_pass

    该优化器会尝试分析数据通过寄存器的传播过程,去除中间过程冗余的指令

    stack_pinning_pass

    该优化器会尝试分析 SP 的变化,提前计算出栈上读写操作的偏移

    stack_propagation_pass

    该优化器会尝试分析数据通过栈的传播过程,去除中间过程冗余的指令

    symbolic_rewrite_pass

    该优化器会尝试通过符号执行和表达式特征匹配的方法,在没有遇到分支指令且 SP 没有变化时,在比特粒度下对寄存器、栈以及内存中数据的前后变化进行分析,从而对表达式进行简化

    NoVmp 还原示例

    NoVmp 对于线性代码的还原效果比较好,但是对于循环和分支代码的还原效果比较差

    简单代码还原

    测试代码:

    int main(){
    	VMProtectBegin(MARKER_TITLE);
    	printf("test");
            __asm{
                    in al, dx
                    out dx, al
            }
    	VMProtectEnd();
    }
    

    还原结果:

    Lifted & optimized virtual-machine at 000000000011F53D
    Optimizer stats:
     - Block count:       4     => 2     (-50.00%).
     - Instruction count: 551   => 10    (-98.19%).
    Special instructions:
     - 0000000000000000: in al, dx
     - 0000000000000001: out        dx, al
    -- Virtualized real references to register 'r15'
    -- Virtualized real references to register 'r14'
    Register allocation step 0...
    Frame size:         0x0 bytes.
    Instruction count:  14
    
    Halting register virtualization as it did not improve the result.
    
    Frame size:         0x0 bytes.
    Instruction count:  14
     -- rbp + 0x8   := r14
     -- rbp + 0x0   := r15
            push rbp
            mov rbp, rsp
            sub rsp, 0x18
        +0x0     strq     rbp          -0x10        r14
            mov qword ptr [rbp - 0x10], r14
        +0x0     strq     rbp          -0x8         r15
            mov qword ptr [rbp - 0x8], r15
        +0x0     movq     rcx          &&base
            lea rcx, [rip + routine_base - 0x124000 + 0x0000000000000000]
        +0x0     addq     rcx          0x19c34
            add rcx, 0x19c34
        +0x0     vxcallq  0x1118b
            call 0x1118b
        +0x0     vpinrw   rdx:16
            in al, dx
        +0x0     vpinwb   rax:8
        +0x0     vpinrb   rax:8
        +0x0     vpinrw   rdx:16
            out dx, al
        +0x0     lddq     r15          rbp          -0x10
            mov r15, qword ptr [rbp - 0x10]
        +0x0     lddq     r14          rbp          -0x18
            mov r14, qword ptr [rbp - 0x18]
        +0x0     vexitq   0x118b2
            mov rsp, rbp
            pop rbp
            jmp 0x118b2
    routine_base:block_1d155:
            push rbp
            mov rbp, rsp
            sub rsp, 0x18
            mov qword ptr [rbp - 0x10], r14
            mov qword ptr [rbp - 0x8], r15
            lea rcx, [rip + routine_base - 0x124000 + 0x0000000000000000]
            add rcx, 0x19c34
            call 0x1118b
    block_1d4ac:
            in al, dx
            out dx, al
            mov r15, qword ptr [rbp - 0x10]
            mov r14, qword ptr [rbp - 0x18]
            mov rsp, rbp
            pop rbp
            jmp 0x118b2
    

    复杂代码还原

    测试代码:

    int main(){
    	VMProtectBegin(MARKER_TITLE);
    	for (int i = 0; i < 3; i++) {
    		p();
    		if (i == 0) {
    			q();
    		}
    		else {
    			r();
    		}
    		s();
    	}
    	VMProtectEnd();
    }
    

    还原过程出错:

    [*] Error: Assertion failure, !allocated_register at NoVmpNoVmpdemo_compiler.hpp:671
    [*] Unexpected error: Assertion failure, !allocated_register at NoVmpNoVmpdemo_compiler.hpp:671
    

    指令集

    VTIL 指令集定义在 VTIL-Architecturearchinstruction_set.hpp

    OPCODE OP1 OP2 OP2 Description
    MOV Reg Reg/Imm OP1 = ZX(OP2)
    MOVSX Reg Reg/Imm OP1 = SX(OP2)
    STR Reg Imm Reg/Imm [OP1+OP2] <= OP3
    LDD Reg Reg Imm OP1 <= [OP2+OP3]
    NEG Reg OP1 = -OP1
    ADD Reg Reg/Imm OP1 = OP1 + OP2
    SUB Reg Reg/Imm OP1 = OP1 - OP2
    MUL Reg Reg/Imm OP1 = OP1 * OP2
    MULHI Reg Reg/Imm OP1 = [OP1 * OP2]>>N
    IMUL Reg Reg/Imm OP1 = OP1 * OP2 (Signed)
    IMULHI Reg Reg/Imm OP1 = [OP1 * OP2]>>N (Signed)
    DIV Reg Reg/Imm Reg/Imm OP1 = [OP2:OP1] / OP3
    REM Reg Reg/Imm Reg/Imm OP1 = [OP2:OP1] % OP3
    IDIV Reg Reg/Imm Reg/Imm OP1 = [OP2:OP1] / OP3 (Signed)
    IREM Reg Reg/Imm Reg/Imm OP1 = [OP2:OP1] % OP3 (Signed)
    POPCNT Reg OP1 = popcnt OP1
    BSF Reg OP1 = OP1 ? BitScanForward OP1 + 1 : 0
    BSR Reg OP1 = OP1 ? BitScanReverse OP1 + 1 : 0
    NOT Reg OP1 = ~OP1
    SHR Reg Reg/Imm OP1 >>= OP2
    SHL Reg Reg/Imm OP1 <<= OP2
    XOR Reg Reg/Imm OP1 ^= OP2
    OR Reg Reg/Imm OP1 |= OP2
    AND Reg Reg/Imm OP1 &= OP2
    ROR Reg Reg/Imm OP1 = (OP1>>OP2)
    ROL Reg Reg/Imm OP1 = (OP1<<OP2)
    TG Reg Reg/Imm Reg/Imm OP1 = OP2 > OP3
    TGE Reg Reg/Imm Reg/Imm OP1 = OP2 >= OP3
    TE Reg Reg/Imm Reg/Imm OP1 = OP2 == OP3
    TNE Reg Reg/Imm Reg/Imm OP1 = OP2 != OP3
    TL Reg Reg/Imm Reg/Imm OP1 = OP2 < OP3
    TLE Reg Reg/Imm Reg/Imm OP1 = OP2 <= OP3
    TUG Reg Reg/Imm Reg/Imm OP1 = OP2 u> OP3
    TUGE Reg Reg/Imm Reg/Imm OP1 = OP2 u>= OP3
    TUL Reg Reg/Imm Reg/Imm OP1 = OP2 u< OP3
    TULE Reg Reg/Imm Reg/Imm OP1 = OP2 u<= OP3
    IFS Reg Reg/Imm Reg/Imm OP1 = OP2 ? OP3 : 0
    JS Reg Reg/Imm Reg/Imm Jumps to OP1 ? OP2 : OP3, continues virtual execution
    JMP Reg/Imm Jumps to OP1, continues virtual execution
    VEXIT Reg/Imm Jumps to OP1, continues real execution
    VXCALL Reg/Imm Calls into OP1, pauses virtual execution until the call returns
    NOP Placeholder
    SFENCE Assumes all memory is read from
    LFENCE Assumes all memory is written to
    VEMIT Imm Emits the opcode as is to the final instruction stream
    VPINR Reg Pins the register for read
    VPINW Reg Pins the register for write
    VPINRM Reg Imm Imm Pins the memory location for read, with size = OP3
    VPINWM Reg Imm Imm Pins the memory location for write, with size = OP3

    参考文章

    https://docs.vtil.org/

    https://github.com/vtil-project

    https://github.com/can1357/NoVmp

    https://github.com/0xnobody/vmpattack

    https://back.engineering/17/05/2021/

    https://bbs.pediy.com/thread-266206.htm

  • 相关阅读:
    vue列表排序实现中的this问题
    JavaScript:JSON 和 JS 对象
    vue项目设置每个页面的title
    webpack开发和生产两个环境的配置详解
    关于vuex的理解
    vue的路由配置
    js 的静态获取和动态获取
    7 Dockerfile指令详解 && VOLUME 指令
    HAProxy负载均衡保持客户端和服务器Session亲缘性的3种方式
    haproxy开启日志功能
  • 原文地址:https://www.cnblogs.com/algonote/p/14899403.html
Copyright © 2011-2022 走看看