試着解讀 Win7 的 MBR

試着解讀 Win7 的 MBR(NASM 反彙編)

; int 19h 會將引導扇區(MBR)的內容讀到 0x7c00 位置,然後開始執行
; 0x7c00 位置的代碼就是這裏的代碼

; 段指針 ss,ds,es 都爲 0
; 棧頂 = 0x7c00
00000000  33C0              xor ax,ax
00000002  8ED0              mov ss,ax
00000004  BC007C            mov sp,0x7c00
00000007  8EC0              mov es,ax
00000009  8ED8              mov ds,ax

; ==============================

; 將引導扇區複製一份到 0x600 處,然後從副本中繼續執行
; 0x500-0x7BFF 是自由空間,可以隨意使用

; movsb 源地址   DS:SI = 0:0x7c00
; movsb 目標地址 DS:DI = 0:0x600
; movsb 長度     CX    = 0x200     // 即 512
0000000B  BE007C            mov si,0x7c00
0000000E  BF0006            mov di,0x600
00000011  B90002            mov cx,0x200
; 設置 SI、DI 爲自增模式(std 爲自減模式)
00000014  FC                cld
; 複製字節 DS:SI -> DS:DI,重複 CX 次
00000015  F3A4              rep movsb
; 跳轉到 0:0x61C 處繼續執行
00000017  50                push ax
00000018  681C06            push word 0x61c
; pop IP; pop CS
0000001B  CB                retf

; ==============================

; 0:0x61C 就是這裏

; 允許 CPU 響應可屏蔽中斷
0000001C  FB                sti

; ==============================

; 查找活動分區

; 只有 4 個分區表項,所以循環 4 次
0000001D  B90400            mov cx,0x4
; 從 0x600 開始的 0x1BE 位置,即後面的分區表位置
00000020  BDBE07            mov bp,0x7be

; 循環開始

; 分區表項的第一字節爲“活動分區標記”:
; 0x80-0xFF 表示活動分區(有符號數爲 -128 ~ -1)
; 0x00 表示非活動分區
; 其它值(0x00 - 0x7F)爲無效標記(有符號數爲 1 ~ 127)

; 判斷標記是否小於 0,即判斷標記是否在 0x80-0xFF 範圍內
; cmp 是有符號數比較
00000023  807E0000          cmp byte [bp+0x0],0x0
; 如果在 0x80-0xFF 範圍內,則跳轉到 0x34 開始讀取“分區引導記錄 PBR”的代碼(一個扇區)
00000027  7C0B              jl 0x34

; 不在 0x80-0xFF 範圍內,也不是 0,肯定在 0x01-0x7F 範圍內,則跳轉到 0x13B,打印錯誤信息
; short、near、far 在反彙編中體現不出差別,都會跳轉到目標所指的地址
00000029  0F850E01          jnz near 0x13b

; 如果是 0,則表示非活動分區,繼續分析下一個分區表項
; 每個分區表項的長度爲 16 字節,也就是 0x10
0000002D  83C510            add bp,byte +0x10

; 檢查下一個分區表項,直到 CX = 0
00000030  E2F1              loop 0x23

; ==============================

; 循環 4 次都沒有發現活動分區,無法啓動 OS,按照規範調用 Int 18h

; 早期的 BIOS 的 Int 18h 中斷服務程序就是啓動 ROM-Basic,
; 現在的 BIOS 一般是打印錯誤信息,然後 hlt(掛起 CPU)。

; ROM BASIC 是一個 ROM 解釋器,允許用戶運行和創建 BASIC 程序。
; 今天的電腦不再包括 BASIC ROM; 但是,使用 IBM 兼容計算機的用戶
; 仍可能會遇到與 ROM BASIC 相關的錯誤消息。
00000032  CD18              int 0x18

; ==============================

; 找到活動分區,嘗試讀取活動分區的第一個扇區

; 將啓動設備的“設備號”寫入 [bp+0x0](分區表項的第一個字節,活動分區標記的位置)
; dl 中的值是 int 19h 留下的“啓動設備號”
00000034  885600            mov [bp+0x0],dl

; 保存當前分區表項的地址(int 13h 可能會修改 bp)
00000037  55                push bp

