活动分区的引导记录 DBR 分析

原文链接:http://www.mouseos.com/win7/

前话:
   windows 7 的 DBR 是由 MBR 的分区表1 指示的活动分区(即,分区1)的引导记录。

  这部分是由 MBR 代码从磁盘的 2048 号扇区读 1 扇区取到 0x7c00,然后跳到 0x7c00 继续执行。

  因此,这部分代码仍是在 0x7c00 区域里运行,而原来的 MBR 已经被覆盖。

 

一、 DBR 的结构

下面来看一看 DBR 的结构,如下:

位置(hex)
大小(bytes)
描述
000 - 001
2 bytes
跳转代码:jmp short 0x54 它将跳到 0x54 处执行
002 - 053
82 bytes
这部分是 DBR 执行期间变量区域。
054 - 18B
312 bytes
这部分是 DBR 代码部分
18C - 1FD
114 bytes
这部分是 DBR 的数据区,字符串常量
1FE - 1FF
2 bytes
DBR 的标志(55AA)

第二部分的变量区域,是 DBR 被加载到 0x7c00 执行时,由于代码中需要使用临时的地方存储数据。

因此,开辟了这部分作为局部变量区,但其中也包含了一些常量数据。

 

 

二、 DBR 代码分析

现在来看一看 DBR 的代码部分。

(1)DBR 前期准备工作

代码如下:

00000000  EB52              jmp short 0x54

... ...

00000054  FA                cli
00000055  33C0              xor ax,ax
00000057  8ED0              mov ss,ax
00000059  BC007C            mov sp,0x7c00
0000005C  FB                sti
0000005D  68C007            push word 0x7c0
00000060  1F                pop ds
00000061  1E                push ds
00000062  686600            push word 0x66
00000065  CB                retf

由第 1 条指令 jmp short 0x54 跳到 0x54 处执行。

这段代码目的是,设置 sp 为 0x7c00,并且 cs 为 0x07c0,然后继续往下执行。但此时 cs 已经变为 0x07c0,而不是之前的 0x0000

 

(2)检测文件系统

代码接着:

00000066  88160E00          mov [0xe],dl                  /* 驱动器 ID,即:0x80 */
0000006A  66813E03004E5446  cmp dword [0x3],0x5346544e    /* 是否 "NTFS" ? */
         -53
00000073  7515              jnz 0x8a                      /* 出错处理 */

这里使用了变量 [0x0e],这个变量就位于前面所说的变量区域,用来放置 驱动器(硬盘)的 ID 值。

代码将检测分区是否为 NTFS 文件系统。这个 [0x03] 是常量,在本例 windows 7 中,它的值是 "NTFS" 字符串。

不是 NTFS 系统的话,将打印错误信息,然后 hlt

 

(3)检测是否支持 int 0x13 扩展

代码接着:

00000075  B441              mov ah,0x41
00000077  BBAA55            mov bx,0x55aa
0000007A  CD13              int 0x13          /* 测试是否支持 int 0x13 扩展 */

0000007C  720C              jc 0x8a           /* not support */
0000007E  81FB55AA          cmp bx,0xaa55    
00000082  7506              jnz 0x8a          /* not support */
00000084  F7C10100          test cx,0x1
00000088  7503              jnz 0x8d          /* support */

0000008A  E9DD00            jmp word 0x16a    /* 出错处理 */

和 MBR 代码一样,这里也要检测 BIOS 是否支持 int 0x13 扩展功能,如果不支持的话,将打印出错信息,最终将 hlt 停机处理

 

 

(4)下一步,获取磁盘参数

这些磁盘参数,包括:

★ 磁盘的 cylinders 数量

★ 磁盘的 heads 数量

★ 每道的 sectors 数

★ 磁盘总共有多少扇区

★ 每个扇区有多少字节。

代码接着:

0000008D  1E                push ds
0000008E  83EC18            sub sp,byte +0x18      /* 预留 0x18 个空间 */
00000091  681A00            push word 0x1a         /* 写 buffer 的第 1 个 word */

