28 KiB
Windows 驱动分支(模型识别 → callback / 设备栈 / I/O 路径)
**先识别 driver model,再恢复真正的 callback / 设备栈 / I/O 路径。** 看到 `.sys` 就按「DriverEntry → MajorFunction[] → IOCTL」一条线往下走,会漏掉 KMDF、NDIS miniport、StorPort miniport、文件系统 minifilter 等一大批真实驱动——它们的 WDM dispatch 由 framework/port driver 持有,真正的业务入口是**注册给 framework 的 callback**。七个最容易产生事实性错误的点:① 把所有 .sys 都当 WDM;② 把 GsDriverEntry 当业务入口;③ 把 MajorFunction 为空解释成"无 I/O";④ 不知道 KMDF/NDIS/minifilter 的 callback registration;⑤ 不知道 IRP completion 是异步控制流;⑥ 不知道 IOCTL 的 Method 决定 buffer 语义;⑦ 2026 年仍用旧的 Windows 驱动签名假设。
</CORE RULE>
一、先确认到底是哪一种 Windows driver
.sys
├─ raw WDM / NT driver
├─ KMDF
├─ PnP function driver
├─ upper / lower filter
├─ file-system minifilter
├─ legacy file-system filter
├─ NDIS miniport / filter
├─ StorPort / storage miniport
├─ AVStream / PortCls / 其他 miniport
└─ 特殊安全 / 虚拟化类 driver
另外:UMDF driver 通常不是内核 .sys 业务主体,而是由 Wudfhost.exe 装载的用户态 DLL。
WDM 直接处理 IRP;KMDF 则由 framework 接住 IRP,再调用驱动注册的 event callback。
经验:
MajorFunction[]看起来什么都没有 ≠ 这个 driver 没有 I/O 接口——它可能压根不是 raw WDM。
二、DriverEntry 不一定是真正的 DriverEntry
PE header 的 entry point 可能落到 GsDriverEntry(工具链自动生成的初始化 wrapper),之后才调用开发者写的 DriverEntry:
AddressOfEntryPoint → GsDriverEntry → security/runtime init → 真实 DriverEntry
反编译器把入口函数命名为 DriverEntry 也别盲信:看它有没有再调用一个 NTSTATUS f(PDRIVER_OBJECT, PUNICODE_STRING) 并完成 callback 初始化。否则容易把编译器包装层当成业务代码分析。
三、WDM:真正重要的是 DRIVER_OBJECT 赋值图
传统 WDM 的关键初始化:
DriverObject->DriverUnload / DriverObject->DriverExtension->AddDevice
DriverObject->MajorFunction[IRP_MJ_CREATE / _CLOSE / _READ / _WRITE
/ _DEVICE_CONTROL / _INTERNAL_DEVICE_CONTROL / _PNP ...]
MajorFunction 是 "IRP major code → dispatch routine" 的函数指针数组。所以 stripped WDM 最有效的逆向方法是找赋值点:
mov [DriverObject + xx], handler
然后恢复成映射表:
IRP_MJ_DEVICE_CONTROL → sub_140003820
IRP_MJ_CREATE → sub_140001A00
IRP_MJ_CLOSE → sub_140001A00
IRP_MJ_PNP → sub_140004C10
这比普通 call graph 有意义得多。逐项看 handler 时的关注点:
IRP_MJ_DEVICE_CONTROL(0x0E,用户态交互)、_INTERNAL_DEVICE_CONTROL(0x0F)、_CREATE(0x00)/_CLOSE(0x02)、_READ(0x03)/_WRITE(0x04)- 每个 handler 用
IoGetCurrentIrpStackLocation(irp)取参数 → 分支处理(IOCTL 码分派) - 参数校验(InputBufferLength/OutputBufferLength)——长度校验缺失是驱动类漏洞常见成因(漏洞面分析转 re-vuln 思路)
设备对象与符号链接:
IoCreateDevice的 DeviceName(\Device\MyDriver);IoCreateSymbolicLink的 SymbolicLinkName(\DosDevices\MyDriver→ 用户态\\.\MyDriver)- 没符号链接时用户态可直接
DeviceIoControl打\\.\名(或无符号名只能内部引用) - 动态核对:
!drvobj <名>看设备对象链、!devobj <设备>看设备名与 AttachedDevice——与静态字符串对照,确认运行期没被改 - 多层设备栈(filter 驱动):看到
IoAttachDevice/IoAttachDeviceToDeviceStack即为拦截型驱动(文件/键盘过滤等)
服务注册与加载入口:
- SCM 注册:
CreateServiceW(Type=SERVICE_KERNEL_DRIVER、Start=2 自动/3 手动/0 引导)或 INF 安装;恶意加载器常用Start=3+ 手动启动 - 注册表:
HKLM\SYSTEM\CurrentControlSet\Services\<驱动名>的ImagePath指向.sys - 启动失败码:577 = 签名错误、1275 = 未签名驱动被拒(x64)——先查测试签名状态再看代码
- 从用户态样本(re-binary-core)的创建服务调用反推驱动名,与静态
.sys对应
四、KMDF:千万别按 WDM 找
KMDF 中 framework 自己注册 WDM dispatch,再把 IRP 转成 WDFREQUEST / WDFDEVICE / WDFQUEUE,调用驱动注册的:
EvtDriverDeviceAdd / EvtIoRead / EvtIoWrite / EvtIoDeviceControl / EvtIoInternalDeviceControl / EvtDevicePrepareHardware / EvtDeviceD0Entry …
典型路径:
DriverEntry → WdfDriverCreate → EvtDriverDeviceAdd → WdfDeviceCreate → WdfIoQueueCreate → EvtIoDeviceControl / EvtIoRead / ...
特殊情况:
!drvobj显示的 MajorFunction 大部分指向Wdf01000.sys—— 这完全正常。 不要得出"驱动逻辑在 Wdf01000.sys":真正的业务入口是它交给 WDF 的 callback。
五、KMDF stripped 后找不到明显 callback 赋值
WDF 初始化大量使用 WDF_*_CONFIG / WDF_OBJECT_ATTRIBUTES 结构体:
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&cfg, ...);
cfg.EvtIoDeviceControl = Handler;
WdfIoQueueCreate(..., &cfg, ...);
优化器可能把这一过程拆得很碎:stack struct 初始化 → 某 offset 写函数地址 → WdfIoQueueCreate。
所以不要只搜 xref → WdfIoQueueCreate,要向前做 dataflow:
WDF_IO_QUEUE_CONFIG → 哪个 offset 被写了函数地址 → 对应哪个 EvtIo*
否则看到 sub_140017260 却不知道它实际是 EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL。
六、.sys 没有漂亮的 DriverEntry → IRP 路径,可能是 miniport
Windows 有大量「miniport + Microsoft port/framework driver」结构:
- NDIS:
vendor.sys+ndis.sys共同形成一个驱动层——vendor NDIS miniport 的 AddDevice、部分 IRP dispatch 都是 ndis.sys 提供的,vendor 通过 miniport callback 参与工作 - 所以
DriverObject->MajorFunction[]几乎都不是 vendor 的,不等于 vendor driver 没功能 - 要找的是
NdisMRegisterMiniportDriver/NdisFRegisterFilterDriver及注册结构里的 callback - 同样思路适用于 StorPort / PortCls / AVStream / 其他 class or port-miniport 架构
七、NDIS:别当普通 IRP 网络驱动
要恢复的是 miniport / filter 的 callback 图:
miniport: MiniportInitializeEx / MiniportHaltEx / MiniportPause / MiniportRestart
MiniportOidRequest / MiniportSendNetBufferLists / MiniportReturnNetBufferLists …
filter: FilterAttach / FilterDetach / FilterRestart / FilterPause
FilterSendNetBufferLists / FilterReceiveNetBufferLists / FilterOidRequest …
数据主体是 NET_BUFFER_LIST / NET_BUFFER,且 filter module 堆叠在 miniport 之上:
protocol → NDIS → filter A → filter B → miniport → NIC
所以看不到
IRP_MJ_READ/IRP_MJ_WRITE对网卡驱动完全不奇怪;不要强行找ReadFile()对应的 handler。
八、device stack 必须恢复(行为可能来自任何一层)
PnP 世界:Upper Filter → Function Driver (FDO) → Lower Filter → Bus Driver (PDO);I/O Manager 把 IRP 交给最上面的 device object,再由驱动继续向下传。
典型误判:
应用打开 Device X → 某行为发生 → 一定是 X 的 function driver 做的
不一定——行为可能来自 upper filter / lower filter / class filter / bus filter。动态分析真正应恢复:
DeviceObject → AttachedDevice chain → DriverObject → image owner
而不只是 \Device\Foo → foo.sys。
九、INF 往往比 .sys 更快解释驱动是什么
驱动包通常是 .sys + .inf + .cat (+ coinstaller/DLL/firmware)。INF 直接给出:
Hardware ID / Compatible ID / service name / ServiceBinary / StartType / class / AddService / UpperFilters / LowerFilters / AddFilter
- Win10 1903+ 的设备 filter 可用
AddFilter声明 upper/lower/filter level;更老的驱动大量通过注册表UpperFilters/LowerFilters插入设备栈 - 只有
foo.sys时,你丢失的不只是安装文件——而是foo.sys在整个设备栈中的位置
十、文件系统 minifilter:完全是另一套世界
看到 FltRegisterFilter 应立即切换模型:
FLT_REGISTRATION → FLT_OPERATION_REGISTRATION[]
IRP_MJ_CREATE → PreCreate / PostCreate
IRP_MJ_READ → PreRead / PostRead
IRP_MJ_WRITE → PreWrite / PostWrite
IRP_MJ_SET_INFORMATION → ...
FltMgr 按 altitude 排序 minifilter:pre-operation 从高 altitude 往低走,post-operation 返回时反向执行。所以:
PreRead A → PreRead B → filesystem → PostRead B → PostRead A
不是奇怪的 recursion,而是正常的 Filter Manager pipeline。
十一、Altitude 不是随便写的版本号
minifilter 的 Altitude = "320000" 不是 build ID、优先级数值或版本号——它决定该 filter 在 minifilter stack 中的位置,且 Microsoft 分配 altitude 范围。
因此分析多 filter 相互作用("A 抢先看到文件""B 为什么看到的是修改后的请求")必须考虑 altitude。
CmRegisterCallbackEx(注册表回调)同样带 altitude,并用于 callback ordering。
十二、IOCTL 先解 CTL_CODE
一个 IOCTL(如 0x222004)不要一直当 magic integer——拆成 DeviceType / Access / Function / Method:
| Method | 数据在哪 |
|---|---|
METHOD_BUFFERED |
Irp->AssociatedIrp.SystemBuffer |
METHOD_IN_DIRECT / METHOD_OUT_DIRECT |
MdlAddress 与锁定/映射的页 |
METHOD_NEITHER |
可能直接携带原始用户态虚拟地址 |
Microsoft 明确规定 DeviceIoControl 的 transfer method 来自 IOCTL 自身的 TransferType。所以最好把 switch (IoControlCode) 恢复成带语义的注释再逆 handler:
IOCTL_800: Function = 0x800, Method = BUFFERED, Access = ANY
IOCTL_801: Function = 0x801, Method = NEITHER
十三、"内核直接解引用一个很低的地址":先确认是不是 METHOD_NEITHER 的用户指针
- BUFFERED 关注
AssociatedIrp.SystemBuffer;DIRECT 涉及MdlAddress - NEITHER 甚至可能直接携带原始用户态虚拟地址,I/O Manager 不负责替你锁定或验证(Microsoft 明确指出 neither I/O 的用户地址只在合适的调用线程上下文中才能安全访问)
所以反编译看到内核 dereference 一个"很低"的地址:
别立即判断"内核指针损坏" —— 先确认是不是 METHOD_NEITHER 的用户指针。
安全审计时这也应立即提高优先级:ProbeForRead / ProbeForWrite、try/except、RequestorMode、thread context 变得关键。
十四、IRP 控制流不是普通函数调用图(completion edge)
WDM 最大的坑之一是 asynchronous completion:
Dispatch → IoCopyCurrentIrpStackLocationToNext → IoSetCompletionRoutine
→ IoCallDriver → STATUS_PENDING → ……下层异步完成 → CompletionRoutine
IoCallDriver返回了 ≠ 这次 I/O 已结束- completion routine 返回
STATUS_MORE_PROCESSING_REQUIRED会阻止 I/O Manager 继续完成原 IRP,让当前 driver 接管后续 processing(Microsoft 有明确规定)
逆向 CFG 应增加一类边:completion edge——否则很多关键 cleanup / copy-back / retry 代码在普通静态 call graph 中像"没人调用"。
十五、STATUS_PENDING 不是失败
做驱动协议 RE 时,status = IoCallDriver(...); if (status == STATUS_PENDING) ... 不要按用户态直觉理解成"请求没完成/调用失败"——这是正常 asynchronous I/O。
真正终态在 IRP->IoStatus.Status 与 completion path。记录协议时应区分:
- dispatch return status
- final I/O completion status
十六、PAGE section 是语义,不只是 section 名
所有以 PAGE 开头的 section(PAGE / PAGEABC / PAGEDATA …)都被当作 pageable section。
- 函数在
PAGE→ 设计上它应只在允许 page fault 的 IRQL 上运行 - 相反:ISR / DPC / spinlock-held path /
DISPATCH_LEVEL+对应的代码必须 resident
所以 section 本身就是调用上下文线索。
十七、偶发 0xD1 不一定是随机内存破坏
DRIVER_IRQL_NOT_LESS_OR_EQUAL(0xD1)优先检查:当前 IRQL、fault address、读/写/执行、faulting instruction、目标代码/数据是否 pageable(典型原因:DISPATCH_LEVEL 或更高 IRQL 访问 pageable memory / 执行 pageable code,或访问无效、已释放的指针)。
特别阴的一类:
99.99% 都没问题,压力测试突然炸
可能只是那个 PAGE section 平时一直 resident,直到内存压力把它真正换出去。所以「平时运行正常」≠「IRQL/pageability 正确」。
十八、Driver Verifier 开着时行为可能故意"不正常"
Verifier 可主动做:Special Pool / Force IRQL Checking / Pool Tracking / I/O Verification / Deadlock Detection / DMA Verification / Low Resources Simulation / Force Pending I/O Requests / IRP Logging / 同步延迟模糊 / Code Integrity checking …
也就是说它会故意让 allocation 失败、强制 pending、改变时序、更激进地驱逐 pageable memory。因此:
- 实验机一开 Verifier 就大量
STATUS_INSUFFICIENT_RESOURCES→ 不要误判成"malware 正在耗尽资源",这可能是 verifier 的 fault injection - "只有开 Verifier 才 BSOD" 通常反而说明潜伏的 driver bug 被放大出来了
十九、Verifier 抓到 IRP 错误:别只看 crash 栈顶部
I/O Verification 追踪 IRP 生命周期,检测非法 IoCallDriver 参数、重复/错误 complete、IRP 还挂着 cancel routine 却 complete、IRQL 变化错误、已释放的 IRP、stack-lifetime 已结束的 event/IOSB……违反时触发 0xC9 DRIVER_VERIFIER_IOMANAGER_VIOLATION。
这类 crash 的最后一个函数可能只是 verifier 发现问题的位置,真正犯错的地方可能更早几十步。要跟完整条链:
allocation → forward → pending → cancel → completion → free
二十、静态地址 ≠ 运行态地址:先想 PE relocation
.sys 仍是 PE image;不能加载到 preferred ImageBase 时,loader 按 .reloc 应用 base relocation。
所以 IDA: sub_140012340 vs runtime: fffff806'72b12340 是正常的。做 memory-vs-disk 对比前必须先:
runtime VA → 减 runtime image base → RVA → 对比对应 section
否则会得到大量假 mismatch。
二十一、PDB 找到了也可能不是完整符号
Windows RE 的巨大优势是 PDB,但要区分 public symbols 与 private/full symbols:public 通常不给完整 local variables、static functions、source lines/type universe。
所以 nt!KeSomething 有名字、但大量内部 sub_xxx 没名字,不代表符号加载失败——可能只是你拿到的是 public symbols。
二十二、PDB 同名不代表匹配(signature + age)
symbol store 用 signature + age 选择版本——文件名相同没有任何意义。遇到:
*** WARNING: Unable to verify timestamp / symbols could not be loaded
先检查 exact binary/PDB matching;一般直接 .symfix → .reload /f → !sym noisy,比手工硬套一个"看起来版本一样"的 PDB 可靠。
二十三、!analyze -v 是入口,不是结论
Probably caused by : foo.sys 不能直接写成"foo.sys 一定是根因"——它可能只是:
- 最后解引用坏指针的人(受害者)
- 下层已经把内存写坏
- filter stack 中最后一个可识别模块
pool corruption / UAF / DMA corruption 场景下,真正写坏内存的 driver 往往早就离开栈了。继续结合 lm / locks / process / memory / bugcheck parameters。
二十四、UMDF:看到 DLL + Wudfhost,立刻换思路
UMDF driver 是 DLL,由 Wudfhost.exe 加载;Wudfhost 负责 I/O dispatch、驱动加载、驱动分层、线程池、与 kernel reflector 通信——一个 Wudfhost 还可能同时承载多个 UMDF driver/device stack。
所以 Wudfhost.exe 崩了不能推出"Wudfhost 自己有 bug"——先确定:哪个 UMDF DLL、哪个 device stack、哪个 callback。
!wdfkd.wdfumdevstacks 可直接枚举 UMDF 设备栈(kernel 调试或 attach 到 wudfhost.exe 的用户态调试都可用):
!process 0 0 wudfhost.exe # 找 host 进程
.process /P <addr> # 切到该进程
!wdfkd.wdfumdevstacks # device stack / PDO 名 / UMDriver image path / transfer mode
其他有用命令:!wdfkd.wdfumtriage、!wdfkd.wdfldr、!wdfkd.wdfumirps、!wdfkd.wdfumfile、!wdfkd.wdflogdump <drivername.dll> -m。
二十五、UMDF 崩溃 ≠ kernel 崩溃
UMDF 跑在用户态 host 里,所以 driver access violation 通常先杀 Wudfhost,而不是立即整个 Windows bugcheck。
- UMDF dump 位于
%ProgramData%\Microsoft\WDF(UMDF 2.15 / Win10 1507 起;此前在%windir%\system32\LogFiles\WUDF);用WinDbg -z <dmp>打开,!analyze对它同样有效 - WER 报告有三类事件:
WUDFHostProblem/WUDFUnhandledException/WUDFVerifierFailure(含 ExitCode、Operation、Message(编码 IRP 的 Major/MinorFunction)、HardwareId) - 所以确认是 UMDF 后:userspace dump / 进程上下文通常比一上来抓 kernel dump 更直接
二十六、"驱动存在但加载不了":先查签名与策略,不要先怀疑 DriverEntry
Win10 1607 起,新的 release kernel driver 原则上要通过 Microsoft Hardware Dev Center(WHCP)签名路径;PnP package、catalog、boot driver 又各有细节。而现在(2026 年)还有一条更硬的变化:
Microsoft 的 Windows Driver Policy 明确:2026 年 4 月安全更新后,旧式 cross-signed 驱动默认不再受信任,内核默认只接受 WHCP 签名的驱动(另有 Microsoft 维护的 allow list)。适用于 Win11 24H2 / 25H2 / 26H1 与 Server 2025 及后续版本。
实施方式是分阶段的:先进入评估模式(只审计不拦截;系统需满足约 100 小时运行 + 2–3 次启动相关条件才转入强制执行;期间若发现不合规的 cross-signed 驱动,会继续留在评估模式并重置计数);企业可用 Application Control for Business(原 WDAC)策略覆盖(策略须由 Secure Boot PK/KEK 中的权威签名)。
所以遇到:
十年前的
.sys、签名看起来完全存在、以前能加载、现在新系统突然 block
不要直接判断"签名损坏"——很可能是 legacy signing policy 已经过期。
二十七、Test Mode 也不是"未签名什么都能跑"
- 即使
TESTSIGNING ON,在启用 HVCI / Memory Integrity 的环境中,测试驱动仍要求有数字签名(只是可以用自建 test certificate) - 且 HVCI 仍可能否掉 test-signed 驱动(实践中需临时关掉 Memory Integrity 再装/测)
- 另外 Secure Boot 开启时
bcdedit /set testsigning on可能被阻止或失败(多数场景要关 Secure Boot)
所以 TESTSIGNING=ON + unsigned .sys + 加载失败 不能说"Test Mode 没生效"——还要看 HVCI / Memory Integrity、Secure Boot 与签名。加载失败的典型码:0xC0000428(STATUS_INVALID_IMAGE_HASH),CodeIntegrity 日志 Event ID 219。
二十八、HVCI 下老驱动的奇怪失败:可能是设计本身不兼容
HVCI(Memory Integrity)的核心:内核内存页永远不能同时可写+可执行(W+X),可执行代码不能被直接修改,内核内存变可执行只能经由 CI 验证。官方兼容要求:
- 默认 NX、用 NX 分配(
NonPagedPoolNx) - 不用既写又可执行的 section;不直接修改可执行系统内存
- 不在内核使用动态代码(自解密 stub、JIT、运行时生成代码)
- 不把数据文件当可执行加载;section 对齐为 0x1000 的整数倍
因此老驱动里若有:分配 RWX pool / 生成 trampoline / runtime patch kernel text / JIT code——HVCI off 时也许能跑,on 时可能直接失败。
不要第一时间认为"新 Windows 把 undocumented API 改了"——可能撞的是 CI/VBS 策略。
补充时间点:Microsoft 计划自 2026-10-13 起在符合条件的 Win11 设备上默认启用 Memory Integrity(前提之一正是"没有不兼容的内核驱动")。
二十九、x64 上"老教程的 kernel patching"思维尤其危险
Microsoft 明确要求 x64 driver 不应修改 kernel code、IDT/GDT、undocumented kernel structures——这类行为可能触发 CRITICAL_STRUCTURE_CORRUPTION(历史的 KPP 就是为保护内核代码与关键结构)。
所以分析现代合法驱动时,如果没看到 SSDT hook / inline ntoskrnl patch / IDT hook,不是"它功能少"——现代驱动更应该通过 documented callback、filter model、WFP、minifilter、Ob callbacks、process/thread/image notify、registry callback 实现观察与控制。
三十、安全产品不 hook SSDT 也完全可以深度监控进程
Windows 正式提供 PsSetCreateProcessNotifyRoutineEx*、ObRegisterCallbacks 等;Microsoft 自己的 sample 就演示「进程创建通知 + 进程句柄 access restriction」的组合。
所以看到 EDR/AV driver 没有 SSDT hook,绝对不能得出"它没监控进程"。应搜 callback-registration graph:
PsSetCreateProcessNotifyRoutine* / PsSetCreateThreadNotifyRoutine*
PsSetLoadImageNotifyRoutine* / ObRegisterCallbacks / CmRegisterCallbackEx
FltRegisterFilter / Ndis*Register* / StorPort / WDF 注册点 ...
这比找 inline hook 更适合现代 Windows。
三十一、一个函数没有任何 xref,也可能是 callback
Windows 内核与 Linux 一样高度 callback-driven:DriverObject dispatch、PnP、ISR/DPC、timer、work item、completion routine、KMDF event、NDIS callback、FltMgr callback、Ob callback、registry callback、process notify……
所以反编译器显示 0 callers 不能自动标 dead code。先看它的地址是否:
写进 registration structure / 传给 registration API
写进 DriverObject / 写进 WDF config
Windows driver RE 应同时维护 call graph 与 callback-registration graph。
三十二、同一个 IRP 被很多 driver 碰过是正常的
app → upper filter → function driver → lower filter → bus
每层都可能:修改 stack location、设置 completion routine、forward、complete。所以看到「同一个 MajorFunction 在四个 .sys 里都有 handler」不是重复实现——这正是 Windows layered I/O model。
逆向时最好恢复 IRP flow graph,而不只是每个 driver 单独的 CFG。
三十三、Minifilter unload 后还有 completion 行为,不一定 UAF
Filter Manager 专门负责 minifilter 生命周期,并能协调 unload 时仍未完全结束的操作(也允许 minifilter 安全动态卸载)。
所以捕获 trace 时看到「开始 unload → 还有旧 I/O completion → 最后才彻底消失」并不自动意味着"已卸载代码仍被调用"。需要精确区分四态:
unload initiated → instance detach → outstanding operations drain → image actually gone
决策树
拿到 Windows driver
├─ 先确定模型:WDM / KMDF / UMDF / minifilter / NDIS / miniport / filter driver
├─ entry 看起来奇怪
│ ├─ GsDriverEntry? framework wrapper? 真正 DriverEntry 在下一层?
├─ 找不到 IRP handler
│ ├─ KMDF callback? NDIS/miniport callback? minifilter callback? UMDF?
├─ 恢复安装上下文:INF / service / hardware ID / UpperFilters·LowerFilters / AddFilter
├─ 恢复 I/O
│ ├─ DriverObject MajorFunction / IOCTL CTL_CODE / buffering method
│ ├─ IRP stack / pending / completion edge
├─ 文件系统:FltRegisterFilter / FLT_REGISTRATION / pre-post callback / altitude
├─ 地址·符号怪
│ ├─ PE relocation? runtime image base? PDB exact match? public vs private PDB?
├─ 随机 crash
│ ├─ IRQL? PAGE section? UAF/pool? pending/completion lifetime? DMA?
│ └─ 是否开着 Driver Verifier?
├─ driver 加载失败
│ ├─ architecture? signing? 2026 legacy cross-sign policy? Secure Boot? HVCI? INF/PnP 绑定?
└─ 怀疑 rootkit
├─ 不要只找 SSDT → callback ownership / filter·device stack / registered notify callbacks
├─ loaded image · executable memory · runtime code integrity
└─ cross-view + dump analysis
工具准备
参考 re-analyze/platform-tips——驱动加载/内核调试属动态执行,默认沙箱(调试机 VM)内进行;静态分析部分免沙箱。
- 反编译与驱动类型:re-ghidra(默认;Data Type Manager 导入 Windows 内核类型,字段名直接参与反编译)/ re-ida(FLIRT 内核签名 + 类型库)。验证:导入
.sys后能反编译出带参数签名的函数 - WinDbg 内核调试(re-windbg):双机/VM 串口或 KDNET。验证:
lm看到目标驱动、.reload /f <驱动名>加载符号 - 符号:Microsoft 公共符号
srv*C:\symbols*https://msdl.microsoft.com/download/symbols;驱动自带 PDB 同名放置;遇到匹配问题用.symfix+!sym noisy。验证:!process 0 0输出带nt!前缀 - 测试签名(仅调试 VM,且注意 HVCI):
bcdedit /set testsigning on重启(多数场景需关 Secure Boot);验证bcdedit /enum {current}为 Yes - WDF/UMDF 扩展:
!wdfkd.wdfumdevstacks/wdfumtriage/wdfldr/wdfumirps/wdflogdump;UMDF dump 在%ProgramData%\Microsoft\WDF - Verifier:
verifier(分析目标 bug 时先确认它开没开,见 §18–19)
该平台的坑(汇总)
- 把所有
.sys当 WDM:KMDF/NDIS/miniport/minifilter 的 WDM dispatch 由 framework 持有 - 把
GsDriverEntry当业务入口;把MajorFunction为空当"无 I/O" - 在 KMDF 上找
IoCallDriver链路:!drvobj指向 Wdf01000.sys 是正常的,去找 Evt* callback(含WDF_*_CONFIG的 dataflow) - 在 miniport 上找 IRP handler:去找
Ndis*/StorPort/PortCls注册 callback;网卡驱动没有 READ/WRITE 也正常 - 把设备行为只归给 function driver:先恢复 AttachedDevice 链与 image owner
- 漏看 INF:
UpperFilters/LowerFilters/AddFilter决定它在设备栈中的位置 - 把 minifilter 的 pre/post 流水线当 recursion;把 altitude 当版本号
- 把 IOCTL 当 magic integer:先解
CTL_CODE(尤其 Method) - 把
METHOD_NEITHER的用户指针当"内核指针损坏" - 把
IoCallDriver返回当 I/O 结束、把STATUS_PENDING当失败 - 忽略
PAGEsection 的 IRQL 语义,把偶发 0xD1 当随机破坏 - 把 Driver Verifier 的故障注入当恶意行为;只看 0xC9 栈顶
- 忘了 PE relocation 就做内存-磁盘对比;拿文件同名当 PDB 匹配
- 把
!analyze -v的 "Probably caused by" 当结论 - 把 Wudfhost 崩溃当它自己的 bug、把 UMDF 崩溃按 kernel crash 分析
- 2026 年仍按旧签名假设判断"加载不了"(legacy cross-sign 已默认不受信;Test Mode 在 HVCI 下也要签名)
- 把 HVCI 下的失败当"API 变了"(是 W+X/动态代码等设计不兼容)
- 只找 SSDT hook 判断安全产品有没有监控;把 0 xref 当 dead code
附:Windows 专属的两份附录
- windows-gotchas —— 版本差异组:结构布局(EPROCESS/DRIVER_OBJECT/_KPCR 偏移随 build 变)、导出符号(
KeServiceDescriptorTableWin8 起不导出)、签名要求(attestation 等)、PatchGuard 随版本变强——同一条经验在不同 Windows 版本可能给出相反结论 - windows-decision-tree —— Windows 专属场景决策树与证据分级(目标类型 → 静态主线 / 交互闭环 / rootkit 分支)