; 分區表項之後的第二個字節(用來存儲嘗試次數)
; 如果讀取扇區失敗,會再次嘗試讀取,總共嘗試 5 次
00000038  C6461105          mov byte [bp+0x11],0x5
; 分區表項之後的第一個字節(用來標記是否支持 int 13h 擴展)
0000003C  C6461000          mov byte [bp+0x10],0x0

; ==============================

; 檢查是否支持 int 13h 擴展

; int 13h 的 41h 號功能:檢查是否支持 Int 13H 擴展
00000040  B441              mov ah,0x41
; int 13h 的參數:固定值 55AAh
; 另一個參數 DL:驅動器號
00000042  BBAA55            mov bx,0x55aa
; 檢查是否支持 int 13h 擴展
; 返回值:成功 CF = 0,失敗 CF = 1
;         成功 BX = 0xaa55,失敗 BX = 0x55aa
00000045  CD13              int 0x13
; 恢復當前分區表項的地址
00000047  5D                pop bp

; ==============================

; 如果進位標誌(即 CF)爲 1,則表示不支持 int 13h 的擴展功能,
; 則跳轉到 0x59,使用普通 int 13h 讀磁盤
00000048  720F              jc 0x59

; 如果返回值 BX 不是 AA55h,則表示不支持 int 13h 的擴展功能,
; 則跳轉到 0x59,使用普通 int 13h 讀磁盤
0000004A  81FB55AA          cmp bx,0xaa55
0000004E  7509              jnz 0x59

; 若支持 Int 13h 擴展,則返回值 CX 的第 1 位表示是否支持第一個子集,
; 第 2 位表示是否支持第二個子集。

; test 指令將兩個操作數進行邏輯與運算,並根據運算結果設置相關的標誌位。
; 這裏檢查 int 13h 是否支持第一個子集。
00000050  F7C10100          test cx,0x1
; 若不支持,則跳轉到 0x59,使用普通 int 13h 讀磁盤
00000054  7403              jz 0x59

; 將 [bp+0x10] 的值設置爲非 0,表示支持 int 13h 擴展
00000056  FE4610            inc byte [bp+0x10]

; ==============================

; 開始嘗試讀取扇區,最多嘗試 5 次

; pushad 指令壓入 32 位寄存器(保護現場)
; 入棧順序是:EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI。
00000059  6660              pushad

; 判斷是否支持 int 13h 擴展(非 0 支持,0 不支持)
0000005B  807E1000          cmp byte [bp+0x10],0x0
; 若不支持,則跳轉到 0x87,使用普通 int 13h 的 CHS 方式讀取數據
0000005F  7426              jz 0x87

; ==============================

; 下面開始使用 int 13h 的擴展功能讀取分區引導記錄(一個扇區)
; 擴展 Int 13h 通過 LBA(邏輯塊地址)讀取磁盤數據

; 在棧中填寫“磁盤地址數據包”(這裏的塊大小爲扇區大小,即 512 字節)
; struct DiskAddressPacket {
;   BYTE  PacketSize;  // 本結構體大小(值爲 16)
;   BYTE  Reserved;    // 保留爲 0
;   WORD  BlockCount;  // 要讀取多少個塊
;   DWORD BufferAddr;  // 讀取後保存到哪裏(段:偏移)
;   QWORD BlockNum;    // 從哪個塊開始讀取(塊號從 0 開始計數)
; }

; 從哪個塊開始讀取(高 32 位)
00000061  666800000000      push dword 0x0
; 從哪個塊開始讀取(低 32 位)
; [bp+0x8] 是活動分區的起始扇區位置(相對於磁盤開頭的偏移量,從 0 開始計數)
00000067  66FF7608          push dword [bp+0x8]
; 讀取後保存到哪裏(段地址)
0000006B  680000            push word 0x0
; 讀取後保存到哪裏(偏移地址)
0000006E  68007C            push word 0x7c00
; 讀多少個塊
00000071  680100            push word 0x1
; 結構體大小:16 字節(同時保留的字節爲 0)
00000074  681000            push word 0x10

; ==============================

