1,栈帧简介:
之前几期学习分享了关于栈帧系列知识的学习,我们知道函数调用过程中,每一个调用函数都是以栈帧为单位,在其内存空间上做相关操作,每一个调用函数的栈帧存有其返回地址,调用函数结束后,根据返回地址回到上层栈帧空间,直至main()函数的调用。
如下是栈帧的空间分布:
![](https://img-blog.csdnimg.cn/20200613102036712.png)
2,栈越界攻击介绍及原理
栈越界实质上来说,其实也即是上图缓存区域存放的数据超过其大小。缓冲区溢出是指当计算机程序向缓冲区内填充的数据位数超过了缓冲区本身的容量。溢出的数据覆盖在合法数据上。理想情况是,程序检查数据长度并且不允许输入超过缓冲区长度的字符串。但是绝大多数程序都会假设数据长度总是与所分配的存储空间相匹配,这就为缓冲区溢出埋下隐患。
操作系统所使用的缓冲区又被称为堆栈,在各个操作进程之间,指令被临时存储在堆栈当中,堆栈也会出现缓冲区溢出。 当一个超长的数据进入到缓冲区时,超出部分就会被写入其他缓冲区,其他缓冲区存放的可能是数据、下一条指令的指针,或者是其他程序的输出内容,这些内容都被覆盖或者破坏掉。可见一小部分数据或者一套指令的溢出就可能导致一个程序或者操作系统崩溃。
常见程序代码:
#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
char buf[10];
strcpy(buf, argv[1]);
cout<<buf;
return 0;
}
连续输入10个字符就产生了溢出。
C语言常用的strcpy、sprintf、strcat 等函数都非常容易导致缓冲区溢出问题。
3,栈越界攻击危害及防范
缓冲区溢出的一个致命的使用就是让程序执行它本来不愿意执行的函数。这是一种常见的通过计算机网络攻击系统安全的方法。通常,输入给程序一个字符串,这个字符串包含一些可执行代码的字节编码,成为攻击代码。另外,还有一些字节会用一个指向攻击代码的指针覆盖返回地址。那么,执行ret指令的效果就是跳转到攻击代码。
一种攻击形式,攻击代码会使用系统调用启动一个外壳程序,给攻击者提供一组操作系统函数。
另一种攻击形式,攻击代码会执行一些未授权的任务,修复对栈的破坏,然后第二次执行ret指令,(表面上正常返回给调用者)。
那么如何防护/对抗缓冲区溢出攻击?
(1)栈随机化(主要受linux系统版本限制,老版本不支持栈随机化):使得栈的位置在程序每次运行时都有变化。为了在系统插入攻击代码,攻击者不但要插入代码,还需要插入指向这段代码的指针(指向攻击代码的首地址/栈地址),这个指针也是攻击字符串的一部分。产生这个指针需要知道这个字符串放置的栈地址。老的系统版本,如果在相同的系统运行相同的程序,栈的位置是相当固定的。所以黑客可以在一台机器上研究透系统上的栈是如何分配地址的,就可以入侵其它主机。
实现的方式:程序开始时,在栈上分配一段0~n字节之间的随机大小的空间。分配的范围n必须足够大,才能获得足够多样的栈地址变化,但是又要足够小,不至于浪费程序太多的空间。
(2)栈破坏检测(主要受GCC版本的限制,老的GCC版本不支持栈破坏检测):检测到何时栈被破坏。从strcpy等函数我们可以看到,破坏通常发生在当超越局部缓冲区的边界时。在C语言中,没有可靠的方法来防止对数组的越界写。但是,我们能够在发生了越界写的时候,并且,在其还没有造成任何有害结果之前,尝试检测到它,并且把程序终止。
实现的方