试着解读 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

 

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