拥有正确的堆栈变化信息对于正确的分析至关重要,虽然 IDA 尽力提供良好且正确的结果,然而,有时它仍然会失败(通常是由于错误或冲突的信息)。
在这篇文章中,我们将向您展示如何检测和解决以下问题:
“sp-analysis failed”
“positive sp value has been detected”
这两个示例均来自 Windows 10(版本 10.0.17763.475)的 notepad.exe 32 位版本,并使用了 Microsoft 公共符号服务器中的 PDB 符号。
注意:在许多情况下,反编译器会尝试恢复并仍然产生合理的反编译,但如果您需要 100% 确定结果,最好修复它们。
发现问题的源头
解决这些问题的第一步通常是:
-
切换到反汇编视图(如果您在反编译器中); -
在“Option”>“General...” 中的 “Disassembly, Disassembly line parts ” 下启用“Stack pointer”;
-
查找每条指令之前添加的 SP 值(实际上是 SP 增量值)的异常或意外变化。
为了检测“异常变化”,我们首先需要知道什么是“通常”。
这里有些例子:
-
push
指令应将 SP 增量增加字节数(例如,push eax 增加 4,push rbp 增加 8) -
相反,pop 指令将其减少相同的量 -
call 指令通常要么减少 SP (由于参数压栈)(x86 上的 stdcall 或 thiscall 函数),要么保持不变,稍后通过单独的指令减少。 -
跳转两端的值(有条件或无条件)应该相同 -
函数入口和返回指令处的值应为 0 -
在 prolog 和 epilog 之间,SP 增量应该保持相同,除了调用周围的小区域之外,它可以通过压入参数来增加,但随后应该在基本块结束之前返回到“中性”。
在第一个示例中,我们可以看到 loc_406F9D
的 SP 增量为 00C,第一次跳转也是 00C,但第二个跳转是 008。
因此问题可能出在第二个块中。
00C mov ecx, offset dword_41D180
00C call _TraceLoggingRegister@4 ; TraceLoggingRegister(x)
008 push offset _TraceLogger__GetInstance____2____dynamic_atexit_destructor_for__s_instance__ ; void (__cdecl *)()
00C call _atexit
00C pop ecx
008 push ebx
00C call __Init_thread_footer
00C pop ecx
008 jmp short loc_406F9D
我们可以看到,在调用_TraceLoggingRegister@4
之后,00C 变为 008。
乍一看,这是有道理的,因为 @4 后缀表示带有 4 个字节参数的 stdcall 函数(这意味着它从堆栈中删除了 4 个字节)。
然而,如果你真正深入分析它,你会发现它不使用堆栈参数,而是使用寄存器 ecx。该文件可能已使用链接时代码生成进行编译,该技术将 stdcall 转换为 fastcall 以加快代码速度。
在第二种情况下,反汇编如下所示:
在这里,问题立即显而易见:call 指令后 SP 增量变为负值。
看来 IDA 决定该函数从堆栈中减去 0x14 字节,而只有 3 次压入(3 * 4 = 12 或 0xC)。
您还可以进入 StringCopyWorkerW 并观察它以 retn 0Ch 结尾 – 这是一个正确的数字的指示符。
修正错误的栈指针增量
如何实际修复错误的增量取决于具体情况,但通常有两种方法:
-
仅修复出现问题的地方。为此,请按 Alt-K(Edit > Functions > Change stack pointer…)并输入正确的 SP 增量。在第一个示例中,它应该为 0(因为该函数不使用任何堆栈参数),而在第二个示例中,它应该为 12 或 0xc。通常这是间接调用的唯一选择。 -
如果从多个地方调用同一个函数会导致堆栈不平衡问题,编辑函数属性(Alt+P 或 Edit > Functions > Edit function…),修改 “Purged bytes” 值。
这个简单的示例表明,即使有调试符号也不能保证 100% 正确的结果,以及为什么为用户提供修改选项很重要。
原文始发于微信公众号(二进制磨剑):IDA 技巧(27) 修复栈指针
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论