4、用户态监控:API Hook技术、ETW利用、用户态日志采集
聊到EDR的用户态监控,我脑子里第一个蹦出来的词就是「暗战」。
为什么这么说?因为攻击者最常活动的区域就在用户态。他们调用API读写文件、创建进程、注入代码,这些动作全得经过操作系统提供的接口。而我们EDR要做的,就是在这些接口上「安插眼线」。
说白了,用户态监控就是一场钩子与反钩子的博弈。今天我就把三种最核心的手段掰开揉碎了讲给你听。
4.1 API Hook技术:最经典的「截胡」手段
API Hook,顾名思义,就是把系统API的执行流程给「钩」住。当某个进程想调用CreateFile打开一个恶意文件时,我们的代码先于原始函数执行,检查参数、记录日志,甚至直接拦截。
常见的Hook点:
- IAT Hook:修改导入地址表,把函数地址指向我们的检测函数。简单,但容易被绕过。
- Inline Hook:直接修改函数前几个字节,跳转到我们的代码。我在项目中遇到过,有些恶意软件会做完整性校验,一改就崩。
- Detour Hook:微软的Detours库提供的方式,相对稳定,但部署起来稍重。
核心思路:Hook不是目的,上下文才是。你不仅要记录「调用了什么API」,还要记录「谁调用的、参数是什么、调用栈长什么样」。
举个例子,检测进程注入时,我会重点关注OpenProcess + VirtualAllocEx + WriteProcessMemory + CreateRemoteThread这个组合拳。单独看每个API都合法,但串在一起,基本就是恶意行为。
// 伪代码:Inline Hook 示例
// 原始函数地址
LPVOID pOriginalCreateFile = GetProcAddress(GetModuleHandle("kernel32.dll"), "CreateFileW");
// 保存前5字节
BYTE originalBytes[5];
memcpy(originalBytes, pOriginalCreateFile, 5);
// 写入JMP指令跳转到我们的检测函数
BYTE jmpCode[5] = { 0xE9, 0x00, 0x00, 0x00, 0x00 };
DWORD dwRelativeOffset = (DWORD)MyCreateFileHook - (DWORD)pOriginalCreateFile - 5;
memcpy(&jmpCode[1], &dwRelativeOffset, 4);
WriteProcessMemory(GetCurrentProcess(), pOriginalCreateFile, jmpCode, 5, NULL);
// 检测函数
HANDLE WINAPI MyCreateFileHook(LPCWSTR lpFileName, DWORD dwDesiredAccess, ...) {
// 记录日志
LogApiCall("CreateFileW", lpFileName, GetCurrentProcessId());
// 恢复原始字节,调用原始函数
WriteProcessMemory(GetCurrentProcess(), pOriginalCreateFile, originalBytes, 5, NULL);
HANDLE hResult = CreateFileW(lpFileName, dwDesiredAccess, ...);
// 重新Hook
WriteProcessMemory(GetCurrentProcess(), pOriginalCreateFile, jmpCode, 5, NULL);
return hResult;
}
避坑指南:我曾经在Hook NtCreateThreadEx时,没处理好递归调用,导致死循环蓝屏。记住:Hook函数内部绝对不能直接调用被Hook的原始函数,必须先恢复原始字节,调用完再重新Hook。
4.2 ETW(Event Tracing for Windows):微软官方给的「后门」
说实话,我个人越来越喜欢用ETW。为什么?因为它是操作系统原生支持的,不需要修改任何代码,稳定性比API Hook高一个量级。
ETW的原理很简单:Windows内核和许多系统组件(如.NET、CLR、内核)都会在关键操作时主动抛出事件。我们只需要写一个消费者程序,订阅这些事件即可。
EDR常用的ETW Provider:
| Provider名称 | GUID | 监控内容 |
|---|---|---|
| Microsoft-Windows-Kernel-Process | {22FB2CD6-0E7B-422B-A0C7-2FAD1FD0E716} | 进程创建、终止、线程创建 |
| Microsoft-Windows-Kernel-File | {EDD08927-9CC4-4E65-B970-C2560FB5C289} | 文件创建、删除、重命名、读写 |
| Microsoft-Windows-Kernel-Registry | {70EB4F03-C1DE-4F73-A051-33D13D5413BD} | 注册表操作 |
| Microsoft-Windows-DotNETRuntime | {E13C0D23-CCBC-4E12-931B-D9CC2EEE27E4} | .NET程序集加载、JIT编译 |
你想想看,攻击者用mimikatz抓密码,或者用cobalt strike的execute-assembly加载.NET payload,这些动作都会触发ETW事件。我们只要订阅了对应的Provider,就能零侵入地拿到第一手情报。
我的经验:ETW的缺点是事件量巨大。一个正常的Windows系统每秒可能产生数万个事件。我建议在EDR中做两级过滤:第一级在内核态或驱动层做粗筛,只保留关键事件;第二级在用户态做深度分析。
// 伪代码:ETW消费者订阅示例
// 1. 开启Trace
EVENT_TRACE_LOGFILEW traceLogFile = { 0 };
traceLogFile.LoggerName = L"EDR-Process-Monitor";
traceLogFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD;
traceLogFile.EventRecordCallback = (PEVENT_RECORD_CALLBACK)EventRecordCallback;
TRACEHANDLE hTrace = OpenTraceW(&traceLogFile);
if (hTrace == INVALID_PROCESSTRACE_HANDLE) {
// 处理错误
}
// 2. 启用指定的Provider
ENABLE_TRACE_PARAMETERS enableParams = { 0 };
enableParams.Version = ENABLE_TRACE_PARAMETERS_VERSION_2;
EnableTraceEx2(
hTrace,
&GUID_KERNEL_PROCESS, // 进程Provider
EVENT_CONTROL_CODE_ENABLE_PROVIDER,
TRACE_LEVEL_INFORMATION,
0,
0,
0,
&enableParams
);
// 3. 开始处理事件
ProcessTrace(&hTrace, 1, 0, 0);
// 4. 回调函数
VOID WINAPI EventRecordCallback(PEVENT_RECORD pEventRecord) {
// 解析事件头,获取进程ID、时间戳等
// 根据EventHeader.EventId判断事件类型
// 记录到日志或发送到分析引擎
}
4.3 用户态日志采集:把散落的信息「串」起来
API Hook和ETW解决了「谁干了什么」的问题,但日志采集解决的是「当时环境什么样」的问题。
我个人习惯把用户态日志采集分为三类:
- 系统日志:Windows Event Log(Security、System、Application),比如登录成功/失败、服务创建、计划任务注册。
- 应用日志:IIS日志、数据库日志、第三方安全软件日志。这些往往是攻击者忽略的盲区。
- EDR自采日志:进程内存扫描结果、网络连接快照、文件哈希值、注册表键值快照。
举个例子,有一次我排查一个横向移动事件。攻击者用WMI远程执行了命令,API Hook只抓到了IWbemServices::ExecMethod调用,但不知道执行了什么。我回头去查Windows Event Log 4688(进程创建),发现目标机器上瞬间弹出了一个cmd.exe,父进程是wmiprvse.exe。再结合EDR自采的命令行参数,完整还原了攻击链。
关键点:日志采集一定要关联上下文。单看一条日志可能毫无意义,但把进程树、时间线、网络流串起来,就能看到全貌。
嗯,这里要注意一个坑:日志量太大。我曾经见过一个EDR部署后,每天产生200GB的日志,把存储打满了。我的建议是:
- 对高频低危事件(如DNS查询、文件读取)做采样或聚合
- 对低频高危事件(如驱动加载、进程注入、注册表自启动项修改)做全量记录
- 设置日志轮转策略,保留最近30天即可
知识体系总览
下面这张图,是我梳理的用户态监控核心逻辑。你可以把它当作一张「作战地图」:
从这张图你能看到,三大手段不是孤立的。我通常的做法是:API Hook做深度检测(比如检测内存修改),ETW做广度覆盖(比如监控所有进程创建),日志采集做上下文补充(比如查历史记录)。三者配合,才能构建一个相对完整的用户态监控体系。
最后说一句:用户态监控再强,也防不住内核态的攻击。如果攻击者加载了驱动,直接绕过所有Hook和ETW,那用户态就彻底瞎了。所以,下一层——内核态监控,才是真正的「硬仗」。