磁盘操作资料

MBR Date
  自磁芯大战以来,病毒从DOS时代的病毒发展到WINDOS系统的病毒,从变形、加密到智能化现在的病毒是让人防不胜防,现在网络上病毒大肆泛滥,给人们带来的很大的危害,本人在此仅做抛砖引玉,介绍病毒的原理,希望大家共同研究交流。(本文参考了网络上的部分文章,并引用了部分内容。)
要学DOS下的病毒,首先你必须要学会或掌握汇编语言。DOS下病毒一般分为引导型病毒、文件型病毒、混合型病毒等。大部分病毒是感染COM和EXE文件,因此你必须了解COM文件和EXE文件结构。
一 .COM文件结构及原理
.COM 文件比较简单,.COM文件包含程序的一个绝对映象―――就是说,为了运行程序准确的处理器指令和内存中的数据,MS-DOS通过直接把该映象从文件拷贝到内存而加载.COM程序,它不作任何改变。为加载一个.COM程序,MS-DOS首先试图分配内存,因为.COM程序必须位于一个64K的段中,所以.COM文件的大小不能超过65,024(64K减去用于PSP的256字节和用于一个起始堆栈的至少256字节)。如果MS-DOS不能为程序、一个PSP、一个起始堆栈分配足够内存,QQ:9750406则分配尝试失败。否则,MS-DOS分配尽可能多的内存(直至所有保留内存),即使.COM程序本身不能大于64K。在试图运行另一个程序或分配另外的内存之前,大部分.COM程序释放任何不需要的内存。
分配内存后,MS-DOS在该内存的头256字节建立一个PSP,如果PSP中的第一个FCB含有一个有效驱动器标识符,则置AL为00h,否则为0FFh。MS-DOS还置AH为00h或0FFh,这依赖于第二个FCB是否含有一个有效驱动器标识符。建造PSP后,MS-DOS在PSP后立即开始(偏移100h)加载.COM文件,它置SS,DS和ES为PSP的段地址,接着创建一个堆栈.为创建一个堆栈,MS-DOS置SP为0000h,若已分配了至少64K内存;否则,它置寄存器为比所分配的字节总数大2的值.最后,它把0000h推进栈(这是为了保证与在早期MS-DOS版本上设计的程序的兼容性)。MS-DOS通过把控制传递偏移100h处的指令而启动程序.程序设计者必须保证.COM文件的第一条指令是程序的入口点。注意,因为程序是在偏移100h处加载,因此所有代码和数据偏移也必须相对于100h.汇编语言程序设计者可通过置程序的初值为100h而保证这
一点(例如通过在原程序的开始使用语句org 100h).
二 EXE文件结构
 EXE 文件比较复杂,每个EXE文件都有一个文件头,结构如下:
       EXE文件头信息    
    ―――――――――――――――――――
     ├ 偏移量 ┤   意义         ┤
     ├00h-01h ┤MZ‘EXE文件标记     ┤
     ├2h-03h ┤文件长度除512的余数  ┤
     ├04h-05h ┤...............商  ┤
     ├06h-07h ┤重定位项的个数      ┤
     ├08h-09h ┤文件头除16的商     ┤
     ├0ah-0bh ┤程序运行所需最小段数 ┤
     ├0ch-0dh ┤..............大.... ┤
     ├oeh-0fh ┤堆栈段的段值 (SS)   ┤
     ├10h-11h ┤........sp      ┤
     ├12h-13h ┤文件校验和       ┤
     ├14h-15h ┤IP           ┤
     ├16h-17h ┤CS           ┤
     ├18h-19h ┤............  ┤
     ├1ah-1bh ┤............     ┤
     ├1ch   ┤............      ┤ 
 ―――――――――――――――――――――――――
.EXE文件包含一个文件头和一个可重定位程序映象。文件头包含MS-DOS用于加载程序的信息,例如程序的大小和寄存器的初始值。文件头还指向一个重定位表,该表包含指向程序映象中可重定位段地址的指针链表。文件头的形式与EXEHEADER结构对应:
EXEHEADER STRUC
exSignature dw 5A4Dh ;.EXE标志
exExraBytes dw ? ;最后(部分)页中的字节数
exPages dw ? ;文件中的全部和部分页数
exRelocItems dw ? ;重定位表中的指针数
exHeaderSize dw ? ;以字节为单位的文件头大小
exMinAlloc dw ? ;最小分配大小
exMaxAlloc dw ? ;最大分配大小
exInitSS dw ? ;初始SS值
exInitSP dw ? ;初始SP值
exChechSum dw ? ;补码校验值
exInitIP dw ? ;初始IP值
exInitCS dw ? ;初始CS值
exRelocTable dw ? ;重定位表的字节偏移量
exOverlay dw ? ;覆盖号
EXEHEADER ENDS程序映象,包含处理器代码和程序的初始数据,紧接在文件头之后。它的大小以字节为单位,等于.EXE文件的大小减去文件头的大小,也等于exHeaderSize的域的值乘以16。MS-DOS通过把该映象直接从文件拷贝到内存加载.EXE程序然后调整定位表中说明的可重定位段地址。
定位表是一个重定位指针数组,每个指向程序映象中的可重定位段地址。文件头中的exRelocItems域说明了数组中指针的个数,exRelocTable域说明了分配表的起始文件偏移量。每个重定位指针由两个16位值组成:偏移量和段值。 为加载.EXE程序,MS-DOS首先读文件头以确定.EXE标志并计算程序映象的大小。然后它试图申请内存。首先,它计算程序映象文件的大小加上PSP的大小再加上EXEHEADER结构中的exMinAlloc域说明的内存大小这三者之和,如果总和超过最大可用内存块的大小。则MS-DOS停止加载程序并返回一个出错值。否则面,它计算程序映象的大小加上PSP的大小再加上EXEHEADER结构中exMaxAlloc域说明的内存大小之和,如果第二个总和小于最大可用内存块的大小,则MS-DOS 分配计算得到的内存量。否则,它分配最大可用内存块。分配完内存后,MS-DOS确定段地址,也称为起始段地址,MS-DOS从此处加载程序映象。如果exMinAlloc域和exMaxAlloc域中的值都为零,则MS-DOS把映象尽可能地加载到内存最高端。否则,它把映象加载到紧挨着PSP域之上。接下来,MS-DOS读取重定位表中的项目调整所有由可重定位指针说明的段地址。对于重定位表中的每个指针,MS-DOS寻找程序映象中相应的可重定位段地址,并把起始段地址加到它之上。一旦调整完毕,段地址便指向了内存中被加载程序的代码和数据段。 MS-DOS在所分配内存的最低部分建造256字节的PSP,把AL和AH设置为加载 .COM程序时所设置的值。MS-DOS使用文件头中的值设置SP与SS,调整SS初始值,把起始地址加到它之上。MS-DOS还把ES和DS设置为PSP的段地址.最后,MS-DOS从程序文件头读取CS和IP的初始值,把起始段地址加到CS之 上,把控制转移到位于调整后地址处的程序。

三、引导型病毒原理
了解引导型病毒的原理,首先要了解引导区的结构。软盘只有一个引导区,称为DOS BOOT SECTER ,只要软盘做了格式化,就会存在。其作用为查找盘上有无IO.SYS DOS.SYS,若有则引导,若无则显示‘NO SYSTEM DISK...’等信息。硬盘有两个引导区,在0面0道1扇区的称为主引导区,内有主引导程序和分区表,主引导程序查找激活分区,该分区的第一个扇区即为DOS BOOT SECTER。绝大多数病毒感染硬盘主引导扇区和软盘DOS引导扇区。

