Evading Logging and Monitoring
Unlike anti-virus and EDR (Endpoint Detection and Response) solutions, logging creates a physical record of activity that can be analyzed for malicious activity.
Once logs are created, they can be kept on the device or sent to an event collector/forwarder.
Once they are off the device, the defense team decides how to aggregate them
Generally accomplished using an indexer and a SIEM (Security Information and Event Manager).
The primary target for an attacker is the event logs, managed and controlled by ETW (Event Tracing for Windows).
Event Tracing
ETW handles almost all event logging at both the application and kernel level
Event Ids are a core feature of Windows Logging
Events are sent and transferred in ZML format
Extensions of ETW
Component --> Purpose
Example of Event Id
4624
An account was successfully logged on
Approaches to Log Evasion
Common event IDs to avoid causing
The above event IDs can monitor the process of destroying logs or “log smashing.
Tracing Instrumentation
ETW is broken up into three separate components
Event Controllers are used to build and configure sessions.
We can think of the controller as the application that determines how and where data will flow.
Event Providers are used to generate events.
The controller will tell the provider how to operate, then collect logs from its designated source
There are also four different types of providers with support for various functions and legacy systems.
Event Consumers are used to interpret events.
The consumer will select sessions and parse events from that session or multiple at the same time.
This is most commonly seen in the
“Event Viewer”
.
Overall Process
Events originate from the providers.
Controllers will determine where the data is sent and how it is processed through sessions.
Consumers will save or deliver logs to be interpreted or analyzed.
Reflection for Fun and Silence
Within PowerShell, ETW providers are loaded into the session from a .NET assembly:
PSEtwLogProvider
In a PowerShell session, most .NET assemblies are loaded in the same security context as the user at startup
In the context of ETW (Event Tracing for Windows), an attacker can reflect the ETW event provider assembly and set the field
m_enabled
to$null
.At a high level, PowerShell reflection can be broken up into four steps: Obtain .NET assembly for PSEtwLogProvider. Store a null value for etwProvider field. Set the field for m_enabled to previously stored value. At step one, we need to obtain the type for the PSEtwLogProvider assembly. The assembly is stored in order to access its internal fields in the next step.
At step two, we are storing a value ($null) from the previous assembly to be used.
At step three, we compile our steps together to overwrite the
m_enabled
field with the value stored in the previous line.
We can compile these steps together and append them to a malicious PowerShell script. Use the PowerShell script provided and experiment with this technique.
To prove the efficacy of the script, we can execute it and measure the number of returned events from a given command.
Before
After
Patching Tracing Function
ETW is loaded from the runtime of every new process, commonly originating from the CLR (Common Language Runtime).
Within a new process, ETW events are sent from the userland and issued directly from the current process.
An attacker can write pre-defined opcodes to an in-memory function of ETW to patch and disable functionality.
High Level POC
We know that from the CLR, ETW is written from the function
EtwEventWrite
. To identify “patch points” or returns, we can view the disassembly of the function.
ret 14h
will end the function and return to the previous application.The parameter of ret (
14h
) will specify the number of bytes or words released once the stack is popped.At a high level, ETW patching can be broken up into five steps:
Steps for ETW
Obtain a handle for
EtwEventWrite
Modify memory permissions of the function
Write opcode bytes to memory
Reset memory permissions of the function (optional)
Flush the instruction cache (optional)
At step one, we need to obtain a handle for the address of
EtwEventWrite
. This function is stored withinntdll
. We will first load the library usingLoadLibrary
then obtain the handle usingGetProcAddress
.
At step two, we need to modify the memory permissions of the function to allow us to write to the function. The permission of the function is defined by the
flNewProtect
parameter;0x40
enables X, R, or RW access (memory protection constraints).
At step three, the function has the permissions we need to write to it, and we have the pre-defined opcode to patch it.
Because we are writing to a function and not a process, we can use the infamous
Marshal.Copy
to write our opcode.
At step four, we can begin cleaning our steps to restore memory permissions as they were.
We can compile these steps together and append them to a malicious script or session. Use the C# script provided and experiment with this technique.
After the opcode is written to memory, we can view the disassembled function again to observe the patch.
In the above disassembly, we see exactly what we depicted in our LIFO diagram (figure 2).
Once the function is patched in memory, it will always return when
EtwEventWrite
is called.Although this is a beautifully crafted technique, it might not be the best approach depending on your environment since it may restrict more logs than you want for integrity.
Providers via Policy
Last updated