返回介绍

第 8 章 数据类型与数据结构

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

要理解二进制程序的行为,首先必须对程序调用的库函数进行分类。调用 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 仅显示它们的类型。

enter image description here

图 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 会向新修改的函数的所有调用方传播这些信息,从而改进对此处显示的所有相关函数调用的附加说明。请注意,在调用函数中,参数名称 fptr 已作为注释(➌)进行传播,并对之前使用哑名的变量(➍)进行了重命名。

.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 技术交流群。

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

发布评论

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