逆向工程核心原理-7

逆向工程核心原理-第七章 栈帧

作用

栈帧再程序中用于声明局部变量, 调用函数.同时利用EBP访问栈内局部变量, 参数, 函数返回地址等的手段.(其实个人理解是栈帧就像是高中学的参考系,用ESP来当参考系是非常困难的, 因为它会移动, 这样访问其他数据是的值也需要根据ESP的变动而变动,然而用EBP这一个静物来充当参考系的话,访问其他数据就会变得非常的简单.只需要加上一个系数就行了,然后就是函数的返回地址这一个作用, 相当于一个门, 这个门是被调用函数的入口(只不过要注意,这个入口门是”没有多大的意义的”,重要的是旁边的出口门,以及它本身的位置可以用来访问变量),也是调用函数的出口, 旁边还存着调用函数的入口, 当我们需要返回调用函数的时候回到这个门就行了)

栈帧

函数调用的基本步骤

Untitled

  用自己的话来描述就是先将"老EBP" (就是调用函数的EBP)压入栈中保存, 然后将现在的ESP作为"新EBP"来使用, 执行完被调用函数的语句后, 将"新EBP"给ESP,然后此时栈顶指针又回到了起点,再弹出"老EBP"的值给EBP, 然后就回到调用函数了.

运行实例程序(下好断点跟准备工作)

执行到main函数

Untitled

要注意从现在开始我们每一步都要观察栈中情况,跟ESP,EBP的情况

可以看到栈中esp指针的位置存了一个地址401250,这个是main函数要返回的地址

ESP = 0019FF2C 当前位置

EBP = 0019FF70 存的是老ebp

PUSH EBP

将老EBP的值压入栈中

栈中情况

Untitled

ESP = 0019FF28 比上次 - 4

EBP = 0019FF70 跟上一次比没有变化

MOV EBP , ESP

将当前ESP的赋给EBP,此时的EBP为”新EBP”

栈中情况(因为没有用到push, pop等指令,所以没有变化)

Untitled

ESP = 0019FF28

EBP = 0019FF28 此时EBP记录着现在ESP的位置 , 这时的

                        EBP就可以用来访问临时变量等操作了.

ps:可以修改地址的参考点(以ESP,EBP作为参考)

在栈窗口右键

Untitled

就可以看到EBP的位置了

Untitled

SUB ESP , 8

为变量a , b开辟栈中的空间

栈中情况 , 可以看到有了两个空位在EBP指针的上方

Untitled

ESP = 0019FF20 比上次减了8

EBP = 0019FF28

mov dword ptr [ebp-4],1

等同于a = 1;

mov dword ptr [ebp-8],2

等同于b = 1;

看到了一个IDA里面非常熟悉的

Untitled

平常看到这种挺怕的 , 放在这里加深一下记忆吧.

(DWORD)(EBP - 4)

其中(DWORD*)代表的是指针类型可以忽略,所以可以看成是*(EBP - 4)就很好理解了

执行完上面的语句后栈内情况

Untitled

此时

ESP = 0019FF20

EBP = 0019FF28 因为是mov指令,所以两个都没变

add函数参数的传递以及调用

这5行代码就是调用add函数的全部过程

Untitled

在上一章的结尾我们总结了参数入栈的规律,可以看到这里是b比a先入栈,因为接下来函数调用要根据栈的规则进行.

观察栈中情况

Untitled

此时

ESP = 0019FF14 比上一次减少了12

EBP = 0019FF28 仍然没有变化

为什么多压了一次栈呢?是因为在执行CALL命令之前,CPU先会把函数的返回地址压入栈,函数执行完后根据这个地址返回(注意,这个返回跟栈没有关系,返回的是IP指针)

开始执行add()函数&生成栈帧

long add(long a, long b)

开始执行时对应的汇编指令

Untitled

将”老EBP”的值压入栈中并生成”新EBP”

执行后栈内情况

Untitled

可以看到有了新的EBP

此时

