返回介绍

9.2 IDA 绘图

发布于 2024-10-11 21:05:43 字数 11373 浏览 0 评论 0 收藏 0

因为交叉引用反映的是地址之间的关系,因此,若想要描绘二进制文件的图形,它们自然就成为我们的起点。特定于某些类型的交叉引用,我们可以绘制大量有用的图形,用于分析二进制文件。初学者可以把交叉引用看成是图形中的边(连接各点的线)。根据我们希望生成的图形的类型,节点(图形中的点)可以是各指令、称为 基本块 (basic block)的指令组或者是整个函数。IDA 提供两种独特的绘图功能:利用捆绑绘图应用程序的遗留绘图功能,以及集成的交互式绘图功能。我们将在下面几节中介绍这两种绘图功能。

9.2.1N IDA 外部(第三方)图形

IDA 的外部图形功能采用第三方图形应用程序来显示 IDA 生成的图形文件。IDA 6.1 之前的 Windows 版本自带一个名为 wingraph321 的捆绑图形应用程序。IDA 6.0 的非 Windows 版本可配置默认使用 dotty2 图形查看器。从 IDA 6.1 开始,所有 IDA 版本均自带并可配置使用 qwingraph3 图形查看器,它是 wingraph32 的跨平台 Qt 端口。虽然 Linux 用户仍然可以看到 dotty 配置选项,但默认情况下 IDA 已停用这些选项。通过编辑<IDADIR>/cfg/ida.cfg 中的 GRAPH_VISUALIZER 变量,可配置 IDA 使用该图形查看器。

1. Hex-Rays 在以下地址提供 wingraph32 的源文件: http://www.hex-rays.com/idapro/freefiles/wingraph32_src.zip
2. dotty 是 graphviz 项目提供的一个图形查看工具。
3. Hex-Rays 在以下地址提供 qwingraph 的源文件: http://www.hex-rays.com/idapro/freefiles/qwingraph_src.zip

用户请求外部图形时,IDA 会生成该图形的源文件并将其保存到一个临时文件内,然后启动指定的第三方图形查看器来显示该图形。IDA 支持两种图形规范语言,图形描述语言(GDL)4 和 graphviz5 项目使用的 DOT6 语言。通过编辑<IDADIR>/cfg/ida.cfg 目录中的 GRAPH_FORMAT 变量,可配置 IDA 使用图形规范语言。此变量的法定值为 DOTGDL 。你必须确保你在此处指定的语言与你在 GRAPH_VISUALIZER 中指定的查看器兼容。

4. 有关 GDL 的参考信息,参见 http://www.absint.com/aisee/manual/windows/node58.html
5. 参见 http://www.graphviz.org/
6. 有关 DOT 的参考信息,参见 http://www.graphviz.org/doc/info/lang.html

使用 View ▶Graphs (查看▶图形)子菜单可以生成 5 种类型的图形。可在 IDA 中使用的外部图形包括:

  • 函数流程图;

  • 整个二进制文件的调用图;

  • 目标符号的交叉引用图;

  • 源头符号的交叉引用图;

  • 自定义的交叉引用图。

对于其中的流程图和调用图,IDA 能够生成和保存 GDL(不是 DOT)文件以供 IDA 独立使用。这些选项可以在 File ▶Produce file (文件▶生成文件)子菜单中找到。如果你配置的图形查看器允许你保存当前显示的图形,则你可以保存其他类型的图形的规范文件。使用外部图形存在许多限制。第一条也是最重要的限制是外部图形并非交互式图形。你所选择的外部图形查看器的功能决定了你能够对外部图形所进行的控制(通常仅限于缩放和平移)。

基 本 块

