现在的位置: 首页 > 综合 > 正文

程序分析的方法

2019年05月02日 ⁄ 综合 ⁄ 共 3330字 ⁄ 字号 评论关闭

1.3 程序分析的方法(1)

对程序的攻击一般分为两个步骤:分析阶段--从程序中收集相关信息,转换阶段--根据收集到的信息对程序进行修改。而分析程序则有两种基本的方法:只是分析程序代码本身(静态分析)和运行程序搜集信息(动态分析)。

静态分析只要一个输入--程序自身P,如图1-8所示。

 
图 1-8

在过去的这些年里,人们已经开发了很多程序静态分析方法。它们大多是由软件工程研究人员为了减少程序中可能出现的漏洞,或者编译器设计者为了优化程序而研发的,但也有一些破解高手为了去除程序中的软件保护代码而自己开发了一些程序静态分析方法。静态分析收集到的信息是保守的。说它是"保守的"是因为:只有那些确定一定以及肯定的信息才会被收集到,即使只有略微一点点疑问的信息都不会被静态分析工具收集。例如,如果一个静态分析工具告诉你"在程序执行到第45行时,变量x的值一定是42",那你就可以确定这一事实必定会发生 。有时,使用这种保守方法收集到的信息可能会让你错过一些实际上会确实发生的事实
。但是它至少不会欺骗,不会告诉你一个真实的谎言。

动态分析是通过运行程序,并给程序输入一定的数据的方式来收集信息的,如图1-9所示。

 
图 1-9

而动态分析收集到的信息的精确性则取决于输入数据的完备性。所以对由动态分析收集到的信息只能这么说:"在第45行,变量x的值貌似一直是42啊!好吧,至少对已经试过的数据来说是这样"。只有根据使用静态分析获得的信息进行的代码转换才是安全的,我们可不想把程序改得到处是漏洞(当然在这里我们必须要假设所进行的代码转换是保持语义不变的,即完全不改变程序的原意)。换言之,根据动态分析所收集到的信息进行的代码转换是不安全的--如果输入的数据集不完备,那么所做的修改很可能就把程序给改错了。

攻击者是根据自己的意图选用不同的代码分析与转换方法的。比如,如果他只想破解某个软件,那他只需要一些最基本的静态和动态分析工具就够了--他可以在调试器里运行要被破解的程序,直到弹出一个"本程序尚未注册"的对话框来,接着他只要找到程序中弹出这一对话框的代码所在的位置,反汇编它,从中找出if today's date > license date then...这类语句,再用一个编辑器把相关的代码删掉即可。但他如果是想搞清楚一个大型程序中所使用的某个算法的话,那一个能把可执行文件完全反编译回源码状态的反编译器对他就会比较有用。

逆向工程的例子

为了更形象地说明这一问题,我们来看下面这个逆向工程的例子。假设你的老板给了下列的这些字符串,要求你对它进行分析。

 

老板同时还告诉你,这些十六进制码是从我们竞争对手的Java程序中"抠"出来的,其中包含有一个很重要的秘密算法。这些十六进制码代表的实际上就是下面这个程序,当然作为一个逆向工程师你现在还不知道这一点。

 

既然任务就是搞清楚这个"天大的"秘密,所以你最好是把这些字节码转换成清晰易读的Java源代码。而且,如果在这些代码中使用了一些用来愚弄你的代码混淆手段或者其他什么抗逆向分析技术,你最好捎带着把它们也一起干掉。

首先,应该反汇编这些十六进制码,即把这堆难看的字节串转换成带符号的汇编代码。对于Java字节码来说,有很多工具(比如jasmin或者javap)能帮你完成这一任务(如下所示),所以你可以很轻松地迈出这一步。

 



我把Java源码和反汇编结果中对应的部分一一标上了同样深浅的阴影和虚线框,方便你把它们对照起来。在反汇编结果里,每一行中一对方括号括起来的部分是汇编指令对应的字节码。

Java字节码的设计使我们反汇编它很容易。因为在Java字节码中包含了足够多的信息,能让我们能轻松恢复出各个变量的数据类型以及函数的控制流。但对于x86或者其他处理器的机器码来说,我们的日子就要难过许多。因为在这些机器码中很容易被插入一些用来愚弄反汇编器的代码或数据。在第3章中将详细讨论这一点。

1.3 程序分析的方法(2)

