VMprotect静态跟踪 && 字节码反编译

VMprotect静态跟踪

(1)虚拟执行特点:

  1. 虚拟执行是静态分析与动态执行的一个折中办法
  2. 虚拟执行时对内存访问做了一定的控制以防止出现异常
    允许读写静态内存与堆栈访问
    忽略其他内存访问与修改

解决了异常问题后,就可以从入口点一直虚拟执行到出口了
在这里插入图片描述

(2)执行引擎的虚拟执行

分析虚拟机的一般传统方法:

  • 找到关键位置
  • 动态执行并使用记录断点记录数据
  • 输出记录日志

优点:

  • 寻找关键位置时间相对较短

缺点:

  • 多路径时只能走其中一条路径
  • 分析多个虚拟机时要重复做相同的工作

虚拟执行方法:

  • 虚拟执行代码
  • 根据已分析字节码灵活控制代码

流程:

  • 输出记录日志

优点:

  • 虚拟执行不会对系统造成伤害
  • 完整的字节码流程

缺点:

  • 指令正确但操作数的值不可靠
  • 复杂度较高,开发时间较久

(3)分析条件跳转的两条出边

因为虚拟执行是不依赖运行时信息的,所以它无法判断应该走哪一条,必须把两条边都走过一遍
在这里插入图片描述

  • 基本块(BasicBlock)执行前备份运行时环境
  • 执行到跳转处分析指令流,获得修改路径关键点
  • 退回基本块起始位置
  • 重新执行并控制路径
    在这里插入图片描述

字节码反编译

(1)中间表示语言

VMP的Hanlder只能算是低级中间语言,缺少一些例如数据依赖,流程走向等信息,还不满足反编译的条件。需要将其转换为包含更多信息的高级中间语言形式

Push c
Push b
Add. d
Pop EFL
Pop A

转换后:

Stk1=push c
Stk2=push b
Stk3,Stk4=Add. d stk2,stk1
EFL=stk4
a=stk3

转换后:

Stk1=c
Stk2=b
Stk3,Stk4=Add. d stk2,stk1
EFL=stk4
A=stk3

优点:

  1. 去掉了对堆栈的依赖,转为直接关联变量
  2. 表达式被转换成SSA(静态单赋值)形式,方便对指令做优化处理

(2)指令化简和优化

常数收缩:

Push 1A2FBCA0
Push F80499D8
Add. d
Pop EFL

采用常数收缩后:

Push 12345678

活跃变量分析:

Stk0=Ctx24
Stk1=Ctx10
Stk2,EFL1=Add. d Stk0,Stk1
Ctx28=EFL1
Ctx30=Stk2
Stk3=Ctx30
Stk4=1111
Stk5,EFL2=Add. d Stk3,Stk4
Ctx28=EFL2
Ctx34=Stk5

去除中间变量后:

Ctx30,Ctx28=Add. d Ctx24,Ctx10
Ctx34,Ctx28=Add. d Ctx30,1111

去除无用变量后:

Ctx30=Add. d Ctx24,Ctx10
Ctx34,Ctx28=Add. d Ctx30,1111

删除无关代码:

VMP在生成的字节码中夹杂了一些自己的指令流,这些指令与原汇编代码没有任何关系,且对还原分析没有任何好处,只会起到干扰的作用,需要根据特征制定一些规则来识别这些垃圾指令

(3)转换汇编指令——树模式匹配

  1. 文本表达转换为树形表达
  2. 收集转换规则(这是最麻烦的一个过程,需要分析VMP将汇编生成字节码的特征来收集将字节码逆向转换回去的规则,这是一个不得不做的体力活)
  3. 使用匹配规则迭代匹配汇编指令
Ctx30=Add. d Ctx24,Ctx10
Ctx34,Ctx28=Add. d Ctx30,1111

在这里插入图片描述

匹配结果如下:
在这里插入图片描述

(4)归类映射寄存器

经过迭代后的最终结果是这样:

