PWN的简单了解
有点想转pwn了,那个web比赛什么都做不出来,我也不知道我学了啥了,学艺不精,痛啊,看见pwn很多的解都比web高,我们这一届也没人学,很吃亏,而且了解了一下pwn,发现pwn攻击的是设备系统,有点心动,但是据说pwn入门很难,还需要一点逆向基础,我就想先了解一下转pwn需要什么前置知识基础,顺便将需要安装的软件和工具配置好,然后后续就打算从做题入手
一、PWN简介
Pwn 是一个骇客语法的俚语词,自"own"这个字引申出来的,这个词的含意在于,玩家在整个游戏对战中处在胜利的优势,或是说明竞争对手处在完全惨败的情形下,这个词习惯上在网络游戏文化主要用于嘲笑竞争对手在整个游戏对战中已经完全被击败(例如:"You just got pwned!")。
在骇客行话里,尤其在另外一种电脑技术方面,包括电脑(服务器或个人电脑)、网站、闸道装置、或是应用程序,"pwn"在这一方面的意思是攻破("to compromise",危及、损害)或是控制("to control")。在这一方面的意义上,它与骇客入侵与破解是相同意思的。例如某一个外部团体已经取得未经公家许可的系统管理员控制权限,并利用这个权限骇入并入侵("owned" 或是 "pwned")这个系统。
pwn可以用来进行客户端攻击和挖取二进制漏洞,感觉完全就是小时候一直看到的黑客啊
听起来很帅,心动了,但看看所需的前置技能心都碎了
1.pwn的保护机制
1.1PIE随机地址保护
地址每次运行都是随机的,因为要覆盖返回地址,得知道要执行的代码要去哪,所以要知道shellcode(一段可执行代码,通常由攻击者编写并注入到目标程序中,以实现未授权的操作,如获取目标系统的shell或执行其他恶意行为。是攻击者用来利用程序漏洞并绕过地址随机化保护的一种工具)的地址,当开启开地址随机化后,就没办法知道了。
有两种绕过方式:
第一种就是一旦泄露出一个变量的地址,就相当于稍微加减计算一下就可以得到所有的地址了
第二种就是覆盖地址只覆盖2字节,也就是4个数字,也就是说只有第4个数字不知道,可以一个个试,只是很麻烦而且这个方法只适合小端序的程序
1.2Canary保护
栈溢出保护是一种缓冲区溢出攻击缓解手段,在ebp的上面,会压入一个Canary的值,在子函数验证完之后,对比Canary的值,看看是否相等。不相等,代表程序被修改,产生了异常
1.3NX保护机制
No-eXecute(不可执行),基本原理是将数据所在内存页标识为不可执行,其实就是让我们不能直接利用程序中的某一段代码或者自己填写代码来获得 shell
2.pwn中常见的漏洞
1>栈溢出(Stack Overflow)
栈溢出是一种常见的安全漏洞,它利用了程序在执行过程中使用的栈内存空间有限的特性。栈是一种数据结构,用来存储函数的局部变量、函数的参数以及函数调用的返回地址等信息。栈的特点是先进后出,即最后进入栈的数据最先被访问到。当攻击者向程序输入过多的数据时,这些数据会超出栈内存所能容纳的范围,从而覆盖了栈中的其他数据,甚至覆盖了函数返回地址。一旦返回地址被篡改,程序就会跳转到攻击者指定的代码执行,从而实现任意代码执行的攻击。
2>堆溢出(Heap Overflow
堆溢出是另一种内存溢出漏洞,但与栈溢出不同,它发生在程序的堆内存区域。堆是用来动态分配内存的区域,程序员可以请求分配任意大小的内存块,并在程序运行期间随时释放它们。堆溢出通常是由于程序在写入数据时超出了申请的内存块大小,导致数据覆盖了相邻的内存块。
3>整数溢出(Integer Overflow)
整数溢出发生在将一个较大的整数赋值给一个较小范围的整数变量时,导致数据超出该变量的存储范围并发生溢出。这种溢出可能导致数据被截断、覆盖或产生不正确的计算结果。攻击者可以利用整数溢出漏洞来绕过安全限制、绕过认证机制或执行其他恶意操作。
4>格式化字符串漏洞(Format String Vulnerability)
格式化字符串漏洞通常发生在C语言等编程语言中,当程序不正确地处理格式化字符串函数(如printf、sprintf等)的输入时。攻击者可以通过构造特制的格式化字符串来读取或写入任意内存地址的数据,甚至执行任意代码。
二、所需前置知识基础
1.Linux
pwn题目给出的文件一般是Linux系统的文件,想要入门pwn,最起码要把文件跑起来,所以Linux的知识是必不可少的
如果想要快速入门,不在于对于知识的精通,学一些常用就行了,但有时间最好系统学习
能够把文件跑起来,知道怎么用命令行,怎么安装工具,就差不多行了
2.C语言
pwn题所有的文件都是用C语言编写的,在反汇编时反出来的也是C语言代码,代码真的很重要,要是连代码都看不懂还怎么找漏洞啊
3.汇编语言
即使反汇编工具能够把汇编语言转成C语言,能看懂汇编语言还是学习pwn的必要条件,不管是分析栈还是分析地址,汇编语言都是很重要的工具。
mov、lea、add、sub、ret、call、push、pop,把这几个彻底弄清楚就差不多能应付前期的学习了。遇到了新的汇编指令,到时候再学就好了,看别的大佬的博客,说其他的不用研究的太深,不然几年都入不了门啊
4.反汇编
在传统的软件开发模型中,程序员使用编译器,汇编器和链接器中的一个或几个创建可执行程序。为了回溯编程过程(或对程序进行逆向工程),使用各种工具来撤销汇编和编译过程。这些工具就叫做反汇编器和反编译器。反汇编器撤销汇编过程,可以得到汇编语言形式的输出结果(以机器语言作为输入),反编译器则以汇编语言甚至是机器语言为输入,其输出结果为高级语言。
反汇编就是把目标代码(目标代码可以理解成能够被CPU识别并执行的代码)转为汇编代码的过程,也可以说是把机器语言转换为汇编语言代码,低级转高级的意思
通常,使用反汇编工具是为了在没有源代码的情况下促进对程序的了解。需要进行反汇编的常见情况包括以下几种。
- 分析恶意软件。
- 分析闭源软件的漏洞。
- 分析闭源软件的互操作性。
- 分析编译器生成的代码,以验证编译器的性能和准确性。
- 在调试时显示程序指令。
5.计算机组成原理
分析程序的时候时常要跟寄存器啥的打交道,还有十六进制地址之类的,但是pwn毕竟不是一个太底层的东西,了解一些概念,以及怎么计算地址,什么间址寻址、间址寻址那些就差不多了。
6.python
在后期编写exp时会用到
那什么又是exp呢?
exp的全称是exploit,用于攻击的脚本与方案
7.elf文件结构
elf文件是Linux下的可执行文件,Windows下的是pe
目标文件就是源代码经过编译后但未进行链接的那些中间文件(Windows的.obj
和Linux的.o
),它与可执行文件的格式非常相似,所以一般跟可执行文件格式一起采用同一种格式存储。
顺便讲一下payload是攻击载荷,是对目标进程劫持控制流的数据;shellcode是调用攻击目标的shell代码,常见的有bash和sh。Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。二者的区别在于sh 遵循POSIX规范:“当某行代码出错时,不继续往下解释”。bash 就算出错,也会继续向下执行。而POSIX表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为 POSIX )。POSIX标准意在期望获得源代码级别的软件可移植性。换句话说,为一个POSIX兼容的操作系统编写的程序,应该可以在任何其它的POSIX操作系统上编译执行。
8.工具的使用
常用到的工具有radare2,pwntools,checksec,gdb,pwndbg,LibcSearch,glibc-all-in-one,patchelf,Ghidra,one_gadget
三、工具软件的安装
1.pwntools
pwntools是一个用python编写的CTF框架和开发库,旨在快速构建原型和开发,并旨在使编写利用脚本尽可能简单。
可以通过命令安装python3下的pwntools包
首先安装pip:
apt install python3-pip
然后就可以安装pwntools:
pip install pwntools
回显是这样的表示安装成功,可以继续下一步
可以验证一下
进入python3输入 from pwn import *
如图显示安装成功,进入python后退出使用ctrl+D
菜鸟笔记之pwn工具篇--pwntools库的基本使用-CSDN博客
PwnTools使用技巧 - ONE_ZJ - 博客园
2.checksec(动态分析)
checksec是一个脚本软件,用于检测二进制文件安全特性,可以用来查看可执行文件的程序架构信息和保护信息。它通过检查一些特定的二进制文件头信息和节段属性,来确定一个程序是否受到了常见的攻击方式的防护,例如栈溢出和ROP攻击
可以发现kali中已经有checksec了
使用 checksec
命令时后面跟要检查的二进制文件的路径
如何使用 checksec 检查 Linux 系统的安全特性-CSDN博客
3.ROPgadget(动态分析)
ROPgadget这个工具允许你在二进制文件中搜索代码gadget片段,以方便ROP的利用。ROPgadget支持x86、x64、ARM、ARM64、PowerPC、SPARC和MIPS架构上的ELF/PE/Mach-O格式。
安装python-capstone:
sudo apt-get install python3-capstone
下载安装文件:
git clone https://github.com/JonathanSalwan/ROPgadget.git
进入目录 :
cd ROPgadget
运行安装脚本:
python3 setup.py develop
安装python-capstone
下载安装文件
运行安装脚本,然后可以通过如下命令检测是否安装成功,如返回版本信息说明成功
ROPgadget --version
使用方法:
搜索/bin/sh:
ROPgadget --binary intoverflow “/bin/sh”
Rop gadgets搜索工具 Ropper 的安装与使用 - robotech_erx - 博客园
ROPgadget使用-CSDN博客
4.one_gadget
one-gadget 是glibc里调用execve(’/bin/sh’, NULL, NULL)的一段非常有用的gadget,就是用来去查找动态链接库里execve("/bin/sh", rsp+0x70, environ)函数的地址的。在我们能够控制ip(也就是pc)的时候,用one-gadget来做RCE(远程代码执行)非常方便,比如有时候我们能够做一个任意函数执行,但是做不到控制第一个参数,这样就没办法调用system(“sh”),这个时候one-gadget就可以搞定了。
通过命令安装
apt install ruby
apt install gem
gem install one_gadget
这里需要是红kali
这样就安装成功了
one_gadget 工具安装与使用教程-CSDN博客
5.libc database search
在进行libc基址泄露的时候,常常不知道远程服务器用的是什么版本的libc,此时便可以通过libc database search这个网站来查询所用的libc版本,只需要将泄露的函数名和泄露的函数绝对地址或绝对地址的后12位填入表单中,点击查找则可以找到可能的libc版本。其原理是由于内存分配是以页为单位,而一个页是4K个字节,也就是需要用12位二进制来存储,所以libc在加载到内存中,低12位地址是固定不变的。
当然在kali中用到的是LibcSearcher
LibcSearcher是python的一个库,用于解决pwn中不明libc版本的情况,可以根据泄露的某函数地址,推测服务端使用的libc版本
可以通过如下命令安装
git clone https://github.com/lieanu/LibcSearcher.git
cd LibcSearcher
python3 setup.py develop
这样就可以了
6.gdb与peda、pwngdb、pwndbg(动态分析)组合安装
GDB 全称“GNU symbolic debugger”,从名称上不难看出,它诞生于 GNU 计划,是 Linux 下常用的程序调试器。发展至今,GDB 已经迭代了诸多个版本,当下的 GDB 支持调试多种编程语言编写的程序,包括 C、C++、Go、Objective-C、OpenCL、Ada 等。实际场景中,GDB 更常用来调试 C 和 C++ 程序。而pwndbg (/poʊndbæg/)是一个GDB插件,它使得用GDB进行调试变得不那么麻烦,且更加专注于底层软件开发人员、硬件黑客、反向工程师和开发人员所需的特性。而pwndbg是GDB上的一种插件,可以使得调试功能更强大。
GDB一般在安装C/C++编译套件的时候会被自动安装到系统中
默认把peda、pwngdb、pwndbg都安装用户的根目录下,可以减少一些文件的改动。
进入根目录后先下载peda
git clone https://github.com/longld/peda.git
然后是Pwngdb
git clone https://github.com/scwuaptx/Pwngdb.git
忍不住吐槽一下学校的公网了,一直装不上,切热点就成功了
然后是pwndbg
git clone https://github.com/pwndbg/pwndbg
这里可以查看一下根目录
可以看到都成功了
gdb+pwndbg的常用基本指令:
r :开始运行程序
b [funcname/*addr] :在某个函数起始处或某个指令地址处下断点
c :继续执行程序
ni :单步步过
s :单步步入
stop :停止执行
q :退出gdb
stack [num] :查看栈帧
info b :查看断点详细信息
delete:删除所有断点
delete [break point num] : 根据断点号删除指定的断点,用空格隔开可以删除多个断点
vmmap :查看程序内存结构
aslr on/off :打开/关闭aslr
got :查看got表信息
plt :查看plt表信息
x/[n/f/u] [addr] :查看任意内存位置的值
n:是正整数,表示需要显示的内存单元的个数,即从当前地址向后显示n个内存单元的内容,一个内存单元的大小由第三个参数u定义。f:表示addr指向的内存内容的输出格式,s对应输出字符串,此处需特别注意输出整型数据的格式:
x 按十六进制格式显示变量.
d 按十进制格式显示变量。
u 按十进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。
c 按字符格式显示变量。
f 按浮点数格式显示变量。u:就是指以多少个字节作为一个内存单元unit,默认为4。u还可以用被一些字符表示:
如b=1 byte, h=2 bytes,w=4 bytes,g=8 bytes.addr:表示内存地址。
演示如:x/22dw 0x0804A220
7.radare2
radare2 是一个 UNIX-like 的逆向工程框架和命令行工具集
可以运用如下代码安装
sudo apt install -y git gcc libssl-dev pkg-config libz-dev libradare2-dev
git clone https://github.com/radareorg/radare2.git
cd radare2
sysconfdir=/usr/share ./sys/install.sh
安装编译依赖
克隆git库
编译和安装
然后可以使用命令检测是否安装成功,这个命令会显示其版本信息
radare2 -v
这样就是成功了
https://zhuanlan.zhihu.com/p/602894743
Kali Linux工具radare2(也称为r2)-CSDN博客
8.kali中vscod的安装及C/C++和python环境配置
先从官网下载压缩包
放到kali中,我是图方便直接在桌面建了一个vscode的文件夹,接下来就可以解压安装了
dpkg -i /home/cyy/Desktop/vscode/vscode.deb
这个命令还是去帮老师给kali系统的电脑装软件的时候学到的,后面跟的是绝对路径/文件名,之前试了一些别的都没用,还是这个管用
这样就可以了
完成后在程序应用中可找到
接下来就是环境的安装了
先检查一下gcc是否安装,一般linux都是自带的
gcc -v
接下来就去vscode的插件商店安装C和python以及汉化插件
最后点开已安装的插件,结果如下显示就可以了,随便用个脚本测试一下正常能用就行了
9.IDA
是个交互式的反汇编器和调试器
IDA详细使用教程_ida使用教程-CSDN博客
IDA使用教程-CSDN博客
10.glibc-all-in-one
glibc包下载工具,可以查看下载不同的libc包,配合patchelf可以切换不同的libc版本。是一个方便的 glibc 二进制文件和调试文件下载器以及源代码自动构建工具
可以通过下面的命令下载安装
sudo git clone https://github.com/matrix1001/glibc-all-in-one.git
cd glibc-all-in-one/
sudo python3 update_list
cat list
sudo ./download 2.40-1ubuntu3_amd64
这样就算是安装完成了
11.patchelf
用于修改可执行文件的动态链接器,可以修改elf中的ld和libc路径的工具
git clone https://github.com/NixOS/patchelf
apt install patchelf
patchelf --version
可以看到版本号,说明安装成功
四、C语言函数调用栈原理
函数调用过程通常使用堆栈实现,每个用户态进程对应一个调用栈结构(call stack)。编译器使用堆栈传递函数参数、保存返回地址、临时保存寄存器原有值(即函数调用的上下文)以备恢复以及存储本地局部变量。也就是说可以将程序的执行过程看作是连续的函数调用
分析程序时,会经常跟寄存器之类的打交道,还有十六进制地址之类的,后悔了,这个上课就该好好听,想着没用,又得靠自己了
1.寄存器
说白了就是用来储存很多位二进制信息的,
要存储多个位(bit)的数据,需要用多个D触发器并联实现,这种电路就是寄存器
补充:发现触发器是什么也不知,服了,补一下,主要看一下D触发器吧,用得到
触发器是时序逻辑电路的基本单元,储存一位二进制信息,具有储存和记忆的功能,信息又双稳态电路(双稳态电路是一种具有两个稳定状态的电子电路。在没有外部输入信号的情况下,双稳态电路可以保持在两个不同的状态之一,通常被称为“高电平”和“低电平”,或者逻辑上的“1”和“0”。当外部输入信号作用于双稳态电路时,电路可以从一个稳定状态切换到另一个稳定状态。)来储存,触发器位的边缘敏感,分为上升沿敏感和下降沿敏感
D触发器是一种最简单的触发器,在边沿触发(当输入信号的边缘(上升沿或下降沿)到达设定阈值时触发相应动作或事件的过程。)到来时,将输入端的值存入其中,并且这个值与当前存储的值无关。在两个有效的脉冲触发的时间之间,D的跳转不会影响触发器储存的值,但是在脉冲边沿到来之前,输入端D必须有足够建立的时间以保持信号稳定
这里看到别人博客的图感觉帮助记忆,借用一下
寄存器是CPU内部的一种告诉存储单元(这里很好理解了嘛,一个D触发器储存一位二进制信息,多个就相当于聚在一起,能储存更多的二进制信息),用于临时存储数据和指令。寄存器是计算机中速度最快的存储器,直接与CPU的运算单元(如ALU)相连
寄存器的主要功能是临时存储处理过程中需要的数据、指令地址和计算结果。寄存器的读写速度非常快
常见寄存器:
ax寄存器:通用寄存器,可用于存放多种数据,保存临时数据,常用于返回值
bp寄存器:存放的是栈帧的栈底地址
sp寄存器:存放的是栈顶的地址
2.栈帧与栈工作的简介
进程中的栈区(stack)用于维护函数调用的上下文,没有栈就没法实现函数调用。栈用于存放函数(包括main函数)里的局部变量,函数调用时要传递的参数等。
在进程的内存空间中,栈是向下生长的,即栈底在高地址,栈顶在低地址,栈底固定住,栈从内存高地址->低地址的路径延伸。
栈帧是存储函数的一些信息的地方,栈帧存储有函数的局部变量,传递给子函数的实际参数,父函数的地址以及上一个栈帧栈底的地址,每一次函数调用时,都会在栈上为这次函数调用维护一个独立的栈帧。一个栈帧由两个寄存器来界定:
- ebp:指向栈底的指针,称为帧指针(Frame Pointer)
- esp:指向栈顶的指针,称为栈指针(Stack Pointer)
栈的特点是先使用高地址,后使用低地址
每一个函数调用都要在栈区创建一个空间
在函数调用的过程中,首先会讲bp寄存器的值进行压栈,以方便在恢复的时候恢复栈底寄存器的值,再之后,会按顺序将局部变量压栈,最后是子函数的实际参数,会按照先入栈的后出栈,从最后一个实际参数先入栈再到第一个实际参数,比如函数a(int a , int b),压栈的方式就是先压栈b的实际参数,再压栈a的实际参数,当这些压栈完成之后,就可以压栈父函数调用子函数的那条语句的下一条语句的地址,紧接着,可以压栈当前栈帧的栈底地址了。
3.缓冲区溢出漏洞
向定长的缓冲区中写入了超长的数据,造成了写入的数据覆盖了其他合法的内存区域。
补充:
缓冲区溢出是指程序运行时,向固定大小的缓冲区写入超过其容量的数据,多余的数据会越过缓冲区的边界覆盖相邻内存空间,从而造成溢出。缓冲区的大小是由用户输入的数据决定的,如果程序不对用户输入的超长数据进行长度检查,同时用户又对程序进行了非法操作或者错误输入,就会造成缓冲区溢出。
4.栈溢出之ret2text
程序在运行过程中,由于递归调用或线程创建过多,导致栈内存不足,从而引发程序错误。栈内存用于存储方法调用时的栈帧,每个方法调用都会在栈上分配一个栈帧,包含方法的局部变量、参数和返回地址等信息。当递归调用层次过深或线程创建过多时,栈内存会被迅速耗尽,导致栈溢出错误。
这里看到别的大佬举的一个例子挺好的
假设在栈中存在如下情况;
补充:
Return Address(返回地址):这是函数执行完毕后需要返回到的地址。当一个函数被调用时,调用该函数的下一条指令的地址会被压入栈中,这样当函数执行完毕后,就可以通过这个返回地址回到调用点继续执行。
Stack Frame Pointer(栈帧指针):这是一个指针,指向当前栈帧的起始位置。它用于访问栈帧中的局部变量和其他数据。在函数调用过程中,栈帧指针会随着栈帧的创建和销毁而变化。
Local Variables: buf(局部变量:buf):这是在栈帧中为局部变量分配的空间。在这个例子中,buf
可能是一个局部变量,比如一个数组或者缓冲区,用于存储函数内部的数据。局部变量的存储空间是在函数调用时分配的,当函数执行完毕并返回后,这些局部变量的存储空间会被释放。
下面是c的源码
int overflow()
{
char buf[8];
gets(buf);
}
补充:
这里的overflow
函数是一个示例,用于展示缓冲区溢出的潜在风险。这个函数的目的是演示如何通过不安全的编程实践导致安全漏洞。overflow一般用于exit的参数中,比如创建指针时,一般判断一下内存是否分配成功
char buf[8];
定义了一个字符数组 buf
,它有8字节的空间。这意味着它可以存储最多7个字符加上一个空字符('\0'
)来表示字符串的结束。
gets(buf);
调用了 gets
函数,这是一个不安全的函数,因为它不会检查用户输入的长度。如果用户输入的字符串超过7个字符,gets
将继续写入 buf
数组,超出其分配的空间。
因为gets没对用户输入内容进行长度限制,要是输入超过7个字符就会出现gets函数导致的缓冲区的溢出,覆盖掉了bp寄存器以及返回地址的值
这将覆盖 buf
数组后面的内存区域,这可能包括:
- 返回地址:函数调用结束后返回的地址。
- 栈帧指针:指向当前栈帧的起始位置。
- 其他局部变量:如果
buf
数组后面有其他局部变量,它们也可能被覆盖。
这里感觉大佬博客有点问题,因为有一个空字符/0,他的博客写的是最多输入8字符,但是算上/0应该是7个
可以通过如上的原理,将返回地址的值修改为程序自带的后门函数(如system(“/bin/sh”))中即可。
5.堆栈操作
函数调用时的具体步骤如下:
1) 主调函数将被调函数所要求的参数,根据相应的函数调用约定,保存在运行时栈中。该操作会改变程序的栈指针。
注:x86平台将参数压入调用栈中。而x86_64平台具有16个通用64位寄存器,故调用函数时前6个参数通常由寄存器传递,其余参数才通过栈传递。
2) 主调函数将控制权移交给被调函数(使用call指令)。函数的返回地址(待执行的下条指令地址)保存在程序栈中(压栈操作隐含在call指令中)。
3) 若有必要,被调函数会设置帧基指针,并保存被调函数希望保持不变的寄存器值。
4) 被调函数通过修改栈顶指针的值,为自己的局部变量在运行时栈中分配内存空间,并从帧基指针的位置处向低地址方向存放被调函数的局部变量和临时变量。
5) 被调函数执行自己任务,此时可能需要访问由主调函数传入的参数。若被调函数返回一个值,该值通常保存在一个指定寄存器中(如EAX)。
6) 一旦被调函数完成操作,为该函数局部变量分配的栈空间将被释放。这通常是步骤4的逆向执行。
7) 恢复步骤3中保存的寄存器值,包含主调函数的帧基指针寄存器。
8) 被调函数将控制权交还主调函数(使用ret指令)。根据使用的函数调用约定,该操作也可能从程序栈上清除先前传入的参数。
9) 主调函数再次获得控制权后,可能需要将先前的参数从栈上清除。在这种情况下,对栈的修改需要将帧基指针值恢复到步骤1之前的值。
步骤3与步骤4在函数调用之初常一同出现,统称为函数序(prologue);步骤6到步骤8在函数调用的最后常一同出现,统称为函数跋(epilogue)。函数序和函数跋是编译器自动添加的开始和结束汇编代码,其实现与CPU架构和编译器相关。除步骤5代表函数实体外,其它所有操作组成函数调用。
压栈(push):栈顶指针ESP减小4个字节;以字节为单位将寄存器数据(四字节,不足补零)压入堆栈,从高到低按字节依次将数据存入ESP-1、ESP-2、ESP-3、ESP-4指向的地址单元。
出栈(pop):栈顶指针ESP指向的栈中数据被取回到寄存器;栈顶指针ESP增加4个字节。
压栈操作将寄存器内容存入栈内存中(寄存器原内容不变),栈顶地址减小;出栈操作从栈内存中取回寄存器内容(栈内已存数据不会自动清零),栈顶地址增大。栈顶指针ESP总是指向栈中下一个可用数据。
调用(call):将当前的指令指针EIP(该指针指向紧接在call指令后的下条指令)压入堆栈,以备返回时能恢复执行下条指令;然后设置EIP指向被调函数代码开始处,以跳转到被调函数的入口地址执行。
离开(leave): 恢复主调函数的栈帧以准备返回。等价于指令序列movl %ebp, %esp(恢复原ESP值,指向被调函数栈帧开始处)和popl %ebp(恢复原ebp的值,即主调函数帧基指针)。
返回(ret):与call指令配合,用于从函数或过程返回。从栈顶弹出返回地址(之前call指令保存的下条指令地址)到EIP寄存器中,程序转到该地址处继续执行(此时ESP指向进入函数时的第一个参数)。若带立即数,ESP再加立即数(丢弃一些在执行call前入栈的参数)。使用该指令前,应使当前栈顶指针所指向位置的内容正好是先前call指令保存的返回地址。