上一篇博文我介绍了Windbg使用的线性扫描(linear sweep)反汇编算法。本文我将介绍IDA使用的递归下降(recursive descent)反汇编算法。(转载请指明来源于breaksoftware的csdn博客)

递归(recursive)可能大家都很清楚,说白了就是自己调用自己。那么什么是recursive descent呢?似乎很难理解。recursive还是有循环和回归的意思,那么recursive descent就可以理解为“不停减少的循环”和“不停减少的回归”。或许这么说还是不是很好理解,那我们来研究下这个算法的思路的来源,这样可以容易理解这个算法的精髓。

回顾《反汇编算法介绍和应用——线性扫描算法分析》,我们知道线性扫描一个很大的缺点是:因为其不知道程序执行流而导致将数据识别为代码。我们可能会骂这个算法不智能,那么如何才能智能起来呢?想想我们的二进制文件在系统中正常运行时是不会出错的,因为CPU总是可以找到真正的指令起始地址,那么我们反汇编算法只要能模拟CPU执行指令就可以得到正确的反汇编结果了。OK!没错,递归下降算法一个主要的思路就是源于这样的思考结果。但是我们反汇编是静态的,而CPU执行指令是动态的,静态分析无法得知动态执行的结果,这个严重的缺陷会导致我们想完全模拟CPU执行去反汇编的思路变得不现实。但是不要退却,没有完美的方案,只有最可以接受的方案。那我们开始研究下怎么修改我们的思路,让我们的算法变得“最令人可以接受”。

研究修改的方法之前我们要了解CPU执行指令“顺序”的一些基础知识,知己知彼百战百胜。

A 顺序流指令

熟悉汇编的朋友,应该对add、sub、mov、push和pop等指令很熟悉,这类指令执行后,会执行与其地址紧接着的下一条指令。CPU识别这类指令如线性扫描一般简单,那么我们的递归下降算法也就如线性扫描方式去识别这样的指令就行了。

B 无条件跳转指令

jmp是无条件跳转指令。CPU执行这条指令后会跳转到jmp指令参数所指向的地址。这个操作对CPU来说,和顺序流指令没什么区别,只是将EIP改成要跳转的地址。但是这个动态的过程却害惨了静态分析的线性扫描算法,那我们递归下降算法要吸取教训:我们从jmp到的地址开始分析下一条指令。貌似这个想法天衣无缝,但是现实往往是残酷的。请问你一定能得到jmp的地址么?对于jmp 00401010这类的指令我们当然可以得到下条指令的地址,即0x00401010。那么jmp eax呢?eax是多少?CPU知道,我们不知道。这个缺陷我们Mark下。

C 有条件跳转指令

ja和je等是有条件跳转指令,即符合某些要求后才执行跳转,不符合要求则执行其紧接着的那条指令。这些指令的执行顺序如同A、B两种指令的灵魂附体。即条件为真,则走A流程分支;条件为假,则走B流程分支。这么一拆解,我想递归下降算法怎么去分析有条件跳转指令就清楚了。

但是有个问题需要说下,CPU执行这类指令时是知道要走A流程分支还是要走B流程分支的,它不会同时一起走这两条流程。而且可能整个程序运行完了,这个指令的一个分支还没走过(比如if(1){}else{},else永远进不去的)。而我们的递归下降算法是要分析出所有分支的!

那怎么办呢?那我们就将A和B分支的地址中的某一个优先分析,另一个延后分析。可是手心手背都是肉,我们如何取舍?这个时候,我们就要学习国羽和国乒的做法——不惜“让球”,也要选择出最有利于目前流程顺利进行的方法。那么A、B这两个孩子谁有缺陷呢?如上所述,A流程分支没缺陷,而B流程分支存在一定的隐患。那我们就将要执行跳转的B流程分支保存到一个延后分析的列表中。

最后说一句:C有B的灵魂,C有B的缺陷。

D 函数调用指令

call指令是函数调用指令,但是目前,我们可以将其看成B流程。或许有人会说call指令怎么会和jmp混为一谈呢?我们看一个call例子

0x0040177f call 0040209C
0x00401785 mov ecx,eax

其执行等效于

push 00401785 // call指令结束的位置,注意该位置不一定是call完后下条指令开始的位置
jmp 0040177F // 跳转到函数地址

可能有人疑惑为什么push进入堆栈的00401785不一定是call完后下条指令的位置?比如 我们在0040209C的代码如下

pop eax
jmp 00401788

那么,我们程序执行完将会进入00401788,从而过掉了00401785开始的指令。

是不是可以将call简单的看成jmp呢?是吧。

最后说一句:D也有B的缺陷。

E 函数返回指令

