EDR事件采集与遥测机制
原创 pandazhengzheng 2026-09-17 22:00 广东

目录
事件接收总览
用户态 Hook
内核遥测:ETW
内核遥测:ETW-TI
内核回调
AMSI 机制
传感器注入时序与 KAPC
遥测的不可绕过性与信任梯度
小结
1. 事件接收总览
1.1 从执行到遥测
一旦 .exe 二进制通过 AV 签名扫描和 AV 模拟(-内存)扫描,它被允许执行。
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────────┐ ┌───────────┐
│ File Write / │ │ Antivirus Scan │ │ Antivirus Emulation │ │ Execution │
│ File Read ├──►│ File ├───►│ Memory ├───►│ Make │
│ │ │ Signature │ │ Signature │ │ Process │
│ │ │ Scanning │ │ Scanning │ │ │
└─────────────────┘ └──────────────────┘ └──────────────────────┘ └───────────┘进程要做任何有用的事(打开文件、创建网络连接、显示窗口),都需要与 Windows 内核交互。没有绕过内核的方法。Microsoft 使 Windows 内核能够生成关于其操作的数据,并允许软件消费这些数据(事件)。EDR 就是这样的消费者。
1.2 事件管道
Process
┌────────────────┐ ┌─────────────┐
│ │ │ │
│ │ │ Windows │
│ │ │ kernel │
├────────────────┤ Syscalls │ │
│ (Hooked) ├───────────────────►│ │
│ │ │ │
│ ntdll.dll ├─────────────────┐ │ │
│ NtApi │ Usermode │ └──────┬──────┘
├────────────────┤ Hooks │ │
│ │ │ │ kernel
│ │ │ │ ETW
│ │ ▼ ▼
│ │ ┌────────────────────────┐
│ │ │ EDR │
│ │ └────────────────────────┘
└────────────────┘ 1.3 两个主要通道
EDR 通过两个主要通道接收数据:
用户态(hooked API):EDR 注入的 DLL hook 了
ntdll.dll的关键函数;内核回调(ETW、ETW-TI、内核回调):内核驱动注册的回调。
这些传感器在系统中的某些东西被添加/删除/改变时产生事件:
文件
注册表键
进程、线程
内存区域
1.4 规则匹配
EDR 包含规则以匹配事件的恶意行为。规则可以是:
精确/脆弱:很好地检测一个特定事物(低误报 FP),易于绕过;
稳健:更通用的检测,更难绕过,更高 FP,更多异常。
1.5 进程内部数据修改的不可见性
关键认知:EDR 不感知进程内部的数据修改本身。进程调用
ntdll!RtlCopyMemory可能产生遥测(ntdll 可被 hook),但用 for 循环逐字节拷贝不产生任何遥测。
这是"data-only 操作"的核心优势——在进程内部用非 API 方式操作数据,不产生遥测。
1.6 扩展的传感器视图
┌──────────────┐
┌─────────────┐ EtwWrite() ┌──────────┐ Kernel callbacks │ │
│ Process ├───────────►│ ├─────────────────────►│ │
│ │ │ │ │ │
├─────────────┤ │ OS │ ETW │ │
┌───────┤ ntdll.dll │ │ ├─────────────────────►│ EDR │
│ ┌───►│ │ syscall │ │ ETW-TI │ │
│ │ ├─────────────┤ │ ├─────────────────────►│ │
│ │ │ amsi.dll │ pipe │ │ │ │
│ └────┤ ├───────────────────────────► AMSI │ │
└──────►│ │ │ │
└─────────────┘ └──────────────┘ EDR 输入源:
用户态 hook / AMSI
内核回调
ETW
ETW-TI
2. 用户态 Hook
2.1 ntdll.dll 的角色
Windows 的官方内核接口是 ntdll.dll(它为我们发出 syscall)。这称为原生 API(NtAPI)。ntdll.dll 为我们调用正确的 syscall。WinAPI(kernel32.dll 等其他 DLL)最终都调用 NtAPI。
WinAPI NtApi Kernel
┌────────────────┐ ┌────────────────┐ ┌─────────────────────────┐ ┌───────────────────────┐
│ kernel32.dll ├──►│ kernelbase.dll ├──►│ ntdll.dll │syscall│ │Kernel │
│ OpenProcess │ │ OpenProcess │ │ NtOpenProcess ├───────┤►│NtOpenProcess │
└────────────────┘ └────────────────┘ └─────────────────────────┘ └───────────────────────┘注意:syscall 编号可能随 Windows 版本变化,因此硬编码不可靠。
2.2 NtAPI 函数的汇编形式
ntdll.dll 中 NtAPI 函数的典型形式,用 syscall 汇编指令执行系统调用:
SysNtCreateFile proc
mov r10, rcx
mov eax, 55h ; SSN (System Service Number)
syscall
ret
SysNtCreateFile endp2.3 Hook 的安装
用户态 hook 是 ntdll.dll 导出函数中的补丁,在实际函数执行前调用另一个 DLL 函数。Windows 提供直接 hook 函数/IAT 的功能。
原始函数(磁盘): EDR Hook 后(内存):
---------------------- -----------------------
mov r10, rcx mov r10, rcx
>mov eax, 50h jmp 0x7ffaeadea621 ← hook 跳转
test byte ptr [0x7FFE0h], 1 test byte ptr [0x7FFE0h], 1
jne 0x17e76540ea5 jne 0x17e76540ea5
syscall syscall
ret ret2.4 常被 hook 的 ntdll 函数
函数名 | 相关攻击者技术 |
|---|---|
NtOpenProcess | 进程注入 |
NtAllocateVirtualMemory | 进程注入 |
NtWriteVirtualMemory | 进程注入 |
NtCreateThreadEx | 进程注入 |
NtSuspendThread | APC shellcode 注入 |
NtResumeThread | APC shellcode 注入 |
NtQueueApcThread | APC shellcode 注入 |
EDR 接收函数调用名及其参数作为遥测。
2.5 KAPC 注入安装 hook
hook 安装通过内核回调(PsSetCreateProcessNotifyRoutine)在新进程创建早期获通知,然后向进程注入 DLL(如 amsi.dll),创建 APC 调用 DLL 中修补原始 ntdll.dll 函数的函数(或通常修补 DLL 导入地址表),使调用绕道进入 amsi.dll。
ntdll.dll 被修补后,每次函数调用都被 amsi.dll 拦截。
EDR 的 KAPC hook 安装:创建执行 hooking 的 APC。"Early Bird APC injection"技术使用相同的 APC 机制,因此可在 KAPC hooking 执行之前运行。
2.6 用户态 hook 的绕过
用户态 hook 可通过以下方式绕过:
Direct syscalls:避免调用
ntdll.dll;Indirect syscalls:调用
ntdll.dll函数,但在 hook 之后;**Patching/restoring
ntdll.dll**:完全移除 hook。
用户态 hook 易于绕过,因为它们完全位于"我们自己的"内存空间,可自由修改。但恢复 ntdll.dll 本身会产生遥测,需使用 direct syscalls 等。
2.7 用户态 hook 的定位与局限
EDR 不应仅依赖用户态 hook,而仅用于辅助遥测。它们提供比内核遥测(如 ETW)更多的信息。内核只"看到" syscall/ntdll.dll 函数,而非最初发起的原始函数。这很有用,因为它生成更通用的检测,无需 hook 所有的 DLL 函数。但可能产生更多误报,因为仅凭 syscall 更难识别"非恶意"行为(可用信息更少)。
例如,CreateFileA()、CreateFileW()、OpenFile() 和 CreateFileTransacted() 最终都调用 NtCreateFile()。
注意:调用栈可显示链中最初调用了哪个函数。