冰凌汇编

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 73|回复: 0
收起左侧

[原创] 调试您的第一个x86程序

[复制链接]
bingling 发表于 2022-5-20 20:07:01

调试已编译的程序是学习x86汇编语言。使用汇编程序时,单步执行代码中编写的每一条指令的唯一方法是使用调试器来调试程序。

GDB是用于调试基于Linux的可执行程序的最流行的调试器之一。GDB也广泛用于开发和逆向工程。本文着重于理解如何使用GDB来单步执行给定x86汇编程序的指令。

目标可执行文件

在本文的后面部分,将向读者介绍如何使用GDB。因此,我们将准备一个用x86汇编编写的简单二进制文件,这样我们就可以使用GDB来理解如何使用GDB来调试二进制文件。以下是我们将使用的程序。

global _start
_start:
mov eax, 8
mov eax, 0xa
mov ebx, eax
mov ecx, [esp]

我们创建了一个名为mov.nasm 的文件,它以一个名为global的指令开头,它告诉我们的链接器该程序的入口点在哪里。 我们指定这个程序的入口点是_start。 第一条指令 MOV EAX,8 将值 8移动到寄存器 EAX 中。

在下一条指令中,我们将 0xa 移动到 EAX寄存器中。 我们告诉程序我们正在移动一个十进制 10的十六进制值。接下来,使用MOV EBX, EAX指令,我们试图将一个寄存器的值移动到另一个寄存器中。 最后,MOV ECX, [ESP] 指令实质上将移动寄存器 ESP 所指向的值。

因此,如果我们指定指令 MOV ECX, [ESP],它将尝试选择 ESP 的地址,并将该 ESP 寄存器指向的值移动到 ECX中。

现在让我们使用 nasm 来组装这个程序。 让我们输入以下命令。

nasm mov.nasm -o mov.o -f elf32

格式为 elf32,输出文件为 mov.o。 现在让我们使用 ld 链接它。 这可以使用以下命令来完成。

ld mov.o -o mov -m elf_i386

mov 将成为最终的二进制文件。 我们将对此进行调试。

使用 GDB 和 GEF 进行调试

为了能够检查寄存器和被移动到寄存器中的值,让我们使用 gdb 使用以下命令打开这个程序。

gdb ./mov

它看起来如下:

$ gdb ./mov
GNU gdb (Ubuntu 9.1-0ubuntu1) 9.1

Copyright (C) 2020 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 “x86_64-linux-gnu”.

Type “show configuration” for configuration details.

For bug reporting instructions, please see:

<http://www.gnu.org/software/gdb/bugs/>.

Find the GDB manual and other documentation resources online at:

<http://www.gnu.org/software/gdb/documentation/>.

For help, type “help”.

Type “apropos word” to search for commands related to “word”…

GEF for linux ready, type `gef’ to start, `gef config’ to configure

78 commands loaded for GDB 9.1 using Python engine 3.8
 2 commands could not be loaded, run `gef missing` to know why.

Reading symbols from ./mov…

(No debugging symbols found in ./mov)

gef➤

我们看到的是 GEF 终端而不是普通的 GDB 终端,因为在这种情况下已经安装了 GEF for gdb。 这使我们在调试程序时变得如此轻松。 GEF 是一个扩展,它可以自动执行各种常用的 GDB 命令,并以图形用户界面外观显示结果。

设置断点

现在让我们在这个程序的入口处设置一个断点。 键入以下内容:

gef➤ break _start
Breakpoint 1 at 0x8049000
gef➤

_start 是该程序的入口点,我们使用命令 break 在入口点设置断点。 接下来,键入 run 以便程序运行,它会在入口点暂停执行,因为我们确实设置了一个断点。

gef➤ run
Starting program: /home/dev/x86/mov

Breakpoint 1, 0x08049000 in _start ()

[ Legend: Modified register | Code | Heap | Stack | String ]

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────

$eax : 0x0

$ebx : 0x0

$ecx : 0x0

$edx : 0x0

$esp : 0xffffd220 → 0x00000001

$ebp : 0x0

$esi : 0x0

$edi : 0x0

$eip : 0x08049000 → <_start+0> mov eax, 0x8

$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]

$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0000

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────

0xffffd220│+0x0000: 0x00000001 ← $esp

0xffffd224│+0x0004: 0xffffd3d3 → “/home/dev/x86/mov”

0xffffd228│+0x0008: 0x00000000

0xffffd22c│+0x000c: 0xffffd3e5 → “SHELL=/bin/bash”

