栈溢出的产生
递归太深,或形成死循环没有终止。Windows 默认给每个线程仅仅分配 1M 内存,如果使用超过这个大小就会发生溢出(除此之外,也有可能发生堆溢出、bss 溢出等)。
函数调用栈的相关知识
函数调用栈是指程序运行时内存一段连续的区域,用来保存函数运行时的状态信息,包括函数参数与局部变量等。
称之为“栈”是因为发生函数调用时,调用函数(caller)的状态被保存在栈内,被调用函数(callee)的状态被压入调用栈的栈顶;在函数调用结束时,栈顶的函数(callee)状态被弹出,栈顶恢复到调用函数(caller)的状态。
函数调用栈在内存中从高地址向低地址生长,所以栈顶对应的内存地址在压栈时变小,退栈时变大。
在一次函数调用中,栈中将被依次压入:函数实参、返回地址、EBP。如果函数有局部变量,接下来就在栈中开辟相应的空间以构造变量。
扩展基址指针寄存器(EBP,Extended Base Pointer) 存放一个指针,指向系统栈最上面一个栈帧的底部。
如图是一个典型的栈帧:
栈顶在上,ESP 永远指向栈顶,EBP 保存调用者的 EBP(如 main() 调用 foo(),这里保存的就是 main() 的 EBP),以方便恢复现场。实参通过 EBP + 位移量
获得,局部变量通过 EBP - 位移量
获得。
栈溢出漏洞
栈溢出漏洞指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致栈中与其相邻的变量的值被改变。
黑客利用栈溢出漏洞,轻则可以使得程序崩溃,重则可以使得攻击者控制程序执行流程。
一种典型的栈溢出攻击:
首先,在退栈过程中,返回地址会被传给 EIP,所以我们只需要让溢出数据用攻击指令的地址来覆盖返回地址就可以了。
(扩展)指令寄存器((E)IP,(Extended) Instruction Pointer)存放当前指令的下一条指令的地址。CPU 该执行哪条指令就是通过 (E)IP 来指示的。
举一个简单的例子:
#include <stdio.h>
#include <string.h>
void success() {
puts("You already controlled it.");
}
void vulnerable() {
char s[12];
gets(s);
puts(s);
return;
}
int main(int argc, char **argv) {
vulnerable();
return 0;
}
这个程序的主要目的读取一个字符串,并将其输出。我们希望可以控制程序执行success函数。
gets() 是一个危险函数,因为它不检查输入字符串的长度,而是以回车来判断是否输入结束,所以很容易导致栈溢出。
历史上,莫里斯蠕虫第一种蠕虫病毒就利用了这个危险函数实现了栈溢出。
我们反编译一下二进制程序后可以得知:
- 该字符串距离 EBP 的长度为 0x14;
- success() 的地址为 0x0804846B。
那么如果我们读取的字符串为 0x14*'a'+'bbbb'+0x0804846B
,就会将 EBP 覆盖为 bbbb,将 retaddr 覆盖为 0x0804846B。
网友评论