欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[翻译]手把手静态分析FinSpy VM:第二部分,VM分析和字节码反汇编

[翻译]手把手静态分析FinSpy VM:第二部分,VM分析和字节码反汇编

原文:http://www.msreverseengineering.com/blog/2018/1/31/finspy-vm-part-2-vm-analysis-and-bytecode-disassembly

1.概述

这是我分为三部分的关于分析和去虚拟化FinSpy VM系列的第二部分。第一部分是着重于从FinSpy VM的x86实现中去混淆以便于分析的教程。本部分是关于FinSpy VM的实际分析,包括分析结果以及分析过程。在第三部分将提供工具来去除FinSpy的VM保护程序。

上次相同的GitHub仓库已更新并包含:

针对FinSpy VM实现的所有的包括注释的反汇编,并在适当的情况下的一些手动反编译

FinSpy VM字节码反汇编程序 

本样本的VM字节码反汇编

压缩和解压的VM字节码程序

引用的IDC和python脚本

引用的注释

2.我的歉意 

我努力将分析FinSpy VM的整个过程编写成一个教程式的文章,而不是更为传统的在反虚拟化文档和一般的安全报告中非常普遍的结果导向式的报告。在我看来,我在第一部分取得了成功,第三部分也适用于这种表达方式。然而,使用这种风格写作第二部分更加困难。静态逆向工程的非线性使得最终的文本难以呈现。静态逆向工程涉及处理不完整的信息并逐渐将其完善成为完整的信息,这涉及到很多跳跃和一开始显得隐晦但多次重复之后变得清晰的笔记。将其写成文档很可能是不连贯的,而且很长(甚至比目前的还要冗长)。

我本该更清楚地知道:当我教静态逆向工程时,我从一些功能的开头开始,经过长时间的动手操作,逐步描述我对代码的理解,以及如何将其转换为IDA数据库的注释。取而代之,视频可能是第二好的选择(视频不像课堂讨论那样互动,但至少它们会连同错误和其它所有东西展示整个过程)。

我已经阅读了本文的一些草稿,试图将标准的面向结果的去虚拟化报告与实际操作的风格结合起来。在对我的草稿不满意之后,我决定做出妥协。也就是说,我的文章同时包括这两种风格。本文的第一部分描述了在分析FinSpy VM后得出的结论。第二部分尽我所能展示了分析过程,包括插入的和外部链接的注释、VM各部分的汇编语言片段以及汇编语言的反编译。

我不知道我是否成功地写了一些易于理解的东西。如果你觉得这个文档太令人兴奋了,我希望你阅读第三部分,它也会被写成一个标准的教程。自从发布第一部分以来,Filip Kafka也发布了对FinSpy VM的分析两位来自微软的研究人员也发表了对FinSpy的分析。希望任何有兴趣对FinSpy VM进行逆向工程的人都可以利用这些资源学习。

第一部分:高层分析总结

3.VM体系结构的基础知识

VM软件保护是解释器。也就是说,VM软件保护将原始x86代码翻译成其自己的专有字节码语言(这个过程称为虚拟化)。在运行时,无论原始的未虚拟化的x86代码何时运行,VM解释器都会执行未虚拟化的x86代码所转换的字节码。这种保护技术的好处是分析人员不能再直接看到他们可能熟悉的原始x86指令。相反,他们必须首先分析解释器,然后使用所获得的知识来理解x86被翻译成的字节码。通过一些反分析技术,这个过程通常非常复杂:

  • 混淆x86解释器
  • 混淆虚拟化后的程序
  • 为每个样本使用不同的语言,目标是要求每个样本都需要人工分析
                操作码字节和指令之间的对应关系是随机的

                花指令


FinSpy VM属于比较简单的。

3.1VM作为解释器

由于VM软件保护是解释器,因此它们与普通解释器有许多共同之处,可用于更多典型的编程语言。

  1. 指令编码。解释器需要它们自己的标准化方式来表达指令。对于字节码语言来说是指令及其必需的相关数据(如指令的操作数)的二进制编码。实际上,解释器通常根据数据结构或类定义编码指令。
  2. VM上下文。解释器总是有一些与它们相关的状态,也被称为VM上下文结构。例如,解释程序可以访问特定于该语言的寄存器,和/或可能是栈或数据区域(这些都是常见的例子,在实践中状态可能只包含这些元素中的一部分和/或特定于该语言的其它元素)。解释器状态通常打包成一个结构或对象以允许解释器的多个副本存在于整个程序的执行过程(例如,允许多个线程运行VM实例)。
  3. 解释。解释器指令的实现:
  • 以一个指向解释器的上下文结构的指针作为输入; 
  • 查询结构以获得它执行需要的任何信息(例如,如果指令使用寄存器的值,并且寄存器保存在上下文结构中,那么指令必须从上下文结构中检索寄存器的值)。
  • 执行指令及其操作数指定的任何操作;
  • 用结果更新上下文结构,例如,将某个值存储到寄存器,数据节或上下文的栈中。

如上所述综合在一起,那么,解释器的框架通常是这样的:

void Interpret(VMContext *vmContext)

{

  while(!vmContext->bShouldExit)

  {

    switch(vmContext->pCurrInsn->opcode)

    {

      // Instruction: add r0, r1, r2 [r0 <- r1+r2]

      case VMOpcode_AddRegReg:

        

        // Updates vmContext->pCurrInsn and vmContext->bShouldExit

        VMAddRegReg(vmContext);

        break;

      // Instruction: add r0, r1, imm32 [r0 <- r1+imm32]

      case VMOpcode_AddRegImm:

        VMAddRegImm(vmContext);

        break;

      

      /* ... more instructions ... */

    }

  }

}

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功