返回介绍

5.1 x86: 3 个参数

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

5.1.1 MSVC

在我们用 MSVC 2010 Express 编译后可以看到:

#!bash
$SG3830 DB ’a=%d; b=%d; c=%d’, 00H
...
        push 3
        push 2
        push 1
        push OFFSET $SG3830
        call _printf
        add esp, 16        ; 00000010H

这和之前的代码几乎一样,但是我们现在可以看到 printf() 的参数被反序压入了栈中。第一个参数被最后压入。

另外,在 32bit 的环境下 int 类型变量占 4 bytes。

那么,这里有 4 个参数 4*4=16 —— 恰好在栈中占据了 16bytes:一个 32bit 字符串指针,和 3 个 int 类型变量。

当函数执行完后,执行" ADD ESP, X "指令恢复栈指针寄存器(ESP 寄存器)。通常可以在这里推断函数参数的个数:用 X 除以 4。

当然,这只涉及 __cdecl 函数调用方式。

也可以在最后一个函数调用后,把几个" ADD ESP, X "指令合并成一个。

#!bash
push a1
push a2
call ...
...
push a1
call ...
...
push a1
push a2
push a3
call ...
add esp, 24

5.1.2 MSVC 与 ollyDbg

现在我们来在 OllyDbg 中加载这个范例。我们可以尝试在 MSVC 2012 加 /MD 参数编译这个示例,也就是链接 MSVCR*.dll ,那么我们就可以在 debugger 中清楚的看到调用的函数。

在 OllyDbg 中载入程序,最开始的断点在 ntdll.dll 中,接着按 F9(run),然后第二个断点在 CRT-code 中。现在我们来找 main() 函数。

往下滚动屏幕,找到下图这段代码(MSVC 把 main() 函数分配在代码段开始处) 见图 5.3

点击 PUSH EBP 指令,按下 F2(设置断点) 然后按下 F9(run),通过这些操作来跳过 CRT-code,因为我们现在还不必关注这部分。

按 6 次 F8(step over)。见图 5.4 现在 EIP 指向了 CALL printf 的指令。和其他调试器一样,OllyDbg 高亮了有值改变的寄存器。所以每次你按下 F8,EIP 都在改变然后它看起来便是红色的。ESP 同时也在改变,因为它是指向栈的

栈中的数据又在哪?那么看一下调试器右下方的窗口:

enter image description here

图 5.1

然后我们可以看到有三列,栈的地址,元组数据,以及一些 OllyDbg 的注释,OllyDbg 可以识别像 printf() 这样的字符串,以及后面的三个值。

右击选中字符串,然后点击”follow in dump”,然后字符串就会出现在左侧显示内存数据的地方,这些内存的数据可以被编辑。我们可以修改这些字符串,之后这个例子的结果就会变的不同,现在可能并不是很实用。但是作为练习却非常好,可以体会每部分是如何工作的。

再按一次 F8(step over)

然后我们就可以看到输出

enter image description here

图 5.2 执行 printf() 函数

让我们看看寄存器和栈是怎样变化的 见图 5.5

EAX 寄存器现在是 0xD(13).这是正确的,printf() 返回打印的字符,EIP 也变了——

事实上现在指向 CALL printf 之后下一条指令的地址.ECX 和 EDX 的值也改变了。显然,printf() 函数的内部机制对它们进行了使用。

很重要的一点 ESP 的值并没有发生变化,栈的状态也是!我们可以清楚地看到字符串和相应的 3 个值还是在那里,实际上这就是 cdecl 调用方式。被调用的函数并不清楚栈中参数,因为这是调用体的任务。

再按一下 F8 执行 ADD ESP, 10 见图 5.6

ESP 改变了,但是值还是在栈中!当然 没有必要用 0 或者别的数据填充这些值。

因为在栈指针寄存器之上的数据都是无用的。

enter image description here

图 5.3 OllyDbg:main() 初始处

enter image description here

图 5.4 OllyDbg:printf() 执行时

enter image description here

图 5.5 Ollydbg:printf() 执行后

enter image description here

图 5.6 OllyDbg ADD ESP, 10 执行完后

5.1.3 GCC

现在我们将同样的程序在 linux 下用 GCC4.4.1 编译后放入 IDA 看一下:

#!bash
main            proc near

var_10          = dword ptr -10h
var_C           = dword ptr -0Ch
var_8           = dword ptr -8
var_4           = dword ptr -4

                push    ebp
                mov     ebp, esp
                and     esp, 0FFFFFFF0h
                sub     esp, 10h
                mov     eax, offset aADBDCD ; "a=%d; b=%d; c=%d"
                mov     [esp+10h+var_4], 3
                mov     [esp+10h+var_8], 2
                mov     [esp+10h+var_C], 1
                mov     [esp+10h+var_10], eax
                call    _printf
                mov     eax, 0
                leave
                retn
main            endp

MSVC 与 GCC 编译后代码的不同点只是参数入栈的方法不同,这里 GCC 不用 PUSH/POP 而是直接对栈操作。

5.1.4 GCC 与 GDB

接着我们尝试在 linux 中用 GDB 运行下这个示例程序。

-g 表示将 debug 信息插入可执行文件中

#!bash
$ gcc 1.c -g -o 1

反编译:

#!bash
$ gdb 1
GNU gdb (GDB) 7.6.1-ubuntu
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/dennis/polygon/1...done.

表 5.1 在 printf() 处设置断点

#!bash
(gdb) b printf
Breakpoint 1 at 0x80482f0

Run 这里没有 printf() 函数的源码,所以 GDB 没法显示出源码,但是却可以这样做

#!bash
(gdb) run
Starting program: /home/dennis/polygon/1

Breakpoint 1, __printf (format=0x80484f0 "a=%d; b=%d; c=%d") at printf.c:29
29 printf.c: No such file or directory.

打印 10 组栈中的元组数据,左边是栈中的地址

#!bash
(gdb) x/10w $esp
0xbffff11c: 0x0804844a 0x080484f0 0x00000001 0x00000002
0xbffff12c: 0x00000003 0x08048460 0x00000000 0x00000000
0xbffff13c: 0xb7e29905 0x00000001

最开始的是返回地址(0x0804844a),我们可以确定在这里,于是可以反汇编这里的代码

#!bash
(gdb) x/5i 0x0804844a
0x804844a <main+45>: mov $0x0,%eax
0x804844f <main+50>: leave
0x8048450 <main+51>: ret
0x8048451: xchg %ax,%ax
0x8048453: xchg %ax,%ax

两个 XCHG 指令,明显是一些垃圾数据,可以忽略 第二个(0x080484f0) 是一处格式化字符串

#!bash
(gdb) x/s 0x080484f0
0x80484f0: "a=%d; b=%d; c=%d"

而其他三个则是 printf() 函数的参数,另外的可能只是栈中的垃圾数据,但是也可能是其他函数的数据,例如它们的本地变量。这里可以忽略。 执行 finish ,表示执行到函数结束。在这里是执行到 printf() 完。

#!bash
(gdb) finish
Run till exit from #0 __printf (format=0x80484f0 "a=%d; b=%d; c=%d") at printf.c:29
main () at 1.c:6
6 return 0;
Value returned is $2 = 13

GDB 显示了 printf() 函数在 eax 中的返回值,这是打印字符的数量,就像在 OllyDbg 中一样。

我们同样看到了”return 0;” 及这在 1.c 文件中第 6 行所代表的含义。1.c 文件就在当前目录下,GDB 就在那找到了字符串。但是 GDB 又是怎么知道当前执行到了哪一行?

事实上这和编译器有关,当生成调试信息时,同样也保存了一张代码行号与指令地址的关系表。

查看 EAX 中储存的 13:

#!bash
(gdb) info registers
eax            0xd      13
ecx            0x0      0
edx            0x0      0
ebx            0xb7fc0000       -1208221696
esp            0xbffff120       0xbffff120
ebp            0xbffff138       0xbffff138
esi            0x0      0
edi            0x0      0
eip            0x804844a        0x804844a <main+45>
...

反汇编当前的指令

#!bash
(gdb) disas
Dump of assembler code for function main:
    0x0804841d <+0>:    push    %ebp
    0x0804841e <+1>:    mov     %esp,%ebp
    0x08048420 <+3>:    and     $0xfffffff0,%esp
    0x08048423 <+6>:    sub     $0x10,%esp
    0x08048426 <+9>:    movl    $0x3,0xc(%esp)
    0x0804842e <+17>:   movl    $0x2,0x8(%esp)
    0x08048436 <+25>:   movl    $0x1,0x4(%esp)
    0x0804843e <+33>:   movl    $0x80484f0,(%esp)
    0x08048445 <+40>:   call    0x80482f0 <printf@plt>
=>  0x0804844a <+45>:   mov     $0x0,%eax
    0x0804844f <+50>:   leave
    0x08048450 <+51>:   ret
End of assembler dump.

GDB 默认使用 AT&T 语法显示,当然也可以转换至 intel:

#!bash
(gdb) set disassembly-flavor intel
(gdb) disas
Dump of assembler code for function main:
    0x0804841d <+0>:    push    ebp
    0x0804841e <+1>:    mov     ebp,esp
    0x08048420 <+3>:    and     esp,0xfffffff0
    0x08048423 <+6>:    sub     esp,0x10
    0x08048426 <+9>:    mov     DWORD PTR [esp+0xc],0x3
    0x0804842e <+17>:   mov     DWORD PTR [esp+0x8],0x2
    0x08048436 <+25>:   mov     DWORD PTR [esp+0x4],0x1
    0x0804843e <+33>:   mov     DWORD PTR [esp],0x80484f0
    0x08048445 <+40>:   call    0x80482f0 <printf@plt>
=>  0x0804844a <+45>:   mov     eax,0x0
    0x0804844f <+50>:   leave
    0x08048450 <+51>:   ret
End of assembler dump.

执行下一条指令,GDB 显示了结束大括号,代表着这里是函数结束部分。

#!bash
(gdb) step
7 };

在执行完 MOV EAX, 0 后我们可以看到 EAX 就已经变为 0 了。

#!bash
(gdb) info registers
eax 0x0 0
ecx 0x0 0
edx 0x0 0
ebx 0xb7fc0000 -1208221696
esp 0xbffff120 0xbffff120
ebp 0xbffff138 0xbffff138
esi 0x0 0
edi 0x0 0
eip 0x804844f 0x804844f <main+50>
...

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

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

发布评论

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