- 献词
- 致谢
- 前言
- 第一部分 IDA 简介
- 第 1 章 反汇编简介
- 第 2 章 逆向与反汇编工具
- 第 3 章 IDA Pro 背景知识
- 第二部分 IDA 基本用法
- 第 4 章 IDA 入门
- 第 5 章 IDA 数据显示窗口
- 第 6 章 反汇编导航
- 第 7 章 反汇编操作
- 第 8 章 数据类型与数据结构
- 第 9 章 交叉引用与绘图功能
- 第 10 章 IDA 的多种面孔
- 第三部分 IDA 高级应用
- 第 11 章 定制 IDA
- 第 12 章 使用 FLIRT 签名来识别库
- 第 13 章 扩展 IDA 的知识
- 第 14 章 修补二进制文件及其他 IDA 限制
- 第四部分 扩展 IDA 的功能
- 第 15 章 编写 IDA 脚本
- 第 16 章 IDA 软件开发工具包
- 第 17 章 IDA 插件体系结构
- 第 18 章 二进制文件与 IDA 加载器模块
- 第 19 章 IDA 处理器模块
- 第五部分 实际应用
- 第 20 章 编译器变体
- 第 21 章 模糊代码分析
- 第 22 章 漏洞分析
- 第 23 章 实用 IDA 插件
- 第六部分 IDA 调试器
- 第 24 章 IDA 调试器
- 第 25 章 反汇编器/ 调试器集成
- 第 26 章 其他调试功能
- 附录 A 使用 IDA 免费版本 5.0
- 附录 B IDC/SDK 交叉引用
第 8 章 数据类型与数据结构
要理解二进制程序的行为,首先必须对程序调用的库函数进行分类。调用 connect
函数的 C 程序要创建网络连接,调用 RegOpenKey
的 Windows 程序要访问 Windows 注册表。但是,要了解如何及为何调用这些函数,还需要进行其他一些分析。
了解如何调用函数,首先需要知道给该函数传递哪些参数。以 connect
函数调用为例,除了该函数被调用这一事实外,更为重要的是,还需要了解程序连接到的具体地址。要逆向工程一个函数的签名(该函数所需参数的数量、类型和顺序),了解传递给该函数的数据尤为关键,这也说明了理解汇编语言如何操纵数据类型和数据结构的重要性。
在本章中,我们将讨论 IDA 如何向用户传递数据信息,数据结构如何存储在内存中,以及如何访问这些数据结构中的数据。将特定数据类型与变量关联起来的最简单方法是,理解该变量作为某个函数(我们对该函数有一定了解)的参数时的用法。如果一个变量作为 IDA 拥有原型的函数的参数,在分析阶段,IDA 会尽其所能推断出该变量的数据类型。如有可能,IDA 会对该变量使用一个从函数原型中提取出的正式名称,而不是为其生成默认的哑名。下面调用 connect
函数的反汇编代码即说明了这一点:
.text:004010F3 push 10h ; namelen .text:004010F5 lea ecx, ➊ [ebp+name] .text:004010F8 push ecx ; name .text:004010F9 mov edx, ➊ [ebp+s] .text:004010FF push edx ; s .text:00401100 call connect
我们看到,每个 push
指令都用被压入的参数的名称(根据 IDA 对函数原型的了解)进行了注释。此外,➊处的两个局部栈变量已经用它们对应的参数进行了命名。多数情况下,与 IDA 生成的哑名相比,这些名称能够提供更多信息。
IDA 传播来自函数原型的类型信息的能力并不仅限于 IDA 类型库中包含的库函数。只要你明确设置函数的类型信息,IDA 就可以传播你的数据库中任何函数的正式参数名称和数据类型。在初始分析阶段,如果通过类型传播没有得出其他结论,IDA 会向所有函数参数分配哑名和一般类型 int
。任何时候,你必须使用 Edit ▶Functions ▶Set Function Type 命令,或者在函数名称上右击鼠标并在上下文菜单中选择 Set Function Type(或使用热键 Y)来设置函数的类型。对于如下所示的函数,上述操作将生成如图 8-1 所示的对话框,你可以在其中输入正确的函数原型。
.text:00401050 ; ======== S U B R O U T I N E ========================= .text:00401050 .text:00401050 ; Attributes: bp-based frame .text:00401050 .text:00401050 foo proc near ; CODE XREF: demo_stackframe+2A ↓ p .text:00401050 .text:00401050 arg_0 = dword ptr 8 .text:00401050 arg_4 = dword ptr 0Ch .text:00401050 .text:00401050 push ebp .text:00401051 mov ebp, esp
如下所示,IDA 假定了 int
返回类型,根据所使用的 ret
指令类型正确推断出该函数采用了 cdecl
调用约定,并将其结合到函数名称中(如我们修改的那样),同时假定所有参数均为`int 类型。由于我们尚未修改参数名称,IDA 仅显示它们的类型。
图 8-1 设置函数的类型
如果我们将该原型修改为 int __cdecl foo(float f, char * ptr)
,IDA 将自动为该函数插入原型注释(➊)并在反汇编列表中更改参数名称(➊),如下所示:
.text:00401050 ; ======== S U B R O U T I N E ========================= .text:00401050 .text:00401050 ; Attributes: bp-based frame .text:00401050 .text:00401050 ➊ ; int __cdecl foo(float f, char *ptr) .text:00401050 foo proc near ; CODE XREF: demo_stackframe+2A ↓ p .text:00401050 .text:00401050 ➋ f = dword ptr 8 .text:00401050 ➋ ptr = dword ptr 0Ch .text:00401050 .text:00401050 push ebp .text:00401051 mov ebp, esp
最后,IDA 会向新修改的函数的所有调用方传播这些信息,从而改进对此处显示的所有相关函数调用的附加说明。请注意,在调用函数中,参数名称 f
和 ptr
已作为注释(➌)进行传播,并对之前使用哑名的变量(➍)进行了重命名。
.text:004010AD mov eax, [ebp+ ➍ ptr] .text:004010B0 mov [esp+4], eax ➌ ; ptr .text:004010B4 mov eax, [ebp+ ➍ f] .text:004010B7 mov [esp], eax ➌ ; f .text:004010BA call foo
返回到导入的库函数,IDA 通常已经知道被调用函数的原型了。在这种情况下,将光标放在函数名称上,你就可以轻易查看该函数的原型。1 如果 IDA 不知道该函数需要哪些参数,此时你至少需要知道导入函数的库的名称(见 Imports 窗口)。如果发生这种情况,你最好通过相关手册或其他可用的 API 文档(如 MSDN 在线文档2 )了解该函数的行为。如果所有其他资源都无法提供帮助,你还可以搜索 Google 。
1. 将光标定位在 IDA 窗口中的任何名称上,IDA 将显示一个类似于工具提示条的弹出窗口,其中包含目标位置多达 10 行的反汇编代码。如果该名称为库函数名,窗口中通常包含用于调用该库函数的原型。
2. 参见 http://msdn.microsoft.com/library/ 。
在本章的剩余部分,我们将讨论如何确定程序何时使用数据结构,如何解析这些结构的组织布局,以及如何利用 IDA 来提高包含这类结构的反汇编代码清单的可读性。由于 C++ 类是 C 结构体的一种复杂扩展,在本章末尾,我们将讨论如何逆向工程已编译的 C++ 程序。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论