内核级攻击

· 2026-08-31 22:00 · 5 阅读

原创 pandazhengzheng 2026-08-31 22:00 广东

目录

  1. 为什么需要内核级攻击

  2. 用户态免杀的极限

  3. 进入内核的途径

  4. BYOVD(Bring Your Own Vulnerable Driver)

  5. 内核读写原语的构建

  6. 关闭 EDR 内核回调

  7. 关闭 ETW-TI

  8. 对 EDR 驱动与用户态进程的攻击

  9. DKOM 与内核结构操作

  10. PatchGuard 与稳定性挑战

  11. EDR 的内核态防御与演进

  12. 完整内核级免杀链

  13. 硬件辅助防御与未来

  14. 内核数据结构的深度剖析

  15. BYOVD 的实战细节与漏洞驱动剖析

  16. PatchGuard 的深度分析与规避策略

  17. 完整内核级免杀链的实战编排

  18. 小结


1. 为什么需要内核级攻击

1.1 用户态无法触及的信号源

EDR 最强的信号源全部位于内核:

  • **PsSetCreateProcessNotifyRoutineEx**:进程创建/退出通知;

  • **PsSetCreateThreadNotifyRoutine**:线程创建/退出通知;

  • **PsSetLoadImageNotifyRoutine**:映像加载通知;

  • **ObRegisterCallbacks**:句柄创建/复制回调;

  • **CmRegisterCallbackEx**:注册表操作回调;

  • MiniFilter:文件系统操作回调;

  • WFP/NDIS:网络回调;

  • ETW-TI:内核埋点的高保真事件。

这些信号源由操作系统在固定内核路径触发,用户态进程无法"不经过"这些路径,也无法在用户态关闭它们。只要系统正常运行,这些信号必然产生。

1.2 用户态免杀的极限

前四节的技术组合后,用户态检测面基本覆盖,但:

  • shellcode 的每一次系统调用仍被 ETW-TI 记录;

  • 进程/线程/映像/句柄操作仍触发内核回调;

  • EDR 的内核驱动仍正常工作,汇总所有信号。

结论:对于依赖内核态高保真信号的 EDR(如 Elastic Defend),纯粹的用户态免杀不足以完全逃过检测。攻击者若要彻底"失聪"EDR,必须进入内核关闭这些信号源。

1.3 内核级攻击的收益与代价

收益

  • 使 EDR 的内核信号源整体失效;

  • 可直接操作内核数据结构,绕过所有用户态与内核态检查;

  • 可破坏 EDR 驱动本身,使其静默或崩溃。

代价

  • 需要漏洞驱动或内核漏洞(门槛高、易被黑名单);

  • 需要规避 PatchGuard(稳定性挑战);

  • 内核操作一旦失误即 BSOD,隐蔽性反而下降;

  • 内核操作本身产生新信号(驱动加载、内核修改),形成新检测面。


2. 用户态免杀的极限

2.1 各检测面的用户态可绕过性

检测面

用户态可绕过性

说明

inline hook

Indirect Syscalls

用户态 ETW

patch EtwEventWrite

内存扫描

Module Stomping + Sleep Mask

调用栈

✓/部分

Indirect Syscalls + Call Stack Spoofing

行为序列

部分

拆分、噪声、LOLBins

ETW-TI

内核埋点,用户态无法绕过

内核回调

OS 固定路径,用户态无法绕过

PPL 自保护

需内核操作或 PPL 绕过漏洞

2.2 ETW-TI 的不可绕过性

ETW-TI 的埋点位于内核深处(如 MiAllocateVirtualMemory 内部、NtQueueApcThread 的内核实现内),在系统调用的内核处理路径上。即使用户态不经过 ntdll stub(Indirect Syscalls),syscall 指令仍进入内核,仍经过这些埋点。用户态无法关闭、绕过或篡改 ETW-TI 事件

唯一方式:进入内核,修改 ETW-TI Provider 的 EnableInfo(使其认为没有消费者),或修改 EDR 驱动中订阅 ETW-TI 的逻辑。

2.3 内核回调的不可绕过性

PsSetCreateProcessNotifyRoutineEx 等回调在 NtCreateUserProcessPspInsertProcess 等内核函数的固定路径上调用。只要进程创建,回调必然触发。用户态无法"不创建进程"或"不让回调触发"。

唯一方式:进入内核,从回调数组中移除 EDR 注册的回调项。


3. 进入内核的途径

3.1 途径概览

途径

门槛

稳定性

隐蔽性

BYOVD(漏洞驱动)

中(驱动加载有信号)

内核漏洞利用

极高

高(无驱动加载)

早期内核介入(bootkit/UEFI)

极高

高(但 Secure Boot 阻拦)

合法签名驱动 + 漏洞

PPL 绕过漏洞

3.2 BYOVD 的主导地位

现代 Windows 64 位强制 驱动签名强制(DSE),仅加载经微软签名(含 EV、Attestation、WHQL)的驱动。攻击者无法直接加载自写的未签名驱动。但 已签名但存在漏洞的合法驱动 可正常加载,其漏洞可被利用获得内核读写原语——这就是 BYOVD(Bring Your Own Vulnerable Driver)。

