3.栈偏移平衡与计算(使用IDA举例)
Last updated
Last updated
相信大家在按F5时,一定遇到过类似的情况
“sp-analysis failed”
“positive sp value has been detected”
https://hex-rays.com/blog/igors-tip-of-the-week-27-fixing-the-stack-pointer/
如果没有遇到过,那这里我造一个类似的情况(注意编译时添加-masm=intel参数):
这一章的主题,说明“为什么反编译器要处理栈平衡”,”反编译器如何处理栈平衡“、“为什么IDA会出现上面这些问题”、“遇到上面的问题应该怎么办”
不同的反编译器对于栈平衡与栈上局部变量的处理方法不同,这里我以最常用的IDA作为举例。
这个东西应该属于基础知识,这里不做介绍。
有需要可以看下面的材料,以及自己搜索一下:
https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/stack-intro/
最重要的两个目标,一个是识别栈上的局部变量,一个是用于判断函数调用约定。
在一般情况下,栈的开辟任务在函数入口完成,并在函数退出时进行回收(依照具体的调用约定),在大部分场景下,栈空间(栈顶)的大小不会变化,调用约定往往也会处理好栈的平衡,保证出入一致;
但是总有些特殊场景:
在C语言可以使用alloc动态分配栈长度,函数中的栈顶(sp)可能是会动态变化的。
C语言编译器支持可变的局部数组,可能会动态分配栈空间,例如下面这个代码
并且,无论是使用栈底(bp)还是使用栈顶(sp)来引用栈上的局部变量,反编译器都需要归一化的处理方法,归一化的方案就是栈平衡的计算。
在IDA中栈平衡就是计算SP的偏移,局部变量的识别也是基于栈底计算的,后面作为主要的举例。
如果栈平衡推导出现错误,那么对于栈上局部变量的还原就可能出错,因为对应到了错误的偏移。
栈偏移的处理存在与反编译器的多个阶段,包括
反编译器前端转换过程中,中间语言会处理记录所有对于栈指针的操作。
数据流分析,栈操作是识别函数调用参数的重要信息。
数据流分析,通过栈指针的操作识别局部变量。
数据流分析,将对于栈指针+偏移的”读取”与”写入“内存操作转换为对函数局部变量的修改。
代码生成,会标记局部变量在栈上的偏移。
当然,在这一章节,我们只讨论最上面的**“反编译器前端转换过程中,中间语言会处理记录所有对于栈指针的操作”**这一步骤
一旦栈偏移的推导完成,对于所有局部变量的引用,反编译器就会归一化,在IDA中就是变为局部变量偏移的引用。
如下图,原先的汇编是[rsp+0x30]和[rsp+0x08]
IDA判断出当前栈偏移是108,就会将[rsp+0x30] 和 [rsp+0x08]转换为相对于栈底位置的局部变量的操作,当然最后算出来的实际值是一样的。
栈局部变量var_D8 = qword ptr -0D8h
栈局部变量var_100 = qword ptr -100h
在IDA中,栈偏移的计算是在反汇编步骤就已经完成了。栈偏移作为基础信息参与后面的工作。
IDA中的Options → General 中可以开启栈指针的显示
开启后在汇编代码的这部分,就会显示栈的偏移了,简单的说前面的数字记录了这行汇编执行前,栈顶相对于栈底的偏移。
这个数字的计算方法也很简单,在中间语言生成时,计算所有栈操作的指令对栈顶的影响(比如push eax
就+4, push rbp
就+8)
在遇到函数调用时,处理调用约定对于栈的影响。
函数开始时这个值为0,如果一切顺利,函数结束时这个值也为零(栈平衡了)。
当遇到通过栈指针+偏移对于栈的内存读取与写入操作,反编译器依赖于这个值,就能知道任意指令情况下的栈顶与栈底的偏移,很容易的就能转换为对实际栈变量的操作。进而识别局部变量在栈上的位置。
看到了上面的例子,聪明的你一定想到了,IDA这种方法虽然适用与大部分情况,但是存在一定的不足。这也是导致出现**“sp-analysis failed”和“positive sp value has been detected”**的主要原因。
1、无法处理的分支情况
再次看一下这个代码
在IDA他会出现这样的问题,当两个控制流合并时,一边栈偏移是0x10,一边栈偏移是0x0C。IDA不知道应该使用哪个,就会出现栈偏移错误的问题。
遇到这种问题,IDA就直接“先到先得”哪个控制流先处理,就用哪个控制流的栈偏移。
2、依赖于调用约定的识别
一些函数调用约定会影响栈的平衡,如果函数调用约定以及调用参数没有正确的识别,就会导致栈平衡的问题。
在IDA中最常用的就是强行调整栈平衡以及配置调用约定.
把光标放到需要调整的汇编语句,使用快捷键alr+k,强制修改栈顶的相对偏移。注意,哪怕修改后IDA的**“sp-analysis failed”**等告警仍可能会存在,只要保证反编译F5能够正常使用就行了。
至于调用约定的识别错误导致的反编译问题,可以通过修改调用约定或者自定义调用约定实现
ref:https://hex-rays.com/blog/igors-tip-of-the-week-51-custom-calling-conventions/
在IDA中的架构中,栈平衡的计算,在机器码转汇编语言时顺带完成了。
这一组件称为“IDA processor module”,它在ida-sdk的idasdkXX\idasdkXX\module文件夹内。
参考下图,指令集中会定义trace_sp功能,专门对可能影响栈的指令进行处理(先判断操作的寄存器是不是栈寄存器,在判断操作数)
例如,下图中的add_stkpnt,就是对栈操作的IDA-API。