『CTF』Pwn入门之数据栈

admin 2025年4月7日01:02:43评论12 views字数 3370阅读11分14秒阅读模式

日期: 2025-04-03

作者: Mr-hello

介绍: 一些简单的数据栈知识。

0x01基础知识、概念

众所周知,一个程序的运行需要占用系统的内存,例如,一个由 C/C++ 编写的程序占用的内存分为如下几个部分:

  • 栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量的值等。

  • 堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由 OS 回收。(与数据结构中的堆结构不同)

  • 全局区、静态区(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。

  • 文字常量区:常量字符串就是放在这里的,程序结束后由系统释放。

  • 程序代码区:存放函数体的二进制代码。

#include<stdio.h>int a = 0;//全局初始化区char *p1;//全局未初始化区main(){int b;//栈char s[] = "abc";//栈char *p2;//栈char *p3 = "123456";//字符串123456在常量区,p3在栈上。static int c = 0;//全局(静态)初始化区p1 = (char *)malloc(10);p2 = (char *)malloc(20);//分配得来得10和20字节的区域就在堆区。strcpy(p1, "123456");//字符串123456放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。}

0x02数据栈结构、特性

在数据结构中的数据栈结构中,遵循 LIFO (后进先出,Last In First Out)的顺序。

程序中的数据栈结构和数据结构中的栈是类似的,在程序的运行过程中,数据栈就是用来支持函数的调用和执行。栈的申请和释放是由系统层面进行操作的,用户层无法进行操作。并且数据栈的效率高,使用方便,在 intel x86 的系统中,堆栈在内存中是从高地址向低地址扩展(这和自定义的堆栈从低地址向高地址扩展不同),如下图所示:

『CTF』Pwn入门之数据栈

因此,栈顶地址是不断减小的,越后入栈的数据,所处的地址也就越低。在32位系统中,堆栈每个数据单元的大小为4字节。小于等于4字节的数据,比如字节、字、双字和布尔型,在堆栈中都是占4个字节的,大于4字节的数据在堆栈中占4字节整数倍的空间。

和数据栈的操作相关的两个寄存器是 EBP 寄存器和 ESP 寄存器,通俗易懂的去讲,EBP 和 ESP 是两个指针,ESP 指针指向数据栈的栈顶,当向数据栈中压入数据时,ESP 指针执行 -4 操作,然后将数据写入 ESP 指向的地址。向数据栈中取出数据的时候,先将 ESP 指针指向的地址上储存的数据取出到内存地址/寄存器中,然后 ESP 指针执行 +4 操作。EBP 指针是用来访问数据栈中的数据。它指向堆栈中间的某个位置(具体位置后文会具体讲解),函数的参数地址比 EBP 的值高,而函数的局部变量地址比 EBP 的值低,因此参数或局部变量总是通过 EBP 加减一定的偏移地址来访问的,比如,要访问函数的第一个参数为 EBP+8

数据栈中存储的数据包括:函数的参数、函数的局部变量、函数的返回地址、寄存器的值(用以恢复寄存器)以及用于结构化异常处理的数据。这些数据是按照一定的顺序组织在一起的,我们称之为一个堆栈帧。一个堆栈帧对应一次函数的调用。在函数开始时,对应的堆栈帧已经完整地建立了(所有的局部变量在函数帧建立时就已经分配好空间了,而不是随着函数的执行而不断创建和销毁的),在函数退出时,整个函数帧将被销毁。

0x03数据栈的建立

接下来,我们去了解一下数据栈的建立过程,上文提到,数据栈是用来支持函数的调用和执行,所以我们使用下面代码进行函数调用时数据栈工作机制的讲解。

int function1(int m, int n){int p = m * n;return p;}int function(int a, int b){int c = a + 1;int d = b + 1;int e = function1(c,d);return e;}int main(){int result = function(3,4);return 0;}

我们从 main 函数执行的第一行代码,即int result = function(3,4);开始跟踪。这时 main 以及之前的函数对应的堆栈帧已经存在在堆栈中,如下图所示:

『CTF』Pwn入门之数据栈

参数入栈

当 function 函数被调用,首先,caller(此时 caller 为 main 函数)把 function 函数的两个参数:a=3,b=4 压入堆栈。参数入栈的顺序是由函数的调用约定决定的,这里不做详细介绍。一般来说,参数都是从右往左入栈的,因此,b=4 先压入堆栈,a=3 后压入,如图:

『CTF』Pwn入门之数据栈

返回地址入栈

我们知道,当函数结束时,代码要返回到上一层函数继续执行,那么,函数如何知道该返回到哪个函数的什么位置执行呢?函数被调用时,会自动把下一条指令的地址压入堆栈,函数结束时,从堆栈读取这个地址,就可以跳转到该指令执行。如果当前 call function 指令的地址是 0x00001482,由于 call 指令占 5 个字节,那么下一个指令的地址为0x00001487,将被压入堆栈:

『CTF』Pwn入门之数据栈

代码跳转到被调用函数执行

返回地址入栈后,代码跳转到被调用函数 function 中执行。到目前为止,堆栈帧的前一部分,是由 caller 构建的;而在此之后,堆栈帧的其他部分是由 callee 来构建。

EBP指针入栈

在 function 函数中,首先将 EBP 寄存器的值压入堆栈。因为此时 EBP 寄存器的值还是归属于 main 函数,用来访问 main 函数的参数和局部变量,因此需要将它暂存在堆栈中,在 function 函数退出时恢复。同时,给 EBP 赋予新值。

  • 将 EBP 压入堆栈
  • 把 ESP 的值赋给 EBP
『CTF』Pwn入门之数据栈
这样一来,我们很容易发现当前 EBP 寄存器指向的堆栈地址就是 EBP 先前值的地址,你还会发现,EBP+4 的地址就是函数返回值的地址,EBP+8 通常是函数的第一个参数的地址(第一个参数地址并不一定是 EBP+8)。因此,通过 EBP 很容易查找函数是被谁调用的或者访问函数的参数(或局部变量)。

为局部变量分配地址

接着,function 函数将为局部变量分配地址。程序并不是将局部变量一个个压入堆栈,而是将 ESP 减去某个值,直接为所有的局部变量分配空间,比如在 function 函数中有 ESP=ESP-0x00E4,如图所示:

『CTF』Pwn入门之数据栈
编译器为局部变量分配的空间远远大于实际所需,而且局部变量之间的地址不一定是连续的,如下图所示:
『CTF』Pwn入门之数据栈

通用寄存器入栈

最后,将函数中使用到的通用寄存器入栈,暂存起来,以便函数结束时恢复。在 function 函数中用到的通用寄存器是 EBX,ESI,EDI,将它们压入堆栈,如图所示:

『CTF』Pwn入门之数据栈
至此,一个完整的堆栈帧建立起来了。

0x04数据栈的销毁

数据栈的销毁和数据栈的创建是相反的。当函数做完所有操作时候,将返回值写入到相应位置后,函数开始清理数据栈。

>> 如果有对象存储在堆栈帧中,对象的析构函数会被函数调用。>> 从堆栈中弹出先前的通用寄存器的值,恢复通用寄存器。>> ESP加上某个值,回收局部变量的地址空间(加上的值和堆栈帧建立时分配给局部变量的地址大小相同)。>> 从堆栈中弹出先前的EBP寄存器的值,恢复EBP寄存器。>> 从堆栈中弹出函数的返回地址,准备跳转到函数的返回地址处继续执行。>> ESP加上某个值,回收所有的参数地址。

0x05总结

熟悉并了解堆栈工作原理,是一个二进制选手的基本功。因为只有充分了解了堆栈工作机制,才可能根据代码执行流程中发现存在的逻辑错误点,进而进行后续溢出等工作。例如在数据栈的销毁篇章中,在第5步进行弹出函数的返回地址,此处如果在函数操作过程中,某变量进行了溢出操作,从而导致返回地址被改写。那么我们就可以做到劫持程序,从而运行我们需要的函数。

免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。

点此亲启

原文始发于微信公众号(宸极实验室):『CTF』Pwn入门之数据栈

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年4月7日01:02:43
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   『CTF』Pwn入门之数据栈https://cn-sec.com/archives/3912904.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息