我破坏了哪个 return 地址?

Which return address am I corrupting?

我有一个非常简单的学习堆栈溢出的程序。

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {

  char buf[128];

  if(argc < 2) return 1;

  strcpy(buf, argv[1]);

  printf("Hello\n");

  return 0;
}

策略是在 argv[1] 中提供大字符串以溢出 buf 并覆盖 return 地址。但是哪个 return 地址呢?我以为是我进入strcpy之前保存的地址,所以当我们正常从strcpyreturn的时候,我们会执行printf。

但是,在我使用 shell 代码负载溢出缓冲区后,将此 return 地址更改为我的 shell 代码。我看到 printf 仍在执行。就算我多加几个printf,也都执行完了。显然,我更改的return地址只影响主函数return,否则我什至看不到正在执行的printfs。

为什么会这样?是不是当我溢出缓冲区把return地址改成我的shell代码时,主程序会直接跳转到我的shell代码而不执行下一个printf?

buf 是一个函数的变量,所以它存在于栈中。您的缓冲区溢出会损坏堆栈

当程序执行进入main时,从main出来的return地址在栈上。接下来,为 buf 分配了 128 个字节。 strcpy 调用的第二个参数长于 128 字节,超出了为 buf 分配的 space,并且可能破坏了 return 地址。

接下来将printf和return地址的参数(指向printf后面的语句)压入栈,执行跳转到printf 函数。执行请求的打印后,return 地址被弹出堆栈,并继续执行下一条语句。

终于到了return 0;语句。分配给堆栈上局部变量的 space 被恢复,并且(损坏的)return 地址从堆栈中弹出,并执行 "returns" 到由字节给定的损坏地址用于破坏内存的字符串。

简而言之,缓冲区溢出只能把已经写在栈上的信息(过去的)涂掉。它不能涂写尚未写入堆栈(未来)的信息。这就是为什么 printf 损坏后的调用仍然可以正常执行的原因;损坏的数据尚未使用。

堆栈,在 strcpy 调用:

xxxx
xxxx
return address from strcpy
&buf    (arg to strcpy)
argv[1] (arg to strcpy)
buf[0]
...
buf[127]
return address from main
argc     (arg to main)
argv[0]  (arg to main)
argv[1]  (arg to main)
...

伤害是在 buf[127] 之后造成的。来自 strcpy 的 return 没有损坏。

注:也有可能栈上没有"return from strcpy";编译器可能已内联函数调用。

在您的典型 PC 上,堆栈向下增长。这意味着在调用 strcpy:

时堆栈的内存布局将如下所示
// ^^^ higher addresses ^^^
[stuff]
[return address of main]
[buf[127]]
[buf[126]]
...
[buf[1]]
[buf[0]]
[argument 2 (pointer to argv[1])]
[argument 1 (pointer to buf)]
[return address of strcpy (points into main)]
[local variables in strcpy]
// vvv lower addresses vvv

通过溢出 buf(写入 buf[128]buf[129] 等),您可以覆盖 main 的调用帧(最重要的是,main 的 return 地址)。您不能影响 strcpy 的调用框架,因为它在内存中存在于 buf 之前。