00000094  B448              mov ah,0x48
00000096  8A160E00          mov dl,[0xe]           /* 驱动器 ID,即:0x80 */
0000009A  8BF4              mov si,sp
0000009C  16                push ss
0000009D  1F                pop ds
0000009E  CD13              int 0x13               /* 获取 driver 参数 */

000000A0  9F                lahf
000000A1  83C418            add sp,byte +0x18
000000A4  9E                sahf
000000A5  58                pop ax            /* 此时 ax 的值为 "每个扇区有多少 bytes" */
000000A7  72E1              jc 0x8a           /* 出错处理 */

/* 测试获取的 drivers 参数中的 "每扇区字节数" 是否与 DBR 中既定的 "每扇区字节数" 相等 */
000000A9  3B060B00          cmp ax,[0xb]      
000000AD  75DB              jnz 0x8a         /* 出错处理 */

先来看一看,蓝色部分标注的代码,这里有一个巧妙之处。

代码片断1:
0000008E  83EC18            sub sp,byte +0x18      /* 预留 0x18 个空间 */
00000091  681A00            push word 0x1a         /* 写 buffer 的第 1 个 word */

这里实际上是预留了 0x18 + 0x02 = 0x1A 个字节,即:26 个字节。

为什么不直接用 sub sp, 0x1a 来预留 0x1a 个字节呢,下面继续解读下去。

这里使用了 int 0x13 的第 0x48 号功能来获取磁盘(硬盘)的参数,这些磁盘参数在上面已经列出。

int 0x13 的 0x48 号功能需要一个缓冲区来存放获取的参数值

那么,现在来看一看这个缓冲区结构的定义,用 c 来描述如下:

用 c 描述如下:

struct driver_parameters 
{
    short buf_size;              /* 缓冲区结构的大小 */
    short flags;                 /* 标志 */
    int cylinders;               /* cylinders 数量 */
    int heads;                   /* heads 数量 */
    int sectors_per_track;       /* 每道的 sectors 数 */
    long long sectors;           /* 磁盘总共的 sectors 数 */
    short bytes_per_sector;      /* 每个扇区的 bytes 数 */

} buffer;

这个磁盘参数结构总共是 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 指向这块缓冲区的地址。

代码片断2:

0000009A  8BF4              mov si,sp
0000009C  16                push ss
0000009D  1F                pop ds

从上面看出,这块缓冲区就是在 stack 开辟出来的 26 个字节的空间,即:0x00:sp

执行完 int 0x13 后,恢复了 stack,如果有错,则转到 0x8a,最终是打印错误信息,然后 hlt

代码片断3:

/* 测试获取的 drivers 参数中的 "每扇区字节数" 是否与 DBR 中既定的 "每扇区字节数" 相等 */
000000A9  3B060B00          cmp ax,[0xb]      
000000AD  75DB              jnz 0x8a         /* 出错处理 */

此时,ax 的值就是 bytes_per_sector,即:每扇区有多少字节。

[0x0b] 地址是 DBR 中存放的值,在本例中它就是 0x200,也就是 512

 

 

(5)以实例看一看,我的 bochs 上为 windows 7 分配的磁盘,它的参数是什么?

bochs 上的 windows 7 实例:

<bochs:40> x /26b si
[bochs]:
0x0000000000007be4 <bogus+    0>:  0x1a  0x00  0x00  0x00  0xff  0x3f  0x55  0xaa
0x0000000000007bec <bogus+    8>:  0x10  0x00  0x00  0x00  0x3f  0x00  0x00  0x00
0x0000000000007bf4 <bogus+   16>:  0xb0  0xff  0x3f  0x01  0x00  0x00  0x00  0x00
0x0000000000007bfc <bogus+   24>:  0x00  0x02

可以看出:

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 这么大的数量,这显然不对的。

注意:

这很显然,cylinders 是按照 10 bits 来表达,它是属于原有的表达方法,32 位的 cylinder 实际上虚的,超过 10 bits 是不能表达出来。

 

 

