VMProtect 3.x- 如何对vmp静态分析(1)

2021-10-12 09:41:07 浏览数 (1)

  • 目的
  • 意向
  • 定义
  • VMProtect 2 - 项目概述
    • VMHook - 概述
      • VMHook - 示例,um-hook
    • VMProfiler - 概述
      • VMProfiler - 虚拟机处理程序分析
      • VMProfiler - 虚拟分支检测算法
    • VMProfiler Qt - 概述
    • VMProfiler CLI - 概述
    • VMEmu - 概述
      • VMEmu - 独角兽引擎,操作码的静态解密
      • VMEmu - 虚拟分支
    • VMAssembler - 概述
      • VMAssembler - 汇编器阶段
      • VMAssembler - 第一阶段,词法分析和解析
      • VMAssembler - 第二阶段,虚拟指令编码
      • VMAssembler - 第三阶段,虚拟指令加密
      • VMAssembler - 第四阶段,C 头文件生成
      • VMAssembler - 示例
  • VTIL - 入门
    • VTIL - 基本块
    • VTIL - VMProfiler 提升
  • 结论 - 最后的话和未来的工作
    • 结论 - 未来的工作

目的


本文的目的是阐述上一篇题为“VMProtect 2 - 虚拟机架构的详细分析”中披露的先前工作,并纠正一些错误。此外,这篇文章将主要关注使用上一篇文章中公开的知识创建静态分析工具,并提供一些详细的、但非官方的VTIL文档。本文还将展示githacks.org/vmp2上的所有项目,但是,这些项目可能会发生变化。

意向


我在这项研究背后的意图是通过本机代码虚拟化和代码混淆来进一步了解软件保护主题,这不是为了牟利或诽谤 VMProtect 的名称。相反,应尊重所述软件的创建者,因为他们的工作显然令人印象深刻,并且可以说经受住了时间的考验。

定义


Code Block:虚拟指令块或代码块是包含在虚拟分支指令之间的虚拟指令序列。这方面的一个例子是 JMP 指令和下一个 JMP 或 VMEXIT 指令之后的任何指令。代码块在 C 中表示为包含虚拟指令向量的结构 ( vm::instrs::code_block_t ),以及包含在结构本身中的代码块的起始地址。关于给定代码块的其他元数据也包含在此结构中,例如代码块是否分支到其他两个代码块、仅分支到一个代码块或退出虚拟机。

VMProtect 2 IL: 中级表示或语言。将编码和加密的虚拟指令视为可用的、本机形式的虚拟指令。那么 IL 将是更高级别的表示,通常 IL 表示是指编译器和汇编器使用的代码的表示。VMProtect 2 IL 的一个例子是 VMAssembler 对什么进行词法分析,或者更具体的包含 IL 的文件。

VMProtect 2 - 项目概述


注意:你可以在这里找到VMProfiler的doxygen

尽管githacks.org/vmp2 上似乎有很多项目,但实际上只有一个大型库项目和继承该库的较小项目。VMProfiler是VMProfiler Qt、VMProfiler CLI、VMEmu和VMAssembler的基础库。这些项目中的每一个都是基于静态分析的,因此VMHook和um-hook不继承VMProfiler。

VMHook - 概述


VMHook是一个非常小的 C 框架,用于挂钩到 VMProtect 2 虚拟机,um-hook 继承了该框架并提供了如何使用该框架的演示。VMHook不用于发现虚拟指令及其功能,而是用于更改它们。

VMHook - 示例,um-hook

代码语言:txt复制
.data
	__mbase dq 0h
	public __mbase

.code
__lconstbzx proc
	mov al, [rsi]
	lea rsi, [rsi 1]
	xor al, bl
	dec al
	ror al, 1
	neg al
	xor bl, al

	pushfq			; save flags...
	cmp ax, 01Ch
	je swap_val

					; the constant is not 0x1C
	popfq			; restore flags...	 
	sub rbp, 2
	mov [rbp], ax
	mov rax, __mbase
	add rax, 059FEh	; calc jmp rva is 0x59FE...
	jmp rax

