原文链接:/win7/
一、 DBR 的结构
下面来看一看 DBR 的结构,如下:
第二部分的变量区域,是 DBR 被加载到 0x7c00 执行时,由于代码中需要使用临时的地方存储数据。
因此,开辟了这部分作为局部变量区,但其中也包含了一些常量数据。
二、 DBR 代码分析
现在来看一看 DBR 的代码部分。
(1)DBR 前期准备工作
由第 1 条指令jmp short 0x54跳到 0x54 处执行。
这段代码目的是,设置 sp 为 0x7c00,并且 cs 为 0x07c0,然后继续往下执行。但此时 cs 已经变为 0x07c0,而不是之前的 0x0000
(2)检测文件系统
这里使用了变量 [0x0e],这个变量就位于前面所说的变量区域,用来放置 驱动器(硬盘)的 ID 值。
代码将检测分区是否为 NTFS 文件系统。这个 [0x03] 是常量,在本例 windows 7 中,它的值是 "NTFS" 字符串。
不是 NTFS 系统的话,将打印错误信息,然后 hlt
(3)检测是否支持 int 0x13 扩展
和 MBR 代码一样,这里也要检测 BIOS 是否支持 int 0x13 扩展功能,如果不支持的话,将打印出错信息,最终将 hlt 停机处理
(4)下一步,获取磁盘参数
这些磁盘参数,包括:
★ 磁盘的 cylinders 数量
★ 磁盘的 heads 数量
★ 每道的 sectors 数
★ 磁盘总共有多少扇区
★ 每个扇区有多少字节。
先来看一看,蓝色部分标注的代码,这里有一个巧妙之处。
这里实际上是预留了0x18 + 0x02 = 0x1A个字节,即:26 个字节。
为什么不直接用sub sp, 0x1a来预留0x1a个字节呢,下面继续解读下去。
这里使用了 int 0x13 的第 0x48 号功能来获取磁盘(硬盘)的参数,这些磁盘参数在上面已经列出。
int 0x13 的 0x48 号功能需要一个缓冲区来存放获取的参数值
那么,现在来看一看这个缓冲区结构的定义,用 c 来描述如下:
这个磁盘参数结构总共是 26 个字节,sectors 数是个 64 位值。
回到前面的代码:push word 0x1a执行完后,sp 实际上对应着的buf_size这个变量,表示 driver_parametes 这个结构体的字节数,即:sizeof(buffer)。
因此,通过 push 来定位到 buf_size 变量,实际上等于buf_size = 0x1a将 buf_size 变量赋值为 0x1a
而后面经过add sp, 0x1a修复 stack 后,代码:pop ax,正好用来取出 buffer 的最后一个 word 值,即:ax = bytes_per_sector
这段代码,玩弄了一点小技俩。
最后,ds:si 指向这块缓冲区的地址。
从上面看出,这块缓冲区就是在 stack 开辟出来的 26 个字节的空间,即:0x00:sp
执行完 int 0x13 后,恢复了 stack,如果有错,则转到 0x8a,最终是打印错误信息,然后 hlt
此时,ax 的值就是 bytes_per_sector,即:每扇区有多少字节。
[0x0b] 地址是 DBR 中存放的值,在本例中它就是 0x200,也就是 512
(5)以实例看一看,我的 bochs 上为 windows 7 分配的磁盘,它的参数是什么?
可以看出:
buf_size = 0x001a = 26 bytes
flags = 0x0000
cylinders = 0xaa553fff = 2857713663 个 cylinders
heads = 0x00000010 = 16 个 heads
sectors_per_track = 0x0000003f = 63 个 sectors
sectors = 0x00000000_013fffb0 = 20971440 个 sectors
bytes_per_sector = 0x0200 = 512 bytes
那么,这块磁盘大小为:sectors * bytes_per_sector = 209771440 * 512 = 10g
但是,cylinders 居然有 0xaa553fff 这么大的数量,这显然不对的。
(5)读分区1 剩余的扇区数
分区1 的 DBR 引导记录,由 MBR 读入,接下着 DBR 引导记录负责将其余的扇区读到内存 0x7c00 下一个扇区的位置。
0x7c00 + 512 = 0x7e00这就是下一个扇区将要读入的存放地方。
由上一篇文章中得知,分区1 位于磁盘的第 2048 扇区,即:0x800 号扇区。
因此,下一步将以 0x800 为起始,接着读 0x801 扇区到 0x7e00,以此类推:0x802 扇区读到 0x8000 ... ...
接着看代码,如下:
这个 ax 值正是 0x200 即:512 bytes, 内存 [0x0f] 是 DBR 使用的变量。
代码:shr word [0x0f], 0x4这里作用是调整为 segment 的增量,即:0x200 调整为 0x20那么,加上 0x7c0 这个 segment 值变成为 0x7e0,将作为 segment
接着,将 dx = ds,此时,dx 为 0x07c0,将要加上 0x20 作为 segment 值使用。
这段代码将要读 0x2000/0x200 也就是 16 个扇区到内存。
内存 [0x11],用于记录,扇区 offset 值,这个值就是基于 0x800(2048),而 [0x16] 用来记每次读多少次。
实际的读扇区工作交由过程 func_0x11d() 执行。 下面将看一看 func_0x11d() 是如何读取
(6)读扇区子过程 func_0x11d()
这个 func 有点长,但是逻辑很清晰:就是读 n 个扇区,这个 n 值来自 [0x16],上面已经看到 [0x16] 被初始置为 1
代码:mov eax, [0x11]这个 [0x11] 是前面说的 offset 值
而add eax, [0x1c]这个 [0x1c] 就是 0x800 值,它是随着 DBR 被一起读到内存,这是常量值。这里算出将要读哪个扇区
这段代码读入 16 个扇区到内存。
(7)接下来,看看读完扇区后,DBR 做些什么工作。
很抱歉,我并不了解 int 0x13 的 ah = 0xbb00 和 ah = 0xbb07 的用途,这里无法做出分析。如果您知道,请不吝留言赐教。
代码后面部分,是:从 0x8c28(0x7c00 + 0x1028)开始到 0x9c00 清 0
最后转到0x7c0:0x27a处继续执行。
地址 0x7c0:0x27a 实际上是分区1 的第 2 个扇区的偏移 0x7a 处。
到此为止,DBR 的工作就完成了。下一步工作将是走到 下一个扇区继续执行。
(8)下面看一看,DBR 的错误处理代码
代码根据错误的类型,选择相应的错误信息打印出来,最后是 hlt 处理。
下面是错误的信息及标志位:
三、最后总结一下 DBR 的大致工作
它的主要工作是:先判断一下磁盘的参数符不符合,然后,从下一扇区开始读 16 个扇区到 0x7e00 处。