在程序届有一句名言:如果你能读懂汇编,一切程序对你来说就是开源。所以要抵达黑客层次,不熟练的掌握反汇编分析技巧那是不可能的。本节我们看看一些反汇编的工具和相关技巧,后续我们再看看一些高级方法该怎么用。
常用的反汇编工具一般需要执行三个步骤,1,加载要反汇编的二进制文件;2,从二进制文件中找到所有机器指令;3,将指令转换为汇编语句;通常第2步是一个难点,由于机器指令与通常的二进制数值无异,因此很容易把不是指令的数值认为是机器指令。为了尽可能降低步骤2的错误,反汇编算法常采用两种模式,分别是线性反汇编和递归反汇编。
线性反汇编其实就是从头走到尾,将所有二进制数值都认为是机器指令,然后将其转换为汇编语句,我们常用的Objdum就是如此。这当然会产生问题,很显然不可能所有二进制数值都是机器指令,因此这种做法容易将原本是数据的数值看做是指令。由此会带来两种错误,一种是将数值转换成无效机器指令,一种更糟糕,数值正好对应了某条机器指令,于是给后面的分析带来巨大的干扰,如下图所示:
左边是正确的反汇编,其中前4字节是数据,如果将前4字节看成指令那就好把他们认为是mov, pop这种指令,由于解读错误,接下来又产生了一系列错误指令,例如接下来的add, mov,最可怕的是错误反汇编的add指令其实是将数据和指令混合在了一起,这样的话就严重影响了后续指令的解读。
最右边的指令对应反汇编从字节20开始视作指令,于是把4字节数据中的后4节也就是20,5c,00,55解读成指令and [rax rax*1 0x55], bl。对于恶意程序而言,他们会特意在代码中穿插一些数据,这样就能干扰反汇编工具,使得安全人员很难对其进行准确的分析。
接下来我们看看递归反汇编。它的基本思路是寻找程序的控制流,它首先从main等程序入口着手,然后先是线性反汇编,如果遇到jump等指令,它就会跳到jump对应的地址继续反汇编。这种情况也容易出问题,因为程序的控制流很难追踪,因为很多跳转其实是隐性跳转,也就是这种跳转不会在二进制文件中给出具体地址,需要在运行时才能确认具体地址,如下图所示:
上图中 jmp BB1中的BB1是显性地址,而call eax就是隐性地址,其中[fptr ecx]对应f1的地址,那么此时只有代码运行起来才能知道eax的值,要不然在静态情况下,反汇编工具基本上无法确定eax对应的具体数值,因此它就很难跳到给定地址进行反汇编。
我们看一个反汇编的实例:
左边是一段c语言代码,右边是对应的反汇编指令,左边C语言的逻辑很简单,它遍历一个数组,检测每个元素值,如果它满足一些条件就直接忽略,满足一些条件就返回它对应下标,满足另一些条件就调用fatal函数后退出,然而对应右边反汇编的指令就很难理解其逻辑。右边汇编语言采用一种叫”跳转表“的方法,在地址4438f9处的指令,mov rax, [rax8 0x49e840],其中0x49e840对应表的入口地址,rax8对应表的偏移,读取的数值就是要跳转的地址。在这种情况下,递归式反编译就相当难做,因为你根本不知道 [rax*8 0x49e840]这个地方会出现什么数值,而且数值会根据程序的运行状态不断改变。
由于静态反汇编面临一系列困难,因此我们需要动态反汇编的帮助。它的基本思路是将代码运行起来,在运行中设置断点,然后从暂停处进行反汇编,就像前面我们用过的那样。这种办法也有缺陷,那就是无法做到分支的充分覆盖。显然在linux上,最常用的动态反汇编莫过于使用gdb了,我们看一个例子,启动linux系统,然后执行gdb /bin/ls 也就是动态反汇编ls命令程序,然后执行命令info files,该命令会显示在运行ls程序时有多少文件被打开读取,目前也就只有ls对应可执行文件被加载,
其中entry point表示ls对应可执行文件被加载到内存后的入口,也就是从改地址对应的指令开始执行,于是在0x409a0处设置断点就能让ls程序在一开始执行时就暂停。于是我们执行命令b *0x409a0,然后执行命令set pagination off, set logging on,set loggin redirect on,前者作用是让gdb不要一下子将太多信息输出到控制台,只要输出几行就行,后者是让gdb将运行过程中的信息输出到文件gdb.txt,然后执行命令run,一运行程序就里面暂停住。
暂停时执行指令display/i $pc,它的意思是让gdb将当前要执行的指令输出到gdb.txt,接着输入命令while 1,它的意思是让gdb逐条指令执行,并将执行的指令输入到gdb.txt,直到所有指令执行完毕程序退出为止。过一会使用ctrl c退出,然后执行命令quit退出gdb,此时我们使用命令wc -l gdb.txt可以发现它已经包含几万条内容。使用head -n 20 gdb.txt可以打印出前20条,使用正则表达式对文件内容进行过滤:
代码语言:javascript复制egrep '^=> 0x[0-9a-f] :' gdb.txt | head -n 20
动态反汇编只能让我们解析程序运行经过的指令,如果代码中包含逻辑炸弹,也就是某些恶意指令只能在某个时间点过后才执行,那么动态反汇编就找不出这些恶意代码。而且动态反汇编会让程序运行的速度变慢,因此有些恶意程序甚至会监控它自身运行速度,一旦发现自己执行慢了就会探测到它正在被动态分析。
综合而言,我们必须静态和动态分析结合着用,后面我们还会看到更多高级技巧。