swap_val:			; the constant is 0x1C
	popfq			; restore flags...
	mov ax, 5		; bit 5 is VMX in ECX after CPUID...
	sub rbp, 2
	mov [rbp], ax
	mov rax, __mbase
	add rax, 059FEh	; calc jmp rva is 0x59FE...
	jmp rax
__lconstbzx endp
end

um-hook 是一个继承 VMHook 的项目,它演示了钩住 LCONSTBZX 虚拟指令并欺骗其立即数。这随后会影响后面的虚拟移位函数结果,最终导致虚拟例程返回 true 而不是 false。

VMProfiler - 概述


VMProfiler是一个 C 库,用于对 VMProtect 2 二进制文件进行静态分析。这是VMProfiler Qt、VMProfiler CLI、VMEmu和VMAssembler的基础项目。VMProfiler还继承了VTIL并包含虚拟机处理程序配置文件和提升器。

VMProfiler - 虚拟机处理程序分析

通过模式匹配算法找到并分类虚拟机处理程序。该算法的第一次迭代只是比较了本机指令字节。然而,这已被证明是无效的,因为对本机指令的更改不会导致不同的结果,但会更改本机指令字节将导致算法错误分类甚至无法识别虚拟机处理程序。考虑以下指令变体,所有这些变体在执行时都有相同的结果,但每个都有自己独特的字节序列。

代码语言:txt复制
0:  36 48 8b 00             mov    rax,QWORD PTR ss:[rax]
4:  48 8b 00                mov    rax,QWORD PTR [rax] 
0:  36 48 8b 04 05 00 00    mov    rax,QWORD PTR ss:[rax*1 0x0]
7:  00 00 

为了处理这种情况,设计并实现了新的分析算法迭代。这个新的再现仍然模式匹配,但是对于虚拟机处理程序的每条指令,都定义了一个 lambda。这个 lambda通过引用接受一个ZydisDecodedInstruction参数,并返回一个布尔值。如果给定的解码指令满足所有比较情况,则结果为真。为此目的使用zydis允许人们在更精细的级别上比较操作数。例如,上图中两条指令的操作数二的类型为ZYDIS_OPERAND_TYPE_MEMORY。此外,这两条指令的内存操作数的基数是RAX. 两条指令的助记符是相同的。这种极简的比较思维正是这种分析算法的基础。

代码语言:txt复制
vm::handler::profile_t readq = {
    // MOV RAX, [RAX]
    // MOV [RBP], RAX
    "READQ",
    READQ,
    NULL,
    { { // MOV RAX, [RAX]
        []( const zydis_decoded_instr_t &instr ) -> bool {
            return instr.mnemonic == ZYDIS_MNEMONIC_MOV &&
                   instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER &&
                   instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_RAX &&
                   instr.operands[ 1 ].type == ZYDIS_OPERAND_TYPE_MEMORY &&
                   instr.operands[ 1 ].mem.base == ZYDIS_REGISTER_RAX;
        },
        // MOV [RBP], RAX
        []( const zydis_decoded_instr_t &instr ) -> bool {
            return instr.mnemonic == ZYDIS_MNEMONIC_MOV && 
            	   instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_MEMORY &&
                   instr.operands[ 0 ].mem.base == ZYDIS_REGISTER_RBP &&
                   instr.operands[ 1 ].type == ZYDIS_OPERAND_TYPE_REGISTER &&
                   instr.operands[ 1 ].reg.value == ZYDIS_REGISTER_RAX;
        } } } };

在上图中,显示了READQ配置文件。请注意,并非虚拟机处理程序的每条指令都必须有一个 zydis lambda。仅足以为其构建独特的配置文件。事实上,还有一些额外的READQ本地指令没有用 zydis 比较 lambdas 来解释。

0 人点赞