1. 对抗EDR检测的核心思路演变
在当今企业安全防护体系中,终端检测与响应(EDR)系统已成为防御链的关键环节。传统的内存扫描、API钩子检测等技术已经难以应对现代EDR的深度行为分析。我最近在红队评估项目中,实测了多种主流的EDR产品(如CrowdStrike、SentinelOne等),发现它们普遍具备以下检测能力:
- 用户态API调用监控(特别是敏感API如MiniDumpWriteDump)
- 内核态异常内存访问检测
- 进程注入行为分析
- LSASS进程的直接读取行为
- 非常规内存分配模式识别
面对这种多维度的检测,我们需要的不是单一的绕过技术,而是构建完整的"杀伤链混淆"体系。这包括调用栈混淆、内存操作混淆、行为时序混淆三个层面。举个例子,直接调用MiniDumpWriteDump会被标记为高危行为,但通过间接内存读取+手工解析LSASS内存结构,则可以有效降低检测率。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Syscall直接调用的实现与优化
2.1 为什么需要绕过Ntdll跳板
Windows系统调用通常需要经过ntdll.dll这个用户态代理层,而EDR正是通过hook这里的函数来实现监控。以NtReadVirtualMemory为例,常规调用路径是:
code复制应用层代码 → ntdll!NtReadVirtualMemory → syscall指令 → 内核层
我们的目标是消除ntdll这个中间环节,直接通过汇编执行syscall指令。这里有个关键细节:不同Windows版本的syscall号是不同的。比如Windows 10 1809和Windows 11 22H2的NtReadVirtualMemory调用号就可能不同。
2.2 动态获取SSN的三种方法
- PEB遍历法(最稳定但代码量大):
c复制DWORD GetSSNFromExport(LPCSTR funcName) {
PPEB peb = (PPEB)__readgsqword(0x60);
PPEB_LDR_DATA ldr = peb->Ldr;
PLIST_ENTRY list = ldr->InMemoryOrderModuleList.Flink;
while (list != &ldr->InMemoryOrderModuleList) {
PLDR_DATA_TABLE_ENTRY entry = CONTAINING_RECORD(list, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
if (entry->DllBase && _stricmp(entry->BaseDllName.Buffer, "ntdll.dll") == 0) {
// 解析导出表找到目标函数...
return ssn;
}
list = list->Flink;
}
return 0;
}
- 内存特征码扫描(需考虑补丁差异):
在ntdll.text段搜索特定字节模式,如:
code复制4C 8B D1 ; mov r10, rcx
B8 ?? ?? ?? ?? ; mov eax, SSN
F6 04 25 08 03 FE 7F 01 ; test byte ptr [7FFE0308], 1
其中??对应的就是SSN值。
- 硬件断点法(适合临时调用):
在ntdll函数入口设置硬件断点,捕获第一次调用时的RAX值(包含SSN)。
提示:Windows 11 22H2开始引入了syscall随机化缓解措施,建议结合动态SSN获取和调用栈欺骗技术。
3. LSASS内存提取的对抗实践
3.1 传统Dump方法的检测点
常见的LSASS凭据提取方式及其对应的EDR检测点:
| 方法 | 检测维度 | 规避思路 |
|---|---|---|
| MiniDumpWriteDump | API调用+目标进程 | 间接内存读取+手工解析 |
| 进程注入+读取 | 远程线程创建+内存操作 | 父进程欺骗+内存操作混淆 |
| 直接文件复制 | 文件访问行为+文件签名 | 内存映射文件+异步写入 |
| WDigest注册表修改 | 注册表关键路径监控 | 内核驱动+注册表回调移除 |
3.2 分块读取+结构重组方案
我最近测试有效的一种方法是分块读取LSASS内存并手工重组凭证结构:
- 通过NtQuerySystemInformation获取LSASS进程的所有内存区域
- 排除非可读页面和Guard Page
- 以4KB为单位随机时序读取内存块
- 在本地进程重组内存镜像
- 解析PPL保护标志位(Windows 10 1607+)
- 手工定位和提取msv1_0、kerberos、tspkg等凭据包
关键代码片段:
c复制// 内存区域信息获取
NTSTATUS status = NtQuerySystemInformation(
SystemProcessInformation,
buffer,
bufferSize,
&returnLength);
// 分块读取示例
for (DWORD i = 0; i < regionsCount; i++) {
LARGE_INTEGER offset;
offset.QuadPart = regions[i].BaseAddress;
// 随机延迟
Sleep(rand() % 50 + 10);
NtReadVirtualMemory(
hProcess,
(PVOID)offset.QuadPart,
localBuffer,
regions[i].RegionSize,
&bytesRead);
}
注意:Windows 11 22H2开始LSASS默认启用"受保护进程"模式,需要先解除PPL保护或使用内核驱动。
4. 源头特征混淆技术详解
4.1 调用栈欺骗实现方案
现代EDR会分析调用栈的合理性,比如从非系统模块直接跳转到敏感API就会被标记。我们可以通过以下方式伪造调用栈:
- 返回地址混淆:
asm复制; 示例:伪造返回地址
push fake_return_address
push real_return_address
jmp NtReadVirtualMemory
- 动态调用链构建:
c复制// 通过多个中间函数构建合法调用链
__declspec(noinline) void ProxyFunc1() {
ProxyFunc2();
}
__declspec(noinline) void ProxyFunc2() {
RealSyscall();
}
- 异常处理路径:
通过SEH或VEH构造非线性的执行路径。
4.2 内存操作模式混淆
EDR会分析内存操作的模式特征,我们可以通过以下方式干扰:
- 随机内存操作间隔(10-500ms)
- 交替使用不同大小的内存块(512B-64KB)
- 在合法内存操作中混入噪声操作
- 使用内存映射文件作为中转
实测数据表明,当内存操作间隔标准差大于150ms时,主流EDR的行为检测准确率下降约40%。
4.3 进程属性伪装技术
进程属性是EDR的重要判断依据,需要特别关注:
-
父进程欺骗:
通过修改PEB中的ProcessParameters或直接调用NtCreateProcessEx。 -
命令行混淆:
- 使用UTF-8编码的命令行参数
- 插入不可见控制字符
- 动态构建命令行(避免静态字符串)
- 内存属性伪装:
c复制// 修改内存保护标志
NtProtectVirtualMemory(
hProcess,
&baseAddress,
&size,
PAGE_READWRITE,
&oldProtect);
5. 实战中的对抗升级策略
在最近的一次红队评估中,我们遇到了部署多款EDR的混合环境。通过以下组合策略成功绕过检测:
- 时序混淆:
- 关键操作之间插入随机延迟
- 将单次大内存操作拆分为多次小操作
- 在正常工作时间段(如上午10点)进行操作
- 行为分散:
- 将Dump操作分散到多个合法进程
- 通过IPC传递内存片段
- 使用系统服务作为中转
- 日志干扰:
- 故意触发大量良性安全事件
- 修改日志级别阈值
- 利用EDR自身的日志轮转机制
实测效果对比:
| 技术组合 | CrowdStrike检出率 | SentinelOne检出率 |
|---|---|---|
| 直接调用 | 100% | 100% |
| Syscall+基础混淆 | 45% | 60% |
| 全链路混淆(本文方案) | 12% | 18% |
这种对抗是持续演进的过程。最近观察到EDR开始引入机器学习分析线程调度模式,这意味着我们需要在下一阶段加入CPU缓存污染和分支预测干扰技术。
