PWN入门笔记-函数调用出入栈逻辑

admin 2026-01-26 14:47:56 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文档详解PWN入门中函数调用的栈逻辑。涵盖栈的基本结构及后进先出特性,解析call、leave、ret指令原理。重点对比32位与64位在参数传递及调用约定上的差异,如cdecl约定。深入剖析函数调用与返回时栈帧的建立与销毁过程,包括EBP和ESP指针变化,为二进制安全学习奠定基础。 综合评分: 84 文章分类: 二进制安全,CTF,漏洞分析,逆向分析


cover_image

PWN入门笔记-函数调用出入栈逻辑

无名氏 无名氏

绿洲安全

2026年1月26日 08:01 北京

免责声明

由于传播、利用本公众号绿洲安全所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号绿洲安全及作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢

基本栈介绍 ¶

栈是一种典型的后进先出 (Last in First Out) 的数据结构,其操作主要有压栈 (push) 与出栈 (pop) 两种操作,如下图所示(维基百科)。两种操作都操作栈顶,当然,它也有栈底。

高级语言在运行时都会被转换为汇编程序,在汇编程序运行过程中,充分利用了这一数据结构。每个程序在运行时都有虚拟地址空间,其中某一部分就是该程序对应的栈,用于保存函数调用信息和局部变量。此外,常见的操作也是压栈与出栈。需要注意的是,程序的栈是从进程地址空间的高地址向低地址增长的

EBP为帧基指针, ESP为栈顶指针

调用(call):将当前的指令指针EIP(该指针指向紧接在call指令后的下条指令)压入堆栈,以备返回时能恢复执行下条指令;然后设置EIP指向被调函数代码开始处,以跳转到被调函数的入口地址执行。

离开(leave): 恢复主调函数的栈帧以准备返回。等价于指令序列movl %ebp, %esp(恢复原ESP值,指向被调函数栈帧开始处)和popl %ebp(恢复原ebp的值,即主调函数帧基指针)。

返回(ret):与call指令配合,用于从函数或过程返回。从栈顶弹出返回地址(之前call指令保存的下条指令地址)到EIP寄存器中,程序转到该地址处继续执行(此时ESP指向进入函数时的第一个参数)。若带立即数,ESP再加立即数(丢弃一些在执行call前入栈的参数)。使用该指令前,应使当前栈顶指针所指向位置的内容正好是先前call指令保存的返回地址。

需要注意,32 位和 64 位程序有以下简单的区别

  • x86

  • 函数参数

    函数返回地址的上方

  • x64

  • System V AMD64 ABI (Linux、FreeBSD、macOS 等采用) 中前六个整型或指针参数依次保存在 RDI, RSI, RDX, RCX, R8 和 R9 寄存器中,如果还有更多的参数的话才会保存在栈上。

  • 内存地址不能大于 0x00007FFFFFFFFFFF,6 个字节长度,否则会抛出异常。

调用约定

cdecl

又称C调用约定,是C/C++编译器默认的函数调用约定。所有非C++成员函数和未使用stdcall或fastcall声明的函数都默认是cdecl方式。函数参数按照从右到左的顺序入栈函数调用者负责清除栈中的参数返回值在EAX中。由于每次函数调用都要产生清除(还原)堆栈的代码,故使用cdecl方式编译的程序比使用stdcall方式编译的程序大(后者仅需在被调函数内产生一份清栈代码)。但cdecl调用方式支持可变参数函数(即函数带有可变数目的参数,如printf),且调用时即使实参和形参数目不符也不会导致堆栈错误。对于C函数,cdecl方式的名字修饰约定是在函数名前添加一个下划线;对于C++函数,除非特别使用extern “C”,C++函数使用不同的名字修饰方式。

32位

call = push rip, jmp

函数调用分为以下几个步骤:

参数入栈: 将参数按照调用约定(C 是从右向左)依次压入系统栈中;

然后执行call指令:

返回地址入栈: 将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行;

代码跳转: 处理器将代码区跳转到被调用函数的入口处;

栈帧调整(这里rip已经进入到被调用的函数里面了):

1.将调用者的ebp压栈处理,保存指向栈底的ebp的地址(方便函数返回之后的现场恢复),此时esp指向新的栈顶位置; push ebp(把ebp地址压栈)

2.将当前栈帧切换到新栈帧(将esp值装入ebp,更新栈帧底部), 这时ebp指向栈顶,而此时栈顶就是old ebp。 mov ebp, esp

 3.给新栈帧分配空间 sub esp, XXX

函数返回分为以下几步:

保存被调用函数的返回值到 eax 寄存器中 mov eax, xxx

恢复 esp 同时回收局部变量空间 mov esp, ebp

将上一个栈帧底部位置恢复到 ebp。 pop ebp(把esp地址里面的值赋给ebp)

弹出当前栈顶元素,从栈中取到返回地址,并跳转到该位置 ret

ret: pop eip; esp+4

执行leave之后地址情况:

 64位

前6个参数依次存放于 rdi、rsi、rdx、rcx、r8、r9 寄存器中

第7个以后的参数存放于栈中


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:绿洲安全 无名氏 无名氏《PWN入门笔记-函数调用出入栈逻辑》

评论:0   参与:  0