欢迎来到 嗅灵易学

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

[翻译]手把手静态分析FinSpy VM:第三部分第一阶段,反混淆FINSPY VM字节码程序

[翻译]手把手静态分析FinSpy VM:第三部分第一阶段,反混淆FINSPY VM字节码程序

原文:http://www.msreverseengineering.com/blog/2018/2/21/wsbjxrs1jjw7qi4trk9t3qy6hr7dye

注意:您可以在此处找到第三部分四个阶段的介绍。

1.介绍

在本系列的第一部分,我们分析了FinSpy VM的x86实现的混淆,并编写了一个工具对其进行反混淆以便于分析。在本系列的第二部分,我们分析了VM指令集,编写了适用于该样本的反汇编程序,并获得了VM字节码。现在我们剩下的工作是去虚拟化:我们希望生成虚拟化之前的原始x86程序。

这个任务写起来相当长,所以我们将分为四个阶段,与我对FinSpy去虚拟化时所做的工作顺序相同(不要把冗长理解为困难)。我们的第一阶段将查看FinSpy VM字节码程序,发现涉及第二组指令的混淆并通过模式替换将其删除。

2.去虚拟化FINSPY:开始观察和方法

在这一过程中,我以反汇编形式首次列出了VM字节码程序。我开始检查VM字节码程序并找出简化字节码的方法,最终全面清除了第二组指令。

你可能希望看看最初的VM字节码反汇编结果,我们将在整个第一阶段逐步完善它。在这个过程中通过发现并应用简化措施我们将逐步获得新的VM字节码反汇编结果。我们将提供所有这些结果的链接。

2.1回顾FINSPY VM指令集和字节码程序

总结第二部分:FinSpy VM使用固定长度的指令编码,每个VM指令由一个长度为0x18字节的结构体表示。 

FinSpy VM程序中的每条指令都有两个与其相关的唯一标识特征。首先,我们可以获得VM字节码指令数组中VM指令的原始位置。例如,第一条指令位于0x0。由于每条指令的长度均为0x18字节,因此第二条指令位于0x18,并且通常第N条指令位于字节码数组中0x18*N处。

指令的第二个标识特征是它的key,一个32位值(编码的FinSpy VM指令中的第一个DWORD),可用于定位特定的VM指令。具体来说,在进入VM之前,将控制权转交给FinSpy VM解释器之前执行的x86代码首先将一个DWORD大小的key压入栈。进入VM之后,FinSpy VM初始化代码加载由x86代码压入的DWORD大小的key,搜索VM字节码数组以找到具有该key的VM指令,然后开始解释从该位置开始的VM指令(事实证明,FinSpy VM指令key会导致去虚拟化中的一些复杂性,我们将在后面看到)。

大多数FinSpy VM指令被假定为按顺序执行。也就是说,一条VM指令完成后,指令处理程序的x86代码会将当前的VM EIP加上0x18,以进入下一条VM指令。VM控制流指令可能会有不同的行为,例如,条件跳转指令像它们对应的x86指令一样相对寻址(如果跳转VM指令会向VM EIP添加一个0x18的倍数的位移;如果没有跳转则在当前指令之后的0x18字节处的VM指令继续执行)。FinSpy VM也有一个用于直接调用的指令和一个用于间接调用的指令。这些调用的行为与期望的相同——在被调用的函数返回后,它们将返回地址压入栈以重新进入VM。

FinSpy VM的指令集由三组指令组成:

  • 第一组:有条件和无条件跳转(即JMP和x86的所有16个条件跳转,如JZ,JNS和JP)。
  • 第二组:访问FinSpy VM的单个专用寄存器的VM指令。
  • 第三组:在其中包含原始x86指令的VM指令。

2.2关于去虚拟化的初步思考

在分析了VM的指令集并仔细阅读VM字节码程序之后,似乎第三组VM指令——那些带有x86机器代码块的指令很容易转换回x86。第三组指令中有三条VM指令:原始X86,直接调用和间接调用(实际上,后两个VM指令最终呈现出比预期更复杂的情况,直接调用是VM去虚拟化中最困难的)。

第一组有条件和无条件的跳转指令似乎也很容易转换回x86,因为它们的实现几乎与x86条件跳转在内部的实现方式相同。由于条件跳转使用相对位移来确定采取跳转的地址,唯一的挑战是确定每个去虚拟化指令的相对位置。因此,一旦我们确切知道跳转指令离目标有多远,我们可以简单地计算位移(的确,在实践中这很容易)。

第二组访问FinSpy VM的单个专用寄存器的VM指令集是通配符。虽然通过分析FinSpy VM我知道这些指令的原始功能,但在分析之前,我不知道如何使用这些指令,也不知道如何对它们进行去虚拟化。为了便于参考,下面从第二部分更详细的论述中总结出这些指令:

mov scratch, 0                      [Operand: none]

mov scratch, imm32                  [Operand: imm32]

shl scratch, imm32                  [Operand: imm32]

add scratch, imm32                  [Operand: imm32]

mov scratch, savedReg32             [Operand: index of savedReg32]

add scratch, savedReg32             [Operand: index of savedReg32]

mov savedReg32, scratch             [Operand: index of savedReg32]

mov dword ptr [scratch], savedReg32 [Operand: index of savedReg32]

mov scratch, dword ptr [scratch]    [Operand: none]

mov dword ptr [scratch], imm32      [Operand: imm32]

mov dword ptr [imm32], scratch      [Operand: imm32]

push scratch                        [Operand: none]

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

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

0 0 0 举报
复制成功