(5)读分区1 剩余的扇区数

分区1 的 DBR 引导记录,由 MBR 读入,接下着 DBR 引导记录负责将其余的扇区读到内存 0x7c00 下一个扇区的位置。

0x7c00 + 512 = 0x7e00 这就是下一个扇区将要读入的存放地方。

由上一篇文章中得知,分区1 位于磁盘的第 2048 扇区,即:0x800 号扇区。

因此,下一步将以 0x800 为起始,接着读 0x801 扇区到 0x7e00,以此类推:0x802 扇区读到 0x8000 ... ...

接着看代码,如下:

代码接着:

000000AF  A30F00            mov [0xf],ax         /* 0x200 值写入 [0xf] 中 */
000000B2  C12E0F0004        shr word [0xf],0x4   
000000B7  1E                push ds
000000B8  5A                pop dx
000000B9  33DB              xor bx,bx
000000BB  B90020            mov cx,0x2000        /* 将要读 16 个扇区 */
000000BE  2BC8              sub cx,ax
000000C0  66FF061100        inc dword [0x11]     /* offset 值,相对于起始扇区 */

000000C5  03160F00          add dx,[0xf]        /* 目的是设置 buffer 的 segment 值 */
000000C9  8EC2              mov es,dx           
000000CB  FF061600          inc word [0x16]     /* 每次读的次数为 1 */
000000CF  E84B00            call word 0x11d

000000D2  2BC8              sub cx,ax           /* 此处作用是:控制读 16 个扇区 */
000000D4  77EF              ja 0xc5

这个 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_0x11d():  */
0000011D  6660              pushad
0000011F  1E                push ds
00000120  06                push es

00000121  66A11100          mov eax,[0x11]
00000125  6603061C00        add eax,[0x1c]      /* 要读的起始扇区,这个值是:0x800 + offset,0x800 来自 [0x1c] */
0000012A  1E                push ds

0000012B  666800000000      push dword 0x0
00000131  6650              push eax            /* start sector */
00000133  06                push es             /* segment */
00000134  53                push bx             /* offset */
00000135  680100            push word 0x1       /* sectors */
00000138  681000            push word 0x10      /* sizeof(struct buffer) */

0000013B  B442              mov ah,0x42
0000013D  8A160E00          mov dl,[0xe]
00000141  16                push ss
00000142  1F                pop ds
00000143  8BF4              mov si,sp
00000145  CD13              int 0x13

00000147  6659              pop ecx
00000149  5B                pop bx
0000014A  5A                pop dx
0000014B  6659              pop ecx
0000014D  6659              pop ecx
0000014F  1F                pop ds
00000150  0F821600          jc word 0x16a     /* 出错 */
00000154  66FF061100        inc dword [0x11]  /* offset 加 1 */
00000159  03160F00          add dx,[0xf]      /* 下一扇区 */
0000015D  8EC2              mov es,dx
0000015F  FF0E1600          dec word [0x16]   /* 每次读的次数 */
00000163  75BC              jnz 0x121

00000165  07                pop es
00000166  1F                pop ds
00000167  6661              popad
00000169  C3                ret

这个 func 有点长,但是逻辑很清晰:就是读 n 个扇区,这个 n 值来自 [0x16],上面已经看到 [0x16] 被初始置为 1

代码:mov eax, [0x11] 这个 [0x11] 是前面说的 offset 值

而 add eax, [0x1c] 这个 [0x1c] 就是 0x800 值,它是随着 DBR 被一起读到内存,这是常量值。这里算出将要读哪个扇区

这段代码读入 16 个扇区到内存。

到此为止,DBR 做的工作是 :

将分区1 的第 2 扇区开始读 16 个扇区到地址 0x7e00  - 0x9fff 上,而扇区1(即,DBR)是由 MBR 读入到 0x7c00

 

 

(7)接下来,看看读完扇区后,DBR 做些什么工作。

代码接着:

