EDR products have the option of using multiple sources to collect information on a Widows operating system. One of these log sources is ETW (Event Tracing for Windows). ETW consumers are now integrated into many EDR endpoint agents in order to receive CLR Runtime traces. As opposed to other mainstream threat detection and prevention products that hook commonly abused Windows API calls in userland, ATP has hooks that operate within the kernel. ATP relies heavily on ETW calls.
Event Tracing for Windows (ETW) is an efficient kernel-level tracing facility that lets you log kernel or application-defined events to a log file. You can consume the events in real time or from a log file and use them to debug an application or to determine where performance issues are occurring in the application. 1
ETW vs AMSI
There is a common misconception that ETW is challenging to bypass in a process because it runs in kernel mode, juxtaposed to the AMSI.dll which runs in user mode. However, this is incorrect because the Microsoft .NET runtime provider sends logs in JSON format from the .NET runtime to any subscribers of that provider. A provider can be the Windows OS or any AV/EDR product that can consume the ETW JSON logs. This means that ETW can be bypassed in-process, just like the AMSI. ETW is not strings detection-based, if you’re using C# it’s gathering information like namespace names, class names, and method names. Renaming all of these in your code is a great first step for bypassing ETW.
AMSI (Anti Malware Scan Interface) supports file and memory scanning for known malicious strings – it is built directly into Windows Defender. However, AMSI is agnostic of antimalware vendor, third party vendors can write their own AMSI provider to extend AMSI’s functionality as they see fit. A great example of creating a custom AMSI provider is the SimpleAMSI Provider project from pathtofile. CarbonBlack and SentinelOne both write their own custom AMSI providers. This creates challenges for the attacker when testing payloads locally due to not having that vendor’s custom AMSI.dll, unless you purchase that product.
We’ll begin by opening up ntdll.dll in the disassembler and filtering the exports for ETW functions. The two most common functions that are called by Microsoft security products are EtwEventWrite and EtwEventWriteFull.
Examining the EtwEventWrite API we see that EtwEventWriteFull is called, which in turn calls EtwpEventWriteFull:
Recent research regarding ETW tampering involves patching the EtwEventWrite function by having it return before the function is called.
Adam Chester of MDSEC goes over this method in this blog.
Some common Github tools for patching ETW use this method to patch EtwEventWrite.
From outflankl’s TamperETW project:
From Flangvik’s NetLoader project :
As we all know, offensive cyber is a game of cat and mouse. Companies that make EDR products and are now inspecting these commonly abused ETW functions for evidence of tampering within their address space. If script kiddies compile these projects without renaming functions or obfuscating the code, they will get caught immediately.
Scrolling down within the disassembler we find a function named NtTraceEvent. Windows functions that begin with ‘Nt’ are functions that operate in userland but can call a function in the kernel. This type of function is known as a syscall.
According to MSDN:
The Windows native operating system services API is implemented as a set of routines that run in kernel mode. These routines have names that begin with the prefix Nt or Zw. Kernel-mode drivers can call these routines directly. User-mode applications can access these routines by using system calls. 2
Following the control flow of NtTraceEvent brings us to a syscall. Well, how about that.
This function is the central switching point for writing an event through Event Tracing For Windows (ETW). Both the NtTraceEvent and ZwTraceEvent functions are exported by name from NTDLL. There, in user mode, the functions are aliases for a stub that transfers execution to the
NtTraceEvent implementation in kernel mode such that the execution is recognised as originating in user mode. 3
Diagram of NtTraceEvent/zwTraceEvent transferring control to the kernel-version of NtTraceEvent:
So technically we are not going to touch ETW, we will patch the syscall that ETW uses so that it loses the capability to write ETW events to the file system. We will do this by patching the NtTraceEvent function so that the syscall simply returns. The assembly code in the red box is never going to be called.
We’ll start our PoC by finding the memory address of NtTraceEvent.
We’ll use VirtualProtect 4 to change permissions on this segment in memory. If we set RWX permissions with VirtualProtect, that is usually an EDR trigger. However in this case, we’ll set RWX permissions and then return the permissions to RX. Then memcpy5 will be used to copy the opcode for a return into the buffer where NtTrace Event is located.
We used the disassembler in order to find two pieces in the above code – the opcode for a return instruction (xc3), and the size of the memory region where NtTraceEvent resides.
And the return:
The assembly tells the story: we see the pointer being incremented by 3 to the NtTraceEvent memory region. Then there is a call to memcpy where a return instruction is copied into the region of memory holding NtTraceEvent. And the bottom of the assembler, VirtualProtect isused to return the permissions on the stack to RX.