Files
reverse-skills-max/vendor-new/rev-skills/.claude/skills/re-kernel/references/windows-kernel.md

28 KiB
Raw Blame History

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 当失败
  • 忽略 PAGE section 的 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 变)、导出符号(KeServiceDescriptorTable Win8 起不导出)、签名要求(attestation 等)、PatchGuard 随版本变强——同一条经验在不同 Windows 版本可能给出相反结论
  • windows-decision-tree —— Windows 专属场景决策树与证据分级(目标类型 → 静态主线 / 交互闭环 / rootkit 分支)