***3.5”软盘格式***
3.5”软盘是双面的,所以零磁道有正反两面,正面为0-17扇区,
反面是18-35扇区。
0 扇区: Boot area (引导扇区);
1 - 9 扇区: 1st FAT area (第一张文件分配表);
10 - 18 扇区: 2st FAT area (第二张文件分配表);
19 - 32 扇区: Root dir area(也叫 File Directory Table,FDT)
文件目录表(根目录)
33-2879 扇区: Data area (数据区)

***硬盘的主引导记录结构***
硬盘的主引导记录结构
偏移 机器码 符号指令 说明
0000 FA CLI ;屏蔽中断
0001 33C0 XOR AX,AX
0003 8ED0 MOV SS,AX ;(SS)=0000H
0005 BC007C MOV SP,7C00 ;(SP)=7C00H
0008 8BF4 MOV SI,SP ;(SI)=7C00H
000A 50 PUSH AX
000B 07 POP ES ;(ES)=0000H
000C 50 PUSH AX
000D 1F POP DS ;(DS)=0000H
000E FB STI
000F FC CLD
0010 BF0006 MOV DI,0600
0013 B90001 MOV CX,0100 ;共512字节
0016 F2 REPNZ
0017 A5 MOVSW ;主引导程序把自己从0000:7C00处搬到
;0000:0600处,为Dos分区的引导程序腾
;出空间
0018 EA1D060000 JMP 0000:061D ;跳到0000:061D处继续执行,实际上就是
;执行下面的MOV指令(001D偏移处)
001D BEBE07 MOV SI,07BE ;07BE-0600=01BE,01BE是分区表的首址
0020 B304 MOV BL,04 ;分区表最多4项,即最多4个分区
0022 803C80 CMP BYTE PTR [SI],80 ;80H表示活动分区
0025 740E JZ 0035 ;找到活动分区则跳走
0027 803C00 CMP BYTE PTR [SI],00 ;00H为有效分区的标志
002A 751C JNZ 0048 ;既非80H亦非00H则分区表无效
002C 83C610 ADD SI,+10 ;下一个分区表项,每项16字节
002F FECB DEC BL ;循环计数减一
0031 75EF JNZ 0022 ;检查下一个分区表项
0033 CD18 INT 18 ;4个都不能引导则进入ROM Basic
0035 8B14 MOV DX,[SI]
0037 8B4C02 MOV CX,[SI+02] ;取活动分区的引导扇区的面,柱面,扇区
003A 8BEE MOV BP,SI ;然后继续检查后面的分区表项
003C 83C610 ADD SI,+10
003F FECB DEC BL
0041 741A JZ 005D ;4个都查完则去引导活动分区
0043 803C00 CMP BYTE PTR [SI],00 ;00H为分区有效标志
0046 74F4 JZ 003C ;此分区表项有效则继续查下一个
0048 BE8B06 MOV SI,068B ;068B-0600=018B,取"无效分区"字符串
004B AC LODSB ;从字符串中取一字符
004C 3C00 CMP AL,00 ;00H表示串尾
004E 740B JZ 005B ;串显示完了则进入死循环
0050 56 PUSH SI
0051 BB0700 MOV BX,0007
0054 B40E MOV AH,0E
0056 CD10 INT 10 ;显示一个字符
0058 5E POP SI
0059 EBF0 JMP 004B ;循环显示下一个字符
005B EBFE JMP 005B ;此处为死循环
005D BF0500 MOV DI,0005 ;读入活动分区的引导扇,最多试读5次
0060 BB007C MOV BX,7C00
0063 B80102 MOV AX,0201
0066 57 PUSH DI
0067 CD13 INT 13 ;读
0069 5F POP DI
006A 730C JNB 0078 ;读盘成功则跳走
006C 33C0 XOR AX,AX
006E CD13 INT 13 ;读失败则复位磁盘
0070 4F DEC DI
0071 75ED JNZ 0060 ;不到5次则再试读
0073 BEA306 MOV SI,06A3 ;06A3-0600=00A3,即"Error loading"串
0076 EBD3 JMP 004B ;去显示字符串,然后进入死循环
0078 BEC206 MOV SI,06C2 ;06C2-0600=00C2,即"Missing.."串
0076 EBD3 JMP 004B ;去显示字符串,然后进入死循环
0078 BEC206 MOV SI,06C2 ;06C2-0600=00C2,即"Missing.."串
007B BFFE7D MOV DI,7DFE ;7DFE-7C00=01FE,即活动分区的引导扇
;区的最后两字节的首址
007E 813D55AA CMP WORD PTR [DI],AA55;最后两字节为AA55H则有效
0082 75C7 JNZ 004B ;无效则显示字符串并进入死循环
0084 8BF5 MOV SI,BP
0086 EA007C0000 JMP 0000:7C00 ;有效则跳去引导该分区
0080 49 6E 76 61 6C Inval
0090 69 64 20 70 61 72 74 69-74 69 6F 6E 20 74 61 62 id partition tab
00A0 6C 65 00 45 72 72 6F 72-20 6C 6F 61 64 69 6E 67 le.Error loading
00B0 20 6F 70 65 72 61 74 69-6E 67 20 73 79 73 74 65 operating syste
00C0 6D 00 4D 69 73 73 69 6E-67 20 6F 70 65 72 61 74 m.Missing operat
00D0 69 6E 67 20 73 79 73 74-65 6D 00 00 FB 4C 38 1D ing system...L8.
00E0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00F0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0100 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0110 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0120 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0130 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0140 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0150 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0160 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0170 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0180 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0190 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
01A0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
01B0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 80 01 ................;分区表
01C0 01 00 06 0F 7F 9C 3F 00-00 00 F1 59 06 00 00 00 ......?....Y....
01D0 41 9D 05 0F FF 38 30 5A-06 00 40 56 06 00 00 00 
A....80Z..@V....
01E0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
01F0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 55 AA ..............U.


使用INT 13H的02功能调用把位于硬盘保留扇区中0道0头1扇区处的硬盘主引导记录读到内存的ES:BX处。现在把读出程序代码进行如下分析:

1、移动主引导记录程序
0E74:7C00 33C0 XOR AX,AX ;AX清零
0E74:7C02 8ED0 MOV SS,AX ;SS清零
0E74:7C04 BC007C MOV SP,7C00 ;SP=7C00,堆栈设在0:7C00H
0E74:7C07 FB STI ;开中断
0E74:7C08 50 PUSH AX
0E74:7C09 07 POP ES ;ES=0
0E74:7C0A 50 PUSH AX
0E74:7C0B 1F POP DS ;DS=0
0E74:7C0C FC CLD
0E74:7C0D BE1B7C MOV SI,7C1B ;源地址为0:7C1BH
0E74:7C10 BF1B06 MOV DI,061B ;目的地址为0:061BH
0E74:7C13 50 PUSH AX
0E74:7C14 57 PUSH DI
0E74:7C15 B9E501 MOV CX,01E5 ;移动01E5字节
0E74:7C18 F3 REPZ ;将主引导记录从0:7C1B-0:7DFF
0E74:7C19 A4 MOVSB ;移至0:061B-0:07FF
0E74:7C1A CB RETF ;转移到0:061B,继续执行程序

2、顺序查找四个硬盘分区表,寻找自举标志
0E74:061B BEBE07 MOV SI,07BE ;SI指向硬盘分区表1的自举标志

