MBR结构与引导代码深度分析
· 10 分钟阅读 · @admin

> 本文记录了早期对MBR(主引导记录)的分析笔记。随着近期对Bootkit类恶意软件的深入研究,重新整理发布,以备参考。
MBR结构概述
以Windows 7为例(Windows 7至Windows 10的引导代码基本一致),MBR占用1个扇区,大小为512字节(0x200)。MBR结构可分为三个主要区域:
- 第一区域(0x000-0x162):MBR可执行代码段
- 第二区域(0x163-0x1b2):错误提示信息,当分区表查找失败时在屏幕显示这些字符串
- 第三区域(0x1be-0x1fd):分区表数据

MBR版本识别
位于偏移0x1b5处的值指示错误信息字符串的相对偏移。以Windows 7为例,此处值为0x63,加上基址0x100得到0x163,恰好对应第二区域的起始位置。不同Windows版本此值有所差异:
- Windows XP:0x2c
- Windows Vista:0x62
- Windows 7~10:0x63
MBR完整性检测方法
为检测MBR是否被恶意修改,可对正常MBR的代码区域计算SHA256哈希值作为白名单基准。具体方法是:
- 计算从偏移0x000到错误提示字符串起始位置之前的所有代码段
- 以Windows 7为例,计算范围为0x000至0x162
- 将计算结果与已知良好版本的SHA256值比对
- 若不匹配,则可能MBR已被篡改
MBR代码区SHA256白名单表
| 系统版本 | 字符串偏移 | 白名单SHA256 |
|---|---|---|
| Windows XP | 0x2c | b5ed343494f0326a08aa6abf7cc9aa4d96207532cf0d2b39453c6eb7bede19e3 |
| Windows Vista | 0x62 | 4799e8c92d32bca8e5103110a322523adb7a3909324132bd9abab8f3345e094a |
| Windows 7及以上 | 0x63 | 088995559ab317af9b3291408da689651e8353f62e0a478d92eb0b5a947063fd |
MBR数据结构定义
typedef struct _MBR
{
BYTE bootCode[440]; // 引导代码(440字节)
DWORD diskSignature; // 磁盘签名
WORD reserved; // 保留字段
PARTITION partitionTable[4]; // 分区表(最多4个主分区)
WORD sectorEndSignature; // 扇区结束标志(0x55AA)
} MBR;
分区表结构详解
从0x1be至0x1fd为分区表区域。MBR最多支持四个主分区,每个分区表项占16字节。分区表采用CHS(柱面-磁头-扇区)寻址方式,结构定义如下:
typedef struct _PARTITION
{
BYTE bootIndicator; // 引导标志(0x80=活动分区,0x00=非活动分区)
BYTE head; // 起始磁头号
WORD sector:10; // 起始扇区号(10位)
WORD cylinder:6; // 起始柱面号(6位)
BYTE type; // 分区类型标志(参考:https://en.wikipedia.org/wiki/Partition_type)
BYTE lastHead; // 结束磁头号
WORD lastSector:10; // 结束扇区号(10位)
WORD lastCylinder:6; // 结束柱面号(6位)
DWORD relativeSector; // 分区相对起始扇区(LBA地址)
DWORD numberSectors; // 分区总扇区数
} PARTITION;
VBR(卷引导记录)结构
活动分区的首个扇区为VBR,同样占用一个扇区。VBR开头的EB 52 90指令用于跳过BPB结构,直接执行引导代码。VBR结构分为三个区域:
- 第一区域(0x0b-0x53):BIOS参数块(BIOS Parameter Block),即NTFS_BPB结构
- 第二区域(0x54-0x18b):引导代码区(bootStrapCode)
- 第三区域(0x18c-0x1f6):错误提示字符串,引导失败时显示

