欢迎来到《制作小操作系统》第 3 集。
在本集中,我将讨论如何实现堆栈和函数调用。此外,我们将讨论如何使用 GDB 来调试我们的引导加载程序,然后我们利用 bios 中断来清除屏幕并在我们的代码中进行一些重构。最后,我们将为我们的项目创建 make 文件以使事情变得更简单。
linux高级usb安全开发与源码分析
linux程序设计与安全开发
-
恶意软件开发
-
-
-
windows网络安全防火墙与虚拟网卡(更新完成)
-
-
windows文件过滤(更新完成)
-
-
USB过滤(更新完成)
-
-
游戏安全(更新中)
-
-
ios逆向
-
-
windbg
-
-
还有很多免费教程(限学员)
-
-
-
更多详细内容添加作者微信
-
-
让我们深入了解如何实现堆栈,而不是讨论它的重要性。
正如我们在前几集中所讨论的,CPU 具有程序逻辑段的寄存器。
事实上,这是细分的想法。为了告诉 CPU,我们想要内存中称为堆栈的某个地方,我们应该设置一些 CPU 寄存器。
我们的引导加载程序应该适合 512 字节(然后我们创建一个第二阶段引导加载程序来绕过这个限制)并放置地址 0x7c00:
现在我们想在内存中的某个地方找到堆栈。
我们有三个寄存器来做到这一点。堆栈段、堆栈指针和基本指针。
堆栈段保存堆栈信息所在的内存块的地址。
注意:同样,在实模式下,段寄存器乘以16以创建实地址。
堆栈指针寄存器保存堆栈的顶部,基本指针寄存器保存堆栈的底部。
假设我们希望在地址 0x8400 上具有堆栈区域。
为了在地址 0x8400 上具有字节号 33792 的堆栈区域,我们应该将堆栈段寄存器设置为 0x840。因为:
0x8400 = 0840 * 16
我们可以使用以下代码实现它:
mov ax, 0x840
mov ss, ax
以下是内存的更新视图:
在 x86 体系结构上,堆栈在内存中向下增长。(堆栈的增长方向与内存地址增长方向相反。
是时候确定堆栈的大小了。为此,我们将堆栈指针和基本指针都设置为相同的地址。
此地址与堆栈段偏移,并确定堆栈大小。
例如,如果我们将“SP”和“BP”设置为寻址0x2000(是的,这些不是段寄存器,如果我们的意思是0x2000,则无需设置0x200)
结果将是:
如图所示,无论我们在 BP 和 SP 上放置什么,都会偏离 SS.in 以制作 8KB 堆栈的顺序,我们使用以下代码将 0x2000 设置为 SP 和 BP:
mov ax, 0x2000
mov sp, ax
mov bp, ax
为了进行测试,我们将一些值推送并弹出到堆栈中,以确保一切都正确完成:
push ax
push ax
pop bx
现在我们需要测试我们的引导加载程序并检查其运行时。我们还需要检查堆栈值,以确保我们的代码正常工作。
调试器是关键。我们使用 GDB 来调试我们的程序。GDB 在我们的项目中非常方便,因为它可以连接到 qemu 并调试我们的引导加载程序。
因此,要调试我们的程序,首先我们需要告诉 QEMU 加载我们的程序,但不要启动它,并且还要侦听默认端口(即 1234),以便调试器连接到它。
为此,我们将 qemu 命令更改为:
qemu-system-i386 -fda boot.bin -s -S
上述命令的结果是 qemu 等待调试器的连接:
然后我们需要使用以下命令运行 gdb:
gdb boot.asm
然后我们将看到GDB程序打开,如下图所示:
现在是时候将 GDB 连接到 QEMU 了。为此,我们在 gdb 提示符下使用以下命令:
target remote :1234
现在是时候设置断点以启动程序,然后运行它了。
为此,我们在 gdb 中使用以下命令:
b *0x7c00
此命令在地址 0x7c00 处创建一个断点,该断点是引导加载程序代码的开头。
现在是时候查看我们的代码了。为此,我们告诉 GDB 在内存中显示操作码的汇编等效物。为此,我们使用:
layout asm
这将导致以下输出:
如果已准备好前进到断点,请立即执行此操作。为此,我们在 gdb 命令提示符下键入以下命令:
continue
结果如下:
如您所见,GDB 将我们的代码反汇编为 32 位汇编操作码,因此使用 EAX、EBP 和 ...
要解决此问题,我们应该告诉 GDB 将操作码反汇编为 16 位,而不是 32 位。
标准方法是以下命令:
set architecture i8086
但这行不通。这不是我们的错,但据我了解,gdb 从目标获取架构,在我们的例子中是 qemu i386,并且因为它是一台 32 位机器,gdb 更喜欢继续使用目标架构,不服从我们的设置架构命令。
因此,要解决这个问题,我们需要外部帮助。我从这个 GitHub gist 中找到了一个解决方案。
我们需要两个名为“target.xml”和“i386–32bit.xml”的文件。(我把这些文件放在一个名为“gdb_asset”的文件夹中,你可以在项目仓库中访问它们)
为了解决这个问题,我们在 gdb 命令提示符下运行以下命令:
set tdesc filename gdb_asset/target.xml
然后我们需要通过触发反汇编命令来刷新装配布局:
disassemble $eip-10, 5
结果是:
如您所见,GDB 现在将操作码反汇编为 16 位汇编操作码。
现在,我们可以在程序中使用 Step Command 来执行第 1 步指令。为此,请执行以下操作:
stepi
或者干脆
si
如图底部所示,程序计数器或 IP 寄存器现在位于 0x7c03。这意味着 CPU 已在地址 0x7c00 执行操作码,即 mov ax,0x7c0,现在正在等待我们的命令执行0x7c03。
如果我们再次键入“si”命令,它将继续突出显示的指令并等待我们的命令。
但就目前而言,我更愿意看看“ax”寄存器是否真的有0x7c0值。要检查寄存器值,我们使用以下命令:
info registers register_name
在我们的例子中,它将是:
i r ax
(是的,我们可以使用 I R 代替 Info Register)
结果是:
ax 0x7c0 1984
结果说明:寄存器名称,十六进制值,十进制值
现在,我们可以使用多个步骤,直到达到0x7c12或推送指令。
在此地址,CPU 刚刚执行了 push ax,将寄存器 ax 中的值推送到堆栈。
我们想检查堆栈,看看它是否有效。
要做到这一点,首先,我们从前面的说明中知道“斧头”0x2000价值。
现在要检查它是否真的推送到堆栈,有一些计算:
正如我们之前所讨论的,“sp”显示了堆栈的顶部。它的价值0x2000。
现在,通过将某些东西推送到堆栈,“sp”有一个新值,即 0x2000–2(2 是推送到堆栈的“AX”的大小)
0x2000 - 0x02 = 0x1ffe
我们知道 “sp” 在堆栈段区域内,因此我们应该将堆栈指针地址添加到堆栈段,以显示内存中的 “sp” 地址:
0x8400 + 0x1ffe = 0xa3fe
现在,如果我们使用以下命令查看此地址:
x/4x 0xa3fe
我们将看到以下结果:
如您所见0x2000在堆栈上。任务通过!
现在我们已经成功地实现了一个堆栈,我们可以用它来定义函数并调用它们,所有这些都挂载在堆栈上。
例如,这是一个清除屏幕的BIOS功能。我只是从 Joe Bergeron 的博客中复制了它:
clear_screen:
mov ah, 0x07 ; tells BIOS to scroll down window
mov al, 0x00 ; clear entire window
mov bh, 0x07 ; white on black
mov cx, 0x00 ; specifies top left of screen as (0,0)
mov dh, 0x18 ; 18h = 24 rows of chars
mov dl, 0x4f ; 4fh = 79 cols of chars
int 0x10 ; calls video interrupt ret
ret
然后我们调用这个函数,而不是跳跃。通过调用函数,返回地址自动推送到堆栈,并在执行“ret”指令时从堆栈中弹出并跳转到该地址。
我们可以为我们的程序定义一个例程,例如:首先清除屏幕,然后打印 Hello, world!在屏幕上,然后完成。
现在我们有了函数,我们可以像这样做:
call clear_screen
call print_text
call finish
到目前为止,我们的程序是这样的:
bits 16
mov ax, 0x7c0
mov ds, ax
mov ax, 0x840
mov ss, ax
mov ax, 0x2000
mov sp, ax
mov bp, ax
push ax
push ax
pop bx
call clear_screen
call print_text
call finish
print_text:
mov ah, 0x0E
mov bh, 0x00
mov bl, 0x00
mov si, 0
mov cx, 0
print_loop:
cmp cx, 14
je exit
lea si, msg
add si, cx
mov al, [si]
int 0x10
inc cx
jmp print_loop
ret
clear_screen:
mov ah, 0x07 ; tells BIOS to scroll down window
mov al, 0x00 ; clear entire window
mov bh, 0x07 ; white on black
mov cx, 0x00 ; specifies top left of screen as (0,0)
mov dh, 0x18 ; 18h = 24 rows of chars
mov dl, 0x4f ; 4fh = 79 cols of chars
int 0x10 ; calls video interrupt
ret
exit:
mov ax, 0
finish:
hlt
msg: db "Hello, world!"
times 510-($-$$) db 0
dw 0xAA55
结果将是:
似乎有很多步骤可以组装和运行 QEMU,然后启动 GDB 并对其进行配置。
所以我做了一个 makefile,让我们的生活更轻松:
boot.bin: boot.asm
nasm -f bin -o boot.bin boot.asm
.PHONY: run debug
run:
$(MAKE) && qemu-system-i386 -fda boot.bin -s -S
debug:
#qemu-system-i386 -s -S -fda boot.bin
gdb -ex "target remote :1234" -ex "b *0x7c00" -ex "set tdesc filename gdb_asset/target.xml" -ex "layout asm"
通过使用这个 makefile,我们可以简单地在单独的终端中运行 “make run”,然后运行 “make debug”。这将打开连接到 qemu 的 gdb,通过 16 位架构进行反汇编,应如下所示:
在 gdb 命令提示符下输入“continue”后,gdb 将跳转到地址0x7c00,这标志着我们程序的开始。
这就是现在的全部内容。如果您有兴趣访问此项目的代码以及更多内容,请查看以下 GitHub 存储库:
https://github.com/flydeoo/mya
另外,请查看第 3 集的发布:
https://github.com/flydeoo/mya/releases/tag/v0.031
感谢您的阅读,下期再见。
原文始发于微信公众号(安全狗的自我修养):第 3 集:使用 GDB 跟踪堆栈和函数调用
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论