4. 静态分析基础:反汇编基础、识别驱动入口点、定位 MajorFunction 与派遣函数表
静态分析是驱动安全测试的起点,不依赖实际运行环境,通过逆向工程手段解析驱动二进制文件的结构与逻辑。对于 Windows 内核驱动(.sys 文件),静态分析的核心任务包括:理解反汇编代码、定位驱动入口点(DriverEntry)、以及提取 MajorFunction 派遣函数表。本节将系统讲解这三项基础技能,并辅以实际案例与可视化图表。
4.1 反汇编基础:从二进制到控制流
反汇编是将机器码转换为汇编指令的过程。驱动静态分析通常使用 IDA Pro、Ghidra 或 WinDbg(配合反汇编引擎)。理解以下关键概念是分析驱动的前提:
- 调用约定:Windows 内核驱动使用
__stdcall(参数从右向左压栈,由被调用者清理栈)。x64 平台则使用__fastcall(前四个参数通过 RCX、RDX、R8、R9 传递)。 - 常见指令:
mov(数据传送)、call(函数调用)、jmp/jz/jnz(控制转移)、lea(有效地址加载)、push/pop(栈操作)。 - 内核特有结构:
DRIVER_OBJECT、DEVICE_OBJECT、IRP等结构体在反汇编中以偏移量形式出现。
💡 分析技巧: 在 IDA 中加载驱动后,首先查看
NT_HEADER 中的 AddressOfEntryPoint,该字段指向驱动入口点(通常是 DriverEntry 或 GsDriverEntry)。对于使用 GS 安全检查的驱动,入口点可能是 GsDriverEntry,它会调用实际的 DriverEntry。
4.2 识别驱动入口点:DriverEntry 与 GsDriverEntry
驱动入口点是静态分析的第一个关键锚点。Windows 驱动加载器在加载驱动后,会调用入口点函数。入口点通常具有以下特征:
| 入口点类型 | 特征 | 常见位置 | 安全意义 |
|---|---|---|---|
| DriverEntry | 标准入口点,接收 DRIVER_OBJECT 和注册表路径 |
PE 入口点直接指向 | 初始化设备、注册派遣函数 |
| GsDriverEntry | 由 /GS 编译选项生成,先调用安全 Cookie 初始化 |
PE 入口点指向 GsDriverEntry | 存在缓冲区安全检查 |
| 自定义入口点 | 通过 /ENTRY 链接选项指定 |
非标准位置 | 可能用于隐藏或反逆向 |
🔍 定位方法: 在 IDA 中,按
G 键跳转到 DriverEntry 或 GsDriverEntry。如果符号被剥离,可通过搜索特征指令序列定位:mov [rcx+0B8h], rax(设置 MajorFunction 表)或 call WdfVersionBind(WDF 驱动)。
以下是一个典型的 DriverEntry 反汇编片段(x64 平台):
; DriverEntry 函数开始
DriverEntry proc near
mov [rsp+8], rbx ; 保存非易失寄存器
mov [rsp+10h], rsi
push rdi
sub rsp, 20h ; 分配局部栈空间
; 获取 DriverObject 指针 (RCX)
mov rdi, rcx ; rdi = DriverObject
; 创建设备对象
lea rdx, aDeviceName ; "\\Device\\MyDriver"
mov rcx, rdi ; DriverObject
call IoCreateDevice ; 调用 IoCreateDevice
; 检查返回值
test eax, eax
js short failure ; 如果失败则跳转
; 设置 MajorFunction 表
lea rax, MyCreateClose ; 函数地址
mov [rdi+70h], rax ; IRP_MJ_CREATE (0x00)
mov [rdi+78h], rax ; IRP_MJ_CLOSE (0x02)
lea rax, MyDeviceControl
mov [rdi+0B0h], rax ; IRP_MJ_DEVICE_CONTROL (0x0E)
; 设置卸载例程
lea rax, MyUnload
mov [rdi+68h], rax ; DriverUnload
xor eax, eax ; 返回 STATUS_SUCCESS
jmp short epilogue
failure:
; 错误处理
mov eax, 0C0000001h ; STATUS_UNSUCCESSFUL
epilogue:
; 恢复寄存器并返回
add rsp, 20h
pop rdi
retn
DriverEntry endp
4.3 定位 MajorFunction 与派遣函数表
DRIVER_OBJECT 结构体中的 MajorFunction 数组是驱动安全分析的核心目标。该数组包含 28 个函数指针(IRP_MJ_XXX),每个指针对应一个 IRP 主功能码的处理函数。通过分析派遣函数表,可以快速了解驱动支持哪些操作,以及每个操作的处理逻辑。
MajorFunction 数组在 DRIVER_OBJECT 中的偏移(x64):
| 偏移量 | IRP 主功能码 | 名称 | 常见用途 |
|---|---|---|---|
| 0x70 | IRP_MJ_CREATE (0x00) | Create | 打开设备句柄 |
| 0x78 | IRP_MJ_CLOSE (0x02) | Close | 关闭设备句柄 |
| 0x80 | IRP_MJ_READ (0x03) | Read | 从设备读取数据 |
| 0x88 | IRP_MJ_WRITE (0x04) | Write | 向设备写入数据 |
| 0xB0 | IRP_MJ_DEVICE_CONTROL (0x0E) | DeviceControl | 设备 I/O 控制(IOCTL) |
| 0xB8 | IRP_MJ_INTERNAL_DEVICE_CONTROL (0x0F) | InternalDeviceControl | 内部 I/O 控制 |
| 0xE0 | IRP_MJ_PNP (0x1B) | PnP | 即插即用管理 |
| 0xE8 | IRP_MJ_POWER (0x1C) | Power | 电源管理 |
⚠️ 安全关注点: 派遣函数表中的
IRP_MJ_DEVICE_CONTROL 是最常被攻击的目标。恶意驱动或存在漏洞的驱动可能在此处实现任意内存读写、权限提升或拒绝服务。分析时应重点关注该派遣函数的 IOCTL 分发逻辑。
4.4 实战案例:分析一个简单的驱动派遣函数表
以下是一个实际驱动中 DriverEntry 设置派遣函数表的反汇编片段(x86 平台,IDA 反汇编结果):
.text:00011000 DriverEntry proc near
.text:00011000
.text:00011000 var_4 = dword ptr -4
.text:00011000 arg_0 = dword ptr 8
.text:00011000 arg_4 = dword ptr 0Ch
.text:00011000
.text:00011000 push ebp
.text:00011001 mov ebp, esp
.text:00011003 push ebx
.text:00011004 push esi
.text:00011005 push edi
.text:00011006 mov edi, [ebp+arg_0] ; edi = DriverObject
.text:00011009 push 0 ; DeviceName = NULL
.text:0001100B push 0 ; Exclusive = FALSE
.text:0001100D push 0 ; DeviceType = FILE_DEVICE_UNKNOWN
.text:0001100F push edi ; DriverObject
.text:00011010 call IoCreateDevice
.text:00011015 test eax, eax
.text:00011017 jl short loc_1102F
.text:00011019 mov [edi+4], eax ; DeviceObject
.text:0001101C mov eax, offset sub_11100 ; Create/Close 函数
.text:00011021 mov [edi+34h], eax ; MajorFunction[0] = Create
.text:00011024 mov [edi+3Ch], eax ; MajorFunction[2] = Close
.text:00011027 mov eax, offset sub_11200 ; DeviceControl 函数
.text:0001102C mov [edi+70h], eax ; MajorFunction[14] = DeviceControl
.text:0001102F xor eax, eax
.text:00011031 pop edi
.text:00011032 pop esi
.text:00011033 pop ebx
.text:00011034 pop ebp
.text:00011035 retn 8
.text:00011035 DriverEntry endp
分析步骤:
- 定位 DriverEntry:通过入口点或特征指令
call IoCreateDevice找到该函数。 - 识别 MajorFunction 赋值:查找
mov [edi+offset], reg指令,其中offset对应DRIVER_OBJECT.MajorFunction数组的索引偏移。 - 提取派遣函数地址:记录每个
offset对应的函数地址(如sub_11100、sub_11200)。 - 跳转到派遣函数:在 IDA 中按
G跳转到这些地址,分析其内部逻辑。
📊 派遣函数表提取结果:
| IRP 主功能码 | 偏移 (x86) | 函数地址 | 函数名称(推测) |
|---|---|---|---|
| IRP_MJ_CREATE (0x00) | 0x34 | 0x11100 | sub_11100 |
| IRP_MJ_CLOSE (0x02) | 0x3C | 0x11100 | sub_11100(复用) |
| IRP_MJ_DEVICE_CONTROL (0x0E) | 0x70 | 0x11200 | sub_11200 |
4.5 常见陷阱与注意事项
- 符号剥离:发布版驱动通常剥离符号,函数名变为
sub_XXXXX,需通过上下文推断功能。 - 内联函数:部分驱动使用内联汇编或宏来设置派遣函数表,可能不直接出现
mov [edi+offset]模式。 - WDF 驱动:基于 Windows Driver Framework (WDF) 的驱动通过
WdfDeviceCreate和WdfDeviceInitSetXxx注册派遣函数,需跟踪框架回调。 - 派遣函数复用:多个 IRP 主功能码可能指向同一个函数(如 Create 和 Close 共用),分析时需注意函数内部的分支逻辑。
- 动态注册:部分驱动在运行时动态修改
MajorFunction表(如通过IoRegisterDriverReinitialization),静态分析可能遗漏。
🛠️ 工具推荐: 使用 IDA Pro 的
DriverWorks 插件或 KdCore 脚本可以自动解析 DRIVER_OBJECT 结构并标注派遣函数表。对于 Ghidra,可使用 WindowsDriverAnalyzer 扩展。
通过本节的学习,你应该能够独立完成一个驱动二进制文件的静态分析入门工作:从反汇编基础到定位入口点,再到提取完整的派遣函数表。这是后续进行漏洞挖掘、恶意行为分析和安全加固的基石。