; int 13h 的 42h 號功能:擴展讀
00000077  B442              mov ah,0x42
; int 13h 的參數 DL:驅動器號
00000079  8A5600            mov dl,[bp+0x0]
; int 13h 的參數 DS:SI = 0:SP 表示“磁盤地址數據包”的地址(之前在棧中填寫的數據)
0000007C  8BF4              mov si,sp
; 開始擴展讀
0000007E  CD13              int 0x13

; ==============================

; 將標誌寄存器的低 8 位寫入 AH 中(保護現場)
00000080  9F                lahf
; 擴展讀完畢,將 SP 指向下一個分區表項(add 指令會影響標誌寄存器)
00000081  83C410            add sp,byte +0x10
; 將 AH 的內容寫入標誌寄存器的低 8 位(恢復現場)
00000084  9E                sahf
; 跳過普通 int 13h 調用,跳轉到 0x9b,檢查是否讀取成功
00000085  EB14              jmp short 0x9b

; ==============================

; 下面開始使用普通 int 13h 讀取分區引導記錄(一個扇區)
; 普通 int 13h 通過 CHS(柱面、磁頭、扇區)讀取磁盤數據

; 通過 int 13h 的 02h 號功能讀取扇區
; AL = 01 表示要讀取的扇區數
00000087  B80102            mov ax,0x201
; ES:BX 表示讀取後保存到哪裏
0000008A  BB007C            mov bx,0x7c00
; DL 驅動器號(00H-7FH:表示可移動磁盤,80H-0FFH:表示固定磁盤)
0000008D  8A5600            mov dl,[bp+0x0]
; DH 磁頭
00000090  8A7601            mov dh,[bp+0x1]
; CL 扇區(其中低 6 位是扇區,高 2 位是柱面的高 2 位)
00000093  8A4E02            mov cl,[bp+0x2]
; CH 柱面(柱面的低 8 位,加上前面的高 2 位,共 10 位)
00000096  8A6E03            mov ch,[bp+0x3]
; 開始讀取
00000099  CD13              int 0x13

; ==============================

; 讀取完畢,恢復 32 位寄存器(恢復現場)
; 與入棧順序 EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI 相反
0000009B  6661              popad

; 無論是普通 int 13h 還是擴展 int 13h,成功則 CF = 0,失敗則 CF = 1
; 如果讀取成功,則跳轉到 0xbb,檢查讀取的扇區是否有啓動標記
0000009D  731C              jnc 0xbb

; 如果讀取失敗,則嘗試次數 -1
0000009F  FE4E11            dec byte [bp+0x11]
; 若嘗試次數未達到 5 次,則跳轉到 0xb0 處復位磁盤,再次嘗試讀取
000000A2  750C              jnz 0xb0

; ==============================

; 若 5 次嘗試都失敗,此時 [bp] 中存儲的是 int 19h 給出的啓動設備號。
; 如果啓動設備是 0x80 則跳轉到 0x136,打印錯誤信息。
000000A4  807E0080          cmp byte [bp+0x0],0x80
000000A8  0F848A00          jz near 0x136
; 如果啓動設備不是 0x80(而是 0x81-0xFF 之間的設備)
; 則嘗試從 0x80 讀取數據,而不是從 int 19h 給出的啓動設備中讀取數據。
000000AC  B280              mov dl,0x80
000000AE  EB84              jmp short 0x34

; ==============================

; 復位磁盤驅動器,再次嘗試讀取

; 保存 bp(保護現場),因爲 int 13h 可能會改寫 bp
000000B0  55                push bp

; int 13h 的第 0 號功能:復位磁盤系統
000000B1  32E4              xor ah,ah
; int 13h 參數:DL = 驅動器號
000000B3  8A5600            mov dl,[bp+0x0]
; 開始復位磁盤
000000B6  CD13              int 0x13

; 恢復 bp(恢復現場)
000000B8  5D                pop bp

; 再次嘗試讀取扇區
000000B9  EB9E              jmp short 0x59

; ==============================

; 讀取成功

; 檢查讀取的扇區是否含有啓動標記 55 AA
000000BB  813EFE7D55AA      cmp word [0x7dfe],0xaa55
; 如果沒有,則跳轉到 0x131,打印出錯信息
000000C1  756E              jnz 0x131

