为了搞明白FAT32文件系统中的操作系统是如何启动的,花了两天时间分析了一下Win10安装镜像的启动过程。
UltraISO可以制作Win10的安装U盘,制作完毕后U盘就被格式化成了FAT32文件系统。然后可以用WinHex打开U盘,将扇区导出到文件,并用ndisasm.exe反汇编,便可以得到指令和地址的对应关系。通过逆向分析,正好可以用来学习FAT32的启动过程,这比只看书或者用Bochs模拟真实多了。
计算机上电自检后,将硬盘中的Master Boot Reocrd(第0
扇区)加载到0000:0x7C00
处,然后就从这里开始执行。
MBR首先将自己移动到了0000:0x0600
处,然后扫描主分区表,将可启动分区的Dos Boot Record(第256
扇区)加载到了0000:0x7C00
处。在安装了int 13h
的hook程序(将CHS读取转换为LBA读取)后,最终将控制权交给DBR。
DBR是标准的FAT32启动扇区,但是受只有一个扇区的限制,DBR只是将U盘上的第268
扇区读取到了0000:8000
处并跳转到该处运行,此外还提供了显示字符和读取扇区的例程。
第268
扇区在FAT32文件系统根目录中查找BOOTMGR
,并将其加载到2000:0000
处。
BOOTMGR
可以在U盘中看见,应该是UEFI的引导程序,足足有400KB
,逆向分析太难了,也没什么意义。
;ndisasm.exe -k 0x1d,0x17 -k 0x187,0x79 FAT32MBR
00000000 FA cli
00000001 31C0 xor ax,ax
00000003 8ED8 mov ds,ax
00000005 8EC0 mov es,ax
00000007 8ED0 mov ss,ax
00000009 BC007C mov sp,0x7c00
0000000C FB sti
0000000D FC cld
;将代码移动到0x0600处,并跳转到相对地址0xdc
0000000E 89E6 mov si,sp
00000010 BF0006 mov di,0x600
00000013 B90001 mov cx,0x100
00000016 F3A5 rep movsw
00000018 EADC060000 jmp 0x0:0x6dc
;int 13h的hook数据
;struct DiskAddressPacket
;{
; BYTE PacketSize = 0x10;
; BYTE Reserved = 0x00;
; WORD BlockCount = 0x0001;
; DWORD BufferAddr = 0000:7c00;
; QWORD BlockNo = 0x00000000_00000000
;}
;16字节int 13h使用的Disk Address Packet
;1字节驱动器号 = 0x80
;2字节每磁道扇区数 = 0x3f
;2字节磁头数 = 0xff
;2字节柱面数 = 0x03d3
0000001D skipping 0x17 bytes
;int 13h的hook代码
00000034 1E push ds
00000035 0E push cs
00000036 1F pop ds
00000037 3A161000 cmp dl,[0x10]
0000003B 7406 jz 0x43 ;如果int 13h操作的驱动器是启动U盘,则需要hook
0000003D 1F pop ds ;恢复ds
0000003E EA36E700F0 jmp 0xf000:0xe736 ;调用原始int 13h
;如果ax为0x54fb则直接返回,不知道这是干嘛用的
00000043 3DFB54 cmp ax,0x54fb
00000046 7505 jnz 0x4d
00000048 8CD8 mov ax,ds
0000004A FB sti
0000004B EB1D jmp short 0x6a
;08号功能
0000004D 80FC08 cmp ah,0x8
00000050 751B jnz 0x6d
00000052 E88100 call 0xd6 ;调用原始int 13h
00000055 8A361300 mov dh,[0x13]
00000059 FECE dec dh ;dh = ds:[0x13] - 1 = 0xfe = 254
0000005B 8B0E1500 mov cx,[0x15]
0000005F 86CD xchg cl,ch ;ch = 柱面数低8位,
00000061 C0E106 shl cl,byte 0x6 ;cl = 柱面数的高2位
00000064 0A0E1100 or cl,[0x11] ;组合cl
00000068 31C0 xor ax,ax ;设置ah = 0
0000006A F8 clc ;复位carry位
0000006B EB65 jmp short 0xd2 ;恢复ds,返回
;非0x2~0x4号功能则调用原始int 13h
0000006D 80FC02 cmp ah,0x2
00000070 72CB jc 0x3d
00000072 80FC04 cmp ah,0x4
00000075 77C6 ja 0x3d
00000077 60 pusha
00000078 80CC40 or ah,0x40 ;将0x2~0x4号功能修改为0x42~0x44号功能
0000007B 50 push ax
0000007C BE0000 mov si,0x0 ;si指向ds:0000处的DiskAddressPacket
0000007F C7041000 mov word [si],0x10 ;si.PacketSize = 0x10
00000083 30E4 xor ah,ah
00000085 894402 mov [si+0x2],ax ;si.BlockCount = 0x00al,int 13h入口数据
00000088 895C04 mov [si+0x4],bx ;si.BufferAddr = es:bx,int 13h入口数据
0000008B 8C4406 mov [si+0x6],es
0000008E 6631C0 xor eax,eax
;si.BlockNo = 0x00000000_XXXXXXXX,接下来计算X部分
00000091 6689440C mov [si+0xc],eax ;ax = 磁头编号 * 扇区数
00000095 88F0 mov al,dh
00000097 F6261100 mul byte [0x11]
0000009B 88CF mov bh,cl ;bh = cl = 柱面号高2位和扇区数全6位
0000009D 88EB mov bl,ch ;bl = ch = 柱面号低8位
0000009F C0EF06 shr bh,byte 0x6 ;在bx中保存柱面号全10位
000000A2 81E13F00 and cx,0x3f ;只保留扇区数全6位
;di = ax = 在目标柱面上的扇区偏移,扇区数从1~63
000000A6 01C8 add ax,cx
000000A8 48 dec ax
000000A9 89C7 mov di,ax
;ax = 磁头数 * 扇区数 * 柱面编号
000000AB A11300 mov ax,[0x13]
000000AE F7261100 mul word [0x11]
000000B2 F7E3 mul bx
;dx:ax中位CHS转换后的LBA
000000B4 01F8 add ax,di
000000B6 81D20000 adc dx,0x0
000000BA 894408 mov [si+0x8],ax
000000BD 89540A mov [si+0xa],dx
;恢复0x0000007B处保存的ax,ah为新的功能号,al为读取扇区数目
000000C0 58 pop ax
000000C1 30C0 xor al,al
000000C3 8A161000 mov dl,[0x10]
000000C7 E80C00 call 0xd6 ;调用原始int 13h
;恢复0x00000077处的pusha,但是保持ah的值
000000CA 88260300 mov [0x3],ah
000000CE 61 popa
000000CF A10200 mov ax,[0x2]
000000D2 1F pop ds
000000D3 CA0200 retf 0x2 ;hook代码的出口,弹出EFLAGS
;调用0x0000003F处的程序,即原始int 13h的入口地址
000000D6 9C pushf ;int 13h是中断返回,会弹出EFLAGS
000000D7 FF1E2200 call far [0x22]
000000DB C3 ret
;在MBR执行之前,dl中会保存驱动器号,这好像是一个默认的约定
;BIOS也是用int 13h加载的MBR,或许从那会就保留下来了
;if(dl > 0x8f);
;else [0x062d] = dl
000000DC 80FA8F cmp dl,0x8f
000000DF 7F04 jg 0xe5
000000E1 88162D06 mov [0x62d],dl
;显示'Start boot from USB device...' 0x0d 0x0a
000000E5 BE8707 mov si,0x787
000000E8 E88D00 call 0x178
;扫描主分区表,查找启动分区
;si = 0x7be, ax = 0, cx = 4
;do{
; if([si] == 0x80){
; ax++;
; bp = si;
; }
; si += 0x10;
; cx--;
;}while(cx != 0);
;if(--ax != 0){
; int 18h;
;}
000000EB BEBE07 mov si,0x7be ;主分区表在偏移0x1be处
000000EE 31C0 xor ax,ax
000000F0 B90400 mov cx,0x4 ;主分区表个数
000000F3 F60480 test byte [si],0x80
000000F6 7403 jz 0xfb
000000F8 40 inc ax
000000F9 89F5 mov bp,si
000000FB 81C61000 add si,0x10
000000FF E2F2 loop 0xf3
00000101 48 dec ax
00000102 7402 jz 0x106
00000104 CD18 int 0x18
;读取可启动分区的第1扇区,即FAT32的DBR
;di = 5;
;while(1){
; si = &DiskAddressPacket;
; si.BlockCount = 1;
; si.BlockNo = bp.LBAStart;
; ax = 0x42;
; dl = [0x062d];
; int 13h;
; if(carry){
; if(di-- == 0) goto 0x0770;
; ah = 0;
; dl = [0x062d];
; int 13h;
; }
; else goto 0x0731
;}
00000106 BF0500 mov di,0x5 ;重试次数
00000109 BE1D06 mov si,0x61d ;DiskAddressPacket地址
0000010C C744020100 mov word [si+0x2],0x1
00000111 668B4608 mov eax,[bp+0x8]
00000115 66894408 mov [si+0x8],eax
00000119 B80042 mov ax,0x4200
0000011C 8A162D06 mov dl,[0x62d]
00000120 CD13 int 0x13
00000122 730D jnc 0x131
00000124 4F dec di
00000125 7449 jz 0x170
00000127 30E4 xor ah,ah
00000129 8A162D06 mov dl,[0x62d]
0000012D CD13 int 0x13
0000012F EBD8 jmp short 0x109
;校验0x55aa
00000131 A1FE7D mov ax,[0x7dfe]
00000134 3D55AA cmp ax,0xaa55
00000137 7537 jnz 0x170
;安装int 13h的hook代码
00000139 FA cli
0000013A 66A14C00 mov eax,[0x4c]
0000013E 66A33F06 mov [0x63f],eax ;将int 13h入口存储在0x0000003E处的jmp指令处
;0x0413属于BIOS Data Area,Bochs模拟值为0x027F(639)
;表示0xA0000以下1kb内存的数量(从0开始计算)
;也是Extended BIOS Data Area的起始地址
;EBDA在0x80000~0xA0000,最长128kb
;0x040E保存EBDA的段基址,Bochs模拟值为0x9FC0
;639kb = 0x9FC00
00000142 BE1304 mov si,0x413
00000145 8B04 mov ax,[si]
00000147 48 dec ax
00000148 8904 mov [si],ax
;左移10位转换为字节,右移4位转换为段地址,最终得到0x9f80
;0x60字的hook代码和数据0000:061d ==> 0x9f80:0000
0000014A C1E006 shl ax,byte 0x6
0000014D 8EC0 mov es,ax
0000014F 31FF xor di,di
00000151 BE1D06 mov si,0x61d
00000154 B96000 mov cx,0x60
00000157 FC cld
00000158 F3A5 rep movsw
;修改int 13h的入口地址,偏移17为0x00000034处的push cs指令
0000015A C7064C001700 mov word [0x4c],0x17
00000160 A34E00 mov [0x4e],ax
00000163 FB sti
;将dl设置为驱动器号,跳转到DBR
00000164 8A162D06 mov dl,[0x62d]
00000168 89EE mov si,bp
0000016A FA cli
0000016B EA007C0000 jmp 0x0:0x7c00
;显示Boot failed
00000170 BEAA07 mov si,0x7aa
00000173 E80200 call 0x178
00000176 EBFE jmp short 0x176
;显示以空字符结尾的字符串
;一次显示一个字符,si中是字符串地址
;while((ax = [si++]) != 0){
; ah = 0x0e;
; bx = 0x07;
; int 10h;
;}
;return;
00000178 AC lodsb
00000179 20C0 and al,al
0000017B 7409 jz 0x186
0000017D B40E mov ah,0xe
0000017F BB0700 mov bx,0x7
00000182 CD10 int 0x10
00000184 EBF2 jmp short 0x178
00000186 C3 ret
00000187 skipping 0x79 bytes
;ndisasm.exe -k 3,0x57 -k 0x169,0x97 FAT32DBR
;DBR引导扇区
00000000 EB58 jmp short 0x5a
00000002 90 nop
00000003 skipping 0x57 bytes
0000005A 33C9 xor cx,cx
0000005C 8ED1 mov ss,cx
0000005E BCF47B mov sp,0x7bf4 ;栈后12字节的空间留作它用
00000061 8EC1 mov es,cx
00000063 8ED9 mov ds,cx
00000065 BD007C mov bp,0x7c00
;原始的nop指令清0
00000068 884E02 mov [bp+0x2],cl
;dl设置为DBR中保存的驱动器号0x80
;调用0x41功能检测是否支持int 13h的扩展
0000006B 8A5640 mov dl,[bp+0x40]
0000006E B441 mov ah,0x41
00000070 BBAA55 mov bx,0x55aa
00000073 CD13 int 0x13
;检测carry、bx和cl
00000075 7210 jc 0x87
00000077 81FB55AA cmp bx,0xaa55
0000007B 750A jnz 0x87
0000007D F6C101 test cl,0x1
00000080 7405 jz 0x87
;在nop指令处记录是否支持int 13h扩展
00000082 FE4602 inc byte [bp+0x2]
00000085 EB2D jmp short 0xb4
;不支持,调用0x08功能查询驱动器信息
00000087 8A5640 mov dl,[bp+0x40]
0000008A B408 mov ah,0x8
0000008C CD13 int 0x13
0000008E 7305 jnc 0x95
;失败则按照柱面编号最大1024、磁头编号最大256、扇区编号最大63
00000090 B9FFFF mov cx,0xffff
00000093 8AF1 mov dh,cl
;计算总扇区数目,保存在[bp - 0x8] = [0x7bf8]
00000095 660FB6C6 movzx eax,dh
00000099 40 inc ax
0000009A 660FB6D1 movzx edx,cl
0000009E 80E23F and dl,0x3f
000000A1 F7E2 mul dx ;eax = 磁头数 * 扇区数
000000A3 86CD xchg cl,ch
000000A5 C0ED06 shr ch,byte 0x6
000000A8 41 inc cx
000000A9 660FB7C9 movzx ecx,cx ;ecx = 柱面数
000000AD 66F7E1 mul ecx
000000B0 668946F8 mov [bp-0x8],eax ;eax = 总扇区数
;一些检查
000000B4 837E1600 cmp word [bp+0x16],byte +0x0 ;FAT12/16共用的字段
000000B8 7538 jnz 0xf2
000000BA 837E2A00 cmp word [bp+0x2a],byte +0x0 ;版本号大于0
000000BE 7732 ja 0xf2
;读取第268扇区
000000C0 668B461C mov eax,[bp+0x1c] ;隐藏扇区数目,DBR相对MBR的偏移
000000C4 6683C00C add eax,byte +0xc ;12为下一需要读取扇区的偏移
000000C8 BB0080 mov bx,0x8000 ;加载到0000:8000
000000CB B90100 mov cx,0x1
000000CE E82B00 call 0xfc ;读取1个扇区
000000D1 E92C03 jmp 0x400 ;跳转到0000:8000
;显示0x0d 0x0a 'Disk error'
000000D4 A0FA7D mov al,[0x7dfa]
;while((al = [si++] != 0x00)){
;if(al == 0xff) goto 0x7ced
; ah = 0xe;
; bx = 0x7;
; int 10h;
;}
;goto 0x7cf7
000000D7 B47D mov ah,0x7d
000000D9 8BF0 mov si,ax
000000DB AC lodsb
000000DC 84C0 test al,al
000000DE 7417 jz 0xf7
000000E0 3CFF cmp al,0xff
000000E2 7409 jz 0xed
000000E4 B40E mov ah,0xe
000000E6 BB0700 mov bx,0x7
000000E9 CD10 int 0x10
000000EB EBEE jmp short 0xdb
;显示0x0d 0x0a 'Press any key to restart' 0x0d 0x0a
000000ED A0FB7D mov al,[0x7dfb]
000000F0 EBE5 jmp short 0xd7
;显示0x0d 0x0a 'Remove disks or other media'
000000F2 A0F97D mov al,[0x7df9]
000000F5 EBE0 jmp short 0xd7
;任意键重启
000000F7 98 cbw
000000F8 CD16 int 0x16
000000FA CD19 int 0x19
;从eax扇区开始,读取cx个扇区至es:bx
000000FC 6660 pushad
000000FE 807E0200 cmp byte [bp+0x2],0x0
00000102 0F842000 jz near 0x126
;LBA读取方式,在栈上构造DAP
00000106 666A00 o32 push byte +0x0
00000109 6650 push eax
0000010B 06 push es
0000010C 53 push bx
0000010D 666810000100 push dword 0x10010
00000113 B442 mov ah,0x42
00000115 8A5640 mov dl,[bp+0x40]
00000118 8BF4 mov si,sp
0000011A CD13 int 0x13
0000011C 6658 pop eax
0000011E 6658 pop eax
00000120 6658 pop eax
00000122 6658 pop eax
00000124 EB33 jmp short 0x159
;非LBA(CHS)读取方式
00000126 663B46F8 cmp eax,[bp-0x8]
0000012A 7203 jc 0x12f
0000012C F9 stc ;大于总扇区数目则设置出错
0000012D EB2A jmp short 0x159
;合理的LBA
0000012F 6633D2 xor edx,edx
00000132 660FB74E18 movzx ecx,word [bp+0x18] ;每磁道扇区数
00000137 66F7F1 div ecx ;eax = 磁道号
0000013A FEC2 inc dl
0000013C 8ACA mov cl,dl ;cl = dl = 扇区号,从1开始
0000013E 668BD0 mov edx,eax
00000141 66C1EA10 shr edx,byte 0x10
00000145 F7761A div word [bp+0x1a] ;磁头数
00000148 86D6 xchg dl,dh ;dh = 磁头号
0000014A 8A5640 mov dl,[bp+0x40]
0000014D 8AE8 mov ch,al ;ch = 柱面号低8位
0000014F C0E406 shl ah,byte 0x6
00000152 0ACC or cl,ah ;cl组合柱面号和扇区号
00000154 B80102 mov ax,0x201
00000157 CD13 int 0x13
;检查是否读取成功
00000159 6661 popad
0000015B 0F8275FF jc near 0xd4
;继续读取下一扇区
0000015F 81C30002 add bx,0x200
00000163 6640 inc eax
00000165 49 dec cx
00000166 7594 jnz 0xfc
00000168 C3 ret
00000169 skipping 0x97 bytes
;ndisasm.exe -k 0x81,2 -k 0x147,0xb9 FAT32268
;[bp - 0x4] = [0x7bfc] = 第2簇的起始地址
00000000 660FB64610 movzx eax,byte [bp+0x10] ;FAT表数目
00000005 668B4E24 mov ecx,[bp+0x24] ;FAT表所占扇区数目
00000009 66F7E1 mul ecx
0000000C 6603461C add eax,[bp+0x1c] ;隐藏扇区
00000010 660FB7560E movzx edx,word [bp+0xe] ;保留扇区
00000015 6603C2 add eax,edx
00000018 668946FC mov [bp-0x4],eax ;第2簇的起始地址
;[0x7bf4] = 上次读取的FAT表扇区
0000001C 66C746F4FFFFFFFF mov dword [bp-0xc],0xffffffff
;0xfcf2的代码在DBR扇区偏移0x000000f2处,地址0x7cf2
00000024 668B462C mov eax,[bp+0x2c] ;根目录起始簇号
00000028 6683F802 cmp eax,byte +0x2
0000002C 0F82C2FC jc near 0xfcf2 ;根目录起始簇号 < 2
00000030 663DF8FFFF0F cmp eax,0xffffff8
00000036 0F83B8FC jnc near 0xfcf2 ;根目录起始簇号 >= 0x0ffffff8
;根据eax中的簇号读取扇区
0000003A 6650 push eax
0000003C 6683E802 sub eax,byte +0x2 ;转换为从0开始
00000040 660FB65E0D movzx ebx,byte [bp+0xd]
;si = 每簇扇区数
00000045 8BF3 mov si,bx
00000047 66F7E3 mul ebx
0000004A 660346FC add eax,[bp-0x4] ;簇的起始扇区
;0xfcfc的代码在DBR扇区偏移0x000000fc处,地址0x7cfc
;bx = 0x8200
0000004E BB0082 mov bx,0x8200
00000051 8BFB mov di,bx ;di = 目录项地址
00000053 B90100 mov cx,0x1
00000056 E8A3FC call 0xfcfc ;读取一个扇区
;扫描刚读取的扇区目录项,查找"BOOTMGR "
;未找到跳转到0x7b,找到跳转到0x83
00000059 382D cmp [di],ch ;第1字节全0表示最后目录项
0000005B 741E jz 0x7b
0000005D B10B mov cl,0xb ;11字节的短文件名目录项名称长度
0000005F 56 push si ;si中已经保存每簇扇区数
00000060 BE697D mov si,0x7d69 ;"BOOTMGR "
00000063 F3A6 repe cmpsb
00000065 5E pop si
00000066 741B jz 0x83 ;找到BOOTMGR
00000068 03F9 add di,cx
0000006A 83C715 add di,byte +0x15
;下一个目录项
0000006D 3BFB cmp di,bx
0000006F 72E8 jc 0x59
;下一个扇区
00000071 4E dec si
00000072 75DA jnz 0x4e
;计算下一簇,再进行查找
00000074 6658 pop eax
00000076 E86500 call 0xde
00000079 72BF jc 0x3a
;在根目录中没有找到"BOOTMGR "
0000007B 83C404 add sp,byte +0x4 ;0000003A栈平衡
0000007E E971FC jmp 0xfcf2
00000081 skipping 0x2 bytes ;加载BOOTMGR时的es
;找到BOOTMGR
00000083 83C404 add sp,byte +0x4 ;0000003A栈平衡
00000086 8B7509 mov si,[di+0x9] ;BOOTMGR起始簇号高2字节
00000089 8B7D0F mov di,[di+0xf] ;BOOTMGR起始簇号低2字节
0000008C 8BC6 mov ax,si
0000008E 66C1E010 shl eax,byte 0x10
00000092 8BC7 mov ax,di ;eax = BOOTMGR起始簇号
00000094 6683F802 cmp eax,byte +0x2
00000098 0F8256FC jc near 0xfcf2 ;BOOTMGR起始簇号 < 2
0000009C 663DF8FFFF0F cmp eax,0xffffff8
000000A2 0F834CFC jnc near 0xfcf2 ;BOOTMGR起始簇号 >= 0x0ffffff8
;根据eax中的簇号加载BOOTMGR
000000A6 6650 push eax
000000A8 6683E802 sub eax,byte +0x2
000000AC 660FB64E0D movzx ecx,byte [bp+0xd]
000000B1 66F7E1 mul ecx
000000B4 660346FC add eax,[bp-0x4] ;簇的起始扇区
000000B8 BB0000 mov bx,0x0
000000BB 06 push es
000000BC 8E068180 mov es,[0x8081] ;es
000000C0 E839FC call 0xfcfc ;BOOTMGR会加载到0x20000
000000C3 07 pop es
000000C4 6658 pop eax
;更新下一簇的加载地址
000000C6 C1EB04 shr bx,byte 0x4
000000C9 011E8180 add [0x8081],bx ;更新段地址
000000CD E80E00 call 0xde ;计算BOOTMGR的下一簇
000000D0 0F830200 jnc near 0xd6 ;>= 0x0F FF FF F8则加载完毕
000000D4 72D0 jc 0xa6 ;< 加载下一簇
000000D6 8A5640 mov dl,[bp+0x40] ;在dl中保存驱动器号,MBR将控制权交给DBR时有相同的操作
000000D9 EA00000020 jmp 0x2000:0x0 ;跳转到BOOTMGR
;根据eax中的簇号,读取FAT表扇区得到下一簇号
000000DE 66C1E002 shl eax,byte 0x2 ;FAT项偏移
000000E2 E81100 call 0xf6 ;根据FAT项偏移读取相应扇区
000000E5 26668B01 mov eax,[es:bx+di] ;bx中保存FAT项在扇区中的偏移
000000E9 6625FFFFFF0F and eax,0xfffffff ;取低28位
000000EF 663DF8FFFF0F cmp eax,0xffffff8 ;和0x0F FF FF F8进行比较
000000F5 C3 ret
;根据eax中的FAT项偏移,读取对应的扇区
;如果新的FAT项所在扇区已经在内存中,则直接读取
;否者需要读取新的扇区
000000F6 BF007E mov di,0x7e00
000000F9 660FB74E0B movzx ecx,word [bp+0xb] ;每扇区字节数
000000FE 6633D2 xor edx,edx
00000101 66F7F1 div ecx ;eax = FAT项所在扇区, edx = 扇区中偏移
00000104 663B46F4 cmp eax,[bp-0xc]
00000108 743A jz 0x144 ;新的FAT项所在扇区已经在内存中
;读取新扇区
0000010A 668946F4 mov [bp-0xc],eax ;已经读取的FAT表扇区的LBA
0000010E 6603461C add eax,[bp+0x1c] ;隐藏扇区数
00000112 660FB74E0E movzx ecx,word [bp+0xe] ;ecx = 保留扇区数
00000117 6603C1 add eax,ecx ;第1个FAT表中该FAT项的LBA
;根据FAT表工作方式读取正确的FAT表
0000011A 660FB75E28 movzx ebx,word [bp+0x28] ;FAT表工作方式
0000011F 83E30F and bx,byte +0xf
00000122 7416 jz 0x13a ;互为镜像
;只有一个活动FAT表
00000124 3A5E10 cmp bl,[bp+0x10]
00000127 0F83C7FB jnc near 0xfcf2 ;活动FAT表编号 >= FAT表数目
0000012B 52 push dx
0000012C 668BC8 mov ecx,eax ;ecx = 第1个FAT表中该FAT项的LBA
0000012F 668B4624 mov eax,[bp+0x24] ;FAT表所占扇区大小
00000133 66F7E3 mul ebx ;跳过不活动的FAT表
00000136 6603C1 add eax,ecx
00000139 5A pop dx
;FAT表互为镜像
0000013A 52 push dx
0000013B 8BDF mov bx,di ;将FAT项所在扇区读取到0x7e00
0000013D B90100 mov cx,0x1
00000140 E8B9FB call 0xfcfc ;读取一个扇区
00000143 5A pop dx ;该FAT项在该扇区中的偏移
;直接读取
00000144 8BDA mov bx,dx
00000146 C3 ret
00000147 skipping 0xB9 bytes