内存破坏之——栈内存

来源:互联网 发布:手机隐形录音软件 编辑:程序博客网 时间:2024/05/03 10:56

本文结合Mario Hewardt和Daniel Pravat写的《Advanced Windows Debugging》第五章所讲的内容稍微进行整合总结而成。

1、 栈简介
堆栈(英语:stack),也可直接称栈。台湾作堆叠,在计算机科学中,是一种特殊的串行形式的数据结构,它的特殊之处在于只能允许在链结串行或阵列的一端(称为堆栈顶端指标,英语:top)进行加入资料(英语:push)和输出资料(英语:pop)的运算[1]。
这里写图片描述

栈内存则是在计算机中具有上述属性的动态内存区域,程序可以将数据压入栈中,也可以将数据从栈中弹出,在I386的机器中,栈顶位置由寄存器ESP进行定位。

2、 栈内存破坏情形
2.1 栈溢出
很简单的一个操作:
tcscpy(pDest, pSource);
上述操作对于编程人员来说是正常不过的事情,一般的字符串拷贝过程,但是如果放在下面这个例子(也可以参考书中第五章overrun的例子)中,则有可能会造成栈溢出的情况。
如果输入的参数字符串长度小于10,该程序则会正常执行,但是如果输入一个长度大于10的字符串,则会出现程序崩溃的情况,而这种情况则是由栈溢出造成的。
在Windbg中执行程序,并输入一个较长长度的字符串。会发现Windbg直接给出一个STATUS_STACK_BUFFER_OVERRUN的错误状态字。
这里写图片描述

void Fun(TCHAR *szData);int _tmain(int argc, _TCHAR* argv[]){    if(argc>1)    {        Fun(argv[1]);    }    return 0;}void Fun(TCHAR *szData){    TCHAR szStr[10];    _tcscpy(szStr, szData);    // do sth else}

OK,为什么在这种情况下产生栈溢出呢,参考文章函数调用过程堆栈变化分析可以知道局部变量时存放在栈中,而在上述执行字符串拷贝的操作时,是对栈内存进行拷贝,而且使用了没有分配空间的栈内存,从而出现栈溢出的错误。

2.2 使用已释放的栈内存
这种错误比较容易出现在多线程操作过程中对于共享的内存区域进行操作。
2.3 函数调用约定不匹配
__stdcall和__cdecl分别是两种函数调用约定,它们不一样的地方就是在参数堆栈恢复时,__stdcall是自动恢复,但是__cdecl是手动恢复。
例如:

int __stdcall func1(int a, int b, int c){    int d = a + b + c;    return d;}int __cdecl func2(int a, int b, int c){    int d = a + b + c;    return d;}int _tmain(int argc, _TCHAR* argv[]){    int a, b;    a = func1(1, 2, 3);    b = func2(1, 2, 3);    return 0;}

反汇编之后的汇编代码是:

    int a, b;    a = func1(1, 2, 3);013D1A8E  push        3  013D1A90  push        2  013D1A92  push        1  013D1A94  call        func1 (13D11EAh)  013D1A99  mov         dword ptr [a],eax      b = func2(1, 2, 3);013D1A9C  push        3  013D1A9E  push        2  013D1AA0  push        1  013D1AA2  call        func2 (13D11EFh)  013D1AA7  add         esp,0Ch  013D1AAA  mov         dword ptr [b],eax      return 0;

可以看见调用结束后对堆栈的处理上,__cdecl需要在调用结束后进行堆栈的平衡。那如果在使用函数时没有使用正确的调用约定也会导致栈出问题。例如dll中的函数使用的是__stdcall,但是在实际使用时使用__cdecl进行声明,那么就会出现堆栈不平衡的情况,出现栈内存错误。

参考资料
[1] 维基百科 http://zh.wikipedia.org/wiki/%E5%A0%86%E6%A0%88

0 0
原创粉丝点击