线性扫描和递归遍历


DalvikVM 做字节码验证时用的是线性扫描法

反汇编算法的两种:

  • recursive traversal, 递归遍历

控制流根据一条指令是否被另一条指令引用来决定是否对其进行反汇编


1. 顺序流指令

顺序流指令将执行权传递给紧随其后的下一条指令。顺序流指令的例子包括简单算术指令,如add;寄存器与内存之间的传输指令,如mov;栈操作指令,如push和pop。这些指令的反汇编过程以线性扫描方式进行。

2. 条件分支指令

条件分支指令(如x86 jnz)提供两条可能的执行路径。如果条件为真,则执行分支,并且必须修改指令指针,使其指向分支的目标。但是,如果条件为假,则继续以线性模式执行指令,并使用线性扫描方法反汇编下一条指令。因为不可能在静态环境中确定条件测试的结果,递归下降算法会反汇编上述两条路径。同时,它将分支目标指令的地址添加到稍后才进行反汇编的地址列表中,从而推迟分支目标指令的反汇编过程。

3. 无条件分支指令

无条件分支并不遵循线性流模式,因此,它由递归下降算法以不同的方式处理。与顺序流指令一样,执行权只能传递给一条指令,但那条指令不需要紧接在分支指令后面。事实上,根本没有要求规定在无条件分支后必须紧跟一条指令。因此,也就没有理由反汇编紧跟在无条件分支后面的字节。

递归遍历反汇编器将尝试确定无条件跳转的目标,并将目标地址添加到要反汇编的地址列表中。遗憾的是,某些无条件分支可能会给递归下降反汇编器造成麻烦。如果跳转指令的目标取决于一个运行时值,这时使用静态分析就无法确定跳转目标。x86的jmp eax指令就证实了这个问题。只有程序确实正在运行时,eax寄存器中才会包含一个值。由于寄存器在静态分析过程中不包含任何值,因此无法确定跳转指令的目标,也就无法确定该从什么地方继续反汇编过程。

4. 函数调用指令

函数调用指令的运行方式与无条件跳转指令非常相似(包括无法确定call eax等指令的目标),唯一的不同在于,一旦函数完成,执行权将返回给紧跟在调用指令后面的指令。在这方面,它们与条件分支指令类似,因为它们都生成两条执行路径。调用指令的目标地址被添加到推迟进行反汇编的地址列表中,而紧跟在调用后面的指令则以类似于线性扫描的方式进行反汇编。

从被调用函数返回时,如果程序的运行出现异常,递归下降就有可能失败。例如,函数中的代码可能会有意窜改该函数的返回地址,这样,在函数完成时,控制权将返回到一个反汇编器无法预知的地址。下面的错误代码就是一个简单的例子。在这个例子中,函数foo在返回调用方之前,对返回地址加了1。

foo proc near
FF 04 24 inc dword ptr [esp] ;
increments saved return addr
C3 retn
foo endp
; -------------------------------------
bar:
E8 F7 FF FF FF call foo
05 89 45 F8 90 add eax, 90F84589h
结果,在调用foo之后,控制权实际上并未返回给 处的add指令。正确的反汇编过程如下所示:

foo proc near
FF 04 24 inc dword ptr [esp]
C3 retn
foo endp
; -------------------------------------
bar:
E8 F7 FF FF FF call foo
05 db 5 ;formerly the first
byte of the add instruction
89 45 F8 mov [ebp-8], eax
90 nop
以上代码更清楚地展示了程序的实际流程。实际上,函数foo将控制权返回给了位于 处的mov指令。值得注意的是,线性扫描反汇编器可能也同样无法正确对这段代码反汇编,只是原因稍有不同。

5. 返回指令

有时,递归下降算法访问了所有的路径。而且,函数返回指令(如x86 ret)没有提供接下来将要执行的指令的信息。这时,如果程序确实正在运行,则可以从运行时栈顶部获得一个地址,并从这个地址开始恢复执行指令。但是,反汇编器并不具备访问栈的能力,因此反汇编过程会突然终止。这时,递归下降反汇编器会转而处理前面搁置在一旁的延迟反汇编地址列表。反汇编器从这个列表中取出一个地址,并从这个地址开始继续反汇编过程。递归下降反汇编算法正是因此而得名。

递归下降算法的一个主要优点,在于它具有区分代码与数据的强大能力。作为一种基于控制流的算法,它很少会在反汇编过程中错误地将数据值作为代码处理。递归下降算法的主要缺点,在于它无法处理间接代码路径,如利用指针表来查找目标地址的跳转和调用。然而,通过采用一些用于识别指向代码的指针的启发(heuristics)式方法,递归下降反汇编器能够提供所有代码,并清楚地区分代码与数据。代码清单1-2是对前面代码清单1-1中的switch语句应用递归下降反汇编器所得到的结果。

 

 

  • linear sweep, 线性扫描

线性扫描反汇编算法采用一种非常直接的方法来确定需要反汇编的指令的位置:一条指令结束、另一条指令开始的地方。因此,确定起始位置最为困难。进行反汇编时,可以维护一个指针来标注当前正在反汇编的指令的起始位置。在反汇编过程中,每一条指令的长度都被计算出来,并用来确定下一条将要反编译的指令的位置。为此,对由长度固定的指令构成的指令集(如MIPS)进行反汇编有时会更加容易,因为这时可轻松定位随后的指令。线性扫描算法的主要优点在于:它能够完全覆盖程序的所有代码段。线性扫描方法的一个主要缺点,是它没有考虑到代码中可能混有数据。

posted on 2012-10-11 22:53  Happykiller  阅读(733)  评论(0编辑  收藏  举报

导航