栈溢出原理笔记(一)

系统栈的工作原理

1.内存的不同用途
        简单来说,缓冲区溢出就是在大缓冲区的数据复制到小缓冲区中,由于没注意小缓冲区的边界,”撑爆“了小缓冲区。从而冲掉了小缓冲区相邻内存区域的数据。
根据不同的操作系统,一个进程可能被分配到不同内存区域中去执行,但是不管什么样的系统,什么计算机架构,进程使用的内存都可以按照功能分为4部分:
        代码区:可执行指令
        数据区:用于存储全局变量
        堆区:  进程可以在堆区动态的请求一定大小的内存,并在用完之后归还给堆区。动态分配和回收是堆区的特点
        栈区:  用于动态的存储函数之间的调用关系,以保证被调用函数返回时恢复到母函数中继续执行
        这只是简单的内存划分,如果想了解关于内存更详细的论述,请参考《深入理解计算机系统》,windows下,PE文件代码段中包含的二进制机器代码会被装入内存的代码区,处理器将到这里一条一条的取出指令和操作数,送入算术逻辑单元运算,如果代码请求开辟动态内存,则会在内存的堆区分配一块区域返回给代码区的代码使用,当函数调用发生时,函数的调用关系等信息会动态的保存在栈区。
程序中所使用的缓冲区可以是在堆区、栈区、数据区,不同地方的缓冲区利用方法不同。
2.栈与系统栈
        栈是一种数据结构,是一种先进后出的数据表,用于标识栈的属性两个:栈顶、栈底 。内存中的栈区指的就是系统栈,由系统自动维护。
3.函数调用时发生了什么
        请看如下C代码:
 

       int B(int b1,int b2){
                int var_b1=b1+b2;
                int var_b2=b1-b2;
                return var_b1*var_b2;
        }
        int A(int a1,int a2){
                int var_a1;
                var_a=B(a1,a2)+a1;
                return a1;               
        }
        int main(int argc,char **argv,char **envp){
                int var_main;
                var_main=A(4,3);
                return var_main;
        }

        根据操作系统的不同、编译器和编译选项的不同,同一文件不同函数的代码在内存代码区中的分布可能相邻,也可能不相邻,可能有先后顺序,也可能没有,但他们都是在代码所映射的节里
函数调用时,伴随的系统栈中的操作如下:
在main函数调用A的时候,首先在自己的栈帧中压入函数返回地址,然后位A创建新栈帧并压入系统栈 ,
在函数A调用B的时候,同样先在自己的栈帧中压入返回地址,然后为B创建新栈帧并压入系统栈
B返回时,B的栈帧被弹出系统栈,A栈帧的返回地址被露在栈顶,处理器跳到返回地址处执行
在A返回时,A的栈帧被弹出系统栈,main函数栈帧中的返回地址被露在栈顶。处理器跳到返回地址执行
4.寄存器与函数栈帧
        每一个函数独占自己的栈帧空间,当前正在运行的函数总是在栈顶,win32系统提供两个寄存器用于标识位于系统栈顶端的栈帧
         ESP:栈指针寄存器,存放一个指针,该指针永远指向系统栈最上面的栈帧的栈顶
         EBP:基址指针寄存器,该指针永远指向系统栈最上面的栈帧的底部
         函数栈帧:ESP和EBP之间内存空间为当前栈帧
         在函数栈帧中一般包含以下几种信息:、
         局部变量:为函数举报变量开辟的内存空间
         栈帧状态值:保存前栈帧的顶部和底部(实际上只保存前栈帧的底部,前栈帧的顶部可以通过堆栈平衡得到)
         函数返回地址:保存当前函数调用前的“断点”信息,也就是函数调用前的指令位置
         函数栈帧的大小不固定,一般和局部变量的多少有关
5.函数调用约定与相关指令
        调用约定描述了函数传递参数的方式和栈协同工作的技术细节,下面列出几种调用方式:
                                C                Syscall                Stdcall                BASIC                FORTRAN                PASCAL
参数入栈顺序 右→左         右→左                右→左                左→右                 左→右                左→右
谁恢复栈平衡 母函数                子函数                子函数                子函数                子函数                子函数
        对于Visual C++,支持3种函数调用约定:

        调用约定声明                参数入栈顺序                谁恢复栈平衡
        __cdecl                                右→左                                母函数
        __fastcall                        右→左                                子函数
        __stdcall                        右→左                                子函数

        除了入栈方向和恢复平衡不同之外,参数传递有时也会有所不同。例如,每一个C++类成员函数都有一个this指针,在windows下,这个指针保存在ECX中
,但如果用GCC编译,这个指针会作为最后一个参数入栈

        函数调用大致包括以下几个步骤:
         参数入栈
         返回地址入栈
         代码区跳转
         栈帧调整:具体包括
         保存当前栈帧状态值(push ebp)
         将当前栈帧切换到新栈帧(mov ebp,esp)
         给新栈帧分配空间(把ESP减去所需空间大小,抬高栈顶)
         
        函数返回大致包括以下几个步骤:
         保存返回值(通常保存在EAX中)
         弹出当前栈帧,恢复上一个栈帧:具体包括
         在堆栈平衡的基础上给ESP加上栈帧的大小,降低栈顶,回收当前栈帧空间
         将当前栈帧底部保存的前栈帧EBP值弹入EBP,恢复出上一个栈帧
         将函数返回地址弹给EIP

    跳转

posted @ 2019-10-14 19:55  暮日温柔  阅读(906)  评论(0编辑  收藏  举报