在计算机程序中, 基本块 是一条或数条指令的组合,它拥有唯一一个指向块起始位置的入口点和唯一一个指向块结束位置的退出点。一般来说,除最后一条指令外,基本块中的每条指令都将控制权转交给它后面的“继任”指令。同样,除第一条指令外,基本块中的每条指令都从它“前任”指令那里接收控制权。通常,为判定基本块,应忽略函数调用指令并未将控制权转交到当前函数这一事实,除非已知被调用的函数无法正常返回。基本块在行为方面有一个重要的特点,即一旦基本块中的第一条指令开始执行,块中的其他指令都会执行,直到最后一条指令。这个特点会对程序的运行时检测产生重大影响,因为这时不再需要为程序中的每一条指令设置一个断点,或者逐步执行程序,以记录程序执行的每一条指令。相反,你可以为每个基本块的第一条指令设置断点,当这些断点被触发时,相关块中的每一条指令都被标记为“已执 行”。Pedram Amini 的 PaiMei7 框架中的 Process Stalker 组件就是以这种方式执行的。

7. 参见 http://pedram.redhive.com/code/paimei/

1. 外部流程图

将光标放在一个函数中,选择 View▶Graphs ▶Flow Chart(热键为 F12),IDA 将生成并显示一个外部流程图。这种外部流程图与 IDA 最近引入的集成式反汇编图形视图非常类似。在入门级编程课程中,你不会看到这些流程图。将这些图形叫做控制流图形也许更为恰当,因为它们将一个函数的指令划分成基本块,并使用边来表示块之间的流。

图 9-6 是一个相对简单的函数的部分流程图。如你所见,外部流程图提供的地址信息非常少,这使得我们很难将流程图与其对应的反汇编代码清单关联起来。

enter image description here

图 9-6 外部流程图

从函数的入口点开始,沿着函数中第一条指令的普通和跳转流,即可生成流程图图形。

2. 外部调用图

函数调用图可帮助我们迅速理解程序中函数调用的层次结构。首先为程序中的每个函数创建一个图形节点,然后再根据函数之间的调用交叉引用将函数节点连接起来,即可生成调用图。为一个函数生成调用图的过程可以看做是一个递归下降过程,即遍历该函数调用的所有函数。许多时候,只要发现库函数,就可以停止针对调用树的递归下降过程。因为通过阅读与该库函数有关的文档,就可以轻易得知该库函数的运行方式,而不必尝试对该函数的反汇编版本进行逆向工程。实际上,对动态链接二进制文件而言,你不可能递归下降到它的库函数,因为动态链接二进制文件中并没有这些函数的代码。为静态链接二进制文件生成图形也面临着另一个挑战,因为静态链接二进制文件中包含链接到程序的所有库的代码,这时生成的函数调用图可能非常巨大。

为了讨论函数调用图,我们以下面这个简单的程序为例。基本上,这个程序仅仅创建了一个简单的函数调用层次结构:

#include   

void depth_2_1() {  
   printf("inside depth_2_1\n");  
}  

void depth_2_2() {  
   fprintf(stderr, "inside depth_2_2\n");  
}  

void depth_1() {  
   depth_2_1();  
   depth_2_2();  
   printf("inside depth_1\n");  
}  

int main() {  
   depth_1();  
}

使用 GNU gcc 编译一个动态链接的二进制文件后,可以使用 View▶Graphs ▶Function Calls 要求 IDA 生成一个函数调用图,从而得到与图 9-7 类似的图形。在这个图形中,我们截去了图形的左半部分,以提供更多细节。图中用圈圈住的部分是与 main 函数有关的调用图。

图 9-7 外部函数调用图

细心的读者可能已经留意到,编译器分别用 putsfwrite 替换了 printffprintf ,因为前两个函数在打印静态字符串时更加高效。IDA 利用不同的颜色来表示图形中不同类型的节点,不过你不能以任何方式配置这些颜色。8

8. 为了提高可读性,本章中描述的图形已在 IDA 以外经过编辑,以删除节点颜色。

前面的程序代码相当简单,但为什么它对应的图形要复杂两倍呢?这是因为几乎所有的编译器都会插入包装代码,用于初始化和终止库,并在将控制权转交给 main 函数之前正确配置相关参数。