; 如果有,則保存啓動設備號([bp+0x0] 在 word 的低 8 位)
; push 指令一次至少壓入 2 個字節,不允許只壓入 1 個字節(也就是 2 字節對齊)
000000C3  FF7600            push word [bp+0x0]

; ==============================

; 打開 A20 地址線(打開後才能訪問 1M 以上的內存)

; 調用函數 0x156,等待鍵盤的“輸入緩衝區”爲空
; 只有當輸入緩衝區爲空時纔可以向其端口寫入命令
000000C6  E88D00            call 0x156
; 如果鍵盤的“輸入緩衝區”始終爲滿,則跳轉到 0xE2
000000C9  7517              jnz 0xe2

; 禁止中斷
000000CB  FA                cli

; 通知鍵盤,準備向鍵盤的輸出端口寫入數據
; 0xD1 命令碼:表示要寫數據到鍵盤控制器的 P2 端口
; P2 端口的 1 位用於打開 A20 線
000000CC  B0D1              mov al,0xd1
000000CE  E664              out 0x64,al
; 調用函數 0x156,等待鍵盤的輸入緩衝區爲空
; 緩衝區爲空,表示上面的命令被接受
000000D0  E88300            call 0x156

; 打開 A20 地址線
000000D3  B0DF              mov al,0xdf
000000D5  E660              out 0x60,al
; 調用函數 0x156,等待鍵盤的輸入緩衝區爲空
; 緩衝區爲空,表示上面的命令被接受
000000D7  E87C00            call 0x156

; 0xff 不知道什麼作用
000000DA  B0FF              mov al,0xff
000000DC  E664              out 0x64,al
; 調用函數 0x156,等待鍵盤的輸入緩衝區爲空
; 緩衝區爲空,表示上面的命令被接受
000000DE  E87500            call 0x156

; 允許中斷
000000E1  FB                sti

; ==============================

; int 1ah 的 bbh 號功能:
; 不知道做什麼用的
000000E2  B800BB            mov ax,0xbb00
000000E5  CD1A              int 0x1a

; 如果 EAX 不爲 0 則跳轉到 0x127,開始執行分區引導記錄
; 不知道做什麼用的
000000E7  6623C0            and eax,eax
000000EA  753B              jnz 0x127

; 如果 EBX 不爲 0x41504354 則跳轉到 0x127,開始執行分區引導記錄
; 不知道做什麼用的
000000EC  6681FB54435041    cmp ebx,0x41504354
000000F3  7532              jnz 0x127

; 如果 CX 不爲 0x102 則跳轉到 0x127,開始執行分區引導記錄
; 不知道做什麼用的
000000F5  81F90201          cmp cx,0x102
000000F9  722C              jc 0x127

; ==============================

; 填寫 32 位寄存器(EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI)
000000FB  666807BB0000      push dword 0xbb07
00000101  666800020000      push dword 0x200
00000107  666808000000      push dword 0x8
0000010D  6653              push ebx
0000010F  6653              push ebx
00000111  6655              push ebp
00000113  666800000000      push dword 0x0
00000119  6668007C0000      push dword 0x7c00
0000011F  6661              popad

; int 1ah 的 bbh 號功能:
; 不知道做什麼用的
00000121  680000            push word 0x0
00000124  07                pop es
00000125  CD1A              int 0x1a

; ==============================

; 恢復設備驅動器號到 DL
00000127  5A                pop dx
00000128  32F6              xor dh,dh
; 跳轉到分區引導記錄,繼續引導
0000012A  EA007C0000        jmp 0x0:0x7c00

; ==============================

; 沒有發現活動分區,無法啓動 OS,按照規範調用 Int 18h
; 早期的 BIOS 的 Int 18h 中斷服務程序就是啓動 ROM-Basic,
; 現在的 BIOS 一般是打印錯誤信息,然後 hlt。
; ROM BASIC 是一個 ROM 解釋器,允許用戶運行和創建 BASIC 程序。
; 今天的電腦不再包括 BASIC ROM; 但是,使用 IBM 兼容計算機的用戶
; 仍可能會遇到與 ROM BASIC 相關的錯誤消息。
0000012F  CD18              int 0x18

