• 温习一下当年的汇编


    假设和说明

    本文以32Bit的X86 Windows为原型进行说明。栈的增长方向为高地址向低地址。

    开发环境为Visual Studio,语言是C++。没有涉及到Delphi、Borland C++、Visual Basic等。

    文中的任何栈地址、寄存器值以及内存址,都会与大家的环境不同。这些值是基于笔者当前环境的当前镜像。

    另外一篇文章会介绍C call、Standard call、Fast call和This call。本文默认基于C call。

    示例代码

    C++代码

    int FuncOne(int a, int b)

    {

    int sum;

    sum = a + b;

    return sum;

    }

    void main()

    {

    FuncOne(0x100, 0x200);

    }

    对应的汇编代码

    clip_image001

    操作方式

    · 用Visual Studio建立一个Visual C++的Win32 Console工程,在主文件中,删除所有代码,把上述C++代码粘贴上。

    · 设置端点在main下面的左括号上,如下图

    clip_image002

    · 按F5运行程序,当断点触发时,如下图

    clip_image003

    · 点击Visual Studio的主菜单“Debug”,点击Windows子菜单,然后分别选择Memory1、Disassembly和Registers。如下图红色部分。

    clip_image005

    · 修改你的Visual Studio窗口布局,如下图(这是我喜好的风格)

    clip_image007

    首先修改右上角的Columns列,这里我修改为4,以便于每次stack的操作,我们能更清楚地看到。

    其次在Address对应的那个输入框上,输入@esp,然后按回车。

    clip_image009

    上面的窗口为Disassembly窗口,当前断点所在处为push ebp

    clip_image011

    上面的窗口为Registers窗口,显示几个重要的寄存器。

    代码说明

    代码由Prologue、代码call和Epilogue组成。

    Prologue

    PUSH EBP

    保存old Frame Pointer地址到栈上

    MOV EBP,ESP

    保存当前栈地址,因为下面要修改栈地址

    SUB ESP, 0C0H

    预留出C0个字节的空间,供本地变量及其他使用。如编译器的对栈的检查,如Edit & Continue。后面我们修改这些设置,就会发现预留空间的变化。

    PUSH EBX

    这三个寄存器经常用,所以不管下面代码是否实际使用,都保留上。这是编译器的行为。

    PUSH ESI

    PUSH EDI

    Epilogue

    POP EDI

    恢复寄存器的原址

    POP ESI

    POP EBX

    ADD ESP, 0CH

    清理栈

    MOV ESP,EBP

    恢复栈地址

    POP EBP

    恢复Frame Pointer地址

    栈的变化

    01031440 push ebp

    执行前:ESP = 003FFD50。执行后:EIP = 01031441 ESP = 003FFD4C

    我们可以看到,在入栈操作后,ESP减少了4,即我们开始提到的,栈的空间是高地址向低地址增长的。EIP的始终指向当前要执行的代码的地址。

    继续按F10,一直走到下面这行:

    clip_image012

    此时,ESP = 002AF954

    在Memory窗口,输入@esp,然后回车

    clip_image013

    在灰色光标闪烁的地方,就是当前的栈顶,里面的值为0。按F10,此时代码进行到下一行:

    clip_image014

    观察此时的栈情况,首先ESP = 002AF950

    然后看memory窗口

    clip_image015

    最下面一行显示0x200已经入栈了,表示为一个DWORD类型的00 02 00 00。

    继续按F10,ESP = 002AF94C

    然后看Memory窗口

    clip_image016

    第二个参数0x100也已经入栈了,表示为一个DWORD类型的00 01 00 00。

    这时要注意,代码运行到了func call的地方。

    clip_image017

    在这里,我们要按F11,同时记住当前的ESP是002AF94C,该call下一行要执行的代码是add esp, 8,地址是0103146D。

    按F11进入到call中后,首先观察寄存器的情况:ESP = 002AF948

    ESP又减少了4,为什么?我们要观察Memory窗口

    clip_image018

    当前ESP指向的地址是什么?就是上面的add esp,8的地址。

    所以在当前环境下,我们可以总结出:

    · 右面参数入栈

    · 左面参数入栈

    · 返回地址入栈

    (这个与call方式有关,我们在下一篇文章中再谈)

    上面的add esp,8 代表什么?因为我们push了两个参数,共占用了栈的8个字节,所以我们需要恢复栈的原始状态。加8即代表回到了原来的地址。如果是1个参数,则就是加4了。

    Prologue的说明

    clip_image019

    Q: 为什么要ebp-0C0h,而不是其他的数字?

    A: 看下面一行的30h,乘以sizeof(DWORD)之后,即是0C0h。这段空间包含了参数使用的栈空间、Edit&Continue的空间和编译器对于栈保护的空间。

    Q: move ax, 0CCCCCCCCh什么意思?

    A: CC代表了int 3。所以上述代码实际上是填充了0C0h个int3在该栈空间内。如果代码“不小心”运行到了这里,则会立刻中断。

    Edit & Continue

    在Visual Studio中右击project,选择Property,修改General中的Debug Information Format为下图(默认为:Program Database for Edit And Continue (/ZI)

    clip_image021

    重新debug,我们会看到汇编代码如下:

    clip_image022

    上面的填充CC以及sub esp的操作已经没有了。如果修改代码如下:

    clip_image023

    则汇编代码变化为:

    clip_image024

    注意962BC3地址的sub esp,8,因为我们有两个local变量,所以栈预留了8个字节。那么如果我们有很多个变量呢?大家可以自己试验一下。

    FQQ

  • 相关阅读:
    php通过ip获取地理位置的方法
    python程序怎么运行起来的
    php cURL学习 一个post提交反馈的小例子
    如何运行Python程序
    python初学者的建议
    php5.4新特性实践
    php中json_encode与json_decode注意事项
    Apanta安装Emmet(Zencoding)
    office 2013 电话激活步骤
    js中encodeURI()与encodeURIComponent()区别
  • 原文地址:https://www.cnblogs.com/symphonygcr/p/4060740.html
Copyright © 2020-2023  润新知