返回介绍

6.2 x86

发布于 2025-02-22 14:00:42 字数 3655 浏览 0 评论 0 收藏 0

6.2.1 MSVC

MVSC 2010 编译后得到下面代码

#!bash
CONST SEGMENT
$SG3831 DB ’Enter X:’, 0aH, 00H
$SG3832 DB ’%d’, 00H
35
6.2\. X86 CHAPTER 6\. SCANF()
$SG3833 DB ’You entered %d...’, 0aH, 00H
CONST ENDS
PUBLIC _main
EXTRN _scanf:PROC
EXTRN _printf:PROC
; Function compile flags: /Odtp
_TEXT SEGMENT
_x$ = -4 ; size = 4
_main PROC
        push    ebp
        mov     ebp, esp
        push    ecx
        push    OFFSET $SG3831 ; ’Enter X:’
        call    _printf
        add     esp, 4
        lea     eax, DWORD PTR _x$[ebp]
        push    eax
        push    OFFSET $SG3832 ; ’%d’
        call    _scanf
        add     esp, 8
        mov     ecx, DWORD PTR _x$[ebp]
        push    ecx
        push    OFFSET $SG3833 ; ’You entered %d...’
        call    _printf
        add     esp, 8
        ; return 0
        xor     eax, eax
        mov     esp, ebp
        pop     ebp
        ret     0
_main ENDP
_TEXT ENDS

X 是局部变量。

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

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

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

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

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

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

下面是一个非常典型的 32 位栈帧结构 ... ... EBP-8 local variable #2, marked in IDA as var_8 EBP-4 local variable #1, marked in IDA as var_4 EBP saved value of EBP EBP+4 return address EBP+8 argument#1, marked in IDA as arg_0 EBP+0xC argument#2, marked in IDA as arg_4 EBP+0x10 argument#3, marked in IDA as arg_8 ... ...

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

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

首先, lea eax, DWORD PTR _x$[ebp] 指令将变量 x 的地址放入 EAX 寄存器。LEA 作用是"取有效地址",然而之后的主要用途有所变化(b.6.2)。

可以说,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()

6.2.2 MSVC+OllyDbg

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

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

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

enter image description here

图 6.1 命令行输出

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

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

enter image description here

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

enter image description here

图 6.3:OllyDbg:scanf() 执行

enter image description here

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

6.2.3 GCC

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

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

和之前一样,参数都是用 MOV 指令放入栈中。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文