Add Ctx30(Ctx24),Ctx10
Add Ctx34(Ctx30),1111

虽然已经转换为汇编指令,但是还无法确定寄存器到底是哪一个,以目前所知的信息也的确无法判断,步过,我们可以尽可能的确定一些信息,以供后面的分析参考。

在转换规则中,预先明确定义了Add指令的第一个参数与结果是同一个寄存器(其他指令也是差不多,类似xchg的指令除外),所以可以推理得到,在指定的区别内Ctx34,Ctx30,Ctx24,是同一个寄存器,这样后面在专门针对寄存器识别的分析时,就可以一下确定这四个寄存器所映射的寄存器了

在这里插入图片描述

(5)转换汇编指令——动态规划

首先来看两段指令

mov eax ,dword ptr[dei+0x100]
add edi,100

其中第一条里面包含了第二条的指令,第一条的权值应该设得更高

add edi,100
Lea ebx,[edi+100]

两条指令仅仅是目标寄存器不同,两条指令的权值应该相等

所谓动态规划,通俗的讲就是制定一些规则,根据实际情况来选择最终匹配结果
这里的意思是对每一个匹配规则设一个权值,使用计算后值最大的那个匹配规则来进行转换
第二段的情况有些特殊,其中两条指令唯一的不同只有目标操作数
。Add指令认为目标操作数与源操作数1相同,而lea指令无此限制。当出现权值一样的情况时,可以同时作为结果,在识别出寄存器后,再根据实际情况来匹配规则,在这两个指令中选出更像的哪一个。

(6)寄存器染色

要识别前面所代表的寄存器,要从以下几个方面进行分析:

  1. 初始化虚拟机时各项所映射的寄存器
  2. 根据汇编转换规则映射或者结束映射某项到某寄存器
  3. 退出虚拟机时通过弹出各项时确定各项最终映射的寄存器

从这三方面可以大体推理出各项所映射的寄存器

但仅仅是这样的话只有在没有跳转指令的字节码中,成功率才最高。因为还得考虑寄存器轮转

(一)基本块内的寄存器轮转:

基本块内的寄存器轮转比较容易简单,只要转换规则正确,就可以识别出寄存器
在这里插入图片描述

(二)基本块间的寄存器轮转:

在这里插入图片描述
在执行set.jmp之前,将Context中所有位置的值都临时存放到了堆栈中,跳向目标地址后又再全部把它弹出到不同位置中去,这样就完成了一次轮换

它比基本块内的寄存器轮转更麻烦,因为其中涉及到了二义性的问题

(三)寄存器的二义性问题

寄存器的二义性问题是一个很严肃的问题,因为如果不能正确分析和处理,将会在成一子放错,满盘皆输的局面。
寄存器的二义性由指令的二义性衍生出,要解决指令的二义性,需要先解决寄存器的二义性问题。

Push\Pop的二义性:

Push\Pop在VMP中存在一种二义性,即传值与传引用。

传值:
当pop指令的作用是传值时,表示员项中国的值放到目标项中去,所映射的寄存器不变。

传引用:
当pop指令的作用是传引用时,不但将值从源项放到目标项中去,且目标项所映射的寄存器也将被覆盖

传值是一般情况,即汇编指令的Push\Pop指令时,传引用时特殊情况,如寄存器轮换等。

Add与Lea等其他指令的二义性:
这两条指令广义上讲也是Push\Pop的二义性:

为Add指令时,pop指令的含义为传引用

为Lea指令时,pop指令的含义为传值

在这里插入图片描述

(四)识别寄存器的二义性步骤

  1. 根据转换规则尽可能确定一些特性,缩小可能的寄存器范围
  2. 无法判断的寄存器将其加入此项的可能性列表中,并建立起传递链表
  3. 退出虚拟机时可知各项真实的寄存器,排除其他可能的寄存器
  4. 确定寄存器后,再重新排除有二义性的指令

猜你喜欢

转载自blog.csdn.net/CSNN2019/article/details/114048802