尽管 Hex-Rays 反编译器最初是为处理编译器生成的代码而编写的,但它在处理手写汇编时也能表现得不错。然而,这类代码可能会使用非标准指令或以非标准方式使用它们,这种情况下,反编译器可能无法生成等效的 C 代码,而不得不退回到 _asm
语句。
分析系统代码
例如,让我们看看 PowerPC 固件中的这个函数。
代码似乎在使用特殊用途寄存器通用(sprg0
/1/2/3)来实现自己的目的,可能是为了存储一些异常处理的信息。由于系统指令通常不会在用户模式代码中遇到,因此反编译器默认不支持它们,默认输出看起来像这样:
尽管指令本身显示为 _asm
语句,反编译器可以检测到它们使用的寄存器,并创建伪变量(_R29
,_R30
,_R31
)来表示执行的操作。然而,通过一些手动操作,可以去掉 _asm
块。
反编译为调用
可以告诉反编译器将特定指令视为函数调用。你甚至可以使用自定义调用约定来指定伪函数的确切输入/输出寄存器。让我们尝试处理未处理的指令。
-
在反汇编视图中,将光标放在指令上(例如 mtsprg0 r29
); -
调用 编辑 > 其他 > 反编译为调用… -
输入原型,考虑输入/输出寄存器。在我们的例子中,我们将使用: void __usercall mtsgpr0(unsigned int value<r29>);
-
对剩余指令重复此操作,例如: void __usercall mtsgpr1(unsigned int<r31>);
void __usercall mtsgpr2(unsigned int<r30>);
void __usercall mtsgpr3(unsigned int<r30>)
int __usercall mftb<r3>();
-
如果没有自动完成,请刷新反编译。
我们得到这样的结果:
不再有 _asm
块!唯一剩下的问题是神秘的变量 v1,它被标记为橙色(“值可能未定义”)。
如果我们查看汇编,会发现传递给 mtsprg2
的 r30
来源于 mflr r3
指令设置的 r3
。该指令读取 lr
(链接寄存器)的值,包含返回给调用者的地址,因此根据定义没有确定的值。然而,我们可以通过为 mflr r3
指令指定这样的原型来使用伪函数,例如 GCC 的 __builtin_return_address
:void * __builtin_return_address ();
注意:我们不需要在这里使用 __usercall
,因为 r3
已经是 PPC ABI 中返回值的默认位置。
最后,反编译看起来整洁有序:
复杂情况
如果你想自动化将原型应用于指令的过程,可以使用反编译器插件或脚本。例如,参见 vds8 反编译器 SDK 示例(也随 IDA 一起提供),它处理一些 ARM 代码中的 SVC
调用。
在更复杂的情况下,例如某些参数无法通过自定义调用约定表示,或者语义更适合用其他方式而不是函数调用表示(例如指令影响多个寄存器),你可以使用“微代码过滤器”生成自定义微代码,然后由反编译器引擎优化并转换为 C 代码。
一个很好的例子是 Markus Gaasedelen 的优秀 microAVX 插件。
更多文章
立即关注【二进制磨剑】公众号
原文始发于微信公众号(二进制磨剑):IDA 技巧(71) 反编译为调用
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论