0E74:061E B104 MOV CL,04 ;查找四个分区
0E74:0620 382C CMP [SI],CH
0E74:0622 7C09 JL 062D ;如果[SI]的第7位为1,即为自
;举标志,转062DH
0E74:0624 7515 JNZ 063B ;如果[SI]不为0,出错,转063BH
0E74:0626 83C610 ADD SI,+10 ;依次检验四个分区表,直至找到

0E74:0629 E2F5 LOOP 0620 ;自举标志
0E74:062B CD18 INT 18 ;找不到自举标志,进入BOOT异
;常处理程序。
0E74:062D 8B14 MOV DX,[SI] ;保存自举驱动器号于DL中
0E74:062F 8BEE MOV BP,SI ;保存自举分区地址指针于BP
0E74:0631 83C610 ADD SI,+10 ;继续检验自举分区后的分区
0E74:0634 49 DEC CX ;自举标志,直至四个分区都
0E74:0635 7416 JZ 064D ;检查完
0E74:0637 382C CMP [SI],CH ;若其余的自举标志不为0,出错

0E74:0639 74F6 JZ 0631 

3、出错,写屏幕程序段
0E74:063B BE1007 MOV SI,0710 ;错误信息输出,死循环
0E74:063E 4E DEC SI
0E74:063F AC LODSB
0E74:0640 3C00 CMP AL,00
0E74:0642 74FA JZ 063E
0E74:0644 BB0700 MOV BX,0007
0E74:0647 B40E MOV AH,0E
0E74:0649 CD10 INT 10
0E74:064B EBF2 JMP 063F

硬盘主引导记录程序的功能是读出自举分区的BOOT程序,并把控制转移到分区BOOT程序。整个程序流程如下:
1 将本来读入到0:7C00H处的硬盘主引导记录程序移至0:61BH处;
⑵ 顺序读入四个分区表的自举标志,以找出自举分区,若找不到,转而执行INT18H的BOOT异常执行中断程序;
⑶ 找到自举分区后,检测该分区的系统标志,若为32位FAT表或16位FAT表但支持13号中断的扩展功能,就转到执行13号中断的41号功能调用进行安装检验,检验成功,就执行42号扩展读功能调用把BOOT区程序读入到内存0:7C00H处,成功,跳到第⑸步,若读失败或系统标志为其它,就调用13号中断的读扇区功能调用把BOOT读到0:7C00H;
⑷ 用13号中断的读扇区功能时,用两种方式分别进行5次试读。第一种方式是直接从自举分区的头扇区读入BOOT程序,若读成功,但结束标志不是55AA,则改用第二种方式,又如果用第一种方式试读五次均不成功,就改用第二种方式。若两种方式试读均失败,就转到出错处理程序; ⑸ 读入BOOT区程序成功,转至0:7C00H处执行BOOT程序。 

NT下对I/O地址的访问
Windows NT 操作系统设置的进程模式会使运行在其中的应用程序访问I/O地址的指令引起保护性的失败。这使得应用程序需要附以一个设备驱动程序进行I/O操作。设备驱动程序运行在内核模式,这使得在这种状态的中运行的进程可以执行I/O操作。

---- Windows 95/98 是仅为 Intel 类型机器设计的,没有额外复杂的I/O需求,而Windows NT 被设计成可以在不同机器机构上进行移植。这使得Windows NT 的系统模式要求驱动程序的编写者要考虑一台机器可能有多种类型的总线,这可能需要在总线之间传递地址。这种模式还要区别I/O空间和内存空间。在多总线的机器中每一总线可以既支持内存又支持I/O循环。

---- 根据定义,I/O寄存器或者端口访问是通过I/O循环实现的。然而,在一些系统中外部总线的I/O空间可以被映像到进程内存空间。硬件抽象层(Hardware Abstract Layer)决定这些。要访问I/O寄存器,驱动程序编写者必须知道寄存器在那一总线,它的I/O空间地址在那条总线。一条总线是由其接口类行 (如 ISA 、PCI 等)和编号(从零开始)决定的。

---- 下面是一个假象设备访问I/O的例子,接口类型:ISA 编号 0 地址 0xE700。设备描述如下: Offset Size Usage 0 1 Command register 1 1 Status register 2 2 Word data register 4 4 Dword data register

---- 用开发NT 设备驱动程序的工具包DriverDorks 可以用以下 步骤访问设备:
---- 建立一个KIoRange的对象映像设备寄存器。
   KIoRange DeviceIos; Status = DevceIos.Initialize(
     Isa, // 总线类型
     0, // 总线号
     0xE700, // 总线地址
     8, // 设备数
     TRUE // 映像到系统空间(如果端口是内存映像的)
     );
   if(NT_SUCCESS(status)) //建立成功

---- 可以用KIoRange 的成员函数访问寄存器:
     //寄存器偏移量
     #define COMMAND 0
     #define STATUS 1
     #define WDATA 2
     #define DDATA 3

     //读状态寄存器
     UCHAR DeviceStatus = DeviceIos.inb(STATUS);

     //写命令寄存器
     DeviceIos.outb(COMMAND,CMD_RESET);

     //写20个字到端口
     DeviceIos.outw(WDATA,buffer,20);

---- 另外也可以建立KIoRegister 的对象来访问设备:
     KIoRegister CommandReg = DeviceIos[COMMAND];
     KIoRegister StatusReg = DeviceIos[STATUS];
     CommandRge=(UCHAR)RESET;  //写 RESET命令
     UCHAR status=StatusReg; //读状态寄存器
     如果在同一函数中频繁访问寄存器用KioRegiser 比用KIoRange 的成员函数的性能好一些。无论如何,数据类型必须正确(UCHAR,USHORT,ULONG),这些决定了到总线上数据的实际大小.