000000D6  B800BB            mov ax,0xbb00
000000D9  CD1A              int 0x1a
000000DB  6623C0            and eax,eax
000000DE  752D              jnz 0x10d
000000E0  6681FB54435041    cmp ebx,0x41504354
000000E7  7524              jnz 0x10d
000000E9  81F90201          cmp cx,0x102
000000ED  721E              jc 0x10d

000000EF  16                push ss
000000F0  6807BB            push word 0xbb07
000000F3  16                push ss
000000F4  68700E            push word 0xe70
000000F7  16                push ss
000000F8  680900            push word 0x9
000000FB  6653              push ebx
000000FD  6653              push ebx
000000FF  6655              push ebp
00000101  16                push ss
00000102  16                push ss
00000103  16                push ss
00000104  68B801            push word 0x1b8
00000107  6661              popad
00000109  0E                push cs
0000010A  07                pop es
0000010B  CD1A              int 0x1a

/* 从 0x7c00+0x1028 地址开始清 0 */
0000010D  33C0              xor ax,ax
0000010F  BF2810            mov di,0x1028
00000112  B9D80F            mov cx,0xfd8
00000115  FC                cld
00000116  F3AA              rep stosb
00000118  E95F01            jmp word 0x27a        /* 转分区1的第 2 个扇区上 */
0000011B  90                nop
0000011C  90                nop

很抱歉,我并不了解 int 0x13 的 ah = 0xbb00 和 ah = 0xbb07 的用途,这里无法做出分析。如果您知道,请不吝留言赐教。

代码后面部分,是:从 0x8c28(0x7c00 + 0x1028)开始到 0x9c00 清 0

最后转到 0x7c0:0x27a 处继续执行。

地址 0x7c0:0x27a 实际上是分区1 的第 2 个扇区的偏移 0x7a 处。

到此为止,DBR 的工作就完成了。下一步工作将是走到 下一个扇区继续执行。

 

 

(8)下面看一看,DBR 的错误处理代码

代码接着:

/* 出错处理 -- 打印错误信息,hlt */
0000016A  A0F801            mov al,[0x1f8]
0000016D  E80900            call word 0x179
00000170  A0FB01            mov al,[0x1fb]
00000173  E80300            call word 0x179
00000176  F4                hlt

00000177  EBFD              jmp short 0x176

/* 打印信息,hlt */
00000179  B401              mov ah,0x1
0000017B  8BF0              mov si,ax
0000017D  AC                lodsb
0000017E  3C00              cmp al,0x0
00000180  7409              jz 0x18b
00000182  B40E              mov ah,0xe
00000184  BB0700            mov bx,0x7
00000187  CD10              int 0x10
00000189  EBF2              jmp short 0x17d
0000018B  C3                ret

代码根据错误的类型,选择相应的错误信息打印出来,最后是 hlt 处理。

下面是错误的信息及标志位:

代码接着:

/* 数据区 */
0000018C  0D 0A 41 20 64 69 73 6B 20 72 65 61 64 20 65 72  A disk read er
0000019C  72 6F 72 20 6F 63 63 75 72 72 65 64 00 0D 0A 42  ror occurredB
000001AC  4F 4F 54 4D 47 52 20 69 73 20 6D 69 73 73 69 6E  OOTMGR is missin
000001BC  67 00 0D 0A 42 4F 4F 54 4D 47 52 20 69 73 20 63  gBOOTMGR is c
000001CC  6F 6D 70 72 65 73 73 65 64 00 0D 0A 50 72 65 73  ompressedPres
000001DC  73 20 43 74 72 6C 2B 41 6C 74 2B 44 65 6C 20 74  s Ctrl+Alt+Del t
000001EC  6F 20 72 65 73 74 61 72 74 0D 0A 00              o restart   

/* 
000001F8  8C
000001F9  A9
000001FA  BE
000001FB  D6
000001FC  00
000001FD  00


/* 标志 */
000001FE  55                
000001FF  AA

 

 

三、最后总结一下 DBR 的大致工作

它的主要工作是:先判断一下磁盘的参数符不符合,然后,从下一扇区开始读 16 个扇区到 0x7e00 处。


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值