IDE PRO学习-栈帧

admin 2023年12月2日22:33:38评论10 views字数 3137阅读10分27秒阅读模式

什么是栈帧

栈帧是在程序的运行时栈中分配的内存块,专门用于特定的函数调用。如果一个函数并未执行,通常他是不需要内存,但是,当函数被调用时,他就可能因为某种原因需要用到内存:

  • 函数的调用方可能希望以参数的方式向该函数传递信息,这些参数需要存储到函数能够找到他们的位置。

  • 在执行任务的过程中,函数可能需要临时的存储空间。

调用约定

简单了解了栈帧之后,我们再来了解什么是调用约定。

调用约定指定调用方放置函数所需参数的具体位置。调用约定可能要求将参数放置在特定的寄存器、程序栈、或者寄存器和栈中。同样重要的是,在传递参数时,程序栈还要决定:被调用函数完成其操作后,由谁负责从栈中删除这些函数。一些调用约定规定,由调用方负责删除它放置在栈中的参数,而另一些调用约定则要求被调用函数负责删除栈中的参数。

_cdecl

cdecl调用约定又被叫做C调用约定,其规定了:调用方按照从右到左的顺序将函数参数放入栈中,在被调用的函数完成其操作时,调用方负责从栈中清除参数。

举例

// cdeclTest.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

void demo_cdecl(int w,int x,int y,int z);
int main(int argc, char* argv[])
{
demo_cdecl(1,2,3,4);
return 0;
}

void demo_cdecl(int w,int x,int y,int z)
{
int a = w+x+y+z;
}

IDE PRO学习-栈帧

_stdcall

stdcall调用约定按从右到左的顺序将函数参数放在程序栈上,使用stdcall的区别在于:函数结束执行时,由被调用者的函数负责删除栈中的函数参数。

举例:

// Test_stdcall.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
void _stdcall demo_stdcall(int w,int x,int y, int z)
{
int a = w+x+y+z;
}

int main(int argc, char* argv[])
{
demo_stdcall(1,2,3,4);
return 0;
}

IDE PRO学习-栈帧

IDE PRO学习-栈帧

fastcall

fastcall是stdcall的一个变体,它向CPU寄存器最多传递两个参数。意思就是:如果指定使用fastcall约定,则传递给函数的前两个参数将分别位于ECX和EDX寄存器中,剩余的其他参数则以类似于stdcall约定的方式从右到左放入栈上。同样,与stdcall相同的是,再返回时,fastcall函数负责从栈中删除参数。

举例:

// Test_fastcall.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#define FASTCALL __fastcall

void __fastcall demo_fastcall(int x,int y)
{
int a = x+y;
}

void __fastcall demo_fastcall2(int x,int y,int z)
{
int a = x+y+z;
}

int main(int argc, char* argv[])
{
demo_fastcall(1,2);
demo_fastcall2(1,2,3);
return 0;
}

IDE PRO学习-栈帧

其他调用约定

如C++中的thiscall调用约定,这里就不详细展开了。

IDA栈视图

在分析代码之前,先掌握几个概念:

  • 正向偏移用于访问函数参数:如:[ebp+4]

  • 负偏移则用于访问局部变量:如:[ebp-76]

以下列代码为例:

void bar(int j,int k);
void demo_stackframe(int a,int b,int c)
{
int x = c;
char buffer[64];
int y = b;
int z = 10;
buffer[0] = 'A';
bar(z,y);
}

IDE PRO学习-栈帧

.text:00401090                 public _demo_stackframe
.text:00401090 _demo_stackframe proc near ; CODE XREF: _main+41p
.text:00401090
.text:00401090 var_78 = dword ptr -78h
.text:00401090 var_74 = dword ptr -74h
.text:00401090 var_60 = dword ptr -60h
.text:00401090 var_5C = dword ptr -5Ch
.text:00401090 var_58 = byte ptr -58h
.text:00401090 var_C = dword ptr -0Ch
.text:00401090 arg_4 = dword ptr 0Ch
.text:00401090 arg_8 = dword ptr 10h
;局部变量以var_为前缀,后面跟一个表示变量与被保存的帧指针之间的距离(以字节为单位),举例:var_C
;var_C:该局部变量与ebp的距离为C字节
;函数参数名以arg_为前缀,后面跟一个表示其与最顶端的参数之间的相对距离的十六进制后缀,如:
;第一个参数一般都是[ebp+4],那么在ida中就是arg_0。
;arg_4:[ebg+8],离第一个参数距离为4字节
.text:00401090
.text:00401090 push ebp
.text:00401091 mov ebp, esp
.text:00401093 sub esp, 78h
.text:00401096 mov eax, [ebp+arg_8]
.text:00401099 mov [ebp+var_C], eax
.text:0040109C mov eax, [ebp+arg_4]
.text:0040109F mov [ebp+var_5C], eax
.text:004010A2 mov [ebp+var_60], 0Ah
.text:004010A9 mov [ebp+var_58], 41h
.text:004010AD mov eax, [ebp+var_5C]
.text:004010B0 mov [esp+78h+var_74], eax
.text:004010B4 mov eax, [ebp+var_60]
.text:004010B7 mov [esp+78h+var_78], eax
.text:004010BA call _bar
.text:004010BF leave
.text:004010C0 retn

了解了上面注释的一些概念后,来分析一下:

  1. 首先demo_stackframe使用了3个参数,a、b、c。他们分别是arg_0,arg_4,arg_8(右边先进栈,那么就在最底下)

  2. 局部变量x由参数c初始化,而c为arg_8,因此var_C与x对应

  3. 同样arg_4与b对应,那么var_5C与y对应

  4. 局部变量z与var_60对应

  5. buffer从var_58开始,对应的是字符A(ASCII 0x41)

  6. mov [esp+78h+var_74], eax该指令,要拆开来看,因为提升堆栈: sub esp, 78h,所以esp+78h=ebp,那么就等于[esp+78h+var_74]=[ebp+var_74]=[esp+4],因为esp与ebp间隔为78。当我们需要call一个函数,会push下一个地址,那么[esp+4]就会变成[esp+8]。

  7. 同样的: mov [esp+78h+var_78], eax当执行call _bar之后,那么就会变成[esp+4],arg_0。

  8. 也就是call指令没有通过push的方式压入栈。


原文始发于微信公众号(loochSec):IDE PRO学习-栈帧

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月2日22:33:38
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   IDE PRO学习-栈帧http://cn-sec.com/archives/2263477.html

发表评论

匿名网友 填写信息