VBR数据结构定义
typedef struct _NTFS_VOLUME_BOOT_RECORD
{
BYTE jumpInstruction[3]; // EB 52 90 跳转指令
BYTE oemID[4]; // "NTFS" 文件系统标识
BYTE dummy[4]; // 0x20 0x20 0x20 0x20 填充字节
NTFS_BPB bpb; // NTFS BIOS参数块
BYTE bootStrapCode[426]; // 引导代码
WORD sectorEndSignature; // 0x55AA 扇区结束标志
} NTFS_VOLUME_BOOT_RECORD;
BIOS参数块(BPB)
BIOS参数块提供了NTFS文件系统的关键信息,结构定义如下:
struct NTFS_BPB
{
WORD bytesPerSector; // 每扇区字节数
BYTE sectorsPerCluster; // 每簇扇区数(簇是NTFS文件系统的基本单位)
WORD reservedSectors; // 保留扇区数
BYTE fatCopies; // FAT副本数(NTFS不使用)
WORD rootDirEntries; // 根目录项数(NTFS不使用)
WORD smallSectors; // 小扇区数(NTFS不使用)
BYTE mediaDescriptor; // 介质描述符(硬盘为0xF8)
WORD sectorsPerFAT; // 每FAT扇区数(NTFS不使用)
WORD sectorsPerTrack; // 每磁道扇区数
WORD numberOfHeads; // 磁头数
DWORD hiddenSectors; // 隐藏扇区数(指定IPL位置)
DWORD largeSectors; // 大扇区数
DWORD reserved; // 保留字段
ULONGLONG totalSectors; // 总扇区数
ULONGLONG MFTLogicalClusterNumber; // $MFT起始簇号
ULONGLONG MirrorLogicalClusterNumber; // $MFTMir起始簇号
DWORD clustersPerMFTRecord; // 每MFT记录簇数
DWORD clustersPerIndexRecord; // 每索引记录簇数
ULONGLONG volumeSerial; // 卷序列号
DWORD checksum; // 校验和
} bpb;
VBR完整性检测
VBR同样可以通过错误显示字符串的偏移来判断操作系统版本。偏移0x1f8处的值为第一个错误信息字符串的偏移,例如值为0x8c时,加上0x100得到0x18c,即错误信息字符串区域的起始位置。
VBR代码SHA256计算应包括:跳转指令EB 50 90(也是代码的一部分)+ VBR代码区域(0x54至错误信息字符串前)。
VBR代码区SHA256白名单表
| 系统版本 | 字符串偏移 | 白名单SHA256 |
|---|---|---|
| Windows XP | 0x83 | 5cb5aa385e0ada266690a2821e3a36ad372720d2ff47c0b1cd9d6ebcab25bf4e |
| Windows Vista | 0x80 | a1932aaba7d6d3adb1637e2ee0c8355706842ba825ea811728165420c518c0b1 |
| Windows 7 | 0x8c | 96d38c1be37b9124fb71d1d0f5c52969f0074687fe17aef0e1bafc54428674f6 |
| Windows 8 | 0x18a(若前面三个值都不匹配,则查看0x1f6-0x1f8的值) | 51643dcce7e93d795b08e1f19e38374ae4deaf3b1217527561a44aa9913ded23 |
IPL(初始程序加载器)
VBR负责加载IPL代码,在IPL中解析NTFS格式并定位bootmgr的位置。IPL的位置由VBR的NTFS_BPB中的hiddenSectors字段(相对扇区起始位置0x1c)指定。
IPL同样可以使用白名单验证其完整性。IPL共占15个扇区,但由于最后一个扇区有时会保存与引导无关的数据,因此只计算前14个扇区。
从Windows 8开始,IPL区域增加了引导程序启动失败时显示的字符串,该字符串偏移由0x177-0x178处指定:
"An operating system wasn't found. Try disconnecting any drives that don't contain an operating system."