如果绘制同一个程序的静态链接版本的函数调用图,将得到一幅一团糟的图形,如图 9-8 所示。

图 9-8 静态链接库的函数调用图

图 9-8 显示了外部图形的一种常见行为,即最初它们总是缩小图形的比率,以显示整幅图形,这可能会导致非常杂乱的显示结果。就这幅特殊的图形而言,WinGraph32 窗口底部状态栏上的显示表明,图中共有 946 个节点和 10 125 条边,将 100 182 个位置彼此连接起来。除了证明静态链接库的复杂程度外,这个图形根本毫无用处。没有任何缩放和平移操作能够简化这幅图形,而且只能通过读取每个节点的标签来迅速确定某个函数(如 main )的位置。当你将图形扩大到足以读取每个节点的标签时,这时窗口却只能容纳少数几个节点。

3. 外部交叉引用图

IDA 可以为全局符号(函数或全局变量)生成两种类型的交叉引用图:目标符号交叉引用图[View▶Graphs ▶Xrefs To(交叉引用目标)]和源符号交叉引用图[View▶Graphs ▶Xrefs From(交叉引用源头)]。要生成“交叉引用目标”图形,必须执行递归上升操作,即回溯所有以选定的符号为目标的交叉引用,直到到达一个没有其他符号引用的符号。在分析二进制文件时,你可以使用“交叉引用目标”图形回答下面的问题:要到达这个函数,必须进行哪些函数调用? 图 9-9 即使用了“交叉引用目标”图形来显示到达 puts 函数的路径。

同样,“交叉引用目标”图形还可以帮助你更加直观地显示引用某个全局变量的所有位置,以及到达这些位置所需的函数调用链。交叉引用图形是唯一能够合并数据交叉引用信息的图形。

为了创建“交叉引用源头”图形,需要执行递归下降操作,即跟踪所有以选定的符号为源头的交叉引用。如果符号是一个函数名,则只跟踪以该函数为源头的调用引用,因此,图形中不会显示对全局变量的数据引用。如果符号是一个初始化全局指针变量(表示它确实指向某个项目),则跟踪其对应的数据偏移量交叉引用。如果要以图形表示以一个函数为源头的交叉引用,最好是绘制以该函数为源头的函数调用图,如图 9-10 所示。

enter image description here

图 9-9 “交叉引用目标”图形

enter image description here

图 9-10 “交叉引用源头”图形

可惜,如果一个函数的调用图非常复杂,那么,在以图形表示该函数的交叉引用时,同样会得到极其杂乱的图形。

4. 自定义交叉引用图

自定义交叉引用图在 IDA 中叫做 用户交叉引用图 (user xref chart ),它在生成交叉引用图方面提供了最大的灵活性,以适应用户的需求。除了将以某个符号为目标和以该符号为源头的交叉引用组合到单独一幅图外,自定义交叉引用图还允许你指定最大递归深度,以及生成的图形应包括或排除的符号类型。

使用 View▶Graphs ▶User Xrefs Chart 可打开如图 9-11 所示的“图形定制”对话框。在根据对话框指定的选项生成的图形中,指定地址范围内的每个全局符号均以节点显示。通常,要生成以单独一个符号为源头的交叉引用图,对话框中的起始和结束地址完全相同。如果起始和结束地址不同,则 IDA 会为指定地址范围内的所有非局部符号生成交叉引用图。如果起始地址是数据库中最低的地址,而结束地址是数据库中最高的地址,在这种极端情况下,生成的图形为整个二进制文件的函数调用图。

enter image description here

图 9-11 “用户交叉引用图”对话框

