如果我覆盖堆栈上的 return 地址会怎样?
What happens if i overwrite the return address on the stack?
我知道这是危险的行为,但我想弄清楚到底发生了什么。
代码如下:
#include<stdio.h>
#include<stdlib.h>
static int count = 0;
void hello(void){
count ++;
fprintf(stderr,"hello! %d\n",count);
}
void foo(void){
void *buf[10];
static int i;
for(i = 0 ; i < 100 ; i++){ // put enough data to overwrite the return address on the stack
buf[i] = hello;
}
}
int main(void){
int buf[1000];
foo();
return 0;
}
结果如下:
……
hello! 83
hello! 84
hello! 85
hello! 86
hello! 87
hello! 88
hello! 89
Segmentation fault (core dumped)
为什么 hello 函数被调用了 89 次?
当函数foo
returns时,pc寄存器应该得到hello函数的地址,不是吗?
于是调用了hello,执行了里面的代码,然后呢?程序 return 不应该到 main 函数吗? “89”从何而来?我好像漏了点什么,谁能指点一下。
嗯,这是 未定义 行为。当您进入未分配的内存时 任何事情 都可能发生,具体取决于 "junk" 目前有什么。
段错误基本上意味着您走出了程序的受保护内存(在您的情况下,它花费了“89 次”)。您的操作系统会保护您免受这种情况的影响,并告诉您您试图在为程序分配的 space 之外写入。
(在旧操作系统中不是这种情况,如果您的程序溢出到系统 space,这样的错误可能会导致您的操作系统崩溃。)
您将函数 hello
的地址写入 foo
的堆栈,超出您使用 buf
保留的 space 的数量 90 次。当 foo
尝试 return 时,它将 hello
的第一个额外地址(之一)加载到程序计数器中,弹出它,然后 "returned" 到它。我说 "one of" 的原因是因为在您保留的数组末尾和 return 地址开始之间的堆栈上可能有一些 space
hello
假定它是由 foo
调用的。但是,它并没有被调用,也就是说foo
的剩余地址没有被压入栈中hello
到return。所以,当 hello
returned 时,它弹出的东西是堆栈中的下一个东西,它又是 hello
.
的地址
从 hello
的角度来看,这又像是一次调用,但实际上并不是一次调用,因为它是 return,因此 hello
保持 return 每次从堆栈中弹出 hello
个地址,直到 运行 从这些地址中取出。
在那些 运行 输出之后,堆栈中未被覆盖的下一个字节,当被视为指针时,指向内存中的其他位置,可能指向未分配给您的进程内存的内容地图。 hello
试图 return 到那个,那是程序出现段错误的时候。
正如 Nahum 所说,这是未定义的行为,但这并不意味着无法解释:
100 - 89 = 11
这正是您本地堆栈的大小 (void *[10] + int)。
试试这个(未定义的行为,可能会擦除您的硬盘或修复全球经济):
#include<stdio.h>
#include<stdlib.h>
static int count = 0;
static void hello(void){
count ++;
fprintf(stderr,"hello! %d\n",count);
}
static void foo(void){
void *buf[1];
static int i;
for(i = 0 ; i <= 100 + 2; i++){ // put enough data to overwrite the return address on stack
buf[i] = hello;
}
}
static void bye(void) {
printf("Bye, bye...\n");
exit(EXIT_SUCCESS);
}
int main(void){
void *buf[1000];
int i;
for (i = 0; i < 1000; ++i) {
buf[i] = &bye;
}
foo();
return 0;
}
我知道这是危险的行为,但我想弄清楚到底发生了什么。
代码如下:
#include<stdio.h>
#include<stdlib.h>
static int count = 0;
void hello(void){
count ++;
fprintf(stderr,"hello! %d\n",count);
}
void foo(void){
void *buf[10];
static int i;
for(i = 0 ; i < 100 ; i++){ // put enough data to overwrite the return address on the stack
buf[i] = hello;
}
}
int main(void){
int buf[1000];
foo();
return 0;
}
结果如下:
……
hello! 83
hello! 84
hello! 85
hello! 86
hello! 87
hello! 88
hello! 89
Segmentation fault (core dumped)
为什么 hello 函数被调用了 89 次?
当函数foo
returns时,pc寄存器应该得到hello函数的地址,不是吗?
于是调用了hello,执行了里面的代码,然后呢?程序 return 不应该到 main 函数吗? “89”从何而来?我好像漏了点什么,谁能指点一下。
嗯,这是 未定义 行为。当您进入未分配的内存时 任何事情 都可能发生,具体取决于 "junk" 目前有什么。
段错误基本上意味着您走出了程序的受保护内存(在您的情况下,它花费了“89 次”)。您的操作系统会保护您免受这种情况的影响,并告诉您您试图在为程序分配的 space 之外写入。
(在旧操作系统中不是这种情况,如果您的程序溢出到系统 space,这样的错误可能会导致您的操作系统崩溃。)
您将函数 hello
的地址写入 foo
的堆栈,超出您使用 buf
保留的 space 的数量 90 次。当 foo
尝试 return 时,它将 hello
的第一个额外地址(之一)加载到程序计数器中,弹出它,然后 "returned" 到它。我说 "one of" 的原因是因为在您保留的数组末尾和 return 地址开始之间的堆栈上可能有一些 space
hello
假定它是由 foo
调用的。但是,它并没有被调用,也就是说foo
的剩余地址没有被压入栈中hello
到return。所以,当 hello
returned 时,它弹出的东西是堆栈中的下一个东西,它又是 hello
.
从 hello
的角度来看,这又像是一次调用,但实际上并不是一次调用,因为它是 return,因此 hello
保持 return 每次从堆栈中弹出 hello
个地址,直到 运行 从这些地址中取出。
在那些 运行 输出之后,堆栈中未被覆盖的下一个字节,当被视为指针时,指向内存中的其他位置,可能指向未分配给您的进程内存的内容地图。 hello
试图 return 到那个,那是程序出现段错误的时候。
正如 Nahum 所说,这是未定义的行为,但这并不意味着无法解释:
100 - 89 = 11
这正是您本地堆栈的大小 (void *[10] + int)。
试试这个(未定义的行为,可能会擦除您的硬盘或修复全球经济):
#include<stdio.h>
#include<stdlib.h>
static int count = 0;
static void hello(void){
count ++;
fprintf(stderr,"hello! %d\n",count);
}
static void foo(void){
void *buf[1];
static int i;
for(i = 0 ; i <= 100 + 2; i++){ // put enough data to overwrite the return address on stack
buf[i] = hello;
}
}
static void bye(void) {
printf("Bye, bye...\n");
exit(EXIT_SUCCESS);
}
int main(void){
void *buf[1000];
int i;
for (i = 0; i < 1000; ++i) {
buf[i] = &bye;
}
foo();
return 0;
}