IPL完整性检测方法
- Windows 7及以下:计算全部14个扇区的SHA256
- Windows 8及以上:需要排除两个部分:
- 错误信息字符串
- 保存该字符串索引的位置(0x177-0x178)
以Windows 10为例,SHA256计算范围为:0x200-0x299 + 0x319-第14扇区结束
可以根据0x256处的跳转指令判断操作系统版本:如果是E9,则为Windows 8及以上版本。
IPL代码区SHA256白名单表
| 系统版本 | 白名单SHA256 |
|---|---|
| Windows XP | 525788a688cfbe9e416122f0bc3cfb32ce9699fd12356b6ccaa173444c7d8f3f |
| Windows Vista | ff1aae04bac3e29f062a7fa17320d7d26363256a69f96840718d45301da71291 |
| Windows 7 | 462afe2322bad3d1c2747d7437d5f6c157e00ca37e5d38ebedd25346b3b488ce |
| Windows 8及以上 | c09d496a1f24086c333468d58256d5db9c73fee945fca74603bdab05f19a6d57 |
注意:以上检测方法均针对代码部分。如果Bootkit修改的不是代码,而是分区表或BPB的hiddenSectors字段,则无法通过代码完整性检测发现感染。
正常系统引导代码分析
配置好调试环境后,使用GDB附加调试。BIOS会在启动后将MBR加载到0x7c00,此时所有段寄存器都为0x0。MBR运行在实模式下,地址为16位。在0x7c00设置断点,调整段为16位模式。
MBR引导代码
初始化与重定位
MEMORY:7C00 loc_7C00:
MEMORY:7C00 xor ax, ax ; 清零AX寄存器
MEMORY:7C02 mov ss, ax ; 设置栈段寄存器SS=0
MEMORY:7C04 mov sp, 7C00h ; 设置栈指针SP=0x7C00
MEMORY:7C07 mov es, ax ; 设置附加段寄存器ES=0
MEMORY:7C09 mov ds, ax ; 设置数据段寄存器DS=0
MEMORY:7C0B mov si, 7C00h ; 源地址=0x7C00
MEMORY:7C0E mov di, 600h ; 目标地址=0x0600
MEMORY:7C11 mov cx, 200h ; 拷贝大小=0x200(512字节)
MEMORY:7C14 cld ; 清除方向标志(递增)
MEMORY:7C15 rep movsb ; 将MBR从0x7C00拷贝到0x0600
MEMORY:7C17 push ax ; 压入AX=0
MEMORY:7C18 push 61Ch ; 压入返回地址0x61C
MEMORY:7C1B retf ; 远返回,跳转到0:0x61C继续执行
查找活动分区
MEMORY:061C sti ; 开启中断
MEMORY:061D mov cx, 4 ; 循环计数器=4(最多4个主分区)
MEMORY:0620 mov bp, 7BEh ; 分区表偏移(0x600+0x1BE)
MEMORY:0623 loc_623:
MEMORY:0623 cmp byte ptr [bp+0], 0 ; 检查引导标志
; bootIndicator最高位为1表示活动分区
; 值<0表示最高位为1(找到活动分区)
; 旧MBR只支持0x80和0x00两个有效值
MEMORY:0627 jl short loc_634 ; 如果<0,找到活动分区,跳转
MEMORY:0629 jnz loc_73B ; 如果>0,跳转到错误处理
; 显示"Invalid partition table"
; 因为0x01~0x7F为无效值
MEMORY:062D add bp, 10h ; 如果=0,指向下一个分区表项
MEMORY:0630 loop loc_623 ; 循环检查下一个分区
MEMORY:0632 int 18h ; 无活动分区,调用BIOS引导失败处理
检测INT 13h扩展支持
调用INT 13h功能号41h,检测是否支持INT 13h扩展功能。如果支持,CF=0,BX=0xAA55,CX最低位=1。
MEMORY:0634 mov [bp+0], dl ; 保存驱动器号
MEMORY:0637 push bp ; 保存分区表指针
MEMORY:0638 mov byte ptr [bp+11h], 5 ; 初始化重试计数
MEMORY:063C mov byte ptr [bp+10h], 0 ; 清除扩展支持标志
MEMORY:0640 mov ah, 41h ; 功能号41h
MEMORY:0642 mov bx, 55AAh ; 特征值
MEMORY:0645 int 13h ; DISK - 检查INT 13h扩展
; BX = 55AAh, DL = 驱动器号
; Return: 不支持则CF=1
; AH = 扩展版本号
; BX = AA55h
; CX = 接口支持位图
MEMORY:0647 pop bp ; 恢复分区表指针(0x7BE)
MEMORY:0648 jb short loc_659 ; CF=1,不支持扩展,跳转
MEMORY:064A cmp bx, 0AA55h ; 检查返回特征值
MEMORY:064E jnz short loc_659 ; 不匹配,跳转
MEMORY:0650 test cx, 1 ; 测试CX最低位
MEMORY:0654 jz short loc_659 ; 为0,不支持,跳转
MEMORY:0656 inc byte ptr [bp+10h] ; [bp+10h]标记支持INT 13h扩展
; 后续通过此值判断使用哪种读取方式
使用扩展INT 13h读取VBR
在堆栈上构建磁盘地址包(DAP),调用INT 13h扩展读取功能,将活动分区的VBR拷贝到0x7C00。读取成功则CF=0,AH=0。
磁盘地址包(DAP)结构:
| 偏移 | 大小 | 说明 |
|---|---|---|
| 0 | 1 | 结构体大小 |
| 1 | 1 | 总是0 |
| 2 | 2 | 要读取的扇区数(最大127个扇区) |
| 4 | 4 | 目标内存地址(段:偏移) |
| 8 | 8 | 起始扇区的LBA地址 |
MEMORY:0659 loc_659: ; CODE XREF: MEMORY:0648,064E
MEMORY:0659 pushad ; 保存所有通用寄存器
MEMORY:065B cmp byte ptr [bp+10h], 0 ; [bp+10h]=1表示支持INT 13h扩展
MEMORY:065F jz short loc_687 ; 不支持扩展,使用标准INT 13h
MEMORY:0661 push large 0 ; 构建DAP结构
; 高32位LBA地址=0
MEMORY:0667 push large dword ptr [bp+8] ; 低32位LBA地址
; [bp+0]为分区表结构
; [bp+8]为分区起始LBA地址
MEMORY:066B push 0 ; 段地址(16位push操作)
MEMORY:066E push 7C00h ; 偏移地址,目标=0x0000:0x7C00
MEMORY:0671 push 1 ; 读取1个扇区
MEMORY:0674 push 10h ; DAP结构大小=16字节
MEMORY:0677 mov ah, 42h ; 功能号42h:扩展读
MEMORY:0679 mov dl, [bp+0] ; 驱动器号
MEMORY:067C mov si, sp ; DS:SI指向DAP结构
MEMORY:067E int 13h ; DISK - IBM/MS扩展读
; DL=驱动器号, DS:SI=DAP指针
使用标准INT 13h读取VBR
如果不支持扩展功能,使用标准INT 13h功能号02h读取:
MEMORY:0687 mov ax, 201h ; AH=02(读扇区),AL=01(1个扇区)
MEMORY:068A mov bx, 7C00h ; 目标地址ES:BX=0:0x7C00
MEMORY:068D mov dl, [bp+0] ; 驱动器号
MEMORY:0690 mov dh, [bp+1] ; 磁头号
MEMORY:0693 mov cl, [bp+2] ; 扇区号
MEMORY:0696 mov ch, [bp+3] ; 柱面号
MEMORY:0699 int 13h ; DISK - 读扇区到内存
; AL=读取扇区数, CH=磁道, CL=扇区
; DH=磁头, DL=驱动器号, ES:BX=目标地址
; Return: CF置位表示错误, AH=状态, AL=实际读取扇区数
VBR代码分析
VBR初始化
VBR第一条指令为短跳转,跳过BIOS参数块:
MEMORY:7C00 jmp short near ptr unk_7C54 ; 跳过BPB结构
设置段寄存器和栈指针:
MEMORY:7C54 loc_7C54:
MEMORY:7C54 cli ; 关闭中断
MEMORY:7C55 xor ax, ax ; 清零AX
MEMORY:7C57 mov ss, ax ; SS=0
MEMORY:7C59 mov sp, 7C00h ; SP=0x7C00
MEMORY:7C5C sti ; 开启中断
MEMORY:7C5D push 7C0h ; 设置DS=0x7C0
MEMORY:7C60 pop ds
MEMORY:7C61 assume ds:nothing
MEMORY:7C61 push ds ; 压入DS
MEMORY:7C62 push 66h ; 压入偏移0x66
MEMORY:7C65 retf ; 远返回,跳转到0x7C0:0x66执行
文件系统检测与扩展功能检测
根据VBR结构的oemID字段检测是否为NTFS文件系统,然后检测INT 13h扩展支持:
MEMORY:7C66 loc_7C66: ; CODE XREF: MEMORY:7C65
MEMORY:7C66 mov ds:0Eh, dl ; 保存驱动器号
MEMORY:7C6A cmp dword ptr ds:3, 'SFTN' ; 检查oemID是否为"NTFS"
MEMORY:7C73 jnz short loc_7C8A ; 不是NTFS,跳转到错误处理
MEMORY:7C75 mov ah, 41h ; 功能号41h
MEMORY:7C77 mov bx, 55AAh ; 特征值
MEMORY:7C7A int 13h ; DISK - 检查INT 13h扩展
; BX = 55AAh, DL = 驱动器号
; Return: 不支持则CF=1
; AH = 扩展版本号
; BX = AA55h
; CX = 接口支持位图
MEMORY:7C7C jb short loc_7C8A ; CF=1,不支持,跳转
MEMORY:7C7E cmp bx, 0AA55h ; 检查返回特征值
MEMORY:7C82 jnz short loc_7C8A ; 不匹配,跳转
MEMORY:7C84 test cx, 1 ; 测试最低位
MEMORY:7C88 jnz short loc_7C8D ; CX=1,支持扩展,跳转
说明:VBR需要使用INT 13h的功能号42h-44h、47h、48h,这些功能需要扩展支持。标准INT 13h只能访问8GB以内的硬盘空间,因此必须支持扩展功能。
错误处理
MEMORY:7C8A loc_7C8A:
MEMORY:7C8A jmp loc_7D6A ; 跳转到错误处理
; 错误处理:调用INT 10h在屏幕显示字符串,然后停机
MEMORY:7D6A loc_7D6A: ; CODE XREF: MEMORY:loc_7C8A
MEMORY:7D6A mov al, byte_1F8 ; 错误字符串偏移
MEMORY:7D6D call loc_7D79 ; 显示"A disk read error occurred"
MEMORY:7D70 mov al, byte_1FB ; 第二个错误字符串偏移
MEMORY:7D73 call loc_7D79 ; 显示"Press Ctrl+Alt+Del to restart"
MEMORY:7D76 loc_7D76: ; CODE XREF: MEMORY:7D77
MEMORY:7D76 hlt ; CPU停机指令
MEMORY:7D77 jmp short loc_7D76 ; 无限循环
; 显示字符串子程序
MEMORY:7D79 loc_7D79: ; CODE XREF: MEMORY:7D6D, 7D73
MEMORY:7D79 mov ah, 1 ; 准备设置AH=0Eh
MEMORY:7D7B mov si, ax ; SI=错误提示字符串偏移
MEMORY:7D7D loc_7D7D: ; 循环显示字符
MEMORY:7D7D lodsb ; 加载字符到AL
MEMORY:7D7E cmp al, 0 ; 检查是否字符串结束
MEMORY:7D80 jz short locret_7D8B ; 是,返回
MEMORY:7D82 mov ah, 0Eh ; INT 10h功能号0Eh
MEMORY:7D84 mov bx, 7 ; 页号和颜色
MEMORY:7D87 int 10h ; VIDEO - 写字符并移动光标(TTY模式)
; AL=字符, BH=显示页(文本模式)
; BL=前景色(图形模式)
MEMORY:7D89 jmp short loc_7D7D ; 继续下一个字符
MEMORY:7D8B locret_7D8B: ; CODE XREF: MEMORY:7D80
MEMORY:7D8B retn ; 返回
获取驱动器参数
如果支持扩展INT 13h,调用功能号48h获取驱动器信息:
MEMORY:7C8D loc_7C8D: ; CODE XREF: MEMORY:7C88
MEMORY:7C8D push ds ; 保存DS
MEMORY:7C8E sub sp, 18h ; 在栈上分配缓冲区
; 用于保存INT 13h功能号48h返回的信息
MEMORY:7C91 push 1Ah ; 结构大小=26字节
MEMORY:7C94 mov ah, 48h ; 功能号48h
MEMORY:7C96 mov dl, ds:0Eh ; 驱动器号(在0x7C66处赋值,通常是0x80)
MEMORY:7C9A mov si, sp ; DS:SI指向缓冲区
MEMORY:7C9C push ss ; 设置DS=SS
MEMORY:7C9D pop ds
MEMORY:7C9E int 13h ; DISK - IBM/MS扩展 - 获取驱动器参数
; DL=驱动器号, DS:SI=缓冲区指针
返回数据结构(存放在DS:SI):

