Simplify是一个通用的反混淆工具,其会通过虚拟执行一个app来理解这个app的行为,并对app的代码进行优化,使代码在和原功能一致的情况下更利于理解和阅读。
介绍
Simplify在github上是这样描述自己的,Simplify是一个通用的反混淆工具,其会通过虚拟执行一个app来理解这个app的行为,并对app的代码进行优化,使代码在和原功能一致的情况下更利于理解和阅读。
Simplify提供了两张图来方便快速的展现其功能。第一张是混淆过的代码,第二张是通过Simplify反混淆后的代码。
实现
smalivm
smalivm提供了一个虚拟机来执行Dalvik方法,因为smalivm是运行在java虚拟机下面的,并没有android的运行环境,故而其无法执行和android相关的方法,而只能模拟执行只依赖java本身环境的方法。smalivm无法真正去运行一个app,而是去模拟整个app的运行流程并去尝试真正运行其中可以运行的方法,这可能就是simplify说自己是虚拟执行的原因吧。下面看下smalivm中一些重要的类。
VirtualMachine
smalivm中主体类,用来执行dex中的方法,构建每个方法的ExecutionGraph。ExecutionGraph
ExecutionGraph描述了一个方法中的指令的执行流程。这个执行图中的每个节点都是opcode。ExecutionContext
每条被执行的opcode都会绑定一个ExecutionContext,这个ExecutionContext用来存储指令被执行后类变量、指令所在方法寄存器的值等。
我们以下面的onCreate为例,简单分析下smalivm的执行流程。
1 | .method protected onCreate(Bundle)V |
针对OnCreate方法,smalivm先生成一个ExecutionContext,这个ExecutionContext绑定的是方法的第一条指令,即invoke-super。然后初始化该方法所在的类和该方法,这个初始化过程主要是将类成员和方法使用的寄存器的初始值存入ExecutionContext中的Heap结构中。之后smalivm会初始化方法的调用图,这个图是指方法中指令之间的调用图。初始图的主要工作是对每条指令生成对应的ExecutionNode,ExecutionNode中定义了如何在smalivm下模拟执行指令。完成这一系列的初始化工作后,smalivm便会开始执行图。执行的入口从rootNode开始,即从方法的第一条指令开始。待执行的指令是放在被称作stack的队列(先进先出)中,所以rootNode首先会被放入stack中。smalivm从stack取指令执行,每次执行完stack中的指令,都会将该条指令的下一条或者可能的若干条指令(这些指令会被绑定到新的ExecutionContext)放入到stack中,直到stack为空。simplify定义了一些限制如max-address-visits,max-call-depth等来确保执行不会落入死循环中。smalivm最终返回一个包含所有可能执行路径的图,并且图上的每一个节点包含了执行到当前节点时类成员和方法寄存器的值。
simplify
simplify在smalivm生成的图基础上,对代码进行优化,优化内容包括常量传播、常量折叠、死代码去除等。下面简单地看下几个主要优化策略的功能。
DeadRemovalStrategy(死代码移除)
移除下列指令,- smalivm在虚拟执行时没有执行到的指令,不包括在异常处理中的指令、goto指令和填充用的nop
- 无效赋值指令,给寄存器赋值后却没有只用该值
- 无效函数执行,调用有返回值的函数,但是并没有调用move-result来获取返回值,并且传入到函数中的寄存器(如果有的话)在后面代码中也没有被使用。
- 去除无效的goto,如果goto跳转到的是紧跟其后的指令,那么这个goto就是个无效的goto
- nop指令
ConstantPropagationStrategy(常量传播)
在执行特定指令(如move, sget等)时,如果在所有的执行路径上,这个指令操作的寄存器值都是一致的,那么可以认为其在操作一个常量。例如,0000001C add-int/lit8 v2, v2, 0x01
这条指令,在所有执行路径上,目标寄存器v2的值都是1,故而这条指令可以替换成const/4 v2, 1
。其它优化
simplify还包含其它优化策略,如PeepholeStrategy,这里不再介绍了。
上面smali经过simplify处理后结果为,
1 | .method protected onCreate(Bundle)V |
写在最后
smalivm模拟执行dex,并且可以真实执行一些函数,这有利于提升静态检测的效果。同时,smalivm会模拟执行所有代码可能执行的到分支,可以弥补动态检测中很多分支执行不到的缺点。不过在实际测试的某些样本中,simplify会出现异常或者运行很长时间都不能输出结果的问题。