硬盘的 dos 管理结构
  1. 磁道,扇区,柱面和磁头数
   硬盘最基本的组成部分是由坚硬金属材料制成的涂以磁性介质的盘片,不同容量硬盘的盘片数不等。每个盘片有两面,都可记录信息。盘片被分成许多扇形的区域,每个区域叫一个扇区,每个扇区可存储 128 × 2 的 n 次方( n = 0.1.2 .3 )字节信息。在 dos 中每扇区是 128 × 2 的 2 次方= 512 字节,盘片表面上以盘片中心为圆心,不同半径的同心圆称为磁道。硬盘中,不同盘片相同半径 的磁道所组成的圆柱称为柱面。磁道与柱面都是表示不同半径的圆,在许多场合,磁道和柱面可以互换使用,我们知道,每个磁 盘有两个面,每个面都有一个磁头,习惯用磁头号来区分。扇区,磁道(或柱面)和磁头数构成了硬盘结构的基本参数,帮这些 参数可以得到硬盘的容量,基计算公式为:
  存储容量=磁头数×磁道(柱面)数×每道扇区数×每扇区字节数
  要点:( 1 )硬盘有数个盘片,每盘片两个面,每个面一个磁头
     ( 2 )盘片被划分为多个扇形区域即扇区
     ( 3 )同一盘片不同半径的同心圆为磁道
     ( 4 )不同盘片相同半径构成的圆柱面即柱面
     ( 5 )公式: 存储容量=磁头数×磁道(柱面)数×每道扇区数×每扇区字节数
     ( 6 )信息记录可表示为:××磁道(柱面),××磁头,××扇区


  2. 簇
   “簇”是 dos 进行分配的最小单位。当创建一个很小的文件时,如是一个字节,则它在磁盘上并不是只占一个字节的空间, 而是占有整个一簇。 dos 视不同的存储介质(如软盘,硬盘),不同容量的硬盘,簇的大小也不一样。簇的大小可在称为磁盘 参数块( bpb )中获取。簇的概念仅适用于数据区。
   本点:( 1 )“簇”是 dos 进行分配的最小单位。
      ( 2 )不同的存储介质,不同容量的硬盘,不同的 dos 版本,簇的大小也不一样。
      ( 3 )簇的概念仅适用于数据区。

  3. 扇区编号定义:绝对扇区与 dos 扇区
   由前面介绍可知,我们可以用柱面 / 磁头 / 扇区来唯一定位磁盘上每一个区域,或是说柱面 / 磁头 / 扇区与磁盘上每一个扇区有 一一对应关系,通常 dos 将“柱面 / 磁头 / 扇区”这样表示法称为“绝对扇区”表示法。但 dos 不能直接使用绝对扇区进行磁盘上的 信息管理,而是用所谓“相对扇区”或“ dos 扇区”。“相对扇区”只是一个数字,如柱面 140 ,磁头 3 ,扇区 4 对应的相对扇区号 为 2757 。该数字与绝对扇区“柱面 / 磁头 / 扇区”具有一一对应关系。当使用相对扇区编号时, dos 是从柱面 0 ,磁头 1 ,扇区 1 开始 (注:柱面 0 ,磁头 0 ,扇区 1 没有 dos 扇区编号, dos 下不能访问,只能调用 bios 访问),第一个 dos 扇区编号为 0 ,该磁道上剩余 的扇区编号为 1 到 16 (设每磁道 17 个扇区),然后是磁头号为 2 ,柱面为 0 的 17 个扇区,形成的 dos 扇区号从 17 到 33 。直到该柱面的 所有磁头。然后再移到柱面 1 ,磁头 1 ,扇区 1 继续进行 dos 扇区的编号,即按扇区号,磁头号,柱面号(磁道号)增长的顺序连续 地分配 dos 扇区号。
    公式:记 dh --第一个 dos 扇区的磁头号
             dc --第一个 dos 扇区的柱面号
             ds --第一个 dos 扇区的扇区号
             ns --每磁道扇区数
             nh --磁盘总的磁头数
  则某扇区(柱面 c ,磁头 h ,扇区 s )的相对扇区号 rs 为:
    rs = nh × ns ×( c - dc )+ ns ×( h - dh )+( s - ds )
  若已知 rs , dc , dh , ds , ns 和 nh 则
    s =( rs mod ns )+ ds
    h =(( rs div ns ) mod nh )+ dh
    c =(( rs div ns ) div nh )+ dc
    要点:( 1 )以柱面 / 磁头 / 扇区表示的为绝对扇区又称物理磁盘地址
       ( 2 )单一数字表示的为相对扇区或 dos 扇区,又称逻辑扇区号
       ( 3 )相对扇区与绝对扇区的转换公式

 4.dos 磁盘区域的划分
   格式化好的硬盘,整个磁盘按所记录数据的作用不同可分为主引导记录( mbr:main boot record ), dos 引导记录( dbros boot record ),文件分配表( fat:file assign table ),根目录( bd:boot directory )和数据区。前 5 个重要信息在磁盘的外 磁道上,原因是外圈周长总大于内圈周长,也即外圈存储密度要小些,可伤心性高些。
    要点:( 1 )整个硬盘可分为 mbr , dbr , fat , bd 和数据区。
       ( 2 ) mbr , dbr , fat ,和 bd 位于磁盘外道。

 5.mbr
    mbr 位于硬盘第一个物理扇区(绝对扇区)柱面 0 ,磁头 0 ,扇区 1 处。由于 dos 是由柱面 0 ,磁头 1 ,扇区 1 开始,故 mbr 不属于 dos 扇区, dos 不能直接访问。 mbr 中包含硬盘的主引导程序和硬盘分区表。分区表有 4 个分区记录区。记录区就是记录有关分区信
息的一张表。它从主引导记录偏移地址 01beh 处连续存放,每个分区记录区占 16 个字节。
分区表的格式 分区表项的偏移 意义   占用字节数
   00 引导指示符 1b
   01 分区引导记录的磁头号 1b
   02 分区引导记录的扇区和柱面号 2b
   04 系统指示符 1b
   05 分区结束磁头号 1b
   06 分区结束扇区和柱面号 2b
   08 分区前面的扇区数 4b
   0c 分区中总的扇区数 4b
   4 个分区中只能有 1 个活跃分区,即 c 盘。标志符是 80h 在分区表的第一个字节处。若是 00h 则表示非活跃分区。例如:
   80 01 01 00 0b fe 3f 81 3f 00 00 00 c 3 dd 1f 00
   00 00 01 82 05 fe bf 0c 02 de 1f 00 0e 90 61 00
   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
   要点:( 1 ) mbr 位于硬盘第一个物理扇区柱面 0 ,磁头 0 ,扇区 1 处。不属于 dos 扇区,
      ( 2 )主引导记录分为硬盘的主引导程序和硬盘分区表。

  6.dbr
  dbr 位于柱面 0 ,磁头 1 ,扇区 1 ,即逻辑扇区 0 。 dbr 分为两部分: dos 引导程序和 bpb ( bios 参数块)。其中 dos 引导程序完成 dos 系统文件( io.sys , msdos.sys )的定位与装载,而 bpb 用来描述本 dos 分区的磁盘信息, bpb 位于 dbr 偏移 0bh 处,共 13 字节。 它包含逻辑格式化时使用的参数,可供 dos 计算磁盘上的文件分配表,目录区和数据区的起始地址, bpb 之后三个字提供物理格 式化(低格)时采用的一些参数。引导程序或设备驱动程序根据这些信息将磁盘逻辑地址( dos 扇区号)转换成物理地址(绝对 扇区号)。
   bpb 格式  序号 偏移地址 意义
            1 03h - 0ah oem 号
            2 0bh - 0ch 每扇区字节数
            3 0dh 每簇扇区数
            4 0eh - 0fh 保留扇区数
            5 10h fat 备份数
            6 11h - 12h 根目录项数
            7 13h - 14h 磁盘总扇区数
            8 15h 描述介质
            9 16h - 17h 每 fat 扇区数
            10 18h - 19h 每磁道扇区数
            11 1ah - 1bh 磁头数
            12 1ch - 1fh 特殊隐含扇区数
            13 20h - 23h 总扇区数
            14 24h - 25h 物理驱动器数
            15 26h 扩展引导签证
            16 27h - 2ah 卷系列号
            17 2bh - 35h 卷标号
            18 36h - 3dh 文件系统号
            dos 引导记录公式:
   文件分配表≡保留扇区数
   根目录≡保留扇区数+ fat 的个数×每个 fat 的扇区数
   数据区≡根目录逻辑扇区号+( 32 ×根目录中目录项数+(每扇区字节数- 1 )) div 每扇区字节数
   绝对扇区号≡逻辑扇区号+隐含扇区数
   扇区号≡(绝对扇区号 mod 每磁道扇区数)+ 1
   磁头号≡(绝对扇区号 div 每磁道扇区数) mod 磁头数
   磁道号≡(绝对扇区号 div 每磁道扇区数) div 磁头数
   要点:( 1 ) dbr 位于柱面 0 ,磁头 1 ,扇区 1 ,其逻辑扇区号为 0
      ( 2 ) dbr 包含 dos 引导程序和 bpb 。
      ( 3 ) bpb 十分重要,由此可算出逻辑地址与物理地址。

  7. 文件分配表
   文件分配表是 dos 文件组织结构的主要组成部分。我们知道 dos 进行分配的最基本单位是簇。文件分配表是反映硬盘上所 有簇的使用情况,通过查文件分配表可以得知任一簇的使用情况。 dos 在给一个文件分配空间时总先扫描 fat ,找到第一个可 用簇,将该空间分配给文件,并将该簇的簇号填到目录的相应段内。即形成了“簇号链”。 fat 就是记录文件簇号的一张表。 fat 的头两个域为保留域,对 fat12 来说是 3 个字节, fat 来说是 4 个字节。其中头一个字节是用来描述介质的,其余字节为 ffh 。介质格式与 bpb 相同。
    第一个字节的 8 位意义:
    7 6 5 4 3  2 1 0
    └───── - ┘ │ │ │┌ 0 非双面
    置 1 │ │ └┤
    │ │ └ 1 双面
    │ │┌ 0 不是 8 扇区
    │ └┤
    │ └ 1 是 8 扇区
    │┌ 0 不是可换的
    └┤
    └ 1 是可换的
    fat 结构含义
    fat12 fat16 意义
    000h 0000h 可用
    ff0h - ff6h fff0h - fff6h 保留
    ff7h fff7h 坏
    ff8h - fffh fff8h - ffffh 文件最后一个簇
    ××× h ×××× h 文件下一个簇
    对于 fat16 ,簇号× 2 作偏移地址,从 fat 中取出一字即为 fat 中的域。
    逻辑扇区号=数据区起始逻辑扇区号+(簇号- 2 )×每簇扇区数
    簇号=(逻辑扇区号-数据区起始逻辑扇区号) div 每簇扇区数+ 2
