;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 主引导记录(MBR)的反汇编分析;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MBR代码分析网上早就有了,但我找到的都是针对老版MBR的分析。这里所谓的老版MBR,指的是早期的MBR,那时大容量硬盘还没有出现,MBR使用传统的Int 13h接口访问硬盘。随着硬盘容量越来越大,传统的Int 13h已经无法完全访问硬盘上所有扇区(Int 13h接口理论上可访问的最大硬盘容量约为8G)。因此,扩展Int 13h规范出台以适应这种变化。MBR的代码也必须相应改动,必要时采用扩展Int 13h来加载引导扇区。我称之为新版MBR。本篇分析的就是新版MBR。MBR其实并不复杂,但要完全理解也需要一些底层的系统知识。code里的一些技巧值得借鉴。seapeak@88 转载时就保持完整;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; 设置栈SS:SP = 0:7C00 ;0000:7C00 xor ax, ax0000:7C02 mov ss, ax0000:7C04 mov sp, 7C00h0000:7C07 sti ; ; DS = ES = 0 ;0000:7C08 push ax0000:7C09 pop es0000:7C0A push ax0000:7C0B pop ds ; ; 将后面的代码复制到低端内存,为加载 ; 活动分区的引导扇区腾出空间,因为引导 ; 扇区也必须加载到0:7C00 ;0000:7C0C cld0000:7C0D mov si, 7C1Bh0000:7C10 mov di, 61Bh0000:7C13 push ax0000:7C14 push di0000:7C15 mov cx, 1E5h0000:7C18 repe movsb ; ; 跳到低端内存的代码继续执行 ;0000:7C1A retf ; ; 开始扫描分区表(Partition Table),寻找活动分区 ;0000:061B mov bp, 7BEh ; 600h+1BEh,分区表起始偏移为1BEh0000:061E mov cl, 4 ; 分区表中有4个分区表项 loc_620:0000:0620 cmp [bp+0], ch ; 是活动分区吗?(此时ch中的值为0)0000:0623 jl loc_62E ; 活动分区的标志是80h,如果解释成有符号数,则小于00000:0625 jnz loc_63A ; 引导标志的合法值只能是0和80h,其它的值则出错0000:0627 add bp, 10h ; 指向下一个表项(每一个表项的长度为10h字节)0000:062A loop loc_620 ; 依次扫描所有分区表项 ; ; 没有发现活动分区,无法启动OS,按照规范调用Int 18h ; 早期的BIOS的Int 18h中断服务程序就是启动ROM-Basic, ; 现在的BIOS一般是打印错误信息 ;0000:062C int 18h ; ; 找到活动分区后,还要检查剩余分区的启动标志是否为0 ; 不允许存在多个活动分区 ; loc_62E:0000:062E mov si, bp loc_630:0000:0630 add si, 10h ; 下一个分区表项0000:0633 dec cx0000:0634 jz loc_64F ; 所以剩余分区都扫描完0000:0636 cmp [si], ch ; 启动标志是否为0?0000:0638 jz loc_630 ; 是则合法,检查下一分区 ; ; 分区启动标志不合法,打印错误信息 ; "Invalid partition table" ; loc_63A:0000:063A mov al, byte_7B5 loc_63D:0000:063D mov ah, 70000:063F mov si, ax loc_641:0000:0641 lodsb0000:06420000:0642 loc_642:0000:0642 cmp al, 00000:0644 jz loc_642 ; 打印完错误信息后,进入死循环0000:0646 mov bx, 70000:0649 mov ah, 0Eh0000:064B int 10h ; 调用Int10h显示一个字符0000:064D jmp short loc_641 ; ; 开始加载活动分区的引导扇区 ; loc_64F: ; ; 将一个标志的初始值清0。这个标志表示是否尝试过备份的引导扇区 ; [bp+10h]的字节肯定是没用的空间 ;0000:064F mov [bp+10h], cl0000:0652 call sub_69B0000:0655 jnb loc_681 loc_657:0000:0657 inc byte ptr [bp+10h] ; 标志已经尝试过加载备份的引导扇区 ; ; 如果活动分区是FAT32分区,尝试加载备份的引导扇区 ;0000:065A cmp byte ptr [bp+4], 0Bh ;FAT320000:065E jz loc_66B0000:0660 cmp byte ptr [bp+4], 0Ch ;FAT32(需用扩展Int 13h访问)0000:0664 jz loc_66B ; ; 加载引导扇区失败,显示错误信息 ; "Error loading operating system" ;0000:0666 mov al, byte_7B60000:0669 jnz loc_63D loc_66B: ; ; FAT32的备份引导扇区号=引导扇区号+6 ;0000:066B add byte ptr [bp+2], 60000:066F add word ptr [bp+8], 60000:0673 adc word ptr [bp+0Ah], 00000:0677 call sub_69B0000:067A jnb loc_681 ; ; 连备份的引导扇区也坏了,就没辙了! ;0000:067C mov al, byte_7B60000:067F jmp short loc_63D ; ; 在把控制权交给引导扇区前,要先检查引导扇区的签名(signature) ; 防止把控制权交给已经损坏的引导扇区 ;0000:0681 loc_681:0000:0681 cmp word ptr ds:[7DFEh], 0AA55h ;签名就是扇区最后的2个字节0000:0687 jz loc_694 ; ; 引导扇区损坏,则尝试用备份引导扇区 ;0000:0689 cmp byte ptr [bp+10h], 0 ;已经是备份的引导扇区?0000:068D jz loc_657 ; ; 备份引导扇区也坏了,死翘翘 ;0000:068F mov al, byte_7B70000:0692 jmp short loc_63D ; ; 交权给引导扇区,让它去完成OS的引导 ;0000:0694 loc_694:0000:0694 mov di, sp ; di = sp = 7C00h0000:0696 push ds0000:0697 push di0000:0698 mov si, bp ; 把活动分区表项指针传给引导扇区0000:069A retf ; 跳到0:7C00 ; ; 读取引导扇区的子过程 ; sub_69B proc near0000:069B mov di, 5 ; 磁盘I/O错允许重试次数为5次 ; ; 取硬盘的磁道参数 ;0000:069E mov dl, [bp+0] ; 分区的启动标志其实就是硬盘号0000:06A1 mov ah, 80000:06A3 int 13h0000:06A5 jb loc_6CA ; 如果取参数失败,认为BIOS肯定不支持扩展Int 13h ; 只好用传统Int 13h ; ; 计算用传统Int 13h能访问的最大逻辑扇区号 ; 计算公式为:(最大磁头号+1)*每道扇区数*(最大磁道号+1) ; 注意这个计算次序是有讲究的,因为(最大磁头号+1)*每道扇区数 ; 的结果可以用16位寄存器就可以存放。如果先用磁道号来计算, ; 乘的结果就必须用两个寄存器来存放,导致第二步乘计算复杂化 ;0000:06A7 mov al, cl0000:06A9 and al, 3Fh0000:06AB cbw ; ax中为第道扇区数0000:06AC mov bl, dh0000:06AE mov bh, ah ; bh = 0,ah肯定为00000:06B0 inc bx0000:06B1 mul bx ; (最大磁头号+1)*每道扇区数0000:06B3 mov dx, cx0000:06B5 xchg dl, dh0000:06B7 mov cl, 60000:06B9 shr dh, cl ; dx中为(最大磁道号+1)0000:06BB inc dx0000:06BC mul dx ; (最大磁头号+1)*每道扇区数*(最大磁道号+1) ; ; 判断引导扇区是否可以用传统Int 13h来访问 ; 如果引导扇区的逻辑扇区号 >= 刚才算出的扇区号,必须用扩展Int13h来读取 ; 否则就用传统Int 13h来访问 ; ; 注意一下双字数的比较方法! ;0000:06BE cmp [bp+0Ah], dx0000:06C1 ja loc_6E60000:06C3 jb loc_6CA0000:06C5 cmp [bp+8], ax0000:06C8 jnb loc_6E60000:06CA ; ; 用传统Int 13h来读引导扇区 ; loc_6CA:0000:06CA mov ax, 201h0000:06CD mov bx, 7C00h0000:06D0 mov cx, [bp+2]0000:06D3 mov dx, [bp+0]0000:06D6 int 13h0000:06D8 jnb locret_72B ; ; 读取失败可以重试,试完规定的次数后还失败就没办法了 ;0000:06DA dec di0000:06DB jz locret_72B ; ; 重试前重置一下磁盘系统 ;0000:06DD xor ah, ah0000:06DF mov dl, [bp+0]0000:06E2 int 13h0000:06E4 jmp short loc_6CA ; ; 检查BIOS是否支持扩展Int 13h ;0000:06E6 loc_6E6:0000:06E6 mov dl, [bp+0]0000:06E9 pusha0000:06EA mov bx, 55AAh0000:06ED mov ah, 41h0000:06EF int 13h0000:06F1 jb loc_7290000:06F3 cmp bx, 0AA55h0000:06F7 jnz loc_7290000:06F9 test cl, 1 ;必须支持Fixed disk access这个功能子集0000:06FC jz loc_7290000:06FE popa ; ; 扩展Int 13h读扇区 ;0000:06FF loc_6FF:0000:06FF pusha ; ; 在栈中构造磁盘地址包(Disk Address Packet) ;0000:0700 push 00000:0702 push 00000:0704 push word ptr [bp+0Ah]0000:0707 push word ptr [bp+8] ; 要读扇区的LBA地址(4个word)0000:070A push 00000:070C push 7C00h ; 引导扇区读到0:7C00h0000:070F push 1 ; 读一个扇区0000:0711 push 10h ; 包长度16个字节0000:0713 mov ah, 42h0000:0715 mov si, sp0000:0717 int 13h0000:0719 popa ; 这里用了一个小技巧,一下子从栈中弹出8个word ; (地址包刚好是8个word)0000:071A popa ; 这才是真正的恢复保存的通用寄存器的值0000:071B jnb locret_72B0000:071D dec di ; 不成功有重试的机会 ; 注意这里也有个小技巧:dec指令不改变CF的状态0000:071E jz locret_72B ; 直到所有重试次数用完 ; ; 重试前重置磁盘系统 ;0000:0720 xor ah, ah0000:0722 mov dl, [bp+0]0000:0725 int 13h0000:0727 jmp short loc_6FF loc_729:0000:0729 popa0000:072A stc locret_72B:0000:072B retn sub_69B endp0000:072C db 'Invalid partition table',00000:0744 db 'Error loading operating system',00000:0763 db 'Missing operating system',00000:07B5 byte_7B5 db 2Ch0000:07B6 byte_7B6 db 44h0000:07B7 byte_7B7 db 63h0000:07FE dw 0AA55h ;MBR的signature--------------------------------------------------------------------------------