懒懒散散

一天就学一会就是玩游戏刷手机~


前言

栈这个东西,百度的也是大概,具体的内存分析还是有待深挖。

在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。在i386机器中,栈顶由称为esp的寄存器进行定位。压栈的操作使得栈顶的地址减小,弹出的操作使得栈顶的地址增大。
栈在程序的运行中有着举足轻重的作用。最重要的是栈保存了一个函数调用时所需要的维护信息,这常常称之为堆栈帧或者活动记录。堆栈帧一般包含如下几方面的信息:

  1. 函数的返回地址和参数
  2. 临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。

正文

说人话栈就是为了应对临时变量所产生的一块独立的内存空间,有调用变量和函数时就由计算机自主分配调用,用完就释放。

  1. 如果所有变量的内存地址都要固定,程序庞大的时候内存消耗过高
  2. 如果变量都是自主分配,麻烦不说,还要自己清理
  3. 如果一部分变量系统分配,一部分自主分配,系统分配的系统会自动清理

那么结果肯定是3好。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>

int hh(int a, int b){
a += 1000;
return a + b;
}

int Add(int a, int b){
int c = 100;
int d = hh(a, b);
c += d;
return c;
}

int main(){

std::cout << Add << std::endl;
int x = Add(250, 150);

return 0;
}

寄存器在x86反汇编的时候已经看到过ebp,esp,eax,edx之类的了,那么细分的话:

  • 32位通用寄存器有:4个数据寄存器(EAX,EBX,ECX,EDX),2个变址寄存器(ESI,EDI)和2个指针寄存器(ESP,EBP)
  • 6个段寄存器(ES,CS,SS,DS,FS,GS)
  • 1个指令指针寄存器(EIP)
  • 1个标志寄存器(EFLAGS)

打断点到int x = Add(250, 150);上反汇编查看:

画图是比较直观的操作,所以继续画画看:
十六进制转十进制不会的就用计算器
00A41A25 push 96h
00A41A2A push 0FAh
参数从右往左进栈,每push一次esp的地址就等于 esp -= 4,这不是一次重复说明了,常量也会占用内存,之所以减法是因为内存模型是从高地址到低地址,比如0xfffd5 ~ 0xfffa1,所以esp通过减法使得上升,至于-4可能是跟32位的地址大小相关吧。
00A41A2F call 00A411C7 跳转到Add函数位置处,其实也不难看出一点,每一条指令最左边的地址都相差4,例如A25 和 A2A ,A就是表示10,中间隔了[26 27 28 29]

往上找一下add的位置,这里call的地方看不太懂变成了什么。

找到后关闭源代码,发现挺长的。

不过慢慢看,发现这个函数的第一步也还是push ebp,将ebp压入栈中,但是这个时候ebp的值就不确定了。然后mov ebp,esp让esp上来,sub esp,0D8h esp-216我就不知道为啥偏移这么多了。
即使配合源码从后面的00731849到00731860我倒是没看明白干了什么。
00731865开始给ebp-8的位置传64h,也就是100,也就是int c = 100;那句源代码。
int d = hh(a,b); a和b都是我们传递的,所以两个mov都没啥问题,就是把eax和ecx在压入栈跳转到hh函数的位置,同样的这个地址跳转的也不是函数内容,更像是在实现的过程。明明已经禁用优化了,换成release的话也不对头,int x=Add那句好像因为没有调用,甚至都没有反汇编。
就不管call,看add esp,8,推测是释放形参用的。然后mov eax的值给ebp-14,ebp-14就是变量d的位置,eax先前说到一般存储可能为返回值。
然后c += d,表达式就是c = c+d, 现在已知这俩的位置,先将ebp-8也就是c的值传给eax寄存器,然后eax寄存器在加上ebp-14里的值,也就是d的值。因为结果是相加后重新赋给c,所以最后还有一步,将eax相加之和的结果在传给ebp-8;
return c就很直接了,把ebp-8的值传给eax。
后面的pop弹出栈还看不明白。反正ret最后回到main函数里就对了,因为函数里面pop ebp的时候,esp应该就会+4回到之前的地址了。

但是函数里面怎么画就不知道了,这个反汇编还是有点问题不太直观。可能还得借助其他工具分析。


通过esp和ebp来回移动可以保持栈平衡,虽然esp到达过的地方数据短时间不会被清除,但是按照固定逻辑,正常情况下esp也不会回头访问了,除非下次用到那块地址了又擦写数据让esp顶上。

这样栈溢出会好理解一点,就比如数组,他是一片连续的内存空间,如果输入的时候不控制,那么后面溢出的入栈之后造成的问题就会五花八门,最常见就是改变程序运行位置,本来连续空间正常执行,一个溢出,导致后面执行位置或者返回位置被修改了。程序未能如期运行可以说是。

这种可控的情况下,vs就搞了那套函数后面带_s的安全函数,只要提前限定长度就能减少溢出的问题,当然不可能完全解决的。


结语

  1. 栈的空间提前分配好了。局部变量通常就入栈,栈通过esp实现局部变量的创建和释放
  2. 栈平衡破坏了,函数在某一段可能不能返回到预期的位置,利用这个原理,可以来实现对目标操作系统的控制权限。
  3. sub esp,x 这个x有可能就能知道有多少个局部变量
  4. 目前常见寄存器:
    1. ebp 栈底
    2. esp 栈顶,栈顶以下的值或是局部变量之类的
    3. eax 函数返回值通常由eax传递
    4. eip 属于cpu执行的位置
    5. ecx,edx 也是存放临时变量的

不过说实在还是挺失败的~纯纯的理论者,真到了实操怎么去控制一个栈溢出都不知道。。。惭愧哦