栈溢出原理(一)系统栈的工作原理

0x01 内存的不同用途

缓冲区溢出(buffer overflow),在电脑学上是指针对程序设计缺陷,向程序输入缓冲区写入使之溢出的内容(通常是超过缓冲区能保存的最大数据量的数据),从而破坏程序运行、趁著中断之际并获取程序乃至系统的控制权。

缓冲区溢出原指当某个数据超过了处理程序回传堆栈地址限制的范围时,程序出现的异常操作。造成此现象的原因有:

  • 存在缺陷的程序设计
  • 尤其是C语言,不像其他一些高级语言会自动进行数组或者指针的堆栈区块边界检查,增加溢出风险。
  • C语言中的C标准库还具有一些非常危险的操作函数,使用不当也为溢出创造条件。

进程使用的内存按照功能大致分成以下 4 个部分:

  • 代码区:这个区域存储着被装入执行的二进制机器代码,处理器会到这个区域取指令并执行。
  • 数据区:用于存储全局变量等。
  • 堆区:进程可以在堆区动态地请求一定大小的内存,并在用完之后归还给堆区。动态分配内存和回收内存是堆区的特点。
  • 栈区:用于动态地存储函数之间的调用关系,以保证被调用函数在返回时恢复到母函数中继续执行。
    在这里插入图片描述
  • CPU是完成工作的工人。
  • 数据区、堆区、栈区等则是用来存放原料、半成品、成品等各种东西的场所。
  • 存在代码区的指令则告诉CPU要做什么,怎么做,到哪里去领原材料,用什么工具来做,做完以后把成品放到哪个货舱去。
  • 值得一提的是,栈除了扮演存放原料、半成品的仓库之外,它还是车间调度主任的办公室。
  • 程序中所使用的缓冲区可以是堆区、栈区和存放静态变景的数据区。缓冲区溢出的利用方法和缓冲区到底属于上面哪个内存区域密不可分。

程序中所使用的缓冲区可以是堆区、栈区和存放静态变量的数据区。

0x02 栈与系统栈

栈的最常见操作有两种:压栈(PUSH)、弹栈(POP)

用于标识栈的属性也有两个:栈顶(TOP)、栈底(BASE)。

PUSH:为栈增加一个元素的操作叫做PUSH,相当于在这摞扑克牌的最上面再放上—张。

POP:从栈中取出一个元素的操作叫做POP,相当于从这摞扑克牌取出最上面的一张。

TOP:标识栈顶位置,并且是动态变化的。每做一次PUSH操作,它都会自增1;相反,每做一次POP操作,它会自减1。栈顶元素相当于扑克牌最上面一张,只有这张牌的花色是当前可以看到的。

BASE:标识栈底位置,它记录着扑克牌最下面一张的位置。BASE用于防止栈空后继续弹栈(牌发完时就不能再去揭牌了)。很明显,一般情况下,BASE是不会变动的。

内存的栈区实际上指的就是系统栈。系统栈由系统自动维护,它用于实现高级语言中函数的调用。

扫描二维码关注公众号,回复: 13119609 查看本文章

0x03 函数调用

根据操作系统的不问、编译器和编译选项的不同,同一文件不同函数的代码在内存代码区中的分布可能相邻,也可能相离甚远,可能先后有序,也可能无序;但它们都在同一个PE文件的代码所映射的一个“节”里。

当CPU在执行调用func_A函数的时候,会从代码区中main函数对应的机器指令的区域跳转到func_A函数对应的机器指令区域,在那里取指并执行;当函数执行完闭,需要返会的时候,又会跳回到main函数对应的指令区域,紧接着调用func_A后面的指令继续执行main函数的代码。

当函数被调用时,系统栈会为这个函数开辟一个新的栈帧,并把它压入栈中。这个栈帧中的内存空间被它所属的函数独占,正常情况下是不会和别的函数共享的。当函数返回时,系统栈会弹出该函数所对应的栈帧。

0x04 寄存器与函数栈帧

ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈地上面-个栈帧的栈顶。

EBP:基址指针寄存器(extended base pointer)-其内存放着一个指针,该指针永远指向系统栈展上面一个栈帧的底部。

函数栈帧:ESP和EBP之间的内存空间为当前栈帧.EBP标识了当前栈帧的底部.ESP标识了当前栈帧的顶部。

  • 局部变量:为函数局部变量开辟的内存空间。
  • 栈帧状态值:保存前栈帧的顶部和底部(实际上只保存前栈帧的底部,前栈帧的顶部可以通过堆栈平衡计算得到),用于在本帧被弹出后恢复出上一个栈帧。
  • 函数返回地址:保存当前函数调用前的“断点”信息,也就是函数调用前的指令位置,以便在函数返回时能够恢复到函数被调用前的代码区中继续执行指令。
  • EIP:指令寄存器(Extended Instruction Pointer),其内存放着一个指针,该指针永远指向下一条等待执行的指令地址。

0x05 函数调用约定与相关指令

在Wndows平台中,这个指针一般是用ECX寄存器来传递的,但如果用GCC编译器编译,这个指针会作为最后一个参数压入栈中。

函数返回的步骤如下:
保存返回值:通常将函数的返回值保存在寄存器EAX中。
弹出当前栈帧,恢复上一个栈帧。
跳转:按照函数返回地址跳同母函数中继续执行。

猜你喜欢

转载自blog.csdn.net/single7_/article/details/110218620