【答疑解惑】如何避免程序崩溃之一
避免程序崩溃,有很多方法,分别针对不同的崩溃原因,我今天想谈谈一种程序员经常碰到的、不管是初学者甚至编程老手都经常犯的错误,就是程序运行时栈的崩溃。
这种错误相信大家都碰到过吧:
为了解释导致它的一种错误,请看以下危险程序:
int aFunc(char* input) {
char name[10];
…
strcpy(name, input);
return 0;
}
如果输入的字符串input长度超过10,程序赖以运行的stack就被破坏了,程序就会崩溃。
解决他的办法很简单,只要加上一个叫栈保护的编译选项就好了,运行时,就会打印栈被破坏这样的提示。
栈保护的编译选项如下:
gcc -fstack-protector-all -D_FORTIFY_SOURCE=2
也可以取消:
编译器堆栈保护原理
我们知道攻击者利用堆栈溢出漏洞时,通常会破坏当前的函数栈。例如,攻击者利用清单中的函数的堆栈溢出漏洞时,典型的情况是攻击者会试图让程序往 name 数组中写超过数组长度的数据,直到函数栈中的返回地址被覆盖,使该函数返回时跳转至攻击者注入的恶意代码或 shellcode 处执行(关于溢出攻击的原理参见《Linux 下缓冲区溢出攻击的原理及对策》)。
图 1. 溢出前的函数栈
图 2. 溢出后的函数栈
如果能在运行时检测出这种破坏,就有可能对函数栈进行保护。目前的堆栈保护实现大多使用基于 “Canaries” 的探测技术来完成对这种破坏的检测。
“Canaries” 探测:
要检测对函数栈的破坏,需要修改函数栈的组织,在缓冲区和控制信息(如 EBP 等)间插入一个 canary word。这样,当缓冲区被溢出时,在返回地址被覆盖之前 canary word 会首先被覆盖。通过检查 canary word 的值是否被修改,就可以判断是否发生了溢出攻击。
常见的 canary word:
- Terminator canaries
- 由于绝大多数的溢出漏洞都是由那些不做数组越界检查的 C 字符串处理函数引起的,而这些字符串都是以 NULL 作为终结字符的。选择 NULL, CR, LF 这样的字符作为 canary word 就成了很自然的事情。例如,若 canary word 为 0x000aff0d,为了使溢出不被检测到,攻击者需要在溢出字符串中包含 0x000aff0d 并精确计算 canaries 的位置,使 canaries 看上去没有被改变。然而,0x000aff0d 中的 0x00 会使 strcpy() 结束复制从而防止返回地址被覆盖。而 0x0a 会使 gets() 结束读取。插入的 terminator canaries 给攻击者制造了很大的麻烦。
- Random canaries
- 这种 canaries 是随机产生的。并且这样的随机数通常不能被攻击者读取。这种随机数在程序初始化时产生,然后保存在一个未被隐射到虚拟地址空间的内存页中。这样当攻击者试图通过指针访问保存随机数的内存时就会引发 segment fault。但是由于这个随机数的副本最终会作为 canary word 被保存在函数栈中,攻击者仍有可能通过函数栈获得 canary word 的值。
- Random XOR canaries
- 这种 canaries 是由一个随机数和函数栈中的所有控制信息、返回地址通过异或运算得到。这样,函数栈中的 canaries 或者任何控制信息、返回地址被修改就都能被检测到了。
目前主要的编译器堆栈保护实现,如 Stack Guard,Stack-smashing Protection(SSP) 均把 Canaries 探测作为主要的保护技术,但是 Canaries 的产生方式各有不同。下面以 GCC 为例,简要介绍堆栈保护技术在 GCC 中的应用。
GCC 中的堆栈保护实现
Stack Guard 是第一个使用 Canaries 探测的堆栈保护实现,它于 1997 年作为 GCC 的一个扩展发布。最初版本的 Stack Guard 使用 0x00000000 作为 canary word。尽管很多人建议把 Stack Guard 纳入 GCC,作为 GCC 的一部分来提供堆栈保护。但实际上,GCC 3.x 没有实现任何的堆栈保护。直到 GCC 4.1 堆栈保护才被加入,并且 GCC4.1 所采用的堆栈保护实现并非 Stack Guard,而是 Stack-smashing Protection(SSP,又称 ProPolice)。
SSP 在 Stack Guard 的基础上进行了改进和提高。它是由 IBM 的工程师 Hiroaki Rtoh 开发并维护的。与 Stack Guard 相比,SSP 保护函数返回地址的同时还保护了栈中的 EBP 等信息。此外,SSP 还有意将局部变量中的数组放在函数栈的高地址,而将其他变量放在低地址。这样就使得通过溢出一个数组来修改其他变量(比如一个函数指针)变得更为困难。
GCC 4.1 中三个与堆栈保护有关的编译选项
-fstack-protector:
启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码。
-fstack-protector-all:
启用堆栈保护,为所有函数插入保护代码。
-fno-stack-protector:
禁用堆栈保护。
GCC 中的 Canaries 探测
下面通过一个例子分析 GCC 堆栈保护所生成的代码。分别使用 -fstack-protector 选项和 -fno-stack-protector 编译清单2中的代码得到可执行文件 demo_sp (-fstack-protector),demo_nosp (-fno-stack-protector)。
部分参考文章:http://www.ibm.com/developerworks/cn/linux/l-cn-gccstack/
- ios 继承UITableViewController,更改tableview样式
- demo1 动态显示view或弹框 动态隐藏view或弹框
- 从零开始的Spring Security Oauth2(一)
- 细说Android事件传递
- swift基础_ set get方法 理解
- 高仿今日头条(2)
- ios tableview 上加 textfiled
- Spring Cloud实战小贴士:Feign的继承特性(伪RPC模式)
- 仿淘宝购买详情页购买缩小动画
- 高仿今日头条(1)
- android Native堆
- Spring Boot中Web应用的统一异常处理
- android bitmap的内存分配和优化
- Gitflow工作流程
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 字符串:听说你对KMP有这些疑问?
- C++ gflags库使用说明
- 字符串:KMP算法还能干这个!
- Day12 :数值的整数次方
- 字符串:前缀表不右移,难道就写不出KMP了?
- HTTP请求头referer
- 【干货】C++基础知识:继承与派生详解
- 文本相似度——编辑距离
- 文本相似度——汉明距离
- python函数——Bunch配置加载
- npm install、npm install --global、npm install --save、npm install --save-dev的区别
- 3种 Springboot 全局时间格式化方式,别再写重复代码了
- pip conda 安装速度慢解决方法
- Linux 中指定使用的GPU
- 未读消息(小红点),前端 与 RabbitMQ 实时消息推送实践,贼简单~