导语:本文的目标是通过示例展示使用Hex-Rays脚本可以提高效率、节约时间,有效解决反编译中的问题。
一、简介
IDA Pro是二进制逆向工程中的标准。它不仅是一个出色的反汇编器和调试器,还可以从Hex-Rays购买额外的许可来扩展更强大的反编译器。然而在反汇编和反编译代码之间切换的能力可以大大缩短分析时间。
反编译器(从现在开始称为Hex-Rays)已经存在了很长时间,并且已经达到了很高的成熟度。但是,好像缺乏关于此主题的简明和完整的资源(教程或其他)。本文的目标是通过示例展示使用Hex-Rays脚本可以提高效率、节约时间。
二、回顾反编译器
为了理解反编译器的工作原理,首先回顾一下正常的编译过程。
编译和反编译以抽象语法树(Abstract Syntax Tree (AST))为中心。本质上,编译器会采用源代码,根据语法将其分解为令牌,然后将这些令牌分组为逻辑表达式。在编译过程阶段,称为解析,代码结构被表示为一个复杂的对象AST。从AST中,编译器将为指定的平台生成汇编代码。
反编译器采取相反的路线。从给定的汇编代码,它返回生成一个AST,并从中生成伪代码。
从代码和汇编之间的所有中间步骤来看,AST非常重要,因为大部分时间都会使用Hex-Rays API,实际上就是正在读取和/或修改抽象语法树(或Hex-Rays中的ctree)。
三、项,表达式和语句
现在我们知道Hex-Rays的ctree是一个树状数据结构。此树的节点是cinsn_t或cexpr_t类型的节点。我们将立即定义这些内容,它们都来自非常基本的类型,即citem_t类型,这一点很重要,如以下代码片段所示:
因此,ctree中的所有节点都具有op属性,表示节点类型(变量,数字,逻辑表达式等)。
op(ctype_t)类型是一个枚举,其中所有常量都被命名为cit_ <xyz>(用于语句)或cot_ <xyz>(用于表达式)。记住这一点,因为这非常重要。检查所有ctype_t常量及其值的快速方法是执行以下代码片段:
产生如下输出:
让我们深入解释两种类型的节点:表达式和语句。
将表达式看作代码的“小逻辑元素”是很有用的。它们从简单类型(如变量,字符串或数字常量)到小代码结构(赋值,比较,加法,逻辑操作,数组索引等)。
这些类型是cexpr_t,它是一个包含多个成员的大型结构。可以访问的成员取决于其op值。例如,获取数值的成员n在处理常量时才有意义。
另一方面,我们有发言权。这些与语言关键字大致相关(if, for, do, while, return等)。它们大多与控制流有关,可以被认为是代码的“大图片元素”。
综上所述,我们已经看到反编译器公开了这个类树结构(ctree),它由两种类型的节点组成:表达式和语句。为了从反编译代码中提取信息或修改反编译代码,我们必须通过依赖于节点类型的方法与ctree节点进行交互。但是,问题出现了:“如何到达节点?”
这是通过Hex-Rays的一个公开类完成的:树访问者(ctree_visitor_t)。这个类有两个虚拟方法,visit_insn和visit_expr,它们在遍历ctree时发现语句或表达式。可以通过继承并重载相应的方法来创建自己的visitor类。
四、示例脚本
在本节中,我们将使用Hex-Rays API来解决两个实际问题:
· 识别对GetProcAddress的调用,动态解析Windows API,将结果地址分配给全局变量。
· 为便于阅读,将与堆栈字符串相关的分配显示为字符而不是数字。
(一)GetProcAddress
我们将通过第一个例子是如何在运行时自动处理动态解析的重命名全局变量。这是恶意软件用于在静态分析工具隐藏其功能的常用技术。图1显示了使用GetProcAddress动态解析全局变量的一个示例。
图1:使用GetProcAddress获取动态API
有几种方法可以重命名全局变量,最简单的方法是手动复制和粘贴。但是,这个任务重复性强,可以使用Hex-Rays API编写脚本。编写任何Hex-Rays脚本,首先一点可视化ctree很重要。Hex-Rays SDK包含一个示例sample5,可用于查看当前函数的ctree。ctree中显示的数据量可能会很大。使用该示例的修改版本来生成图1所示函数的sub-ctree。单个表达式的sub-ctree:'dword_1000B2D8 =(int)GetProcAdress(v0,“CreateThread”);'如图2所示。
图2: GetProcAddress任务的Sub-ctree
在了解使用的sub-ctree的情况下,我们可以编写一个脚本来自动重命名使用此方法分配的所有全局变量。
自动重命名所有局部变量的代码如图3所示。代码通过遍历ctree寻找对GetProcAddress函数的调用。一旦找到,代码将获取正在解析的函数的名称,并找到正在设置的全局变量。然后代码使用IDA MakeName API将地址重命名为正确的函数。
图3: 重命名全局变量的函数
脚本执行完毕后,可以在图4中看到所有的全局变量已经被重命名为相应函数的名称。
图4: 重命名的全局变量
(二)堆栈字符串
接下来的一个例子是处理恶意软件时遇到的一个典型问题:堆栈字符串。这是一种旨在通过在代码中使用字符数组而不是字符串来使分析变得更加困难的技术。在图5中可以看到一个例子,恶意软件将每个字符的ASCII值存储在堆栈中,然后在调用sprintf时引用它。乍一看,很难知道这个字符串的含义是什么(除非你知道ASCII表格)。
图5: Hex-Rays的输出,堆栈字符串很难阅读
我们的脚本会将这些修改为更具可读性的内容。代码的重要部分是前面提到的ctree visitor,如图6所示。
图6: 自定义的ctree visitor
实现的逻辑非常简单。定义一个ctree visitor的子类(第1行)并覆盖它的visit_expr方法,这只会在发现任务时发生(第9行)。要满足的另一个条件是,任务的左侧是一个变量,右侧是一个数字(第15行)。此外,数值必须在可读的ASCII范围内(第20和21行)。
一旦找到这种表达式,我们将右边的类型从数字改为字符串(第26至31行),并用对应的ASCII字符(第32行)替换它的数值。
运行此脚本之后修改的伪代码如图7所示。
图7: 赋值显示成字符
完整的脚本可以在我们的FLARE GitHub存储库decompiler scripts中找到。
五、总结
这两个公认的简单示例应该能够让人了解IDA反编译器API的强大功能。在这篇文章中,我们已经介绍了所有反编译器脚本的基础:ctree对象,一个由表达式和语句表示代码中的每个元素以及它们之间关系组成的结构。通过创建自定义visitor,我们展示了如何遍历树并读取或修改代码元素,从而分析或修改伪代码。