0xffffd230│+0x0010: 0xffffd3f5 → “SESSION_MANAGER=local/x86-64:@/tmp/.ICE-unix/1721,[…]”

0xffffd234│+0x0014: 0xffffd447 → “QT_ACCESSIBILITY=1”

0xffffd238│+0x0018: 0xffffd45a → “COLORTERM=truecolor”

0xffffd23c│+0x001c: 0xffffd46e → “XDG_CONFIG_DIRS=/etc/xdg/xdg-ubuntu:/etc/xdg”

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────

0x8048ffa add BYTE PTR [eax], al

0x8048ffc add BYTE PTR [eax], al

0x8048ffe add BYTE PTR [eax], al

→ 0x8049000 <_start+0> mov eax, 0x8

0x8049005 <_start+5> mov eax, 0xa

0x804900a <_start+10> mov ebx, eax

0x804900c <_start+12> mov ecx, DWORD PTR [esp]

0x804900f add BYTE PTR [eax], al

0x8049011 add BYTE PTR [eax], al

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────

[#0] Id 1, Name: “mov”, stopped 0x8049000 in _start (), reason: BREAKPOINT

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────

[#0] 0x8049000 → _start()

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

gef➤

1.有断点 - 程序执行暂停。 现在,在前面的输出中我们需要观察的东西很少。
2.代码 - 这是我们可以看到接下来要执行的指令的区域。
3.堆栈 - 此区域显示堆栈。 最好知道堆栈在输出中的位置,因为在处理子例程时将使用堆栈。
4.寄存器 - 本部分显示所有寄存器和当前存储在其中的值。

在调试程序时,我们主要在代码部分使用寄存器和指令。 我们也可能偶尔使用堆栈。

EIP 寄存器

现在,让我们看一下 EIP 寄存器。

$eip : 0x08049000 → <_start+0> mov eax, 0x8

EIP 寄存器中的值为 0x08049000。 让我们也看看我们即将执行的第一条指令的地址。

→ 0x8049000 <_start+0> mov eax, 0x8
0x8049005 <_start+5> mov eax, 0xa
0x804900a <_start+10> mov ebx, eax
0x804900c <_start+12> mov ecx, DWORD PTR [esp]
0x804900f add BYTE PTR [eax], al
0x8049011 add BYTE PTR [eax], al

如果你看这个地址,这和我们在EIP 寄存器中看到的一样。 这意味着EIP总是保存下一条要执行的指令的地址。 现在让我们尝试执行这条指令,看看看到 EAX 的值会发生什么。

$eax : 0x0
$ebx : 0x0
$ecx : 0x0
$edx : 0x0
$esp : 0xffffd220 → 0x00000001
$ebp : 0x0
$esi : 0x0
$edi : 0x0
$eip : 0x08049000 → <_start+0> mov eax, 0x8

目前,它为0。 如果我们程序中的第一条指令被执行,EAX 应该包含 8。让我们输入 si 并回车。 si 命令用于单步执行,即执行一条指令。 让我们看看 EAX寄存器发生了什么。

$eax : 0x8
$ebx : 0x0
$ecx : 0x0
$edx : 0x0
$esp : 0xffffd220 → 0x00000001
$ebp : 0x0
$esi : 0x0
$edi : 0x0
$eip : 0x08049005 → <_start+5> mov eax, 0xa

如果您观察到,值 0x8 被移动到寄存器 EAX 中。

在特定地址设置断点

现在我们可以键入 si 来执行下一条指令,但我想向您展示另一个功能。

现在我们已经在这个程序的入口处设置了一个断点。 还有另一种设置断点的方法,即使用指令的地址。 例如,假设我们要在地址 0x804900c 处设置断点。 当程序到达这个特定地址时,它会暂停执行。 所以,让我们尝试这样做。 以下是突出显示地址的代码部分。

→ 0x8049000 <_start+0> mov eax, 0x8
0x8049005 <_start+5> mov eax, 0xa
0x804900a <_start+10> mov ebx, eax
0x804900c <_start+12> mov ecx, DWORD PTR [esp]
0x804900f add BYTE PTR [eax], al
0x8049011 add BYTE PTR [eax], al

以下是在特定地址设置断点的方法:

gef➤ break *0x804900c
Breakpoint 2 at 0x804900c
gef➤

我们需要输入一个星号,然后我们将输入地址。 这是使用地址设置断点的方法。 正如我们在前面的摘录中所看到的,现在设置了断点 2。 现在让我们通过键入c 继续执行这个程序,或者继续观察会发生什么。

0x8049005 <_start+5> mov eax, 0xa
0x804900a <_start+10> mov ebx, eax
→ 0x804900c <_start+12> mov ecx, DWORD PTR [esp]
0x804900f add BYTE PTR [eax], al
0x8049011 add BYTE PTR [eax], al
0x8049013 add BYTE PTR [eax], al
0x8049015 add BYTE PTR [eax], al
0x8049017 add BYTE PTR [eax], al
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────

[#0] Id 1, Name: “mov”, stopped 0x804900c in _start (), reason: BREAKPOINT

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────

[#0] 0x804900c → _start()

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

gef➤

正如我们在突出显示的文本中看到的那样,程序由于断点而停止执行。

列出所有断点:

我们可以使用GDB命令info breakpoints查看所有断点。

gef➤ info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x08049000 <_start>
breakpoint already hit 1 time
2 breakpoint keep y 0x0804900c <_start+12>
breakpoint already hit 1 time
gef➤

正如我们在输出中观察到的,我们能够看到当前有多少断点处于活动状态。 它显示有两个断点,我们还可以看到每个断点被击中多少次的历史记录。 在我们的例子中,每个断点都被命中一次。

现在让我们看看EAX 寄存器发生了什么。

$eax : 0xa
$ebx : 0xa
$ecx : 0x0
$edx : 0x0
$esp : 0xffffd220 → 0x00000001
$ebp : 0x0
$esi : 0x0
$edi : 0x0
$eip : 0x0804900c → <_start+12> mov ecx, DWORD PTR [esp]

8 被新值 0xa 替换。 此外,指令 mov ebx, eax 也被执行,导致寄存器 RBX 中的值为0xa。 现在让我们也执行下一条指令。

→ 0x804900c <_start+12> mov ecx, DWORD PTR [esp]

该指令将 ESP 寄存器指向的值移动到 ECX 中。 让我们检查堆栈以更好地理解这一点。

0xffffd220│+0x0000: 0x00000001 ← $esp
0xffffd224│+0x0004: 0xffffd3d3 → “/home/dev/x86/mov”
0xffffd228│+0x0008: 0x00000000
0xffffd22c│+0x000c: 0xffffd3e5 → “SHELL=/bin/bash”
0xffffd230│+0x0010: 0xffffd3f5 → “SESSION_MANAGER=local/x86-64:@/tmp/.ICE-unix/1721,[…]”
0xffffd234│+0x0014: 0xffffd447 → “QT_ACCESSIBILITY=1”
0xffffd238│+0x0018: 0xffffd45a → “COLORTERM=truecolor”
0xffffd23c│+0x001c: 0xffffd46e → “XDG_CONFIG_DIRS=/etc/xdg/xdg-ubuntu:/etc/xdg”

ESP 指的是什么? 如果您查看堆栈,ESP 指向堆栈顶部的可用值。 因此,在执行当前指令后,该值将被移入 ECX

让我们尝试做一个步骤,看看会发生什么。 如前所述,可以通过键入si 来执行单个步骤。 如果之前输入的命令是 si,我们可以直接回车而不是再次输入 si。 所以之前输入的命令会被重新执行。

让我们观察 ecx的值。

$eax : 0xa
$ebx : 0xa
$ecx : 0x1
$edx : 0x0
$esp : 0xffffd220 → 0x00000001
$ebp : 0x0
$esi : 0x0
$edi : 0x0
$eip : 0x0804900f → add BYTE PTR [eax], al

正如预期的那样,ESP 指向的值 0x1 被移至 ECX

结论

正如我们在本文中看到的,使用 GDB 在调试用 x86 汇编编写的程序时很有用。

我们使用了一个名为 GEFGDB 扩展来简化调试。 我们已经讨论了在调试 ELF 可执行文件时可以派上用场的一些最常见的 GDB 用例。 我们已经看到了各种各样的概念,例如设置断点、检查寄存器和堆栈、使用 EIP 寄存器和列出可用的断点。

接下来,您将学习如何在 x86 中使用 ObjDump 工具。

冰凌汇编免责声明
以上内容均来自网友转发或原创,如存在侵权请发送到站方邮件9003554@qq.com处理。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|站点统计|Archiver|小黑屋|RSS|冰凌汇编 ( 滇ICP备2022002049号 滇公网安备 53032102000029号)|网站地图

GMT+8, 2022-10-2 17:21 , Processed in 0.112878 second(s), 7 queries , Redis On.

冰凌汇编 - 建立于2021年12月20日

Powered by Discuz! © 2001-2022 Comsenz Inc.

快速回复 返回顶部 返回列表