在正向编译过程中,编译器会删除大量辅助信息,例如类型信息。恢复类型信息可以让 IDA 生成更加接近源码的伪代码,有利于减轻工作量。
函数修复
函数修复主要看三点:返回类型、调用方式和参数列表。
返回类型修复,主要判断函数的返回类型是否有意义。这里的 “有意义” 指的是返回值被引用,或者有表达式使用了返回值。
比如,我们发现这个函数的返回值没有其他变量 / 函数接收:
1 | sub_4011F0(Destination); |
再查找这个函数的交叉引用,发现仅有此处调用函数,于是判断这个函数的返回值是没有意义的。
对该函数按下 Y 键,修改函数返回类型为 void
:
1 | void __cdecl sub_4011F0(char *al) |
之后大部分的类型修复都可以使用 Y 键。
调用方式修复,主要是函数套函数,以内层函数的调用方式作为参考,一般修复为 __cdecl
。
参数列表修复,一种是删除无用变量(有些变量 IDA 会以橙色标出),另一种是下一小节的 “基本类型修复”。
基本类型修复
一个最常见的错误:指针类型识别为整数类型。
1 | int __cdecl sub_401270(int a1, const *a2, int a3) |
此处的 a1
应为 int*
,a3
应为 char*
。
修复后的伪代码长这样:
1 | int __cdecl sub_401270(int *a1, const *a2, char *a3) |
继续修复,得到:
1 | void __cdecl sub_401270(int *a1, const const *a2, char *a3) |
数组修复
数组修复考虑数组类型和数组大小,分为局部变量定义的数组和全局变量定义的数组。对于数组大小,可以看看是否有引用未经初始化的数组。
例如,这是一个局部变量数组的修复:
1 | char Str[5]; |
这里的 Source
非常奇怪,因为我们没有在它的作用域内找到任何有关于它的初始化信息,但伪代码却很坦然地接受了这个 “非法” 数组的引用。
实际上,Str
和 Source
是一个数组,但 IDA 错误地识别了数组大小,将 Str
硬生生拆出来一个 Source
数组。
如何恢复?5 + 47 = 52,于是我们将 Str
的大小恢复成 52
即可。
全局变量定义的数组则需要在分析完其交叉引用后确定它的类型和大小信息,修复则需要在 .data
段里(IDA-View 视图)对数组首项点击右键,选择 “Array…”,修改 “Array size” 项。(或者按 D 键)
枚举值修复
C / C++ 程序非常依赖各种宏(这里也称 “枚举”)值,这些枚举值控制着程序的一些功能。
IDA 的类型数据库内置了常见的枚举值,可以直接引入并修复。
例如,在反调试技术与应对手段中,我们提到了 ptrace()
函数。在反编译时,该函数的第一个参数会直接替换为常量值,换句话说,我们丢失了这个枚举值的语义。
1 | ptrace(12LL, pid, 0LL, ®s) |
要修复,选中 12LL
,按下 M 键,选择对应的常量值及其符号名。
1 | ptrace(PTRACE_GETREGS, pid, 0LL, ®s) // 注意枚举值名可能不一样 |
结构体修复
要修复结构体,可以遵循以下步骤:
-
确定结构体大小
- 如果有像
malloc
或new
等显式分配内存的语句,可以直接确定结构体大小。 - 还可以通过构造函数、拷贝函数中的
memcpy
或者局部变量偏移差来间接确定结构体大小
- 如果有像
-
创建相等大小的匿名结构体
在 “Structures” 视图,右键选择 “Add struct type…”(或者按 Insert 键),创建一个结构体,然后按 D 键往新建的结构体里添加字段,填充结构体到先前获得的大小。 -
修改相关变量、参数类型为结构体
Y 键修改变量类型,或者右键选择 “Convert to struct *...”,选择结构体。 -
调整字段大小
点击 D 键可以轮换字段数据类型。如果在伪代码发现数组结构,则需要修改为数组(右键点击基字段,选择 “Array…” 转换为数组)
虚表修复
虚表修复主要是为了重建虚表交叉引用。
具体步骤:
-
找到虚函数表
-
创建结构体,复制虚表内容
-
再次创建结构体,设置这个结构体第一个成员的类型为之前创建的结构体指针
得到正常函数名