图 9-11 中选择的选项为所有自定义交叉引用图的默认选项。每组选项的作用如下。

  • Starting direction(起始方向) 。这两个选项用于决定是搜索以选定的符号为源头的交叉引用、以选定的符号为目标的交叉引用,还是这两种交叉引用。如果其他选项均使用默认设置,将起始方向限制为“交叉引用目标”(Cross references to )将得到一幅“交叉引用目标”图,而将起始方向限制为“交叉引用源头”(Cross references from )将得到一幅“交叉引用源头”图。

  • Parameters(参数) 。Recursive 选项从选定的符号开始执行递归下降(交叉引用源头)或递归上升(交叉引用目标)。Follow only current direction (仅跟踪当前方向)迫使递归仅朝一个方向执行。换句话说,一旦选中这个选项,如果由节点 A 发现节点 B,则递归下降到 B 将添加其他只有从节点 B 才能到达的节点,而新发现的、引用节点 B 的节点将不会添加到图形中。如果取消选中 Follow only current direction 选项,并同时选择两个起始方向,那么,每个添加到图形中的节点将向目标和源头两个方向递归。

  • Recursion depth(递归深度) 。这个选项设置最大递归深度,可用于限制生成的图形的大小。将这个选项设置为1,递归将达到最深,并生成最大的图形。

  • Ignore (忽略) 。这些选项规定将哪些节点排除在生成的图形之外。这是另一种限制图形大小的方法。具体来说,忽略以库函数为源头的交叉引用将得到静态链接二进制文件中非常简化的图形。这种技巧可确保 IDA 识别尽可能多的库函数。库代码识别将在第 12 章中讨论。

  • Print options(打印选项) 。这些选项控制图形格式化的两方面。Print comments(打印注释)会将任何函数注释包含在一个函数的图形节点中。如果选择 Print recursion dots(打印递归点),递归将超越指定的递归限制,这时,IDA 会显示一个包含省略号的节点,表示可以进一步执行递归。

为我们的示例程序中的 depth_1 函数生成的自定义交叉引用图如图 9-12 所示。这里我们使用的是默认设置,递归深度为 1。

enter image description here

图 9-12 函数 depth_1 的用户交叉引用图

用户生成的交叉引用图是 IDA 中最强大的外部图形。外部流程图已经被 IDA 的集成反汇编图形视图取代,其他外部图形不过是用户生成的交叉引用图的精简版本。

9.2.2 IDA 的集成绘图视图

IDA 在 5.0 版中引入了一项人们期待已久的功能,即与 IDA 紧密集成的交互式反汇编图形视图。如前所述,集成绘图模式提供了另外一种界面,以替代标准的文本式反汇编代码清单。在图形模式中,经过反汇编的函数以类似于外部流程图的控制流图形显示。由于这种模式使用的是面向函数的控制流图形,因此,它一次只能显示一个函数。而且,图形模式不能用于显示函数以外的指令。如果希望一次显示几个函数,或者需要查看不属于某个函数的指令,就必须返回面向文本的反汇编代码清单。

在第 5 章中,我们详细介绍了图形视图的基本操作,这里还需要重申几点。要在文本视图与图形视图之间切换,可以按下空格键,或者右击反汇编窗口,然后在上下文菜单中选择 Text View 或 Graph View 。平移图形的最简单方法是单击图形视图的背景,并朝适当的方向拖动图形。对于较大的图形,使用 Graph Overview (图形概览)窗口进行平移会更加方便。“图形概览”窗口中始终显示有一个虚线矩形框,框中的图形与当前反汇编窗口中显示的内容相对应。你可以随时单击并拖动这个虚线框,以重新定位图形视图。因为“图形概览”窗口中显示了整个图形的缩略版本,使用它平移会更加方便,你不必像在反汇编窗口中平移大型图形那样,频繁释放鼠标按钮并重新确定鼠标的位置。