ret和retn等是函数返回指令,同call一样,我们可以将其看成是B流程分支。为什么这么说呢?我们接着以D中的例子为例。假如0x0040209C的代码最后是ret,则该ret等效于

pop EIP 

因为EIP是下条指令的起始地址,则这步操作可以看成

jmp EIP // 当然不能这么写,这儿只是为了说明这是个跳转的过程

这是动态执行的流程,但是我们是静态分析,怎么知道EIP是啥呢?是的,一般情况下,我们无法知道。那么这个时候,该次递归流程就走完了,我们将会去C流程中产生的延时反汇编队列中取出地址来开始再次的递归操作……这就是递归下降算法名称的由来。

是否还记得我们在B中说的那个场景?如果我们jmp eax了而不知eax是啥时,或者call、ret不知跳转地址时,本次递归下降都会结束,并在延时反汇编列表中寻找新的起始反汇编地址。

貌似我们的递归反汇编思路都讲完了。但是还存在很大的缺陷!为什么?还记得我在《反汇编算法介绍和应用——线性扫描算法分析》所说的递归下降算法缺陷么?它可能无法覆盖全部代码。我们举个例子

0x0040177f call 0040209C
0x00401785 mov ecx,eax
.
.
.
0x0040209C ret

如果依我们之上的流程,那么0x00401785将可能分析不到,因为我们将call看成了jmp,我们该分支分析将在0x0040209C处结束,而0x00401785没出现在延迟反汇编队列中。想想,这是多么可怕!于是比较严谨的将call看成jmp将要做必要修改。

D 函数调用指令(修正后)

我们将call看成C流程,即有条件跳转。那么如上那段汇编,我们将产生两个分支:一个是00401785,一个是0040209C。虽然我们将00401785看成一个分支是非常不严谨的(因为下条指令完全由0040209C里的逻辑决定的),但是为了能尽量多的反汇编出代码,我们还是要做这个妥协!因为这个妥协,也将导致递归下降算法产生一个致命的缺陷——将call指令后数据当成指令去反汇编。

这儿有个小细节需要注意,对于Call指令,我们会将跳转分支地址优先分析,紧跟着call指令的分支延迟分析。因为存在一种可能,即跳转分支中或许可以确定返回的地址。如果返回地址和紧跟着call指令的分支地址相同,则照旧进行;如果不相同,则以返回地址为准。举个例子

void TestFun(void* lpfun)
{__asm{mov esp,ebppop ebppop eaxret}
}int _tmain(int argc, _TCHAR* argv[])
{__asm{push xxxcall TestFun_emit 0xE8
xxx:}return 0;
}

在TestFun中,我最后抛出返回地址到eax中。这样堆栈顶部就是lpfun,ret后,EIP变成xxx处地址,并将执行到xxx处,而不是紧跟在call后面的0xE8。我们递归下降算法,优先分析TestFun地址的指令,然后可以通过一些判断,判断出最后返回的地址是我们传入的数据,那么我们传入的数据就是正确的下条指令地址,而0xE8处只是个数据。IDA的反汇编结果是


        想想,如果我们将紧跟call指令的分支优先分析,将会出现将0xE8当成call来解析的情况。那么或许之后得靠跳转分支的分析结果再来纠正,这样还不如优先反汇编跳转分支。

说了这么多,再说说上面所说的如何利用call指令分析的缺陷。通过以上例子,我们发现,如果让递归下降算法不知道其call后跳转分支的返回地址,然后在紧跟call指令的位置插入一些废信息,那就造成IDA分析失败了。看例子

void Fun( void* p )
{__asm{add p,3push ppop eaxmov esp,ebppop ebppush eaxret 4}
}int _tmain(int argc, _TCHAR* argv[])
{int i = 0;__asm {push yyycall Fun_emit 0xE8yyy:_emit 0xE8mov eax,ebp}return 0;
}

我在Fun函数一开始处将地址指向了return 0,然后将这个指针通过push和pop放入eax,让push eax到栈顶,从而在ret时让程序从return 0;开始执行。那么IDA反汇编结果呢?

看!已经错了,当然windbg也是分析错的。

到此,关于反汇编算法的两篇博文写完了。仅供大家参考。

附上测试的工程