ESP = 0019FF10 比原来小了4

EBP = 0019FF10 跟ESP一样了

同时下一条指令是

Untitled

作用是为局部把变量腾出空间

函数内局部变量long x = a, y = b

对应的代码(看注释)

Untitled

观察栈内情况

Untitled

压了两个属于add()的局部变量

同时寄存器EAX的存有运算的结果

Untitled

此时

ESP = 0019FF08

EBP = 0019FF10

删除函数add()的栈帧&函数执行完毕

也就是return (x + y);

Untitled

MOV ESP , EBP

把EBP的值给ESP回到了入口的地方

注意上面的命令执行完了以后,地址401003出的SUB ESP,8不再有效,即函数add()执行完了以后局部变量x , y不再有效(是不是因为EBP的值变了啊?)

POP EBP

注意此时ESP已经回到了入口处,指向的是”老EBP”,弹出时,EBP的值恢复为调用函数的EBP.到这里,add()函数的栈帧就被删除了.

观察栈中情况

Untitled

此时

ESP = 0019FF14

EBP = 0019FF28

RETN

这里储存的时返回地址(针对IP指针的返回地址)

等同于POP IP , 所以ESP的值才会变化(不知道这样想对不对)

观察栈中情况

Untitled

此时

ESP = 0019FF18

EBP = 0019FF28

从栈中删除add()的参数(整理栈)

ADD ESP , 8

为什么要执行这一步,因为ESP + 4跟ESP + 8还残存这传递给add()的参数a跟b,所以我们要消除这些

执行完观察栈中情况

Untitled

此时

ESP = 0019FF20 比刚刚增加了8

EBP = 0019FF28 没有变化

调用约定: 函数调用者(Caller)负责清理存储在栈中的参数, 这种方式成为cdecl方式; 凡是, 被调用者(callee)负责清理保存在栈中的参数, 这种方式成为stdcall方式. 这些函数调用规则成为调用约定.

调用printf()函数(注意要传递两个参数,不是一个)

语句如下

Untitled

第一个语句中的eax是add()的返回值(前面有讲过) , 传递的参数是3

第二个语句是传递”%d\n”这个参数

上面个两个语句也满足了汇编逆序传参的规则,请记住!

然后就是调用函数printf()

然后还有一个

Untitled

这个的作用是把函数的参数从栈中删除.(符合调用约定)

设置返回值

进行异或运算(特点: 两个相同的值进行异或运算,结果为0

Untitled

ps : 为什么设置0不用MOV而是XOR , 因为XOR运算的速度更快

删除栈帧&main()函数中止 return 0;

Untitled

执行后, 栈帧被删除, a , b变量也被删除

观察栈内情况

Untitled

然后继续执行下一条语句

Untitled

然后程序执行流返回到VC启动函数

程序执行至此结束

小结

这一章节的知识非常的重要 , 因为理解了栈的这个执行过程以及原理后 , 才能在静态分析中更加具有函数的整体与模块化的意识.

一个函数的执行流程

  • 将参数逆序压入栈中
  • 调用函数
  • 函数执行前将返回地址压入栈中(针对指令指针IP)
  • 执行函数后先将”老EBP”压入栈中
  • 将ESP的值传给EBP形成”新EBP”
  • 腾出变量空间
  • 执行函数内的语句
  • 将”新EBP”的值给ESP,栈顶回到起点
  • 弹出”老EBP”的值给EBP,又变回了”老EBP”
  • retn返回,此时ESP弹出了一个值,ESP向下
  • 清除传入的参数(调用协定),这时ESP回到原来的位置
  • 函数执行完毕

难点要时刻注意栈顶指针跟指令指针之间的差别 , 以及哪些指令针对SP , 哪些指令针对IP,以及整个流程一个步骤都不能少

文章作者: LamのCrow
文章链接: http://example.com/2021/12/18/ReMain-第七章 栈帧 96c9c5324a2f49878a411a21974a4007/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 LamのCrow