要点:( 1 ) fat 反映硬盘上所有簇的使用情况,它记录了文件在硬盘中具体位置(簇)。
   ( 2 )文件第一个簇号(在目录表中)和 fat 的该文件的簇号串起来形成文件的“簇号链”,恢复被破坏的文件就是根 据这条链。
   ( 3 )由簇号可算逻辑扇区号,反之,由逻辑扇区号也可以算出簇号,公式如上。
   ( 4 ) fat 位于 dbr 之后,其 dos 扇区号从 1 开始。

 8. 文件目录
   文件目录是 dos 文件组织结构的又一重要组成部分。文件目录分为两类:根目录,子目录。根目录有一个,子目录可以有 多个。子目录下还可以有子目录,从而形成“树状”的文件目录结构。子目录其实是一种特殊的文件, dos 为目录项分配 32 字 节。目录项分为三类:文件,子目录(其内容是许多目录项),卷标(只能在根目录,只有一个。目录项中有文件(或子目 录,或卷标)的名字,扩展名,属性,生成或最后修改日期,时间,开始簇号,及文件大小。
   目录项的格式   字节偏移 意义 占字节数
                  00h 文件名 8b
                  08h 扩展名 3b
                  0bh 文件属性 1b
                  0ch 保留 10b
                  16h 时间 2b
                  18h 日期 2b
                  1ah 开始簇号 2b
                  1ch 文件长度 4b
   目录项文件名区域中第一个字节还有特殊的意义: 00h 代表未使用  05h 代表实际名为 e5h ebh 代表此文件已被删除
目录项属性区域的这个字节各个位的意义如下: 7 6 5 4 3 2 1 0
                      未 修 修 子 卷 系 隐 只
                      用 改 改 目 标 统 藏 读
                         标 标 录   属 属 属
                         志 志     性 性 性
  注意: windows 的长文件名使用了上表中所说的“保留”这片区域。
  要点:( 1 )文件目录是记录所有文件,子目录名,扩展名属性,建立或删除最后修改日期。文件开始簇号及文件长度的一张 登记表 .
    ( 2 ) dos 中 dir 列出的内容训是根据文件目录表得到的。
    ( 3 )文件起始簇号填在文件目录中,其余簇都填在 fat 中上一簇的位置上。

9. 物理驱动器与逻辑驱动器
   物理驱动器指实际安装的驱动器。 逻辑驱动器是对物理驱动器格式化后产生的 硬盘逻辑锁巧解在谈论具体的解决方法前,先讲述一下被 " 逻辑锁 " 锁住的硬盘为什么不能用普通办法启动的原因:计算机在引导 dos 系统时将会搜索所有逻辑盘的顺序,当 dos 被引导时,首先要去找主引 导扇区的分区表信息,位于硬盘的零头零柱面的第一个扇区的 obeh 地址开始的地方,当 分区信息开始的地方为 80h 时表示是主引导分区,其他的为扩展分区,主引导分区被定义 为逻辑盘 c 盘,然后查找扩展分区的逻辑盘,被定义为 d 盘,以此类推找到 e , f , g..... " 逻辑锁 " 就是在此下手,修改了正常的主引导分区记录将扩展分区的第一个逻辑盘指向 自己, dos 在启动时查找到第一个逻辑盘后,查找下个逻辑盘总是找到是自己,这样一来 就形成了死循环,这就是使用软驱 , 光驱,双硬盘都不能正常启动的原因。实际上这 " 逻辑锁 " 只是利用了 dos 在启动时的一个小小缺陷,便令不少高手都束手无策。知道了 " 逻辑 锁 " 的 " 上锁 " 原理,要解锁也就比较容易了。以前我看到有位朋友采用 " 热拔插 " 硬盘电源的方法来处理:就是在当系统启动时,先不给被锁的硬盘插上电源线,等待启动完成后再给硬盘 " 热插 " 上电源线,这时如果硬盘没有烧坏的话,系统就可以控制硬盘了。当然这是一种非常危险的方法,大家不要轻易尝试,下面介绍两种比较简单和安全的处理方法。

   方法一:修改 dos 启动文件 首先准备一张 dos6.22 的系统盘,带上 debug 、 pctools5.0 、 fdisk 等工具。然后在一台正常的机器上,使用你熟悉的二进制编辑工具( debug 、 pctools5.0 ,或者 windows 下的 ultraedit 都行)修改软盘上的 io.sys 文件(修改前记住改该文件的属性为正常),具体是在这个文件里面搜索第一个 "55aa" 字符串,找到以后修改为任何其他数值即可。用这张修改过的系统软盘你就可以顺利地带着被锁的硬盘启动了。不过这时由于该硬盘正常的分区表已经被黑客程序给恶意修改了,你无法用 fdisk 来删除和修改分区,而且仍无法用正常的启动盘启动系统,这时你可以用 debug 来手工恢复。使用 debug 手工修复硬盘步骤如下:

a:/>debug
-a
-xxxx:100 mov ax,0201 读一个扇区的内容
-xxxx:103 mov bx,500 设置一个缓存地址
-xxxx:106 mov cx,0001 设置第一个硬盘的硬盘指针
-xxxx:109 mov dx,0080 读零磁头
-xxxx: 10c int 13 硬盘中断
-xxxx:10e int 20
-xxxx:0110 退出程序返回到指示符
-g 运行
-d500 查看运行后 500 地址的内容

这时候会发现地址 6be 开始的内容是硬盘分区的信息,发现此硬盘的扩展分区指向自己,这就使 dos 或 windows 启动时查找硬盘逻辑盘进去死循环,在 debug 指示符下用 e 命令修改内存数据 具体如下:

e6be
xx.0 xx.0 xx.0...............
.............................
.......................55 aa
55 aa 表示硬盘有效的标记,不要修改, xx0 表示把以前的数据 "xx" 改成 0

再用硬盘中断 13 把修改好的数据写入硬盘就可以了,具体如下:
a:/>debug
a 100 表示修改 100 地址的汇编指令
-xxxx:100 mov ax,0301 写硬盘一个扇区
-xxxx: 这里直接按回车
-g 运行
-q 退出

   然后运行 fdisk/mbr (重置硬盘引导扇区的引导程序 ) ,再重新启动电脑就行了。 怎么样?用这种方法处理够简单的吧?而且这种方法还有一个好处就是可以保住盘上的 数据!如果你不需要保数据的话,还有更加简单的处理方法:

   方法二:巧设 bios ,用 dm 解锁大家知道 dm 软件是不依赖于主板 bios 的硬盘识别安装软件,(所以在不能识别大硬盘的老主板上也可用 dm 来安装使用大容量硬盘)。就算在 bios 中将硬盘设为 "none" , dm 也可识别并处理硬盘。首先你要找到和硬盘配套的 dm 软件(找 js 要或去网上荡),然后把 dm 拷到一张系统盘上。接上被锁硬盘,开机,按住 del 键,进 cmos 设置,将所有 ide 硬盘设为 none (这是关键所在 ! ),保存设置,重启动,这时系统即可 " 带锁 " 启动。启动后运行 dm ,你会发现 dm 可以绕过 bios ,识别出硬盘,选中该硬盘,分区格式化,就 ok 了。这么简单?不过这种 方法的弱点是硬盘上的数据将全部丢失。

如何开发自己的操作系统的引导程序?
当你打开计算机时发生了什么?
  1.电源打开;2.BIOS开始执行;3.引导程序开始执行。
  引导程序的规定:你要有一个普通的二进制文件(COM 格式);大小是512个字节;最后两个字节一定是0AA55h;它能被载入到内存地址0x7C00。
  工具:
  NASM――是一个免费的汇编工具(有DOS/windows/Linux三种版本)
  PARTCOPY2.0――DOS下可自由往磁盘拷贝数据的软件
  举例:
  1.Just hang……
  这个简单的引导程序只能挂起:
  hang:
   jmp hang
  times 512-($-$$)-2 db 0
  dw 0AA55h
  连接这个引导程序:
  nasm -f bin -o hang.bin hang.asm
  现在你需要一张格式化磁盘,传送hang.bin到磁盘的引导扇区
  partcopy hang.bin 0 200 -f0
  “0”的意思是指从hang.bin文件的顶端开始传送
  “200”的意思是指拷贝200个字节
  插入磁盘和重新启动机器,测试这个引导程序。
  2.一个实模式下的引导程序
  上面的程序非常简单,下面介绍一个稍微复杂一点的程序。
  bits 16
  org 0x7C00
  start:
  cli;关中断
  mov ax,0x9000;设置堆栈址:0x90000
  mov ss,ax
  mov sp,0
  sti;开中断
  l1:push ds
  mov dl,0;
  重新设置磁盘控制器
  mov ax,0
  int 13h
  pop ds
   jc fail
  push es
  mov ax,0x1000;ES:BX=10000
  mov es,ax
  mov bx,0
  mov ah,2;读磁盘扇区
  mov al,5;读入5个扇区
  mov cx,2;柱面号=0,扇区号=2
  mov dx, 0;磁头号=0,驱动器号=0
  int 13h;ES:BX=来自磁盘上的数据
  pop es
  jc l1
  mov ax,0x10000;设置段寄器
  mov es,ax
  mov ds,ax
  push ax
  mov ax,0
  push ax
  retf
  fail:
  jmp fail
  times 512-($-$$)-2 db 0
  dw 0AA55h
  连接这个引导程序:
  nasm -f bin -o boot.bin boot.asm
  传送boot.bin到磁盘的引导扇区
  partcopy boot.bin 0 200 -f0
  为了使程序可以看到,在编译下面程序
  mov ax,1000h;修改段寄存器
  mov ds,ax
  mov es,ax
  mov si,msg;打印 "JIPPIKAYE!"
  call putstr
  hang:;挂起
  jmp hang
  putstr:
  lodsb
  or al,al
  jz short putstrd
  mov ah,0x0E
  mov bx,0x0007
  int 0x10
  jmp putstr
  putstrd:
  retn
  msg db 'JIPPIKAYE!',13,10,0
  连接和传送:
  nasm -f bin -o boot.bin boot.asm
  partcopy boot.bin 0 200 -f0 200
  在partcopy中最后一个参数“200”意思是指磁盘的偏移地址插入磁盘和重新启动机器,你会看到“JIPPIKAYE”然后挂起。 


Windows 2000的引导过程

事实上,Windows 2000的引导过程是从安装时候就已经开始的。

那我们首先从windows 2000的安装说起。
当windows 2000 setup运行时,它向硬盘上写入MBR(主引导记录),同时在这个磁盘驱动器的第一个可引导分区(就是我们在fdisk后激活的分区)写入引导扇区,引导扇区的内容根据不同的文件系统格式而变化(FAT或者是NTFS)。如果你的机器上曾装有MS操作系统并建立了引导扇区的话,windows 2000 setup将检测它要覆盖的引导扇区是否有效,如果有效的话,windows 2000 setup安装程序将把引导扇区的内容复制到这个分区的根目录中的文件bootsect.dos中。Setup程序在写完引导扇区后,将把windows 2000所用的文件拷贝到硬盘,包括两个引导文件Ntldr和Ntdetect.com。另外,setup还会在引导分区的根目录中建立引导菜单文件boot.ini。
例:
[boot loader]
timeout=3
default=multi(0)disk(0)rdisk(0)partition(1)/WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)/WINDOWS="Microsoft Windows "
multi(0)disk(0)rdisk(0)partition(2)/WINDOWS="Windows Server 2000" /fastdetect

