可变参数函数是指根据调用者需要接受不同数量参数的函数。典型的例子包括C和C++中的printf
和scanf
,但也有其他函数,甚至一些自定义函数(特定于正在分析的二进制文件)。由于每次调用可变参数函数时可能有不同的参数集,因此在反编译器中需要特别处理。在许多情况下,反编译器会自动检测这些函数及其参数,但有时可能需要用户干预。
更改函数原型
对于标准的可变参数函数,IDA通常会应用来自类型库的原型,但如果有非标准函数或IDA未检测到函数是可变参数的,您可以手动进行更改。例如,ARM64上未识别的可变参数函数的反编译原型可能如下所示:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
void __fastcall logfunc(
constchar *a1,
__int64 a2,
__int64 a3,
__int64 a4,
__int64 a5,
__int64 a6,
__int64 a7,
__int64 a8,
char a9)
但当您检查调用点时,您会发现大多数传递的参数被标记为可能未初始化(橙色):
第一个参数看起来像是格式字符串,因此其余参数可能是可变的。因此我们可以尝试更改原型为:
ounter(line
voidlogfunc(constchar *, ...);
这将导致干净的反编译:
调整可变参数
使用正确的原型,反编译器通常可以猜测传递给函数每次调用的实际参数。然而,在某些情况下,自动检测可能会失误,特别是如果函数使用非标准格式说明符或根本不使用格式字符串。在这种情况下,您可以调整传递给调用的实际参数数量。这可以通过上下文菜单命令“添加可变参数”和“删除可变参数”或相应的快捷键数字键盘+
和数字键盘-
来完成。
可变参数调用和尾分支优化
在一些罕见的情况下,您可能会遇到以下问题:尝试添加或删除可变参数时,反编译器似乎忽略了该操作。这可能发生在经过特定优化的函数中。例如,这里是一个似乎有两个调用日志函数的伪代码:
反编译器认为a3
也被传递给调用,但我们可以看到格式字符串没有任何格式说明符,因此a3是误报,应该被删除。然而,在第一个调用上使用“删除可变参数”似乎没有效果。发生了什么?
这是一个罕见的情况,切换到反汇编可以澄清问题。按下Tab
,我们可以在反汇编中看到一个奇怪的现象:只有一个调用!
这是所谓的尾分支合并优化的一个例子,其中相同的函数调用被不同的参数重用。为了更好的代码可读性,反编译器检测到这种情况并创建了一个带有第二组参数的重复调用语句。因为关于可变参数数量的信息附加在实际的调用指令上,所以不能为反编译器插入的“假”调用更改它。您可以为“规范”调用更改它,这可以通过在调用(BL
指令)上按Tab
来发现。在那里删除参数会影响伪代码中的两个调用。
如果您想查看“原始”代码,可以通过在反编译器的分析选项中关闭“取消合并尾分支优化”来实现。
关闭后,只有一个调用,就像在反汇编中一样,但代价是增加了一个跳转
和一些局部变量:
学习资源
立即关注【二进制磨剑】公众号
原文始发于微信公众号(二进制磨剑):IDA技巧(101)反编译可变参数函数调用
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论