第一部分 背景知识简介
几乎所有编写代码的人都有这种体会:如今在计算机这个行业中,许多技术不是你不懂,而是你不知道。所以,在分析之前有些背景知识是必须要知道的。
一. 硬盘结构简介
1. 硬盘参数释疑
到目前为止, 人们常说的硬盘参数还是古老的 CHS (Cylinder/Head/Sector)参数. 那么为什么要使用这些参数, 它们的意义是什么? 它们的取值范围是什么?
很久以前, 硬盘的容量还非常小的时候, 人们采用与软盘类似的结构生产硬盘,也就是硬盘盘片的每一条磁道都具有相同的扇区数,由此产生了所谓的3D参数 (Disk Geometry)。既磁头数(Heads), 柱面数(Cylinders), 扇区数(Sectors),以及相应的寻址方式。
其中:
磁头数(Heads) 表示硬盘总共有几个磁头,也就是有几面盘片, 最大为 255 (用 8 个二进制位存储);
柱面数(Cylinders) 表示硬盘每一面盘片上有几条磁道, 最大为 1023(用 10 个二进制位存储);
扇区数(Sectors) 表示每一条磁道上有几个扇区, 最大为 63 (用 6个二进制位存储);
每个扇区一般是 512个字节(理论上讲这不是必须的, 但好象都取此值)。
据此,磁盘最大容量为:
255 * 1023 * 63 * 512 / 1048576 = 8024 MB ( 1M = 1048576 Bytes )
或硬盘厂商常用的单位:
255 * 1023 * 63 * 512 / 1000000 = 8414 MB ( 1M = 1000000 Bytes )
在 CHS 寻址方式中, 磁头, 柱面, 扇区的取值范围分别为 0 到 Heads - 1,0 到 Cylinders - 1, 1 到 Sectors (注意是从 1 开始)。
2. 基本 Int 13H 调用简介
BIOS Int 13H调用是 BIOS 提供的磁盘基本输入输出中断调用, 它可以完成磁盘(包括硬盘和软盘)的复位, 读/写, 校验, 定位, 诊断, 格式化等功能。它使用的就是 CHS 寻址方式, 因此最大只能访问 8 GB 左右的硬盘 ( 本文中如不作特殊说明, 均以 1M = 1048576 字节为单位).
而更不幸的是,标准的IDE接口容许256个扇区/磁道、65536个柱面及16个磁头。它自己本身可以存取 137438953472(128 GB),但是加上BIOS方面63个扇区与1024个柱面的限制后,就只剩528482304(1024*16*63 = 504MB)可以定址得到,这就是所谓标准IDE硬盘只认前504MB问题。
3. 现代硬盘结构简介
在老式硬盘中, 由于每个磁道的扇区数相等 (与软盘一样), 所以外道的记录密度要远低于内道, 因此会浪费很多磁盘空间。为了解决这一问题, 进一步提高硬盘容量, 人们改用等密度结构生产硬盘, 也就是说, 外圈磁道的扇区比内圈磁道多。采用这种结构后, 硬盘不再具有实际的3D参数, 寻址方式也改为线性寻址, 即以扇区为单位进行寻址。
为了与使用3D寻址的老软件兼容 (如使用BIOS Int13H接口的软件), 在硬盘控制器内部安装了一个地址翻译器, 由它负责将老式3D参数翻译成新的线性参数。这也是为什么现在硬盘的3D参数可以有多种选择的原因 (不同的工作模式对应不同的3D参数, 如 LBA, LARGE, NORMAL)。
4. 扩展 Int 13H 简介
虽然现代硬盘都已经采用了线性寻址, 但是由于基本 Int 13H 的制约, 使用 BIOS Int 13H 接口的程序, 如 DOS 等还是只能访问 8 G 以内的硬盘空间。为了打破这一限制, Microsoft 等几家公司制定了扩展 Int 13H 标准(Extended Int13H,详见附录A), 采用线性寻址方式存取硬盘,所以突破了 8 G 的限制,而且还加入了对可拆卸介质 (如活动硬盘) 的支持。
二. Boot Sector 结构简介
1. Boot Sector 的组成
Boot Sector 也就是硬盘的第一个扇区, 它由 MBR (Master Boot Record),DPT (Disk Partition Table) 和 Boot Record ID(Magic Number) 三部分组成。
MBR 又称作主引导记录,占用 Boot Sector 的前 446 个字节 ( 0 to 0x1BD ),包含了硬盘的一系列参数和一段系统主引导程序。引导程序主要是用来在系统硬件自检完后负责从活动分区中装载并运行系统引导程序(引导操作系统)。它的最后一条执行语句是一条JMP指令,跳到操作系统的引导程序去。这里往往是引导型病毒的注入点,也是各种多系统引导程序的注入点。但是由于引导程序本身完成的功能比较简单,所以我们完全可以判断该引导程序的合法性(比如看JMP指令的合法性),因而也易于修复。象命令fdisk/mbr可以修复MBR和 KV300这类软件可以查杀任意类型的引导型病毒,就是这个道理。
DPT 即主分区表,占用 64 个字节 (0x1BE to 0x1FD),记录了磁盘的基本分区信息。主分区表分为四个分区项, 每项 16 字节, 分别记录了每个主分区的信息(因此最多可以有四个主分区)。
Boot Record ID 即引导区标记,占用两个字节 (0x1FE and 0x1FF), 对于合法引导区, 它等于 0xAA55, 这是判别引导区是否合法的标志.
Boot Sector 的具体结构如下图所示: 见附件
2. 主分区表的结构
主分区表由四个分区项构成, 每一项的结构如下:
BYTE State : 分区状态, 0 = 未激活, 0x80 = 激活 (注意此项)
BYTE StartHead : 分区起始磁头号
WORD StartSC : 分区起始扇区和柱面号, 低字节的低6位为扇区号,高2位为柱面号的第 9,10 位, 高字节 为柱面号的低 8 位
BYTE Type : 分区类型, 如 0x0B = FAT32, 0x83 = Linux 等, 00 表示此项未用
BYTE EndHead : 分区结束磁头号
WORD EndSC : 分区结束扇区和柱面号, 定义同前
DWORD Relative : 在线性寻址方式下的分区相对扇区地址 (对于基本分区即为绝对地址)
DWORD Sectors : 分区大小 (总扇区数)
注意:在 DOS / Windows 系统下, 基本分区必须以柱面为单位划分( Sectors * Heads 个扇区), 如对于 CHS 为 764/255/63 的硬盘, 分区的最小尺寸为 255 * 63 * 512 / 1048576 = 7.844 MB。
3. 扩展分区简介
由于主分区表中只能分四个分区, 有时无法满足需求, 因此设计了一种扩展分区格式。 基本上说, 扩展分区的信息是以链表形式存放的, 但也有一些特别的地方。
首先,主分区表中要有一个基本扩展分区项, 所有扩展分区都隶属于它,也就是说其他所有扩展分区的空间都必须包括在这个基本扩展分区中。 对于DOS / Windows 来说, 扩展分区的类型为 0x05。
除基本扩展分区以外的其他所有扩展分区则以链表的形式级联存放, 后一个扩展分区的数据项记录在前一个扩展分区的分区表中, 但两个扩展分区的空间并不重叠。
扩展分区类似于一个完整的硬盘, 必须进一步分区才能使用。但每个扩展分区中只能存在一个其他分区, 此分区在 DOS/Windows 环境中即为逻辑盘。因此每一个扩展分区的分区表 (同样存储在扩展分区的第一个扇区中)中最多只能有两个分区数据项(包括下一个扩展分区的数据项)。
扩展分区和逻辑盘的示意图如下: 见附件
三. 系统启动过程简介
系统启动过程主要由一下几步组成(以硬盘启动为例):
1. 开机;
2. BIOS 加电或按reset键后都要进行系统复位,复位后指令地址为 0ffff:fff0,这个地方只有一条JMP指令, 跳转到系统自检 ( Power On Self Test -- POST )程序处;
3. 系统自检完成后,将硬盘的第一个扇区 (0头0道1扇区, 也就是Boot Sector)读入内存地址 0000:7c00 处;
4. 检查 (WORD) 0000:7dfe 是否等于 0xaa55, 若不等于则转去尝试其他启动介质, 如果没有其他启动介质 则显示 "No ROM BASIC" 然后死机;
5. 跳转到 0000:7c00 处执行 MBR 中的程序;
6. MBR程序 首先将自己复制到 0000:0600 处, 然后继续执行;
7. 在主分区表中搜索标志为活动的分区,如果没有发现活动分区或有不止一个活动分区, 则转停止;
8. 将活动分区的第一个扇区读入内存地址 0000:7c00 处;
9. 检查 (WORD) 0000:7dfe 是否等于 0xaa55, 若不等于则 显示 "Missing Operating System" 然后停止, 或尝 试软盘启动或;
10. 跳转到 0000:7c00 处继续执行特定系统的启动程序;
11. 启动系统...
以上步骤中 2,3,4,5 步是由 BIOS 的引导程序完成. 6,7,8,9,10步由MBR中的引导程序完成.
一般多系统引导程序 (如 SmartFDISK, BootStar, PQBoot 等)都是将标准主引导记录替换成自己的引导程序, 在运行系统启动程序之前让用户选择要启动的分区。
而某些系统自带的多系统引导程序 (如 lilo, NT Loader 等)则可以将自己的引导程序放在系统所处分区的第一个扇区中, 在 Linux中即为 SuperBlock (其实 SuperBlock 是两个扇区)。
注:以上各步骤中使用的是标准 MBR, 其他多系统引导程序的引导过程可能与此不同。
下面简要说明一下系统复位后的指令地址0ffff:fff0(物理地址0x0fffffff0):
在实地址模式下,内存有两个保留区域:系统初始化区和中断向量表区。地址0x00000~0x003ff 是为中断向量保留的,256个可能的中断,每一个保留4字节的跳转向量;地址0xfffffff0~0xffffffff是为系统初始化保留的,此处一般只有一条JMP指令,跳到系统初始引导程序。
系统复位后,cs = 0x0f000、eip = 0x0000fff0,而系统初始引导程序安排在 0x0ffff0000~0x0ffffffff, 一般为ROM固件,使初始引导程序工作于内存实际地址空间以外的另一存储段中。此区域的16~31位都应为1,cs及eip的初值已保证地址线的16~19位为1,而20~31位,即地址线的高12位,则须由硬件强制置 1,这由一个标志触发器在系统每次复位时置位实现触发。而由于段间转移指令要重新装入cs寄存器,因此,每当执行段间转移指令时,此标志触发器复位,以后,再次访问时,不再向高12位地址线提供“1”信号,程序从此正常地工作于前1MB的地址空间。
第二部分 硬盘MBR主引导代码分析
一.程序流程
(引导扇区是指硬盘相应分区的第一个扇区,是和操作系统有关的,操作系统的引导是由它来完成的;而MBR主引导程序并不负责引导操作系统,MBR是和操作系统无关的,他的任务是把控制权转交给操作系统的引导程序.)
1 将程序代码由0:7C00H移动到0:0600H(注,BIOS把MBR放在0:7C00H处)
2 搜索可引导分区,即80H标志
成功:goto 3
失败:跳入ROM BASIC
无效分区表:goto 5
3 读引导扇区
失败:goto 5
成功:goto 4
4 验证引导扇区最后是否为55AAH
失败:goto 5
成功:goto 6
5 打印错误进入无穷循环
6 跳到0:7C00H进行下一步启动工作
二.代码注释
下面将用汇编语言写出这一段代码,并进行说明。
;MBR.ASM
; MASM MBR
; LINK MBR
; EXE2BIN MBR
.MODEL tiny
.CODE
;设置寄存器及堆栈值
org 0
Head:
Start:
cli
xor ax,ax
mov ss,ax
mov sp,7C00H ;ss:sp=0:7C00H
mov si,sp
push ax
pop es
push ax
pop ds ;es=ds=0
sti
;将程序代码由0:7C00H移动到0:0600H处
cld
mov di,600H
mov cx,100H ;100H Words=512 Bytes,即一个扇区大小
repne movsw
db 0EAH ;这个是FAR JUMP的机器码
dw offset Continue+600H, 0000H ;这个是跳转目的地址,即0:061DH
;搜索可引导分区
Continue:
mov si,600H+1BEH ;si指向分区表
mov bl,4 ;四个分区
FindBoot:
cmp byte ptr[si],80H
je SaveRec ;读扇区位置
cmp byte ptr[si],0
jne Invaild ;无效分区
add si,10H
dec bl
jnz FindBoot
int 18H ;进入ROM BASIC
;读取引导分区的扇区,柱面号
SaveRec:
mov dx,[si]
mov cx,[si+2]
mov bp,si
;检查其余分区表
FindNext:
add si,10H
dec bl
jz SetRead
cmp byte ptr[si],0 ;是否存在非法分区
je FindNext
Invaild:
mov si,offset ErrMsg1+600H
;字符串输出子程序
PrintStr:
lodsb
cmp al,0
je DeadLock
push si
mov bx,7
mov ah,0EH ;输出字符
int 10H
pop si
jmp short PrintStr ;下一字符
DeadLock:
jmp short DeadLock ;无穷循环,也可以写成jmp $
;读引导扇区
SetRead:
mov di,5 ;读取次数
ReadBoot:
mov bx,7C00H
mov ax,201H
push di
int 13H ;cx,dx已经在SaveRec处得到
pop di
jnc GoBoot ;成功则启动
xor ax,ax
int 13H ;reset驱动器,然后再读取
dec di
jnz ReadBoot
mov si,offset ErrMsg2+600H
jmp short PrintStr 失败输出信息,并进入无穷循环
;检查读入的引导扇区
GoBoot:
mov si,offsetErrMsg3+600H
mov di,7C00H+1FEH
cmp word ptr[di],0AA55H
jne PrintStr ;非AA55标志则输出错误信息
mov si,bp ;si指向可启动分区
db 0EAH,0,7CH,0,0 ;跳转至0:7C00H
ErrMsg1 db 'Invaild partition table',0
ErrMsg2 db 'Error loading operating system',0
ErrMsg3 db 'Missing operating system',0
Tail:
FillNum equ 1BEH-(Tail-Head) ;计算填0数目
db FillNum dup(0)
;四个分区表项数据,跟分区情况有关,详细含义另解
PartTable db 80H,1,1,0,4,4,0D1H,2,11H,0,0,0,0FEH,0FFH,0,0
db 0,0,0C1H,3,5,4,0D1H,0FEH,0FFH,0FFH,0,0,0ACH,53H,0,0
db 20H dup(0)
ID dw 0AA55H
end start
;如果开始试用org 600H,那么访问数据时就不必加上600H,如mov si,offset
ErrMsg2+600H
;可写为mov si,offset ErrMsg2,这时就不能用exe2bin得到数据,必须试用debug
;debug mbr.exe
;-nmbr.bin
;-rcx 200
;-wcs:600
;-q
在硬盘的第一个扇区上保存着分区信息,即主分区表,共有四项,读取分区表必须使用bios的int 13h,一般使用debug就可以了:
debug
-a
xxxx:0100 mov ax,201
mov bx,200
mov cx,1
mov dx,80 ;如果是第二个硬盘则是81...
int 13
int 20
xxxx:????
-g=100
这时xxxx:0200开始的512字节就是分区表所在的扇区,前面一部分为MBR,在debug中用-d3be l40就可以看到64字节的分区表信息,16个字节为一项,用-e命令就可以修改,改完后可以重新写回去,只要把前面代码中的mov ax,201改为mov ax,301即可,或者直接把102处的2改成3,比如:
-e 102
xxxx:0102 02.3
-g=100
这样就写回去了,不过修改硬盘的第一个扇区须非常谨慎。
下面说一下分区表项的具体意义,取其中一项举个例子:
80 01 01 00 0B 3F FF 00 3F 00-00 00 81 4F 2F 00
1 (80)引导标志,80代表可引导,00代表不可引导,一般必须且只能有一个分区表项的引导标志为 80,除非你自己修改MBR
2 (01)分区开始磁头
3,4 (01 00)=(0,1)分区开始柱面和扇区(后面后详解)
5 (0B)分区类型(后面有详解)
6 (3F)=(63)分区结束磁头
7,8 (FF 00)=(768,63)分区结束柱面和扇区(同上)
9-12 (3F 00 00 00)=(63)此分区前扇区总数,即相对扇区数
13-16 (81 4F 2F 00)=(002F4F81H=3100545)此分区扇区总数
柱面和扇区共用两个字节表示,而柱面号为10位,最大1023,扇区号为6位,最大63,具体各位分布如下图:
扇区号
_____|____
| |
( 7 6 5 4 3 2 1 0 ) ( 7 6 5 4 3 2 1 0 )
|__| |___________|
|___________________|
|
柱面号
关于分区类型,常见的有:
00 未用,Unused
01 DOS-12(FAT 12)
02 XENIX
04 DOS-16(FAT 16)(分区<32M的,应该已没有了)
05 EXTEND(DOS扩展分区)
06 BIGDOS(>32M)(这个才是现在常说的FAT 16)
07 HPFS(OS/2)(NTFS也是这个标记,好像是)
0B FAT 32
0F 这个一时不确定
50 DM
63 386/ix(unix)
64 NET286(Novell)
65 NET386(Novell)
82 Linux swap
83 Linux native
FF BBT(UNIX Bad Block Table)
下面有几个算式用来计算分区参数:
1)第一分区参数
扇区总数=(结束柱面+1)*磁头数*每柱面扇区数-相对扇区数,例如:3100545=(768+1)*64*63-63
2)其它分区参数
扇区总数=(结束柱面-起始柱面+1)*磁头数*每柱面扇区数,如下例:
00 00 C1 01 05 3F FF FD C0 4F-2F 00 C0 90 0F 00
000F90C0H=1020096,(FF FD)=(1021,63),(C1 01)=(769,1),1020096=(1021-769+1)*64*63
3)第一分区相对扇区=每柱面扇区数
其它分区相对扇区=上一分区相对扇区+上一分区扇区总数
扩展分区信息是一个链状结构,在删除分区时,把前面的分区删掉会导致后面的分区也找不到,原因就在于此,我们从主分区表中取出扩展分区项进行一下分析,如下:
00 00 01 C0 05 FE BF 6E C0 10-2F 00 EF A6 69 00
由此我们可以得到数据:
开始磁头:00
开始柱面扇区:01 C0=(192,1)
用debug
debug
-a100
xxxx:0100 mov ax,201
mov bx,200
mov cx,c001 ;开始柱面扇区号
mov dx,80 ;dh中为开始磁头号,这里为0
int 13
int 20
xxxx:????
-g=100
-d3be l10
读出的扇区中有两个16字节的分区表项和最后的一个55AA标志,这两个分区表项为:
00 01 01 C0 06 FE 7F 97 3F 00-00 00 99 F2 34 00
00 00 41 98 05 FE BF 6E D8 F2-34 00 17 B4 34 00
第一个分区类型为6,其实这是第一个逻辑分区,含义和主分区表项相同
第二个分区类型为5,这其实是指向下一个扩展分区表的
从这里我们可以得到:
开始磁头:0
开始柱面扇区:41 98=(408,1)
继续用debug读出(mov cx,9841)得到
00 01 41 98 06 FE BF 6E 3F 00-00 00 D8 B3 34 00
只有一个表项,是第二个逻辑盘,而且是逻辑盘链的最后一个。
可以看到,主分区表是非常重要的,所以除了小心操作外,还应当进行备份,最安全的备份方式就是用笔抄下来,当然,每次重新进行分区后还应当及时更新。从前面可以看出,分区表里最重要的还是柱面号,其它比如磁头号都是0或者1或者最大值,扇区号都是1或63(最大值),扇区总数什么的也都能算出来,所以分区时最好把各分区的柱面号记下来,这样一旦分区信息被破坏就可以进行恢复了。
如果主分区表不幸丢失或者逻辑分区链被破坏,那么只要从硬盘上找出还存在的分区信息,就有可能部分恢复分区信息,甚至全部可以恢复,不过这样的麻烦大家还是应尽量避免。
在Linux里有一种方法可以恢复主引导扇区(包括主分区表),用如下的命令:
dd if=/boot/boot.NNNN of=/dev/hda bs=512 count=1
其中:boot.NNNN 是我们在安装Linux之前整个主引导扇区的备份,NNNN是分区的主次设备号;bs(buffer size)是指重写的字节数。如只是为了修复主引导记录MBR(比如,想把LILO卸载掉),而不是恢复整个主引导扇区,则用如下的命令:
dd if=/boot/boot.NNNN of=/dev/hda bs=446 count=1
只把主引导扇区的备份文件boot.NNNN的前446个字节重写入主引导扇区。
posted on 2005-07-04 19:39
【Z&Y】幸福小筑 阅读(744)
评论(0) 编辑 收藏 引用 所属分类:
Linux源码分析