反汇编系列——堆栈篇

Posted 牧秦丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了反汇编系列——堆栈篇相关的知识,希望对你有一定的参考价值。

    要反汇编程序,不可避免要接触到堆栈,你首先得会查看堆栈,知道堆栈在某一时刻的确切内容。首先,我们讲述一些与堆栈相关的基础知识。


1、堆栈基础

    汇编语言中的“堆栈”的含义与数据结构中堆栈的含义不同,尽管从操作上来说,它们都是“后进先出”,这个不用赘述。汇编中有一个寄存器esp指向当前栈顶,而栈底的位置是不变的,整个程序运行过程中,通过操作esp来操作堆栈,进行堆栈的压入、弹出及平衡操作。

    

    一些堆栈操作术语:

  • 压入:将一个变量压入到堆栈;
  • 弹出:将一个变量从堆栈中弹出;
  • 平衡堆栈:当函数调用完成后,进行局部变量的释放、返回值的正确处理等;
    这些操作都是针对 esp 操作完成的,所以单次栈的操作可以认为基本没有额外开销。

2、堆栈类型

    根据架构的不同,有两种基本的堆栈类型:向上生长和向下生长的堆栈。

  • 向上生长堆栈:堆栈向高地址增长,当向栈中压入元素时,esp增加。栈顶地址 >= 栈底地址。
  • 向下生长堆栈:堆栈向低地址增长,当向栈中压入元素时,esp减小。栈顶地址 <= 栈底地址。
Windows平台下堆栈向下生长,所以我们后续的章节中默认堆栈向下生长。一个典型的向下生长的堆栈图如下:

3、函数调用 堆栈主要用于函数调用,我们知道,一个函数调用时的例程如下:
  1. 将函数参数入栈;
  2. 将返回处的代码地址入栈(段内调用一般将eip入栈,段间调用将 cs、eip 依次入栈);
  3. jmp到被调函数代码地址处开始执行;
  4. 被调函数分配局部变量内存;
  5. 执行被调函数代码,存好返回值;
  6. 平衡堆栈。
C/C++ 标准中,参数的入栈顺序是没有规定的,也就是说具体入栈顺序依赖于具体实现。如:
int f1()

    cout<<"In f1()"<<endl;
    return 1;


int f2()

    cout<<"In f2()"<<endl;
    return 2;


int foo(int a, int b)

    // ...


foo(f1(), f2());
    像这段代码,我们不知道先执行 f1 还是先执行 f2 ,因为不同的编译器编译出的结果可能不同。用 Microsoft Visual C++ 编译出的代码,先执行 f2 ,后执行 f1 ,也就是说, MSVC 编出的代码,参数从右往左依次入栈。
4、调用约定 谈到函数调用,就不可避免的谈到函数调用约定。主流的调用约定有:PASCAL__cdecl__stdcall等:
  • PASCAL:参数从左往右入栈,被调用者平衡堆栈;
  • __cdecl:即 C 调用约定,参数从右往左入栈,调用者平衡堆栈;
  • __stdcall:即标准调用约定,参数从右往左入栈,被调者平衡堆栈;
由于__cdecl约定由调用者平衡堆栈,所以生成的最终二进制文件较大,而且不同的编译器编出的Dll协同工作差(思考为什么) 但是,在某些函数如printfsprintf等变长参数的函数中,由于被调者(printf) 不知道传递进来的参数有几个,只有调用者知道,所以堆栈平衡操作交由调用者完成,所以必须使用__cdecl调用约定。
5、结语 我们详述了堆栈相关的基本知识,在后续的篇幅中,这些知识将非常关键。下一章将结合一个实际实例介绍IDA Pro逆向出的代码,从而对反汇编有一个基本的了解。




以上是关于反汇编系列——堆栈篇的主要内容,如果未能解决你的问题,请参考以下文章

反汇编系列——函数篇

在反汇编代码中跟踪调用堆栈

挂茶馆_VIP其他杂项系列教程

栈的生长方向理解

VS反汇编分析

Windows 逆向OD 调试器工具 ( OD 附加进程 | OD 调试器面板简介 | 反汇编窗口 | 寄存器窗口 | 数据窗口 | 堆栈窗口 )