我们讨论了函数块,今天我们将展示一个如何在实践中使用它们来处理常见编译器优化的例子。
共享函数尾部优化
在处理一些ARM固件时,你可能会遇到以下情况:
我们对sub_8098C
的反编译以一个奇怪的JUMPOUT语句
结束,如果我们查看反汇编,可以看到它对应于另一个函数(sub_8092C
)中的POP.W指令
的分支。这是怎么回事?
这是一个代码大小优化的例子。POP.W指令
是4字节长,而B
分支只有两个字节,因此通过重用它,编译器节省了两个字节。虽然听起来不多,但这样的节省可以在整个二进制文件的所有函数中累积成相当可观的数量。此外,有时可以重用更长的指令序列,从而带来更大的节省。
我们能否修复数据库以获得干净的反编译并去除JUMPOUT
?当然,答案是肯定的,但具体步骤可能不太明显,所以让我们描述一些方法。
为共享尾部指令创建一个块
首先,我们需要为共享指令(在我们的例子中是POP.W指令
)创建一个块。只有不属于任何函数的指令才能创建块,因此最简单的方法是删除函数,使指令变为“自由”。这可以通过函数窗口中的编辑 > 函数 > 删除函数菜单项,或通过模态“跳转到函数”列表(Ctrl–P, Del)来完成。
一旦删除,共享尾部指令可以作为一个块添加到其他函数中。这可以手动完成:
-
选择指令, -
调用编辑 > 函数 > 附加函数尾部… -
选择引用函数(在我们的例子中是 sub_8098C
)。通常IDA会自动建议它。
或者(半)自动地:
-
跳转到引用分支(例如,通过双击 CODE XREF: sub_8098C+3E↓j
注释) -
重新分析分支(按C)。IDA将检测到执行在当前函数边界之外继续,并自动创建并添加共享尾部指令的块。
无论哪种解决方案都会创建块并将其标记为属于引用函数。
我们可以检查它是否包含在函数图中:
伪代码中不再有JUMPOUT:
将块附加到原始函数
我们为一个函数“解决”了问题,但在此过程中我们破坏了包含共享尾部的函数。如果我们也需要反编译它,可以尝试重新创建它:
然而,IDA在块之前结束它,因为它现在是另一个函数的一部分:
如果我们反编译它,我们会遇到相同的JUMPOUT
问题:
解决方案很简单:如前文所述,一个块可以属于多个函数,因此我们只需将块附加到此函数:
-
选择尾部指令; -
调用编辑 > 函数 > 附加函数尾部… -
选择重新创建的函数(在我们的例子中是 sub_8092C
)。
块获得了一个新的所有者,出现在函数图中,反编译得以修复:
复杂情况
上述例子中有一个由两个函数共享的尾部,但这当然不是极限。考虑这个例子:
这里,POP.W指令
被七个函数共享,其中两个还重用了它前面的ADD SP, SP, #0x10
指令。还有一个块只属于一个函数,但由于函数不再是连续的,它必须被分离。然而,IDA对碎片化函数的处理足够灵活,在一些手动帮助下,所有涉及的函数都有适当的控制流图和良好的反编译。
总结一下,处理共享尾部优化的建议算法如下:
-
删除包含共享尾部指令的函数。 -
将共享尾部指令附加到其他函数(手动或通过重新分析到尾部的分支) -
重新创建已删除的函数并将共享尾部附加到它。
学习资源
立即关注【二进制磨剑】公众号
原文始发于微信公众号(二进制磨剑):IDA技巧(87)函数块和反编译器
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论