X是局部变量。

    C/C++标准告诉我们它只对函数内部可见,无法从外部访问。习惯上,局部变量放在栈中。也可能有其他方法,但在x86中是这样。

    函数序言后下一条指令PUSH ECX目的并不是要存储ECX的状态(注意程序结尾没有与之相对的POP ECX)。

    事实上这条指令仅仅是在栈中分配了4字节用于存储变量x。

    变量x可以用宏 _x$ 来访问(等于-4),EBP寄存器指向当前栈帧。

    在一个函数执行完之后,EBP将指向当前栈帧,就无法通过EBP+offset来访问局部变量和函数参数了。

    也可以使用ESP寄存器,但由于它经常变化所以使用不方便。所以说在函数刚开始时,EBP的值保存了此时ESP的值。

    下面是一个非常典型的32位栈帧结构

    1. EBP-8 local variable #2, marked in IDA as var_8
    2. EBP-4 local variable #1, marked in IDA as var_4
    3. EBP+8 argument#1, marked in IDA as arg_0
    4. EBP+0x10 argument#3, marked in IDA as arg_8

    在我们的例子中,scanf()有两个参数。

    第一个参数是指向“%d”的字符串指针,第二个是变量x的地址。

    可以说,LEA在这里只是把EBP的值与宏 _x$的值相乘,并存储在EAX寄存器中。

    lea eax, [ebp-4]也是一样。

    EBP的值减去4,结果放在EAX寄存器中。接着EAX寄存器的值被压入栈中,再调用printf()。

    之后,printf()被调用。第一个参数是一个字符串指针:“You entered %d …”。

    第二个参数是通过mov ecx, [ebp-4]使用的,这个指令把变量x的内容传给ECX而不是它的地址。

    然后,ECX的值放入栈中,接着最后一次调用printf()。

    让我们在OllyDbg中使用这个例子。首先载入程序,按F8直到进入我们的可执行文件而不是ntdll.dll。往下滚动屏幕找到main()。点击第一条指令(PUSH EBP),按F2,再按F9,触发main()开始处的断点。

    让我们来跟随到准备变量x的地址的位置。图6.2

    可以右击寄存器窗口的EAX,再点击“堆栈窗口中跟随”。这个地址会在堆栈窗口中显示。观察,这是局部栈中的一个变量。我在图中用红色箭头标出。这里是一些无用数据(0x77D478)。PUSH指令将会把这个栈元素的地址压入栈中。然后按F8直到scanf()函数执行完。在scanf()执行时,我们要在命令行窗口中输入,例如输入123。

    scanf()在这里执行。图6.3。scanf()在EAX中返回1,这意味着成功读入了一个值。现在我们关心的那个栈元素中的值是0x7B(123)。

    接下来,这个值从栈中复制到ECX寄存器中,然后传递给printf()。图6.4

    6.2 x86 - 图2

    图6.2 OllyDbg:计算局部变量的地址

    图6.3:OllyDbg:scanf()执行

    6.2 x86 - 图4

    图6.4:OllyDbg:准备把值传递给printf()

    让我们在Linux GCC 4.4.1下编译这段代码

    GCC把第一个调用的printf()替换成了puts(),原因在2.3.3节中讲过了。