验证返回数据
MEMORY:7CA0 lahf ; 保存标志到AH
MEMORY:7CA1 add sp, 18h ; 恢复栈,指向扇区大小
MEMORY:7CA4 sahf ; 恢复标志
MEMORY:7CA5 pop ax ; 弹出扇区大小
MEMORY:7CA6 pop ds ; 恢复DS=0x7C0
MEMORY:7CA7 jb short loc_7C8A ; 出错,跳转到错误处理
MEMORY:7CA9 cmp ax, word_B ; 与NTFS_BPB的bytesPerSector比较
MEMORY:7CAD jnz short loc_7C8A ; 不相等,跳转到错误处理
准备读取IPL
MEMORY:7CAF mov word_F, ax ; 保存扇区大小
MEMORY:7CB2 shr word_F, 4 ; 0x200 >> 4 = 0x20(段地址增量)
MEMORY:7CB7 push ds ; DS=0x7C0
MEMORY:7CB8 pop dx ; DX=0x7C0
MEMORY:7CB9 xor bx, bx ; BX=0
MEMORY:7CBB mov cx, 2000h ; 要拷贝的总字节数
MEMORY:7CBE sub cx, ax ; 0x2000 - 0x200 = 0x1E00
; (16个扇区 - 1个VBR = 15个IPL扇区)
MEMORY:7CC0 inc dword_11 ; 增加起始扇区号
循环读取IPL扇区
循环调用INT 13h读取VBR之后的15个IPL扇区,拷贝到内存0x7E00-0x9BFF。IPL代码结束地址为0x8C27,剩余部分用0填充。
MEMORY:7CC5 loc_7CC5: ; CODE XREF: MEMORY:7CD4
MEMORY:7CC5 add dx, word_F ; 更新目标段地址
; 0x7C0 + 0x20 = 0x7E0(实际是段地址)
; 初始地址0x7E0,每读取一个扇区,DX+0x20
MEMORY:7CC9 mov es, dx ; ES=目标段地址
MEMORY:7CCB inc word_16 ; 增加扇区计数
MEMORY:7CCF call near ptr unk_7D1D ; 调用读取函数,读取IPL的15个扇区
MEMORY:7CD2 sub cx, ax ; 每次减去0x200,CX=0表示读取完成
MEMORY:7CD4 ja short loc_7CC5 ; 未完成,继续循环
读取单个IPL扇区
进入unk_7D1D函数,使用INT 13h扩展读(功能号42h)从硬盘读取一个扇区到内存:
MEMORY:7D1D pushad ; 保存所有通用寄存器
MEMORY:7D1F push ds ; 保存DS
MEMORY:7D20 push es ; 保存ES
MEMORY:7D21 loc_7D21: ; CODE XREF: MEMORY:7D63
MEMORY:7D21 mov eax, var_11 ; 当前扇区号
MEMORY:7D25 add eax, dword_1C ; 加上隐藏扇区数
MEMORY:7D2A push ds ; 构建DAP结构
MEMORY:7D2B push large offset unk_0 ; 高32位LBA=0
MEMORY:7D31 push eax ; 低32位LBA地址
MEMORY:7D33 push es ; 目标段地址
MEMORY:7D34 push bx ; 目标偏移地址(ES:BX为目标内存位置)
MEMORY:7D35 push 1 ; 读取1个扇区
MEMORY:7D38 push 10h ; DAP大小=16字节
MEMORY:7D3B mov ah, 42h ; 功能号42h
MEMORY:7D3D mov dl, byte_E ; 驱动器号
MEMORY:7D41 push ss ; DS=SS
MEMORY:7D42 pop ds
MEMORY:7D43 mov si, sp ; DS:SI指向DAP
MEMORY:7D45 int 13h ; DISK - IBM/MS扩展读
; DL=驱动器号, DS:SI=DAP指针
MEMORY:7D47 pop ecx ; 清理栈
MEMORY:7D49 pop bx
MEMORY:7D4A pop dx
MEMORY:7D4B pop ecx
MEMORY:7D4D pop ecx
MEMORY:7D4F pop ds ; 恢复DS
MEMORY:7D50 jb loc_7D6A ; CF=1,读取失败,跳转到错误处理
MEMORY:7D54 inc var_11 ; 扇区号+1
MEMORY:7D59 add dx, word_F ; 更新段地址
MEMORY:7D5D mov es, dx ; ES=新段地址
MEMORY:7D5F dec word_16 ; 扇区计数-1
MEMORY:7D63 jnz short loc_7D21 ; 未完成,继续循环
MEMORY:7D65 pop es ; 恢复ES
MEMORY:7D66 pop ds ; 恢复DS
MEMORY:7D67 popad ; 恢复所有通用寄存器
MEMORY:7D69 retn ; 返回
TCG(可信计算组)检测
将IPL数据全部读入内存后,调用INT 1Ah中断(AH=0xBB)检测BIOS是否支持TCG:
MEMORY:7CD6 mov ax, 0BB00h ; AH=0xBB,功能号
MEMORY:7CD9 int 1Ah ; Trusted Computing Group - TCG_StatusCheck
; Return: EAX=0表示支持
; EBX=41504354h ('TCPA')
; CH:CL=TCG BIOS版本
; EDX=BIOS TCG功能标志
; ESI=事件日志指针
MEMORY:7CDB and eax, eax ; 测试EAX
MEMORY:7CDE jnz short loc_7D0D ; EAX≠0,不支持TCG,跳过
MEMORY:7CE0 cmp ebx, 'APCT' ; 检查特征值'TCPA'
MEMORY:7CE7 jnz short loc_7D0D ; 不匹配,跳过
MEMORY:7CE9 cmp cx, 102h ; 检查版本号
MEMORY:7CED jb short loc_7D0D ; 版本太低,跳过
MEMORY:7CEF push ss ; 构建TCG调用参数
MEMORY:7CF0 push offset unk_BB07
MEMORY:7CF3 push ss
MEMORY:7CF4 push offset unk_E70
MEMORY:7CF7 push ss
MEMORY:7CF8 push offset unk_9
MEMORY:7CFB push ebx
MEMORY:7CFD push ebx
MEMORY:7CFF push ebp
MEMORY:7D01 push ss
MEMORY:7D02 push ss
MEMORY:7D03 push ss
MEMORY:7D04 push offset unk_1B8
MEMORY:7D07 popad ; 恢复寄存器
MEMORY:7D09 push cs ; ES=CS
MEMORY:7D0A pop es
MEMORY:7D0B int 1Ah ; 调用TCG功能
清零IPL非代码区域
MEMORY:7D0D xor ax, ax ; AX=0
MEMORY:7D0F mov di, 1028h ; 目标偏移
MEMORY:7D12 mov cx, 0FD8h ; 要清零的字节数(4056字节)
MEMORY:7D15 cld ; 清除方向标志
MEMORY:7D16 rep stosb ; 用0填充
MEMORY:7D18 jmp near ptr unk_7E7A ; 跳转到IPL代码(0x7E7A)
IPL代码分析
IPL的15个扇区复制完毕后,开始解析NTFS文件系统以定位bootmgr文件。
计算每簇字节数
MEMORY:7E7A movzx eax, word_B ; DS=0x7C00
; word_B=0x7C0B
; BPB.bytesPerSector(每扇区字节数)
MEMORY:7E80 movzx ebx, byte_D ; byte_D=0x7C0D
; BPB.sectorsPerCluster(每簇扇区数)
MEMORY:7E86 mul ebx ; EAX * EBX = 每簇字节数
; 簇是NTFS文件系统的基本单位
MEMORY:7E89 mov dword_252, eax ; 保存每簇字节数
计算文件记录大小
计算文件记录(File Record)的大小。当簇的大小大于文件记录大小时,clustersPerMFTRecord的值为负数。负数实际表示2的幂次方,例如值为0xF6(-10),实际大小为2^|-10| = 2^10 = 1024字节。
MEMORY:7E8D mov ecx, dword_40 ; dword_40为clustersPerMFTRecord
; 每个文件记录所占的簇数
; 这里值为0xF6(-10的补码)
MEMORY:7E92 cmp cl, 0 ; 检查是否为负数
MEMORY:7E95 jg loc_7EA7 ; >0跳转,但大部分情况为负数
MEMORY:7E99 neg cl ; 取反:-(-10) = 10
MEMORY:7E9B mov eax, 1 ; EAX=1
MEMORY:7EA1 shl eax, cl ; 2^10 = 1024
MEMORY:7EA4 jmp short loc_7EAE ; 跳过正数处理
MEMORY:7EA6 nop ; 对齐
; clustersPerMFTRecord为正数的情况(簇数直接相乘)
MEMORY:7EA7 loc_7EA7:
MEMORY:7EA7 mov eax, dword_252 ; 0x7E52:每簇字节数
MEMORY:7EAB mul ecx ; 簇数 * 每簇字节数 = 文件记录字节数
MEMORY:7EAE loc_7EAE: ; CODE XREF: MEMORY:7EA4
MEMORY:7EAE mov _byte_file_record, eax ; 保存文件记录大小
; _byte_file_record位于0x7E66
总结
本文详细分析了Windows系统的引导过程,从MBR到VBR再到IPL,涵盖了:
- MBR结构与完整性检测:通过SHA256哈希值白名单验证MBR代码完整性
- 分区表结构:详细解析分区表的CHS寻址方式和数据结构
- VBR结构与BPB:NTFS卷引导记录和BIOS参数块的结构定义
- IPL加载器:初始程序加载器的完整性检测方法
- 引导代码流程:从BIOS加载MBR到查找活动分区,再到加载VBR和IPL的完整过程
- NTFS文件系统解析:IPL中对NTFS文件系统的初步解析
理解这些引导机制对于分析Bootkit类恶意软件至关重要,因为Bootkit通常会修改MBR、VBR或IPL来实现持久化和隐蔽性。通过代码完整性检测,可以有效发现针对引导代码的恶意篡改。
需要注意的是,如果恶意软件修改的是非代码区域(如分区表或BPB字段),仅依靠代码区域的完整性检测可能无法发现。因此,完整的Bootkit检测方案还需要结合其他检测手段,如分区表结构验证、引导配置检查等。

