WARNING这是《深入理解计算机系统》(CS
)的笔记,彼时还未上CO,OS等计算机系统基础课,存在理解不到位与错误之处.
C不作数组越界检查,除非认为编程添加。
一个过程中大量信息,包括返回地址,存储的前一过程的变量,参数,本过程的局部变量,等等,都存储在程序运行栈中。而数组当然也存储在这个栈中。但栈帧分配的空间不能无限,当恶意输入使得内存写入操作超出了给数组分配的缓冲区,就会覆盖掉前面的值,这些值包括各种变量、返回信息等。导致程序崩溃。
特别地,将输入特意包括一个二进制(十六进制)机器代码,然后恰好覆盖掉当前过程的返回地址,让这个地址的值变为恶意输入所包含的机器代码的地址值,就能执行恶意内容,如自我复制增殖等。
在没有保护的程序中,机器生成的栈帧地址是容易掌握的,那么上述过程是容易实现的。
安全改进
栈地址随机化
可以想到随机化栈中的地址随机化就能避免缓冲区溢出改变缓冲区以外的值。但毕竟不能真的随机化,这样将需要一个巨大的表来表示指令与地址的关系。随机化的方法是每次执行程序时,为每个栈帧分配空间前,先将 %rsp 下移一个随机数,将中间这部分空出来,达到随机效果。Linux 64位系统上,这个随机范围可达2^32^。
但仍有办法暴力枚举破解。在恶意输出中加一大串nop指令,总能覆盖到有效信息,当产生异常时,就表明这些nop跨过了随机化特意流出的void空间。
栈破坏检测:金丝雀值
这个只会在char buf[ ]中存在,对应读取用户输入。在char buf[ ]所分配的栈中空间的高一个字节(比如buf[8],栈中是0x00-0x07,金丝雀值在0x08-0x15),它是在分配数组空间时,从一个只能段寻址 的只读 的内存空间赋值一个值到金丝雀值的位置上。程序返回前,检查金丝雀值是否和段寻址内存中那个原值一样,不一样就说明溢出了。
金丝雀值只在char 数组即字符串中自动设置。因为读取用户输入用的都是字符串数组。
进一步地,在实现了保护的编译器中,生成的机器代码会重新排布局部变量在运行栈中的顺序。比如将数组放在比普通变量更大地址值的地方,即普通非数组变量放在离栈顶更近的地方,这样即使数组溢出,也不会改变这些普通变量的值。
限制可执行代码区域
虚拟内存被抽象成了页 ,一般使2048或4096字节。支持三种访问形式:读、写、执行(将内存的内容当做机器代码)。以前x86的读和执行的控制合并成了1位的标志,任何可读的都是可读可执行的,这就给了执行恶意代码的机会。
编译形语言可以通过限制可执行代码区域减小被错误执行恶意代码的可能性。
最近AMD 和 Intel 都在内存保护中加入了“NX”(No-Execute,不可执行)位,分开读和执行两种访问方式。检查页是否可执行由硬件完成,没有效率损失。
以上措施已经成为标准,必须设定命令才能让GCC产生不运用上述措施的机器代码。