; ==============================

; 打印出錯信息(索引號 [0x7b7] = 9A)"Missing operating system"
00000131  A0B707            mov al,[0x7b7]
00000134  EB08              jmp short 0x13e

; 打印出錯信息(索引號 [0x7b6] = 7B)"Error loading operating system"
00000136  A0B607            mov al,[0x7b6]
00000139  EB03              jmp short 0x13e

; 驅動器號在 0x01-0x7F 範圍(索引號 [0x7b5] = 63)"Invalid partition table"
0000013B  A0B507            mov al,[0x7b5]

; ==============================

; 打印出錯信息

; 計算字符串起始地址:ax = 索引號
0000013E  32E4              xor ah,ah
; 計算字符串起始地址:ax = 0x700 + 索引號
00000140  050007            add ax,0x700

; DS:SI 指向字符串起始地址
00000143  8BF0              mov si,ax

; 把 DS:SI 指向的存儲單元讀入 AL,然後 SI 自動 +1
; al = [0:0x700]
00000145  AC                lodsb

; al = 要顯示的字符(判斷當前字符是否爲 0x00)
00000146  3C00              cmp al,0x0
; 0x00 表示字符串顯示完畢,停止 CPU
00000148  7409              jz 0x153

; bh = 0 表示頁碼,bl = 7 表示背景色
0000014A  BB0700            mov bx,0x7
; 功能號 0x0E 表示在 Teletype 模式下顯示字符
0000014D  B40E              mov ah,0xe
; 顯示一個字符
0000014F  CD10              int 0x10

; 繼續顯示下一個字符
00000151  EBF2              jmp short 0x145

; 暫停 CPU,等待指令,死循環
00000153  F4                hlt
00000154  EBFD              jmp short 0x153

; ==============================

; 等待鍵盤的“輸入緩衝區”爲空

; 循環次數(65536 次)
00000156  2BC9              sub cx,cx

; 從 64H 端口讀取 1 個字節到 al
; 0x64 端口是“鍵盤控制器”的“讀取狀態寄存器”。下面是它各個位的含義:
; 7 = 1 從鍵盤傳輸時發生奇偶校驗錯誤
; 6 = 1 常規超時
; 5 = 1 鼠標輸出緩衝區已滿
; 4 = 0 鍵盤抑制
; 3 = 1 輸入寄存器中的數據爲命令
;     0 輸入寄存器中的數據爲數據
; 2 = 系統標誌狀態:0=加電或復位,1=自檢正常
; 1 = 輸入緩衝區已滿
; 0 = 輸出緩衝區已滿
00000158  E464              in al,0x64

; 延時,相當於 nop 指令
0000015A  EB00              jmp short 0x15c

; 檢查狀態寄存器第 2 位(輸入緩衝區已滿)是否爲 0
; 若爲 0 則 ZF=1,否則 ZF=0
0000015C  2402              and al,0x2

; CX=CX-1,若 CX != 0 且 ZF=0, 則跳轉到 0x158 繼續下一輪循環
0000015E  E0F8              loopne 0x158

; 直到緩衝區爲空,或者 CX 爲 0(CX 爲 0 表示循環了 65536 次)
00000160  2402              and al,0x2
00000162  C3                ret

; ==============================

; 常量
00000163  49                db "Invalid partition table", 0
0000017B  45                db "Error loading operating system", 0
0000019A  4D                db "Missing operating system", 0

000001B3  00                db 0, 0

000001B5  63                db 0x63 ; 第 1 個字符串的地址
000001B6  7B                db 0x7B ; 第 2 個字符串的地址
000001B7  9A                db 0x9A ; 第 3 個字符串的地址

000001B8  54                db 0x54
000001B9  36                db 0x36
000001BA  55                db 0x55
000001BB  36                db 0x36

000001BC  36                db 0, 0

; ==============================

; 分區表
000001BE  00                times 16 db 0
000001CE  00                times 16 db 0
000001DE  00                times 16 db 0
000001EE  00                times 16 db 0

; ==============================

; 引導標記
000001FE  55AA              db 0x55, 0xAA

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章