8-BitQuest
菜单
[系统安全]

MBR结构与引导代码深度分析

· 10 分钟阅读 · @admin

MBR 像素封面

> 本文记录了早期对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哈希值作为白名单基准。具体方法是:

  1. 计算从偏移0x000到错误提示字符串起始位置之前的所有代码段
  2. 以Windows 7为例,计算范围为0x000至0x162
  3. 将计算结果与已知良好版本的SHA256值比对
  4. 若不匹配,则可能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及以上:需要排除两个部分:
    1. 错误信息字符串
    2. 保存该字符串索引的位置(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,涵盖了:

  1. MBR结构与完整性检测:通过SHA256哈希值白名单验证MBR代码完整性
  2. 分区表结构:详细解析分区表的CHS寻址方式和数据结构
  3. VBR结构与BPB:NTFS卷引导记录和BIOS参数块的结构定义
  4. IPL加载器:初始程序加载器的完整性检测方法
  5. 引导代码流程:从BIOS加载MBR到查找活动分区,再到加载VBR和IPL的完整过程
  6. NTFS文件系统解析:IPL中对NTFS文件系统的初步解析

理解这些引导机制对于分析Bootkit类恶意软件至关重要,因为Bootkit通常会修改MBR、VBR或IPL来实现持久化和隐蔽性。通过代码完整性检测,可以有效发现针对引导代码的恶意篡改。

需要注意的是,如果恶意软件修改的是非代码区域(如分区表或BPB字段),仅依靠代码区域的完整性检测可能无法发现。因此,完整的Bootkit检测方案还需要结合其他检测手段,如分区表结构验证、引导配置检查等。

更多文章