CTF Book
  • 关于本书
  • Web
    • 信息搜集
    • 注入攻击
      • SQL注入
      • XML实体注入
      • SSTI 服务器模板注入
    • 前端安全
      • XSS 跨站脚本攻击
      • CSRF 跨站请求伪造
      • Html5 特性
    • 常见漏洞
      • SSRF 服务端请求伪造
      • File upload 文件上传漏洞
      • Web Cache 欺骗攻击
    • 特定场景漏洞
      • AWS 漏洞系列
        • S3 Bucket信息泄露
      • 未授权访问漏洞
        • redis未授权访问
        • CouchDB未授权访问
        • Docker Remote API未授权访问
        • memcache 未授权访问
        • Jenkins 未授权访问
        • PHP-FPM未授权访问
        • rsync 未授权访问
        • Mongodb未授权访问
      • 服务器配置问题
        • Apache Tomcat样例目录session操纵漏洞
    • PHP 安全
      • PHP 反序列化漏洞
      • PHP 代码审计小结
      • PHP 伪协议总结
      • PHP 内存破坏漏洞
      • PHP mail header injection
      • PHP 弱类型安全总结
      • PHP 各版本特性
    • 逻辑漏洞
    • CTF-Web Trick
  • PWN
    • pwntools简要教程
    • 从任意地址写到控制执行流的方法总结
  • Reverse
    • 栈、栈帧与函数调用
  • Crypto
    • RSA 安全问题
  • Misc
    • zip总结
  • Others
    • CTF常用工具整理
    • 渗透测试工具备忘录
Powered by GitBook
On this page
  • 栈
  • 栈帧
  • 函数调用

Was this helpful?

  1. Reverse

栈、栈帧与函数调用

Previous从任意地址写到控制执行流的方法总结NextRSA 安全问题

Last updated 5 years ago

Was this helpful?

栈

栈是一个先入后出队列。关于算法栈,请自行搜索或参考: 。 在操作系统中,一般用栈来保存函数的状态和局部变量。

Linux的栈位于程序内存空间的末端,从高地址向低地址增长。 在x86架构中,使用esp寄存器指向栈顶的内存地址;在x64架构中,使用rsp寄存器指向栈顶。一般可以简称为sp。

栈帧

栈帧是指函数在被调用时,所拥有的一块独立的用于存放函数所使用的状态和变量的栈空间。

每个函数都对应有一个栈帧。同一个函数多次进入,每次可能会分配到不同的栈帧。整个栈的内容在同一个时刻可以看作是由许多栈帧依序“堆叠”组成的。

对于一个运行中的函数,其使用的栈帧区域被sp和bp寄存器限定(对于x86,sp等价esp,bp等价rsp;对于x64,sp等价rsp,bp等价rbp)。bp指向栈帧的底部,sp指向栈帧的顶部。

(图片来源:)

在函数中使用的所有变量(本地变量、实参),一般使用bp定位。设N为整型字节数,bp+2N是第一个实参的地址,bp-N是第一个本地变量的地址。

函数调用

一个函数调用,一般需要以下步骤

  1. 保存函数的实参

  2. 保存子函数结束后,需要返回的地址(返回到哪里)

  3. 保存父函数的栈帧信息

  4. 在栈上开辟空间供局部变量使用

  5. 执行函数实现的功能

  6. 释放局部变量使用的空间

  7. 根据保存的父函数栈帧信息,恢复父函数栈帧

  8. 根据保存的返回地址,恢复父函数执行流,一般是函数调用指令后的下一条指令

共有多种函数调用方式,这里介绍一种:stdcall。stdcall是标准调用约定,还有其他调用约定:cdecl、PASCAL、fastcall、thiscall,可以自行搜索。

在stdcall中,调用一个函数func(a,b,c)会有一些比较固定的代码(x86架构)。

父函数调用时,会把参数从右至左入栈,实现保存函数实参的功能:

push c  
push b  
push a

然后执行call指令:

call func

这里call指令内部实际上做了两个工作,一个是将这个call指令的下一条语句入栈,实现返回地址的保存。然后把执行流跳转到函数里。所以一个call指令从功能上可以拆分为以下两个指令:

push 本call指令下一条指令的地址  
jmp func

执行流到了func函数内部,会先进行父函数栈帧信息的保存。此时esp和ebp依然维持父函数的栈帧。 当前子函数所有的栈中变量被释放后,esp会回到函数调用前的状态,因此无需保存esp,只要保存ebp的信息即可。

push ebp

此时,子函数的栈帧底部变到esp处:

mov ebp, esp

栈帧底部设置完毕后,可以为局部变量开辟空间,这里开辟了一个32(0x20)字节的栈空间:

sub esp, 20h

注意栈是从高地址到低地址增长的,故这里做减法。

然后就可以开始进行函数的操作,通过ebp定位函数的参数和局部变量空间,并将函数返回值放在eax寄存器中。

在函数的功能代码全部执行完毕后,释放之前开辟的栈空间:

add esp, 20h

此时esp恢复到压入ebp之后的状态,这时栈顶为父函数的ebp值,可以依据这个信息恢复父函数的ebp,进而恢复栈帧:

pop ebp

当前栈顶为返回地址,这时父函数的栈信息已经恢复,只要根据这个返回地址更改执行流,回到父函数call func指令的下一条指令即可。

retn

至此,一个函数的调用流程结束,栈的状态和调用前完全一致,子函数的返回值被存在eax寄存器中。

https://zh.wikipedia.org/wiki/堆栈
http://witmax.cn/c-function-heap-stack.html