图形模式和文本模式的反汇编视图的操作方法并没有明显的差异。如你所愿,双击导航仍然有效,导航历史记录同样如此。任何时候,如果你导航到一个不属于函数的某个位置(如全局变量),反汇编窗口将自动切换到文本模式。一旦你再次导航到函数范围内,IDA 将自动返回图形模式。在图形模式中,访问栈变量的方法与在文本模式中使用的方法完全相同,摘要栈视图在显示的函数的根基本块中显示。和在文本模式下一样,双击任何栈变量,即可访问详细栈帧视图。在图形模式中,文本模式中格式化指令操作数的所有选项仍然有效,访问方法也完全相同。

图形模式下用户界面的主要变化与各图形节点有关。图 9-13 是一个简单的图形节点及其相关的标题栏控制按钮。

enter image description here

图 9-13 典型的展开图形视图

从左到右,节点标题栏上的 3 个按钮分别可用于更改节点的背景颜色,分配或更改节点名称,以及访问以该节点为目标的交叉引用列表。更改节点的颜色可作为一种提醒,告诉你自己,你已经分析了这个节点,或者只是将该节点与其他节点区分开,因为其中包含有你特别感兴趣的代码。只要给节点分配了颜色,文本模式下对应的指令也会使用这种颜色作为背景色。要取消颜色的分配,右击节点的标题栏,在上下文菜单中选择 Set node color to default(设置默认节点颜色)即可。

图 9-13 标题栏上中间的那个按钮用于给节点基本块的第一条指令的地址分配名称。基本块通常是跳转指令的目标,许多节点由于是跳转交叉引用的目标,IDA 会始终为它们分配一个哑名。但是,IDA 也可能不给基本块分配名称。以下面这段代码为例:

.text:00401041                ➊ jg      short loc_401053  
.text:00401043                ➋ mov     ecx, [ebp+arg_0]

➊处的指令拥有两个潜在的“继任者”: loc_401053 和➋处的指令。由于它有两个“继任者”, ➊必须终止一个基本块,这使得➋成为一个新的基本块中的第一条指令,即使它并不是跳转目标,IDA 也没有为其分配哑名。

图 9-13 中最右边的按钮用于访问以该节点为目标的交叉引用列表。由于默认情况下,图形视图并不显示交叉引用注释,使用这个按钮可直接访问并导航到任何引用该节点的位置。与前面讨论的交叉引用列表不同,这里生成的节点交叉引用列表中还包含一个指向节点的普通流的条目(类型为^)。这样做是必要的,因为在图形视图中,某个节点的线性“前任者”到底是哪一个节点,并不总是非常明显。如果你希望在图形模式下查看正常的交叉引用注释,可通过 Options▶General 选择 Cross-Reference 选项卡,将 Number of displayed xrefs (显示的交叉引用数量)选项设置为 0 以外的其他值即可。

为降低图形的混乱程度,可以将图形中的节点单独或与其他节点一起进行分组。要为多个节点分组,在按下 CTRL 键的同时,用鼠标单击将要分组的每个节点的标题栏,然后右击任何选定节点的标题栏,在上下文菜单中选择 Group nodes 即可。这时,IDA 会提示你输入一段文本(默认为组中的第一条指令),作为折叠节点的显示文本。将图 9-13 中的节点分组,并将节点文本更改为 collapsed node demo(折叠节点演示)后,得到如图 9-14 所示的节点。

enter image description here

图 9-14 典型的折叠(分组)图形视图

需要注意的是,这时标题栏上出现了另外两个按钮。按从左到右的顺序,这些按钮分别用于打开被分组的节点和编辑节点文本。打开一个节点是指将组中的节点恢复到最初的形式,它不会改变节点现在属于某个组这一事实。打开一个组后,上面提到的两个新按钮将会消失,取而代之的是一个“折叠组”按钮。使用 Collapse Group 按钮,或右击组中任何节点的标题栏并选择 Hide Group,可以再次将打开的组折叠起来。要完全撤销应用于一个或几个节点的分组,你必须右击折叠节点或一个打开的节点的标题栏,并选择 Ungroup Nodes(取消节点分组)。这项操作会打开当前处于折叠状态的组。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文