这是我的机器上的boot.ini文件,该内容显示装了两个操作系统,win98和win2000,后面的那个参数/fastdetect最常见,是安装系统时默认的,它的作用是使ntdetect忽略秉性和串行设备的枚举。Boot.ini文件中的相关参数还有很多,各有不同的功能,因为与本文没太大关系,所以不作具体介绍,有兴趣的朋友可以到网上找找有关资料。

Windows 2000的启动:
当你按下机器上的power键,计算机就开始启动了,首先是上电自检,通过后bios引导计算机去读取硬盘上的MBR,根据MBR中的信息,找到引导分区,将引导分区内的引导扇区的代码读入内存并把控制权交给该代码。引导扇区代码的作用是向Windows 2000提供磁盘驱动器(硬盘)的结构和格式信息并且从磁盘根目录中读取Ntldr文件,在引导扇区代码将Ntldr加载到内存后,它把控制权交给Ntldr的入口点。如果引导扇区代码在根目录中没有找到Ntldr文件的话,若文件系统为FAT格式,则显示:“Boot:无法找到Ntldr”,若引导文件系统是NTFS格式,则显示:“NTLDR丢失”。然后,Ntldr使用内建的文件系统代码从根目录读取boot.ini文件(Ntldr内建代码与引导扇区文件系统代码不同的是,Ntldr文件系统代码可以读取子目录)。此时,Ntldr清除屏幕,如果boot.ini中存在不止一种引导选项,则显示引导选择菜单,如果在boot.ini制定的超时范围内未有任何动作的话,Ntldr会选择默认的选项。引导选项确定后,Ntldr加载和执行Ntdetect.com(这是一个使用系统bios进行查询计算机基本设备和设置信息的16位实模式程序)。然后,Ntldr开始清除屏幕并显示:“Starting Windows……”进度栏。这个进度栏保持空白,直到Ntldr开始加载引导驱动程序(假如有100个引导驱动程序,则每加载一个文件,进度条增加1%)。在进度条的下面是信息:“For troubleshooting and advanced startup options for windows 2000 , press F8 .”如果此时按下F8键,会出现高级启动菜单,包括:已知的最近正确模式(last known good),安全模式(safe mode),调试模式(debug mode)等等等等。
此后,Ntldr加载合适的内核和HAL映像文件(缺省为Ntoskrnl.exe和HAL.dll),读入SYSTEM注册表hive文件(hive文件是一种包含注册表子树的文件)以确定该加载哪些引导驱动程序,加载引导驱动程序,为Ntoskrnl.exe的执行准备CPU寄存器。之后,Ntldr调用Ntoskrnl.exe并由它开始初始化执行程序子系统并引导系统-启动(system-start)设备驱动程序,在一系列的初始化工作完成后Ntoskrnl.exe为系统本机应用程序作准备并运行smss.exe。
smss的主要任务是:初始化注册表,创建系统环境变量,加载Win32子系统(Win32k.sys)的内核模式部分,启动子系统进程Crss,启动登陆进程winlogon。然后,winlogon开始执行其启动步骤,如创建初始的窗口和桌面对象等等。然后它创建服务控制管理器(SCM)进程(Winnt/System32/Services.exe),它加载所有的标记为自动启动(auto-start)的服务程序和设备驱动程序和本机安全验证子系统(Lsass)进程(Winnt/system32/Lsass.exe)。当一切加载成功且用户在控制台成功登陆后,SCM则认为系统引导成功,注册表中 已知最近正确配置(HKLM/SYSTEM/select/LastKnownGood)由/CurrentControlSet替代。反之,如果用户在引导的时候选择高级菜单中的已知最近正确模式(LastKnownGood)或者加载时驱动程序返回一个严重的或者关键的错误,系统会以LastKnownGood的值作为CurrentControlSet 的值。
之后,我们便看到了熟悉的桌面。至此,windows 2000的引导过程结束。
限于篇幅,本文只简单的讲述一下windows 2000操作系统引导的大体过程,一些细节方面的东东请看我整理的其他windows 2000操作系统方面的文章。


