内核级攻击
原创 pandazhengzheng 2026-08-31 22:00 广东

目录
为什么需要内核级攻击
用户态免杀的极限
进入内核的途径
BYOVD(Bring Your Own Vulnerable Driver)
内核读写原语的构建
关闭 EDR 内核回调
关闭 ETW-TI
对 EDR 驱动与用户态进程的攻击
DKOM 与内核结构操作
PatchGuard 与稳定性挑战
EDR 的内核态防御与演进
完整内核级免杀链
硬件辅助防御与未来
内核数据结构的深度剖析
BYOVD 的实战细节与漏洞驱动剖析
PatchGuard 的深度分析与规避策略
完整内核级免杀链的实战编排
小结
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 等回调在 NtCreateUserProcess、PspInsertProcess 等内核函数的固定路径上调用。只要进程创建,回调必然触发。用户态无法"不创建进程"或"不让回调触发"。
唯一方式:进入内核,从回调数组中移除 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 原理
获取一个已签名但存在漏洞的合法驱动(如
RTCore64.sys、gigabyte.sys、dbutil_2_3.sys、procexp152.sys、iqvw64e.sys等);将驱动落盘(其签名合法,可正常通过
NtLoadDriver加载);通过驱动的 IOCTL 漏洞获得 任意地址读写 原语;
利用该原语定位并关闭 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, 0, NULL);
// 3. 打开设备句柄
HANDLE hDev = CreateFile("\\\\.\\vulndrv", ...);
// 4. 通过 IOCTL 获得内核读写原语
DeviceIoControl(hDev, IOCTL_READ, &readReq, ..., &result, ...);4.5 从物理地址读写到虚拟地址读写
部分漏洞驱动提供物理地址读写(如 RTCore64.sys)。需将物理地址读写转换为虚拟地址读写:
利用
MmGetPhysicalAddress的逆运算,或通过页表自映射(PML4)将虚拟地址翻译为物理地址;或利用漏洞驱动的其他能力(如
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.Token、EPROCESS.ActiveProcessLinks。