BYOVD 是当前最主流的进入内核途径,因其:

  • 门槛适中:漏洞驱动公开可用;

  • 稳定性高:合法驱动加载稳定,漏洞利用成熟;

  • 无需内核漏洞:不依赖 Windows 内核零日。


4. BYOVD(Bring Your Own Vulnerable Driver)

4.1 原理

  1. 获取一个已签名但存在漏洞的合法驱动(如 RTCore64.sysgigabyte.sysdbutil_2_3.sysprocexp152.sysiqvw64e.sys 等);

  2. 将驱动落盘(其签名合法,可正常通过 NtLoadDriver 加载);

  3. 通过驱动的 IOCTL 漏洞获得 任意地址读写 原语;

  4. 利用该原语定位并关闭 EDR 回调。

4.2 典型漏洞驱动

驱动

厂商

漏洞

原语

RTCore64.sys

Micro-Star (MSI)

任意物理地址读写

物理内存读写

gigabyte.sys

Gigabyte

任意 MSR/IO 读写

内核代码执行

dbutil_2_3.sys

Dell

任意内核地址读写

内核读写

procexp152.sys

Sysinternals (ProcExp)

任意内核地址读写

内核读写

iqvw64e.sys

Intel

任意内核地址读写

内核读写

gdrv.sys

Gigabyte

任意物理/内核读写

内核读写

amp.sys

Palo Alto

任意内核读写

内核读写

关键:这些驱动签名合法(WHQL 或 EV),可正常加载。漏洞在于其 IOCTL 处理例程未校验用户态传入的地址/大小,允许用户态指定任意内核地址读写。

4.3 EDR 的反制:漏洞驱动黑名单

EDR(包括 Elastic Defend)维护一个 漏洞驱动黑名单,在映像加载回调(PsSetLoadImageNotifyRoutine)中拦截已知漏洞驱动:

  • 检测驱动映像名、哈希、签名者;

  • 命中黑名单 → 阻断加载(返回 STATUS_ACCESS_DENIED)或加载后立即卸载。

攻击者的应对

  • 使用尚未被列入黑名单的漏洞驱动(零日或新披露);

  • 在 EDR 驱动就位前加载(早期启动);

  • 篡改 EDR 的黑名单数据结构(需已进入内核,鸡生蛋问题);

  • 修改漏洞驱动的映像名/哈希以绕过黑名单匹配(但签名校验仍需通过)。

4.4 BYOVD 的加载流程

// 1. 落盘漏洞驱动
DWORD len = decodeBase64(vulnDriver, driverPath);
// 2. 创建服务并加载
SC_HANDLE hSvc = CreateService(hSCM, "vulndrv", ..., SERVICE_KERNEL_DRIVER, 
                                SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, driverPath);
StartService(hSvc, 0NULL);
// 3. 打开设备句柄
HANDLE hDev = CreateFile("\\\\.\\vulndrv", ...);
// 4. 通过 IOCTL 获得内核读写原语
DeviceIoControl(hDev, IOCTL_READ, &readReq, ..., &result, ...);

4.5 从物理地址读写到虚拟地址读写

部分漏洞驱动提供物理地址读写(如 RTCore64.sys)。需将物理地址读写转换为虚拟地址读写

  1. 利用 MmGetPhysicalAddress 的逆运算,或通过页表自映射(PML4)将虚拟地址翻译为物理地址;

  2. 或利用漏洞驱动的其他能力(如 gdrv.sys 可直接读写虚拟地址)。

更直接的方式:寻找提供虚拟地址读写的漏洞驱动(如 dbutil_2_3.sys),无需转换。


5. 内核读写原语的构建

5.1 原语接口

内核读写原语提供两个操作:

BOOL kRead(PVOID kAddr, PVOID buf, SIZE_T len);  // 读内核内存
BOOL kWrite(PVOID kAddr, PVOID buf, SIZE_T len)// 写内核内存

5.2 通过漏洞驱动 IOCTL

structReadReq { PVOID kAddr; PVOID uBuf; SIZE_T len; };
structWriteReq { PVOID kAddr; PVOID uBuf; SIZE_T len; };
BOOL kRead(PVOID kAddr, PVOID buf, SIZE_T len){
    ReadReq req = { kAddr, buf, len };
    DWORD ret;
return DeviceIoControl(hVulnDev, IOCTL_ARBITRARY_READ, &req, sizeof(req), &ret, sizeof(ret), &ret, NULL);
}

5.3 原语的扩展能力

有了任意内核读写,可:

  • 解析 ntoskrnl 导出:读 PsLoadedModuleList 找到 ntoskrnl.exe 基址,解析其导出表获取 PsSetCreateProcessNotifyRoutine 等符号地址;

  • 扫描未导出符号:通过特征码扫描或从 PDB 获取偏移,定位未导出的全局数组(如 PspCreateProcessNotifyRoutine);

  • 读写 EDR 驱动内存:定位 EDR 驱动的关键数据结构(规则数组、回调上下文);

  • 修改内核数据结构:如 EPROCESS.TokenEPROCESS.ActiveProcessLinks


6. 关闭 EDR 内核回调

6.1 回调的内核数据结构

跳转微信打开