堆栈溢出技术

use*_*466 0 c stack-overflow

int main(void) {
   problem2();
}

void doit2(void) {
    int overflowme[16];
    //overflowme[37] =0;
}

void problem2(void) {
    int x = 42;
    doit2();
    printf("x is %d\n", x);
    printf("the address of x is 0x%x\n", &x);
}
Run Code Online (Sandbox Code Playgroud)

有人会帮我理解为什么溢出[37] = 0; 从doit2函数将覆盖x的值?(请在说明中包含函数doit2的程序计数器和帧指针)谢谢!

它每次都在x86 windows机器上工作(好吧!),项目属性 - >配置属性 - > C/C++ - >代码生成 - >基本运行时检查设置为"默认".所以这不是一个未定义的行为.

nat*_*ose 5

像其他人一样说,这取决于目标和compliler的,但对你的那些保持不变,而且没有在代码的任何其他东西,看起来像他们引入随机性堆栈(相对而言),所以它每次都会做同样的事情.

系统堆栈通常从高地址增长到较低地址.如果堆栈指针是0x1234并且您按下一个值(在32位{4字节}系统上),则堆栈指针将变为0x1230.

数组从最低地址到最高地址寻址.如果你有

char a[2];
Run Code Online (Sandbox Code Playgroud)

并且a [0]为0x0122,则[1]为0x0123.

您的数组doit2是一个自动变量,这意味着它是在进入函数时创建的,并在退出函数时被删除.自动变量必须存在于堆栈或寄存器中.因为它是一个数组它是那么复杂的编译器把它放在RAM而不是寄存器(这使得索引更容易,因为它只是增加了索引*大小的数组的第一个成员的地址).由于堆栈在RAM中,因此编译器将数组放在堆栈中.

在此堆栈上为此数组分配空间意味着堆栈指针sizeof(int)*16小于此数组不存在时的堆栈指针.堆栈指针很有可能指向overflowme[0],而在doit2.

还有其他东西可能在堆栈上,还有一些东西必须在堆栈上.必须在堆栈上的东西是返回指针,它们在调用函数时被推到那里.在32位系统上,每个应占用4个字节.可能在堆栈上的东西(如果编译器想要使用它)是前一帧指针.(显式*)栈上的x86-32帧只是ESP和EBP之间的空间,但它们不是必要的,这样他们往往不使用,EBP只是用作通用寄存器,而不是(通用寄存器可用是一般都很好).但是,使用堆栈帧非常有用,因为它们使调试变得更加容易,因为ESP和EBP充当局部变量边缘的标记.有时需要堆栈帧,例如使用时alloca或C99的可变尺寸的自动数组,因为它们允许用于一个函数中的局部变量的空间由被丢弃mov EPB, ESP或等效instructiosn而非sub size_of_local_variable, ESP所以编译器不必知道该帧的大小.它们还允许相对于EBP而不是ESP来解决局部变量,在alloca变化的情况下.在这种情况下,EBP在当前函数结束之前不会改变,除非通过调用函数进行更改和恢复.

在没有优化的情况下进行编译时,编译器通常总是使用堆栈帧,因为它们使调试变得更容易.使用堆栈帧对代码进行建模,然后在证明不必要之后将代码转换为不使用它们也更容易.

因此,EBP的前值可能会或可能不会驻留在返回地址(在某个地方之间的堆栈problem2),并在最后一个元素doit2overflowme.编译器也可以自由地在堆栈上放置任何其他东西,所以谁知道还有什么可能存在.

problem2的局部变量int x可以放入寄存器或堆栈中.在没有优化的情况下进行编译时,即使本地变量可以进入寄存器,它们也经常会进入堆栈.

这样,让我们假设存在doit2overflowme阵列,旧的帧指针,一个返回地址,并且problem2x堆栈(并且在某些更多的东西{这是真正在较高地址}它)上.

由于&(overflowme[i])与添加第一个元素的地址overflowme(i*{size of int})相同,旧的EBP位于最后一个元素之后overflowme,返回地址位于旧EBP之后,int x位于返回地址之后,x肯定是站在缓冲区溢出的方式.

为什么37的指数发生这种情况尚不清楚.指针数学(假设我上面列出的项目在数组之间的堆栈上x)并不表示它应该基于4字节指针(32位机器),尽管这是一个8字节指针系统(64位机),那么数字更接近我期望的地址x是在如果sizeof(int) == 8.编译器也可以继续为调用分配堆栈空间printf(格式字符串必须在堆栈之后的变量参数),这会影响数学运算(并且还鼓励编译器x放在堆栈上,因为它必须无论如何都把它推到那里).

如果您想要更详细地回答您的问题,请查看此代码的程序集并确定准确的地址.

  • 即使EBP未用作帧基指针,您也可以认为堆栈帧存在,但是帧不会被帧化.