初看缓冲区(堆栈)溢出
先看一段简单的小程序
#include <stdio.h>
#include <string.h>
char name[]="NEUQ_CSA";
int main()
{
char output[8];
strcpy(output,name);
for(int i=0;i<8&&output[i];i++)
printf("\\0x%x",output[i]);
return 0;
}
首先, #include<stdio.h> 和 #include<string.h> 是包含后面使用的strcpy函数和printf函数的头文件。
然后, char name[] = “NEUQ_CSA”; 是把‘name’这个数组赋值,往里面放入‘NEUQ_CSA’这几个字符。
然后是 int main(){ …… } ,这个是重点啦,这就是我们常说的主函数!程序进来就是先找到这个地方,执行里面的语句。
接下来的 char output[8]; strcpy(output, name); 就是让系统给output变量分配8个char的空间,然后把‘name’里面装的字符拷贝给它。
strcpy(des,source) 这个拷贝函数是把第二个参数source的值拷给第一个参数des。它不检查拷贝的长度,它会一直拷贝,直到source到结尾。这就是它的弱点!
下面的 for(int i=0;i<8&&output[i];i++) printf("\\0x%x",output[i]); 只是让大家方便检查 output里面的值而已,我把它以16进制的形式打出来。
打印出来的是:
\0x4e\0x45\0x55\0x51\0x5f\0x43\0x53\0x41
分别为‘N','E','U','Q','_','C','S','A'的十六进制
现在开始用简单的代码演示:
#include <stdio.h>
#include <string.h>
char name[]="abcdefgh";
int main()
{
char output[8];
strcpy(output,name);
for(int i=0;i<8&&output[i];i++)
printf("\\0x%x",output[i]);
return 0;
}
只是对刚才的代码做了简单的修改,此时堆栈数据是这样的:
补充:1.计算机为了能回头继续处理原来的事情,就需要把原来指令的指针EIP保存在堆栈中;当要回去原来的地方时,就把保存在堆栈中的 EIP恢复即可。并且各个函数的局部变量的分配也是在堆栈中。
2.堆栈是一数据结构,遵循“先进后出,后进先出”的规则,就像我们平时叠盘子一样,先放在下面的最后才能取出来,最后放上去的最先取出来。而在操作系统中,存和取的动作就是PUSH和POP。PUSH放一个数据到堆栈中去,POP取一个堆栈中的数据出来。
这样在执行完 main 函数后,只要把保存在堆栈中的 EBP、EIP 恢复回去,就可继续原来的执行过程而没有任何问题。
那第二次输入‘abcdefghijklmnopqrstuvwxyz’时,output分配的还是8个字节,但却拷了26个字母进来,会变成什么样呢?
#include <stdio.h>
#include <string.h>
char name[]="abcdefghijklmnopqrstuvwxyz";
int main()
{
char output[8];
strcpy(output,name);
for(int i=0;i<8&&output[i];i++)
printf("\\0x%x",output[i]);
return 0;
}
结果如下:
由于拷贝的字母过长,不仅把分配给 output 的 8 个字节占据完了,而且还继续往下,把保存的EBP和EIP给占据了。
当执行完main函数后,系统要恢复EBP、EIP,而EIP已经被我们覆盖成ponm了。
但系统不知道,就会去执行原内容保存地址的东东。而那个位置是不可读的,所以就会出错。
我们可通过覆盖 EIP 为任意值来让程序运行到一个错误的地方,那如果我们特意把EIP覆盖成我们想去的程序的地方,那会怎么样呢?
参考文章:Q版缓冲区溢出教程