- 献词
- 致谢
- 前言
- 第一部分 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 交叉引用
20.5 其他调用约定
在第 6 章中,我们讨论了 C 和 C++ 代码常用的许多调用约定。虽然在连接两个已编译模块时,我们必须遵循一个已发布的调用约定,但是在单独一个模块中,函数使用自定义调用约定并不会受到任何限制。高度优化的函数常常使用自定义调用约定,不过,这些函数不能从它们所在的模块之外调用。
下面的代码是一个使用非标准调用约定的函数的前 4 行:
.text:000158AC sub_158AC proc near .text:000158AC ➊ .text:000158AC arg_0 = dword ptr 4 .text:000158AC .text:000158AC push [esp+arg_0] .text:000158B0 ➋ mov edx, [eax+118h] .text:000158B6 push eax .text:000158B7 ➌ movzx ecx, cl .text:000158BA mov cl, [edx+ecx+0A0h]
根据 IDA 的分析,该函数的栈帧中只有一个参数(➊)存在。但是,经过仔细分析,我们发现,这个函数使用了 EAX 寄存器(➋)和 CL 寄存器(➌),但没有进行任何初始化。据此,我们得出的唯一结论是,EAX 和 CL 寄存器应由调用方初始化。因此,我们应把这个函数看成是一个包含 3 个参数的函数,而不是仅包含一个参数的函数。在调用它时,你必须特别小心,以确保它的 3 个参数都处在正确的位置。
通过设置函数的“类型”,IDA 能够指定任何函数的自定义调用约定。通过 Edit ▶ Functions ▶Set function type (编辑▶函数▶设置函数类型)菜单项输入函数的原型并使用 IDA 的_usercall 调用约定,即可做到这一点。用于为上一个示例中的 sub_158AC 设置类型的对话框如图 20-1 所示。
图 20-1 将函数指定为 _usercall
为清楚起见,下面再次提供了函数声明:
int __usercall sub_158AC(struc_1 *, unsigned __int8 index, int)
其中,IDA 关键字 _usercall
用于替代 _cdecl
或 _stdcall
等标准调用约定。要使用 _usercall
,我们需要将保存函数的返回值的寄存器名称附加到函数名称之前(此例中得到 sub_158AC<eax>
),告知 IDA 该寄存器的名称。如果函数不返回值,则可以省略返回寄存器。在参数列表中,还必须在将对应的寄存器名称附加到参数的数据类型之前,为每个基于寄存器的参数提供注释。设置函数的类型后,IDA 将向实施调用的函数传播参数信息,清楚说明函数调用顺序,如下面的代码所示:
.text:00014B9F ➊ lea eax, [ebp+var_218] ; struc_1 * .text:00014BA5 ➋ mov cl, 1 ; index .text:00014BA7 ➌ push edx ; int .text:00014BA8 call sub_158AC
从上述代码中可以明显看出,IDA 识别出:EAX 将保存函数的第一个参数(➊),CL 将保存第二个参数(➋),第三个参数将位于栈上(➌)。
即使在一个可执行文件中,调用约定也可能截然不同。为说明这一点,我们提供了另一个取自同一个二进制文件的、使用自定义调用约定的示例,如下所示:
.text:0001669E sub_1669E proc near .text:0001669E ➊ .text:0001669E arg_0 = byte ptr 4 .text:0001669E .text:0001669E ➋ mov eax, [esi+18h] .text:000166A1 add eax, 684h .text:000166A6 cmp [esp+arg_0], 0
在这个例子中,IDA 同样指出,函数仅访问了栈帧中的一个参数(➊)。仔细分析代码后会发现,在调用这个函数之前,还应对 ESI 寄存器(➋)进行初始化。这个例子说明,即使对同一个二进制文件而言,保存与寄存器有关的参数的寄存器也可能因函数而异。
通过这些例子,我们得到的教训是:你必须了解一个函数如何初始化它所使用的每一个寄存器。如果一个函数在初始化一个寄存器之前使用了该寄存器,那么这个寄存器是用来传递一个参数的。请参阅第 6 章,了解各种编译器和调用约定使用哪些寄存器。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论