Hike News
Hike News

如何使用IDA创建结构体

IDA 无法直接反编译出程序所构造的结构体,但我们可以通过观察伪代码来判断并在 IDA 中创建结构体

Source Code

源码摘自菜鸟教程的 C 语言教程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

struct Books
{
char title[10];
char author[10];
char subject[20];
int book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};

int main()
{
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
}

Problem

在 IDA 中 f5 反编译后得到的结果:

1
2
3
4
5
6
7
8
9
10
int __cdecl main(int argc, const char **argv, const char **envp)
{
printf(
"title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n",
&book,
(char *)&book + 10,
(char *)&book + 20,
(unsigned int)dword_100001040);
return 0;
}

可以看到 IDA 并不能解析出我们构造的结构体。IDA 之所以在分析阶段无法识别结构体,可能源于两个原因。

  1. 首先,虽然 IDA 了解某个结构体的布局,但它并没有足够的信息,能够判断程序确实使用了结构体。
  2. 其次,程序中的结构体可能是一种 IDA 对其一无所知的非标准结构体。

Solution

关于结构体我们所会使用的主要操作包括添加、删除和编辑结构体。首先打开 Structures(结构体)窗口,使用热键 INSERT 打开 Creat Structure/Union(创建结构体/联合)对话框。

Creat Structure/Union(创建结构体/联合)对话框

为了创建一个新的结构体,必须首先在 Structure name(结构体名称)输入框中指定结构体的名称。前两个复选框用于决定新结构体在结构体窗口中的显示位置,或者是否在窗口中显示新结构体。第三个复选框 Creat union(创建联合),指定你定义的是否为 C 风格联合结构体。结构体的大小是它所包含的字段大小的总和,而联合的大小则等于其中最大字段的大小。Add standard structure(添加标准结构体)按钮用于访问 IDA 当前能够识别的全部结构体数据类型。指定结构体的名称并单击 OK 后,IDA 将在结构体窗口中创建一个空结构体定义。

空结构体

为了给新结构体添加字段,必须利用字段创建命令 D、A 和数字键盘上的星号键(*)。D 命令的行为非常依赖于光标的位置,建议采用下面的步骤给结构体添加字段。

  1. 首先将光标放在结构体定义的最后一行(包含 ends 的那一行)并按下 D 键。这时,IDA 就会在结构体的末尾添加一个新字段。新字段的大小取决于你在数据转盘上选择的第一个大小。最初,字段的名称为 field_N,这里的 N 为结构体开头到新字段(如 field_0)开头的数字偏移量。
  2. 如果需要修改字段的大小,首先将光标放在新字段的名称上,然后重复按下 D 键,使数据转盘上的数据类型开始循环,从而为新字段选择正确的数据大小。另外,你还可以使用 Options ► Setup Data Types 来指定一个在数据转盘上不存在的大小。如果新字段是一个数组,右击其名称并在上下文菜单中选择 Array,将打开“数组规范”对话框。
  3. 要更改一个结构体字段的名称,单击字段名称并按下 N 键,或者右击该名称并在上下文菜单中选择 Name,然后在输入框中输入一个名称即可。

根据以上步骤,可以构造如下结构体。

含有四个变量的结构体

在定义自己的结构体时,下面的提示可能会有所帮助。

  1. 一个字段的字节偏移量以一个 8 位十六进制值在结构体窗口的左侧显示。
  2. 每次你添加或删除一个结构体字段,或更改一个现有字段的大小,结构体的新 sizeof 大小都会在结构体定义的第一行反映出来。
  3. 可以给一个结构体字段添加注释,就像给任何反汇编行添加注释一样。右击(或使用热键)希望为其添加注释的字段,在上下文菜单中选择一个注释选项即可。
  4. 与结构体窗口顶部的说明不同的是,只有当一个字段是结构体中的最后一个字段时,使用 U 键才能删除该字段。对于所有其他字段,按下 U 键将取消该字段的定义,这样做仅仅删除了该字段的名称,并没有删除分配给该字段的字节。
  5. 必须对一个结构体定义中的所有字段进行适当的对齐。IDA 并不区分已压缩和未压缩的结构体。为将字段适当对齐,如需要填补字节,那么必须负责添加这些字节。填补字节最好作为适当大小的哑字段添加。在添加额外的字段后,可以选择取消或保留这些字段的定义。
  6. 分配到结构体中间的字节只有在取消关联字段的定义后才能删除,使用 Edit ► Shrink Struct Type(缩小结构体类型)即可删除被取消定义的字节。
  7. 可以在结构体的中间添加新的字节:选择新字节后面的一个字段,然后使用 Edit ► Expand Struct Type(扩大结构体类型)在选中的字段前插入一定数量的字节。
  8. 如果知道结构体的大小,而不了解它的布局,就需要创建两个字段。第一个字段为一个数组,它的大小为结构体的大小减去 1 个字节(size-1);第二个字段应为 1 个字节。创建第二个字段后,取消第一个(数组)字段的定义。这样,结构体的大小被保留下来,随后,当进一步了解该结构体的布局后,可以回过头来定义它的字段及其大小。

通过重复应用这些步骤(添加字段、设置字段大小、添加填补字节等),就可以在 IDA 中创建一个 Books 结构体。

最终的结构体信息

重新给变量设置数据类型为 Book,得到如下结果。

1
2
3
4
5
int __cdecl main(int argc, const char **argv, const char **envp)
{
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", &book, &book.gap1[9], &book.gapC[8], unk_100001040);
return 0;
}

还有一种方法是直接导入新的结构体。IDA 能够解析 C(而非 C++)数据声明,以及整个 C 头文件,并自动为在这些声明或头文件中定义的结构体创建对应的 IDA 结构体。如果碰巧有正在进行逆向的二进制文件的源代码,或者头文件,那么就可以让 IDA 直接从源代码中提取出相关结构体,从而节省大量时间。

IDA 5.2 版引入了 “本地类型” 子窗口,使用 View ► OpenSubviews ► Local Types(查看 ► 打开子窗口 ► 本地类型)打开该窗口,其中列出了所有解析到当前数据库中的类型。新数据库的“本地类型”窗口最初是空的,但是,该窗口能够通过 INSERT 键或上下文菜单中的 Insert 选项解析新的类型。

直接导入结构体

解析新类型时发生的错误将在 IDA 的消息窗口中显示。如果类型声明被成功解析,“本地类型” 窗口将列出该类型及其相关声明。可以看到先前在 Structures 窗口添加的结构体和刚才直接导入的结构体信息。

结构体信息

重新设置变量的数据类型为 Book2,可以得到一个完美的结果:

1
2
3
4
5
6
7
8
9
10
int __cdecl main(int argc, const char **argv, const char **envp)
{
printf(
"title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n",
&book,
book.author,
book.subject,
(unsigned int)book.book_id);
return 0;
}

References

The IDA Pro Book
https://www.runoob.com/cprogramming/c-structures.html