不同WINDOWS平台下磁盘逻辑扇区的直接读写
一、概述
    在DOS操作系统下,通过BIOS的INT13、DOS的INT25(绝对读)、INT26(绝对写)等功能调用实现对磁盘逻辑扇区或物理扇区的读写是很方便的,C语言中还有对应上述功能调用的函数:biosdisk、absread和abswrite等。但在WINDOWS操作系统下编写WIN32应用程序时却再也不能直接使用上述的中断调用或函数了。那么,在WINDOWS操作系统下能不能实现磁盘扇区的直接读写呢?如何实现磁盘扇区的读写呢?为了解决这些问题,笔者查阅了一些相关资料后发现,WINDOWS操作系统也提供了读写磁盘扇区的方法,只是在不同的版本中有着不同的方式和使用限制。最后,笔者编写了一个磁盘扇区直接读写类,不敢独专,特提供出来,希望能对大家有所帮助。
    注:这里INT13表示INT 13H,其它类同。
二、一个读取软盘扇区的例子
    WINDOWS操作系统对所有的存储设备实行了统一管理,而且为了安全起见,操作系统还不允许在WIN32应用程序(工作在Ring3级)中直接调用中断功能,如INT13、INT21、INT25、INT26等。但它同时也提供了一些服务来弥补这种缺憾,在WIN95/98中,VWIN32服务就是其中一种。VWIN32服务是通过一个VXD来实现的,它提供了设备IO功能,通过它,使用API函数DeviceIoControl便可以实现WIN32应用程序和磁盘设备驱动程序间的通信,从而实现对磁盘的存取。VWIN32提供的服务是一系列的控制命令字,它们实现诸如DOS操作系统下的INT13、INT25、INT26和INT21等功能调用。下面是它定义的一些控制命令字:
   VWIN32_DIOC_DOS_IOCTL     (1)  实现INT21 功能
   VWIN32_DIOC_DOS_INT25     (2)  实现INT25 功能
   VWIN32_DIOC_DOS_INT26     (3)  实现INT26 功能  
   VWIN32_DIOC_DOS_INT13     (4)  实现INT13 功能
   VWIN32_DIOC_DOS_DRIVEINFO (6)  实现INT21 730x 功能  