反汇编算法介绍和应用——递归下降算法分析相关推荐

  1. 反汇编算法介绍和应用——线性扫描算法分析

    做过逆向的朋友应该会很熟悉IDA和Windbg这类的软件.IDA的强项在于静态反汇编,Windbg的强项在于动态调试.往往将这两款软件结合使用会达到事半功倍的效果.可能经常玩这个的朋友会发现IDA反汇 ...

  2. (转)反汇编算法介绍和应用——线性扫描算法分析

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/breaksoftware/articl ...

  3. 递归下降分析法的基本思想。_语法分析 | 递归下降分析算法

    递归下降分析算法是自顶向下分析算法的一部分. 递归下降分析算法 也称为预测分析 分析高效(线性时间) 容易实现(方便手工编码) 错误定位和诊断信息准确 被很多开源和商业的编译器所采用 GCC 4.0, ...

  4. 「BUAA OO Unit 1 HW4」第一单元总结 —— 递归下降思想处理表达式

    「BUAA OO Unit 1 HW4」第一单元总结 目录 Part0 前言 0.1 文章简介 0.2 阅读本文后将有的收获 0.3 阅读本文需要的前置知识 Part1 第一次作业 1.1 程序框架 ...

  5. 2020-11-21(线性扫描反汇编算法和递归下降扫描反汇编算法)

    push ebp jmp addr1 db 0xE8 addr1: mov abp,esp sub esp,0x100 根据线性扫描反汇编算法,当反汇编器解析完jmp addr1指令后,会接着从下一个 ...

  6. 0.IDA-基本的反汇编算法

    1.线性扫描 原理: 一条指令结束,另一条指令开始 关键: 确定起始位置  流程: 从起始,逐条反汇编指令,直到完成整个代码段 优点: 可覆盖程序的所有代码段 缺陷: 如果代码段中混有数据! gdb ...

  7. 《Python Cookbook 3rd》笔记(2.19):实现一个简单的递归下降分析器

    实现一个简单的递归下降分析器 问题 你想根据一组语法规则解析文本并执行命令,或者构造一个代表输入的抽象语法树.如果语法非常简单,你可以自己写这个解析器,而不是使用一些框架. 解法 在这个问题中,我们集 ...

  8. 0.IDA的反汇编算法方式

    1.线性扫描 原理: 一条指令结束,另一条指令开始 关键: 确定起始位置 流程: 从起始,逐条反汇编指令,直到完成整个代码段 优点:        可覆盖程序的所有代码段 缺陷:        如果代 ...

  9. C++:编译实验之递归下降分析器

    一.实验目的 1.加深对递归下降分析法一种自顶向下的语法分析方法的理解. 2.根据文法的产生式规则消除左递归,提取公共左因子构造出相应的递归下降分析器. 二.实验内容 根据课堂讲授的形式化算法,编制程 ...

最新文章

  1. ImportError: cannot import name ‘secure_filename‘的解决办法
  2. qt 搜索隐藏文件_MacOS如何搜索隐藏文件?MacOS搜索隐藏文件的方法
  3. python 导入模块中的命令并且将命令更名
  4. 我的WCF之旅 (11): 再谈WCF的双向通讯-基于Http的双向通讯 V.S. 基于TCP的双向通讯...
  5. 促进新一代人工智能产业发展三年行动计划_浙江提出冷链物流创新发展三年行动计划 力争带动产业投资约1000亿元...
  6. 11、oracle数据库下的事务和触发器
  7. md /mdd /ml /mt/mtd
  8. 閱讀10大優點:為什麼你應該每天閱讀
  9. ActivityManager kill reason
  10. 《从入门到精通云服务器》-4
  11. Nerv --- React IE8 兼容方案
  12. Oracle系列:Oracle RAC集群体系结构
  13. 20以内的素数编码C语言,用C语言 输出100以内的素数,
  14. ODrive踩坑(四)AS5047P-SPI绝对值磁编码器,不需每次上电校准无刷电机,直接上电可用
  15. 自己java_一些自己用的java类
  16. JXTA Kitchen
  17. Animation动画播放
  18. 阿里云服务器价格,最新收费标准报价及活动价格表
  19. 资源-Windows10-2020原版镜像下载地址(20H2)以及1809、1803、1709
  20. 关于微信开放平台授权事件接收Url的配置以及参数接收

热门文章

  1. CV算法复现(分类算法4/6):GoogLeNet(2014年 谷歌)
  2. Corner Proposal Network 论文阅读(2020ECCV)
  3. 深度学习--TensorFlow(项目)识别自己的手写数字(基于CNN卷积神经网络)
  4. C语言实现bmp图像对比度扩展
  5. 211大学计算机系分数,这四所211大学,分数线超过不少985,分数低的还真不敢轻易报考...
  6. 属性匹配工具_测试工具链——高效构建Mock服务
  7. const int * 、int * const、int const* 、const int a(){ } 和int a()const { }的区别和联系
  8. 强化学习(五) - 时序差分学习(Temporal-Difference Learning)及其实例----Sarsa算法, Q学习, 期望Sarsa算法
  9. 在Ubuntu 16.04.3 LTS上安装Go 1.10
  10. 在CentOS 6.9 x86_64的nginx 1.12.2上开启标准模块ngx_http_auth_request_module实录