栈溢出

栈溢出原理

并不是要转去学二进制了,毕竟Web对我来说还有很多路要走,学习栈溢出主要是为了应付马上的信安软考

寄存器分配 ESP、EBP、EIP

以32位x86架构为基础,Windows提供三个寄存器帮助定位栈和函数调用--ESP、EBP、EBP

ESP

ESP 用来存储函数调用栈的栈顶指针,指向栈区中最上一个栈帧的栈顶

EBP

EBP 用来存储当前函数状态的基地址,即栈底指针,指向栈区中最上一个栈帧的栈底

EIP

EIP 用来存放下一个执行语句的地址的指令寄存器

函数调用步骤

下面让我们来看看发生函数调用时,栈顶函数状态以及上述寄存器的变化。变化的核心任务是将调用函数(caller)的状态保存起来,同时创建被调用函数(callee)的状态。

所谓被调用函数,就是函数中的函数 Caller's Caller's ebp=Callee's ebp

void caller(arg1,arg2,....argn){
    callee(arg1,arg1,arg2,....argn);
}

Step1.参数入栈

首先将被调用函数(callee)的参数按照逆序依次压入栈内 argn......arg2,arg1

栈溢出

这个栈是从下往上压的,也就是esp位置的先出栈

Step2.返回地址入栈

将调用函数(caller)进行调用之后的下一条指令地址作为返回地址压入栈内。这样调用函数(caller)的 EIP(指令地址)保存在 Return Address中。这里的地址就是callee()运行完接着caller()下一条指令执行的地址

栈溢出

Step3.调用函数的EBP入栈

将当前的 EBP 寄存器的值(也就是调用函数caller()的基地址)压入栈内

并将EBP(栈底)的值更新为当前ESP(栈顶)的地址 → mov esp, ebp

栈溢出

Step4.被调用函数局部变量入栈

到这一步,被调用函数callee的函数帧结构就完整了

在压栈的过程中,esp 寄存器的值不断减小,栈内数据不断变大(对应于栈从内存高地址向低地址生长)。

压入栈内的数据包括:调用参数(argn...arg1)、返回地址(Return Address)、调用函数的基地址(新EBP),以及局部变量(Local Variables)

其中调用参数以外的数据共同构成了"被调用函数(callee)的状态"。在发生调用时,程序还会将被调用函数(callee)的指令地址存到 EIP 寄存器内,这样程序就可以依次执行被调用函数的指令了。

栈溢出

看过了函数调用发生时的情况,就不难理解函数调用结束时的变化。变化的核心就是丢弃被调用函数(callee)的状态,并将栈顶弹出恢复为调用函数(caller)的状态。

Step5.将被调用函数的局部变量弹出栈外

首先被调用函数的局部变量会从栈内直接弹出,栈顶会指向调用函数(caller)的基地址。

栈溢出

Step6.弹出调用函数EBP

然后将基地址内存储的调用函数(caller)的基地址从栈内弹出,并存到 ebp 寄存器内。这样调用函数(caller)的 ebp(基地址)信息得以恢复。此时栈顶会指向返回地址。下图容易误解,其实这里的Caller's Caller's ebp已经变成了Caller's ebp

栈溢出

Step7.弹出返回地址,存入EIP

再将返回地址从栈内弹出,并存到 eip 寄存器内。这样调用函数(caller)的 eip(指令)信息得以恢复到caller()的执行状态。

栈溢出

至此调用函数的状态恢复,之后继续执行调用函数的指令

栈溢出攻击

什么是栈溢出呢?

栈溢出是指向向栈中写入了超出限定长度的数据,溢出的数据会覆盖栈中其它数据,从而影响程序的运行。如果我们计算好溢出的长度,编写好溢出数据,让我们想要的地址数据正好覆盖到函数返回地址

那么被调函数调用完返回主函数时,就会跳转到我们覆盖的地址上。所以控制程序执行指令最关键的寄存器就是 EIP,我们的目标就是让EIP 载入攻击指令的地址。通过这样改变程序流程,接下来我们就可以干很多坏事了!

该怎么构造呢,这里有个简单的缓冲区溢出的例子

void f(char *str){
    char buf[16];
    strcpy(buf,str);
}
void main(){
    char buf[128];
    for(i=0;i<=127;i++)
        buf[i]='A';
    f(buf);
    print("It's Buffer OverFlow!");
}

这是执行被调用函数f()的stack

栈溢出

EIP(Return Address)

其中的buf是局部变量,但是我们从程序中可以看出,128位的*str输入明显越界,会导致数据溢出,溢出就会覆盖EBP、EIP

栈溢出

我们通过精心构造一个位数精准的数据,使得EIP处正好被我们溢出的那几位数据覆盖为AAAA,其中A对应ASCII 0x41

AAAA对应EIP地址为0x41414141

0x41414141覆盖了原来EIP的位置时,f函数在返回时就会将0x41414141弹出来给EIP,假如这时shell程序调用地址是0x41414141,那么main()接下来就会去执行shell,我们就能运行shell实施攻击

(这里全填A是为了方便理解,实际情况构造shell地址肯定不是AAAA哈,自己根据shell地址构造就行了)

防范缓冲区溢出策略

常用的函数有strcpy()sprintf()strcat()vsprintf()gets()scanf(),以及在循环内的函数getc()fgetc()getchat()等都非常容易导致溢出

防范策略有:

系统管理防范策略:关闭不必要的特权程序、及时打系统补丁

开发时的防范策略:注意危险函数的调用、缓冲区不允许执行、静态分析检查指针、堆栈向高地址方向增长等

上一篇:Android推荐用于Atom处理器平台吗?


下一篇:java开发JDBC连接数据库详解