堆栈溢出技术

时间:2010-05-21 22:24:06

标签: 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);
}

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

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

5 个答案:

答案 0 :(得分:5)

就像其他人所说的那样,这取决于目标和编译器,但对于你来说,这些是保持不变的,并且代码中没有任何其他东西看起来像是在向堆栈引入随机性(相对来说) ),所以每次都会做同样的事情。

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

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

char a[2];

且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或等效的指令丢弃,而不是{{1}所以编译器不必知道帧的大小。它们还允许相对于EBP而不是ESP来解决局部变量,在sub size_of_local_variable, ESP的情况下,它会发生变化。在这种情况下,EBP在当前函数结束之前不会改变,除非通过调用函数进行更改和恢复。

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

因此,EBP的先前值可能会或可能不会驻留在返回地址(alloca中的某个地方)与problem2的{​​{1}}中的最后一个元素之间的堆栈上。编译器也可以自由地在堆栈上放置任何其他东西,所以谁知道那里还有什么。

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

因此,我们假设堆栈上有problem2的{​​{1}}数组,旧的帧指针,返回地址和int x的{​​{1}}(在{其真正处于更高地址}下的更多东西。)

由于doit2与将overflowme的第一个元素的地址添加到(i * {problem2}的大小)并且旧的EBP位于最后一个之后相同x的元素和返回地址位于旧EBP之后,&(overflowme[i])位于返回地址之后,overflowme肯定正好位于缓冲区溢出运行的路上。< / p>

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

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

  • 即使没有将EBP用作帧基指针,您也可以认为堆栈帧在那里,但帧不会被帧化。

答案 1 :(得分:3)

可能没有。堆栈中变量的位置取决于编译器和平台。

答案 2 :(得分:2)

你的堆栈看起来像这样:

char overflowme[16]
return address to problem2() from calling doit2()
parameters for doit2(), if it had any
int x = 42
return address to main() from calling problem2()
parameters for problem2(), if it had any
local variables for main(), if it had any

当您写入overflowme[37]时,您将超过overflowme的结尾(因为它只有16个字节)并超过doit2()调用的返回地址并覆盖x

正如其他人所提到的,这在很大程度上取决于平台和编译器,但应该让您对问题有一个很好的可视化。尝试打开调试窗口并显示堆栈,逐步执行代码。

答案 3 :(得分:2)

你很幸运,它只是破坏了x。通常这种代码可以demons fly out of your nose

答案 4 :(得分:0)

它没有:

x is 42
the address of x is 0xbff9ea1c

每次在One True Compiler and Platform(地址变更)上都会发生以上情况,所以很明显你没有确定它是不确定的行为。