如果要对磁盘进行读写,只要使用DeviceIoControl执行相应命令即可,下面的例子用来读取软盘的一个扇区(使用INT13):
    第一步:打开VWIN32服务,HANDLE hDev=CreateFile("
.//VWIN32",0,0,0,0,FILE_FLAG_DELETE_ON_CLOSE,NULL);
    第二步:填充中断所用到的相关寄存器。这里将寄存器放在一个结构中,结构定义如下(有关INT13使用的寄存器情况,请参阅相关资料):
      typedef struct INT13Regs{
  PVOID buffer;   // ebx 寄存器

        BYTE Drive;    // 磁盘号 dl
 BYTE Head;      //磁头号 dh
 WORD EDX_High;  // edx 寄存器 

 BYTE Sector;    //起始扇区 cl
 BYTE Track;     //磁道号   ch
 WORD ECX_High;  //ecx 寄存器

 BYTE Number;    //要读写的扇取数 al
 BYTE CMD;       //命令:2--读,3--写,5--格式化 ah
 WORD EAX_High;  //eax 寄存器

 DWORD EDI;       // edi 寄存器
        DWORD ESI;       // esi
        DWORD EFLAG;     // flags
      }INT13_REGISTERS;

      unsigned char Buffer[512];//定义缓冲区,放置读取扇区数据
      INT13_REGISTERS reg={0};//定义寄存器结构变量
 
      reg.buffer =(void *)Buffer;
      reg.Drive =0;//0-软盘A  1-软盘B 0x80-硬盘c
      reg.Head =0;
      reg.Track=0;
      reg.Sector=1;
      reg.Number=1;
      reg.CMD=2;  //读取
    第三步:调用设备IO API函数DeviceIoControl执行4号命令(即VWIN32_DIOC_DOS_INT13), BOOL b_ret=DeviceIoControl(hDev,4,&reg,sizeof(INT13_REGISTERS),&reg,sizeof(INT13_REGISTERS),&lpRet,0);
如果其返回值不等于零,调用成功,进一步处理....否则调用失败。
    第四步:关闭服务,CloseHandle(hDev);

三、限制或局限
    上面是使用INT13读取软盘扇区的完整步骤,在WIN95/98下它是可以工作的。那么,是否将上面的寄存器结构中的Drive置为0x80就可以读取逻辑硬盘C盘的扇区了呢?回答是否定的。INT13用来存取硬盘的功能在WINDOWS中被忽略了。另外,INT25、INT26虽然可以存取硬盘,但是它们不能工作在FAT32格式的硬盘上。下面的列表将详细列举与磁盘操作相关的中断调用的限制情况(不特殊说明,指的是在WIN95/98操作系统下):

     中断功能             限制及使用情况

      INT13             不可以读写硬盘,仅支持软盘
    INT25/INT26         不可以读/写FAT32硬盘,支持FAT12、FAT16
   INT21(440DH-41H/61H) 不可用(文档资料中说支持FAT12、FAT16、FAT32,实际上没有实现)
   INT21(7305H)         可以读写软盘、硬盘,支持FAT12、FAT16、FAT32,但要求WIN95OSR2及以后版本

    值得一提的是上表中的INT21--7305H功能是专门提供用来支持FAT32的,并且用来替换INT25/INT26,对应的控制命令字是6(即VWIN32_DIOC_DOS_DRIVEINFO),它和INT13、INT25、INT26等中断功能的一个显著区别是:它不使用寄存器来传递参数(INT21--440DH-41H/61H类同),而是使用一个称为DISKIO的结构,寄存器EBX用来保存指向该结构的地址。DISKIO的定义如下:
     typedef struct _DISKIO {
      DWORD  dwStartSector;   // 要读写的起始扇区号
      WORD   wSectors;        // 要读写的扇区数
      DWORD  dwBuffer;        // 用来保存读/写数据的缓冲区
     }DISKIO, * PDISKIO;

另外,在使用该功能时还需要特别设置一些寄存器,如ECX必须为-1,用ESI来表示读写。下面的例子是使用该功能来实现上面的例子功能,即读软盘A的一个扇区。首先定义一个新的寄存器结构供本例使用:
     typedef struct _DIOC_REGISTERS{
       DWORD EBX;
       DWORD EDX;
       DWORD ECX;
       DWORD EAX;
       DWORD EDI;
       DWORD ESI;
       DWORD Flags;
      }DIOC_REGISTERS; 

其实该结构和上面的INT13_REGISTERS是一样的,只不过INT13_REGISTERS将寄存器细分开了,可读性更强些。本例从步骤上说和上面的例子相同,只有寄存器设置一步在内容上有差异。
    第一步:打开VWIN32服务。
    第二步:设置寄存器。
     DIOC_REGISTERS reg = {0};
     DISKIO         dio;
     unsigned char Buffer[512];
     //设置参数结构
     dio.dwStartSector = 0;//注意:和上例不同,不是1,从0开始编号
     dio.wSectors      = 1;
     dio.dwBuffer      = (DWORD)Buffer;
      //设置寄存器
     reg.EAX = 0x7305;    //功能上类似于INT25,绝对读
     reg.EBX = (DWORD)&dio;//参数结构的地址
     reg.ECX = -1;//必须是-1     
     reg.EDX = 1;  //注意:和上例不同,驱动器编号变了,0--缺省 1--A、2--B、3--C 
     reg.ESI = 0;  //ESI的bit0表示读写,0--读、1--写

    在写状态时SI的bit1--bit12,bit15必须是0,bit13、bit14、bit15共同来表示所写数据的类型,具体见下表:
           15 14 13 类型描述
           0  0  0  其它或不知道. 
           0  0  1  FAT数据 
           0  1  0  目录数据 
           0  1  1  一般数据 
           1  x  x  保留。bit15必须是0 

     第三步:调用API。BOOL b_ret=DeviceIoControl(hDev,6,&reg, sizeof(DIOC_REGISTERS),&reg,sizeof(DIOC_REGISTERS),&cb,0);
     第四步:关闭服务。

可以发现,两种方法读到的数据完全一致。

四、WIN2000中的磁盘扇区读写
    在WINNT和WIN2000中磁盘被看做一种标准设备,可以使用CreateFile象打开文件一样打开并存取。CreateFile支持两种方式的磁盘设备--逻辑磁盘(格式为"
//./C:")和物理磁盘(格式为"//./PHYSICALDRIVEx",其中x为数字),例如打开A:盘进行读取操作,只要这样:
     HANDLE hDev=CreateFile("
.//A:",GENERIC_READ,FILE_SHARE_WRITE,0,OPEN_EXISTING,0,0);
如果得到的句柄有效,就可以使用ReadFile来读取了,
     ReadFile(hDev,Buffer,512,&dwRet,0);
读取结束要关闭该句柄,
     CloseHandle(hDev);
这比WIN95/98下的磁盘扇区读取方便多了。
    另外,上面的例子是操作逻辑磁盘的,它包括软驱、硬盘分区等;物理磁盘指的是实际的硬盘,它不关心该硬盘被分成几个区,硬盘的编号是从0开始的,"
//./PHYSICALDRIVE0"表示第一块硬盘,其它依此类推。大家可能马上会想起,利用这种机制可以对硬盘的分区表进行存取了。确实如此,此时便可以对硬盘的主引导扇区(独立存在的一个扇区,包含分区表信息,不同于磁盘分区的BOOT区)进行操作了。
     unsigned char Buffer[512]={0};
     HANDLE hDev=CreateFile("
.//PHYSICALDRIVE0",GENERIC_WRITE,FILE_SHARE_WRITE,0,OPEN_EXISTING,0,0);
     WriteFile(hDev,Buffer,512,&dwRet,0);
     CloseHandle(hDev);

危险!!!千万别这么做!!!

五、一个自适应的磁盘读写类
   由上面的例子可以看出,不同的操作系统下对磁盘扇区的读写有不同的方式,为了能够在各类操作系统下能够使用统一的方法读写磁盘扇区,特设计了一个通用类。该类的设计思想如下:首先编写各类操作系统下的磁盘扇区存取函数,然后通过GetVersionEx来判断操作系统,进而选取对应的函数来实现磁盘扇区的读写。由上面的分析可知,WINDOWS操作系统对INT13的支持是最差的,所以在这里只使用INT25、INT26、INT21--7305等中断调用来实现。类的定义如下:
class CDiskInfo{
public:
 CDiskInfo();
 ~CDiskInfo();
private:
 HANDLE hDev;
        DWORD dwCurrentPlatform;
        void GetPlatform();  //取得操作系统,并存入变量dwCurrentPlatform
   
 BOOL Win2000_AccessSectors(WORD CMD,BYTE bDrive,DWORD dwStartSector,WORD wSectors,LPBYTE lpSectBuff);//用于WIN2000、WINNT等操作系统,
 BOOL Int25_ReadSectors(BYTE bDrive,DWORD dwStartSector,WORD wSectors,LPBYTE lpSectBuff);
        BOOL Int26_WriteSectors(BYTE bDrive,DWORD dwStartSector,WORD wSectors,LPBYTE lpSectBuff);//用于WIN95以前的操作系统
        BOOL Int21_AccessSectors(WORD CMD,BYTE bDrive,DWORD dwStartSector,WORD wSectors,LPBYTE lpSectBuff);//7305功能实现,用于WIN95OSR2、WIN98等操作系统

public:
  //对外统一提供Read和Write操作,类内部根据平台选用适合的函数调用
 BOOL ReadSectors(BYTE bDrive,DWORD dwStartSector,WORD wSectors,LPBYTE lpSectBuff);
 BOOL WriteSectors(BYTE bDrive,DWORD dwStartSector,WORD wSectors,LPBYTE lpSectBuff);
};

该类对外提供了两个接口,即ReadSectors和WriteSectors,其参数是一样的,分别是要读写的磁盘编号bDrive,要存取磁盘的开始扇区号dwStartSector,要读取的扇区数wSectors和读写扇区数据的缓冲区lpSectBuff。这里磁盘编号是从1开始的,即1代表A:,2代表B:,3代表C:,依此类推。扇区的编号从0开始。使用时也很简单,只要作如下声明即可:
        BYTE Buffer[1024];      
  CDiskInfo A;
        BOOL bRet=A.ReadSectors(1,0,2,Buffer);
    详细情况见附带的类文件及测试程序。
六、补充说明
    严格来说,在对磁盘进行读写时,应该遵循以下顺序:打开设备(WIN95/98下为VWIN32服务,WIN2000下为磁盘设备)、锁卷、验证卷的有效性、读/写、开锁卷、关闭设备。这里为了描述上的简洁,忽略了锁卷/开锁卷及验证有效性等操作。有兴趣的朋友可以自行添加。
    另外,该类仅实现了逻辑驱动器的读写,要想实现诸如对物理硬盘的主引导扇区的读写,还需要其它技术,如thunk技术,即编写两个动态库,一个是WIN32动态库,一个是WIN16动态库(thunk技术只可以用动态库实现),其中WIN16动态库转到DPMI模式,调用INT13(或者扩展INT13)来实现物理磁盘扇区的读写。有关thunk技术请参阅相关文档资料。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值