在正向编译过程中,编译器会删除大量辅助信息,例如类型信息。恢复类型信息可以让 IDA 生成更加接近源码的伪代码,有利于减轻工作量。
函数修复
函数修复主要看三点:返回类型、调用方式和参数列表。
返回类型修复,主要判断函数的返回类型是否有意义。这里的 “有意义” 指的是返回值被引用,或者有表达式使用了返回值。
比如,我们发现这个函数的返回值没有其他变量 / 函数接收:1
sub_4011F0(Destination);
再查找这个函数的交叉引用,发现仅有此处调用函数,于是判断这个函数的返回值是没有意义的。
对该函数按下 Y 键,修改函数返回类型为 void:1
void __cdecl sub_4011F0(char *al)
之后大部分的类型修复都可以使用 Y 键。由于是直接修改定义,所以 Y 键也可以顶替 N 键的作用。
调用方式修复,主要是函数套函数,以内层函数的调用方式作为参考,一般修复为 __cdecl。
参数列表修复,一种是删除无用变量(有些变量 IDA 会以橙色标出),另一种是下一小节的 “基本类型修复”。
出现 Bad declaration
IDA 生成的 / 你修改的函数原型中出现不符合 IDA C 语言风格的语法。
但是既然 IDA 可以生成,那么为什么不让我改呢?有点 “只许州官放火,不许百姓点灯” 了。
主要原因有(GPT-5 模型生成):
- C 语法错误
括号/方括号/尖括号不成对
分号、逗号缺失
类型或调用约定拼写错误
参数声明里出现非法符号(
:、::、<>等)
- IDA 不支持的 C/C++ 特性
模板
template<>C++ 作用域解析
ClassName::method位域声明
int x:3某些宏(
__declspec、constexpr等)
- 非标准扩展或奇怪的符号名
冒号、空格、运算符重载标记
C++11 之后的语法(
auto、&&等)
基本类型修复
一个最常见的错误:指针类型识别为整数类型。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17int __cdecl sub_401270(int a1, const *a2, int a3)
{
signed int v3;
int result;
signed int v5;
v5 = 0;
v3 = strlen(a2);
while( v5 < v3 )
{
*(_BYTE *)(a3 + *(_DWORD *)(a1 + 4 * v5)) = a2[v5]; // notice this
++v5;
}
result = v5 + a3;
*(_BYTE *)(v5+ a3) = 0;
return result;
}
此处的 a1 应为 int*,a3 应为 char*。
修复后的伪代码长这样:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17int __cdecl sub_401270(int *a1, const *a2, char *a3)
{
signed int v3;
int *result;
signed int v5;
v5 = 0;
v3 = strlen(a2);
while( v5 < v3 )
{
a3[a1[v5]] = a2[v5];
++v5;
}
result = &a3[v5];
a3[v5] = 0;
return result;
}
继续修复,得到:1
2
3
4
5
6
7
8
9
10
11
12
13
14void __cdecl sub_401270(int *a1, const *a2, char *a3)
{
signed int v3;
signed int v4;
v4 = 0;
v3 = strlen(a2);
while( v4 < v3 )
{
a3[a1[v4]] = a2[v4];
++v4;
}
a3[v4] = 0;
}
数组修复
数组修复考虑数组类型和数组大小,分为局部变量定义的数组和全局变量定义的数组。对于数组大小,可以看看是否有引用未经初始化的数组。
例如,这是一个局部变量数组的修复:1
2
3
4
5char Str[5];
char Source[47];
// ...
scanf("%s", (char)Str);
if ( sub_40100(Str, "flag{") != (_DWORD)Str || Source[32] != 125)
这里的 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) // 注意枚举值名可能不一样
这些枚举值的含义可能需要查阅另外的函数文档。
结构体修复
一些虚拟机逆向需要我们自行修复结构体,最明显的伪代码特点是:对于一个基地址,以固定的偏移值访问连续的指针并赋值,如:1
2
3
4
5
6*a1 = 0;
*(a1 + 4) = 18;
*(a1 + 8) = 0;
*(a1 + 12) = 0;
*(a1 + 16) = &unk_202060;
// ...
要修复结构体,可以遵循以下步骤:
确定结构体大小
- 如果有像
malloc或new等显式分配内存的语句,可以直接确定结构体大小。 - 还可以通过构造函数、拷贝函数中的
memcpy或者局部变量偏移差来间接确定结构体大小
- 如果有像
创建相等大小的匿名结构体
在 “Structures” 视图(从 9.0 开始,改名为 “Local Types”),右键选择 “Add struct type...”(或者按 Insert 键),创建一个结构体,然后按 D 键往新建的结构体里添加字段,填充结构体到先前获得的大小。修改相关变量、参数类型为结构体
Y 键修改变量类型,或者右键选择 “Convert to struct \*...”,选择结构体。调整字段大小
点击 D 键可以轮换字段数据类型。如果在伪代码发现数组结构,则需要修改为数组(右键点击基字段,选择 “Array...” 转换为数组)
以上步骤不必死板执行。例如,如果你在一个结构体中发现了重复的模式,那么你可以将这个模式独立出来设置一个结构体,然后在原来的结构体中增加一个类型为新结构体的成员。
虚表修复
虚表修复主要是为了重建虚表交叉引用。
具体步骤:
找到虚函数表
创建结构体,复制虚表内容
再次创建结构体,设置这个结构体第一个成员的类型为之前创建的结构体指针
得到正常函数名
