CSAPP-note-5
内存布局
x86-64 Linux 内存布局
栈:Runtime stack(8MB limit),用来存储局部变量等。
堆:根据需要动态进行分配。在调用malloc(),calloc(),new()时,在堆上进行内存分配。
数据段(data):静态地分配数据。如:全局变量,静态变量,字符串常量。
文本段/共享存储区(text/shared Libraries):可执行的机器指令(只读)。
让我们看一个例子
1 | char big_array[1L<<24]; /* 16MB */ |
缓冲区溢出
什么是缓冲区溢出?通常指的是我们去访问超过数组大小的内存。
缓冲区溢出bug可能允许远程计算机在受害计算机上执行任意代码。
一个典型的会发生Buffer Overflow的代码是gets()函数。
1 | char *gets(char *dest) |
这段代码没有去限制读入字符的数量,如果传入的dest的大小小于了输入字符串的大小,就会发生缓冲区溢出。
从前面的学习我们知道,一个栈帧的顶部是下一条待执行指令的地址,会使%rip指针跳转到该地址。我们如果得到了Return address处于栈的哪个位置,我们就可以利用缓冲区溢出。注入我们需要执行的代码,并且将Return address替换成我们注入代码的代码地址,就可以实现攻击了。
防止攻击的方法
1.避免代码中的溢出漏洞
- 用fgets替换gets
- 用strncpy替换strcpy
- 不使用scanf读入%s
- 使用fgets读入字符串
- 或者使用%ns,这里的n是大小
2.系统级保护
- 不可执行代码段。x86-64添加了显示的“执行”权限,栈区被标记成不可执行。
- 随机栈偏移。在程序开始时,在堆栈上分配随机数量的空间,移动整个程序的栈地址。使攻击者难以预测插入代码的首地址。每次程序执行时重新定位栈。
3.栈金丝雀
在分配栈空间时,在栈帧顶部设立一个金丝雀值,当发现金丝雀值被修改时,代表发生了溢出。
面向返回的编程攻击
这种攻击方式存在局限性:只能利用已经存在的代码,且无法绕过栈金丝雀。
ret指令是一条非常特殊的指令,当我们遇到ret时,我们会弹出栈顶元素,然后跳转到这个返回地址,继续执行。
所以当我们的栈顶,是一系列ret前的所需执行代码的地址。我们就可以持续的执行。
举一个例子
1 | code A: |
我们会将栈顶的6弹出,并且将%rip设置为6,所以程序会执行6,7,并且由于8仍然是ret,所以我们会将2也视为Return
address,接着%rip会跳转到2,执行add指令。如此循环,我们可以执行一系列的target code,通过ret串起来。
联合体
分配的内存与最大元素相同。在一个时间只能使用一个字段。