把字节码变成汇编代码之后就要进行控制流分析了。我们会在这一步恢复代码中各条指令执行顺序,结果就得到了控制流图(Control Flow Graph,CFG)。图中的各个结点都是由一些顺序执行的指令组成的,多数结点的最后一条指令是一个跳转指令。而如果程序在执行时可能从这个结点跳转到另一个结点执行,那么这两个结点之间就有一条边 。如图1-10所示。

 
图 1-10

控制流图中的结点被称为基本块。在许多编译器或者逆向工程工具中,控制流图是必须构造的核心数据结构 。在控制流图(图1-10)上我也标上了阴影和虚线框,方便你把控制流图和Java源码、反汇编结果对应起来。

接下来,我们就可以使用各种分析方法从代码中收集所需要的信息了。使用这些信息可以进一步简化代码,使之更易于理解。它们甚至也有助于去掉代码中可能使用的反逆向分析技术。一种被广泛使用在编译器和逆向分析工具中的常用方法叫做数据流分析,将在3.1.2节中详细介绍。在这里,我们只是对变量x进行一次常量传播分析 。分析结果将告诉你,在代码的一些位置上某个变量的值是个常量。如图1-11所示。

图1-11中,左边那幅表示分析的初始状态--假定初始时,我们并不知道变量x在各个基本块的入口点和出口点上的值。然后,我们分别分析各个基本块,以获取一些信息。比如说,如果某个基本块中执行了语句x=3,那么显然在这个基本块的出口点x的值就一定是3 ,由此我们就得到了中间的那幅图。此外,如果某个基本块中不包含对变量x的赋值语句,那么变量x在这个基本块的出口点上的值就应该是它在这个基本块的入口点上的值。如果控制流从一个基本块出来之后流向了两个不同的基本块,我们也可以说在这条边上x的值是一样的。而且,我们也可以更进一步地肯定:在那两个基本块的入口点上,x的值是一样的。在逐步地考虑上述因素之后,控制流图就变成了图1-12中左图这个样子。

 
图 1-11
 
图 1-12

现在可以对这张图进行转换了。首先我们已经确定变量x是一个常量3,所以不管x在哪里被使用,我们都可以用常量3来代替它。因此表达式x<=3就一定为真,这个结果使我们可以执行一次"消除死代码"的操作,即把那些不可能被执行到的基本块从控制流图中删掉。此外还可以删除冗余语句。结果,控制流图就被转换成了右边那张图。

请看!是不是比原先那张干净多了?更夸张的是,这张图还可以继续简化下去!我们注意到图1-12中的那个循环实际上就等价于赋值语句i=4,而且这个函数也没有返回值,所以我们甚至可以干脆把整个函数都删掉!在这个例子中,完成这么一次分析似乎还是挺容易的。但是实际情况会比这复杂得多。分析结果在很大程度上是由分析强度和程序复杂度共同决定的。

但目前为止还不能确定的是,刚才消除掉的这段代码究竟是代码混淆器为了增加程序混乱程度而插入的程序,还是仅仅由一个优化性能不怎么样的编译器生成的。唯一可以确定的是,你已经把老板交给你的一堆十六进制数转换成比较简单且结构化的形式,而且其中不相关的部分也已经被去掉了。

逆向工程的最后一步是要把控制流图反编译成源代码形式。如果控制流图的结构很清晰,这个过程对你来说简直就是举手之劳。

 

显然,源码也可以写成其他的形式,比如可以用一个for循环来代替while语句。

我们已经把原始的字节码转换成大家都能读懂的源代码了,这一步对于逆向工程师来说是很有用的。但是故事还没结束(不出你所料)。接下来有人可能要去深入分析这个源码所表达的算法,也有人可能要去修改源码以跳过程序注册验证步骤……而这些则通常都是手工完成的。

在第2章中,我们将向你揭示逆向工程师是如何找出软件中的秘密,或者让程序做他想做的事。在第3章中,我们将更加深入地介绍和讨论破解者们使用的各种工具及分析技巧。我们不光介绍一些市面上能买到的现成工具,还会教你如何使用现有技术定制自己的工具。这样的讲法太简单了:"现有的反编译器只能对付那些静态链接的代码,而我们已经使用x86的动态链接实现软件保护技术了,所以我们的软件保护方法是安全的。"而更有意思的表述可能是这样的:"无论是过去还是将来,使用我们的保护技术保护的软件,通过反编译器反编译生成的代码量会以几何级数增长"。

【上篇】
【下篇】

抱歉!评论已关闭.