栈溢出问题
说到二进制安全,肯定避不开栈溢出,今天就拿栈溢出作为开始,讨论一下它的形成与构造。
1. 程序的内存空间
首先,先介绍一下应用程序内存空间布局,用一张图来说明,简单粗暴。
简单介绍一下就是,每个应用程序都有4个GB的虚拟空间(在windows编程的虚拟内存中介绍了),高1个GB作为内核空间,用户代码无权访问,所有的应用程序共享这部分空间。而低3个GB的空间是用户代码,有stack ,heap ,MMap(存放动态链接库)等等,本文重点介绍stack。
有关堆,栈在虚拟空间中的概念:
1.栈自底向上增长,尺寸动态变化,默认临界大小是8M。
2.Stack和mmap间有个随机偏移量用于防止栈溢出污染mmap。
3.Mappings区间主要布置动态链接库。
4.默认进程堆自顶向下增长。
2. 栈帧
- 栈是一种LIFO的数据结构。
- 应用程序有一到多个用户态栈。
- 栈自底向上增长,由指令PUSH和POP引起其动态变化。
- 局部变量布局在栈中。
- 调用函数时参数由栈传递,返回地址也存储于栈中。
- 函数调用上下文与局部变量共同组成了栈帧——Stack Frame.
总结一下:栈帧=局部变量+函数调用上下文
再简单点的说:栈帧实际上只是一个通俗的说法,关于栈帧的上下界历来有两种说法,一曰以EBP和ESP之间的栈空间视为栈帧,这也是主流说法;一曰以调用参数和ESP之间的栈空间视为栈帧,我个人更倾向于这种说法,因为它便于理解。
3. 经典栈溢出的手法
经典栈溢出的手法概要:
-
先决条件:栈局部变量可控,存在溢出(strcpy)。
-
通过栈空间精心布局,布置shellcode,并用shellcode起始地址覆盖栈帧的ret addr。
存在的问题:
- Shellcode地址在不同PC上不确定
- 每次运行地址都会变化(ASLR)
拿一个例子说明:
出现的问题:
- fread时,指定的长度1024超过了buf尺寸。
- 输入数据长度可控,在input.txt中。
- 可以精心操纵input.txt,对buf进行溢出布置shellcode、覆盖ret addr。
- 当返回的地址可以被控制时,问题就大发了。
具体如何实现呢:
- 确定栈帧布局,计算出buf到ret addr的offset。
- 先对buf填充无效数据,通过调试找出buf的首地址并覆盖ret addr。
- 用一段shellcode填充buf,这段shellcode会弹一个shell。
4. you jump ,i jump esp
以上的栈溢出是存在明显问题的。
首先就是硬编码buffer地址的缺陷:
1. 对于不同版本系统来说,硬编码的地址是硬伤。Windows尚有dll装卸引起的“移位”。
2. 在ASLR引入以后,栈每次运行基址都是随机的。
解决方法:
一般情况下,ESP寄存器中的地址总是指向系统栈中且不会被溢出的数据破坏,函数返回时,ESP所指向的位置恰好是我们所淹没的返回地址的下一个位置。
现在问题就转化成了如何寻找“JMP ESP”的字节码地址。
本质:从内存空间的各个映像中寻觅”JMP ESP”的字节码地址。
-
对Linux来说:
ELF映像本身
加载的so -
对Windows来说:
PE映像本身
加载的dll
要点:尽量择取具有高稳定性的”JMP ESP”指令的地址。
有待补充,未完待续…