CMU 15-213 Intro to Computer Systems Lecture 9
距离上次更新已经 1589 天了,文章内容可能已经过时。
课程主页:http://www.cs.cmu.edu/afs/cs/academic/class/15213-f15/www/schedule.html
课程资料:https://github.com/EugeneLiu/translationCSAPP
课程视频:https://www.bilibili.com/video/av31289365/
这一讲介绍了机器级编程5:高级主题。
内存布局
x86-64 Linux内存布局
- 堆栈
- 运行时的堆栈(限制为8MB)
- 例如,局部变量
- 堆
- 根据需要动态分配
- 调用malloc(), calloc(), new()时使用
- 数据
- 静态分配的数据
- 例如,全局变量,静态变量,字符串常量
- 文本/共享库
- 可执行机器指令
- 只读
缓存区溢出
回忆:内存引用错误示例
产生如下结果:
内存中结构的布局如下:
如果输入大于1,那么会触碰到存储浮点数的内存部分,所以会产生上述错误。
上述现象会产生大问题
- 通常称为“缓存区溢出”
- 当超过分配给数组的内存大小时
- 为什么会产生大问题?
- 这是导致安全漏洞的第一大技术原因
- 总体原因是社会工程/用户无知
- 这是导致安全漏洞的第一大技术原因
- 最常见的形式
- 字符串输入中未经检查的长度
- 特别是对于堆栈上的有界字符数组
- 有时称为stack smashing
考虑如下例子:
上述代码无法限制读取字符的数量,同样会产生缓存区溢出的问题。
例子
反汇编的结果为如下:
echo:
call_echo:
在调用gets函数之前,栈的布局如下:
如果运行如下命令:
那么布局如下:
如果运行如下命令:
那么布局如下:
如果运行如下命令:
那么布局如下:
此时会返回到其他的位置,例如:
这样就会产生安全问题,例如代码注入攻击,缓冲区溢出错误可使远程计算机在受害计算机上执行任意代码。
蠕虫和病毒
- 蠕虫:是一个程序
- 可以自己运行
- 可以将自身的完全正常版本传播到其他计算机
- 病毒:是一段代码
- 将自身添加到其他程序
- 不独立运行
- 两者(通常)在计算机之间传播并造成破坏
如何处理缓存区溢出攻击
- 避免溢出漏洞
- 采用系统级保护
- 让编译器使用“堆栈金丝雀”
避免溢出漏洞
例如在之前代码中限制输入字符的数量。
系统级保护
堆栈随机偏移
在程序开始时,在堆栈上分配随机数量的空间
移位整个程序的堆栈地址
使黑客很难预测插入代码的开始
例如5次执行内存分配代码的结果
程序每次执行时都会重新定位堆栈
- 不可执行的代码段
- 在传统的x86中,可以将内存区域标记为“只读”或“可写”
- 可以执行任何可读的
- X86-64添加了明确的“执行”权限
- 堆栈标记为不可执行
- 在传统的x86中,可以将内存区域标记为“只读”或“可写”
堆栈“金丝雀”(canary)
- 思想
- 将特殊值(“ canary”)放在在缓存区之外的堆栈上
- 退出函数前检查是否损坏
- GCC实现
- -fstack-protector
- 现在是默认值(之前已禁用)
还是echo那段代码,在调用gets之前堆栈的布局如下:
假设输入为
那么堆栈的布局为
面向返回的编程攻击
- 挑战(针对黑客)
- 堆栈随机化使得难以预测缓冲区位置
- 将堆栈标记为不可执行,使其很难插入二进制代码
- 替代策略
- 使用现有代码
- 例如,来自stdlib的库代码
- 将片段串在一起以实现总体预期结果
- 无法克服堆栈金丝雀
- 使用现有代码
- 从小工具(Gadget)构造程序
- 以ret结尾的指令序列
- 由单字节0xc3编码
- 每次运行固定的代码位置
- 代码是可执行的
- 以ret结尾的指令序列
Gadget例子
上述代码的一部分可以对应如下操作
ROP执行
联合
- 根据最大元素分配
- 一次只能使用一个字段
例如
内存布局如下:
对比结构:
内存布局如下:
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Doraemonzzz!
评论
ValineLivere