NtTraceEvent
Writes a user-mode event to an ETW session via a registered trace handle.
Prototype
NTSTATUS NtTraceEvent( HANDLE TraceHandle, ULONG Flags, ULONG FieldSize, PVOID Fields );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| TraceHandle | HANDLE | in | Trace/registration handle obtained from EtwRegister (or kernel EtwRegister equivalent). |
| Flags | ULONG | in | Event flags. ETW_NT_FLAGS_TRACE_HEADER (0x00400000) marks Fields as a fully-formed EVENT_HEADER. |
| FieldSize | ULONG | in | Total size in bytes of the buffer pointed to by Fields. |
| Fields | PVOID | in | Pointer to an EVENT_HEADER followed by EVENT_DATA_DESCRIPTORs for the event payload. |
Syscall IDs by Windows version
| Windows version | Syscall ID | Build |
|---|---|---|
| Win10 1507 | 0x5E | win10-1507 |
| Win10 1607 | 0x5E | win10-1607 |
| Win10 1703 | 0x5E | win10-1703 |
| Win10 1709 | 0x5E | win10-1709 |
| Win10 1803 | 0x5E | win10-1803 |
| Win10 1809 | 0x5E | win10-1809 |
| Win10 1903 | 0x5E | win10-1903 |
| Win10 1909 | 0x5E | win10-1909 |
| Win10 2004 | 0x5E | win10-2004 |
| Win10 20H2 | 0x5E | win10-20h2 |
| Win10 21H1 | 0x5E | win10-21h1 |
| Win10 21H2 | 0x5E | win10-21h2 |
| Win10 22H2 | 0x5E | win10-22h2 |
| Win11 21H2 | 0x5E | win11-21h2 |
| Win11 22H2 | 0x5E | win11-22h2 |
| Win11 23H2 | 0x5E | win11-23h2 |
| Win11 24H2 | 0x5E | win11-24h2 |
| Server 2016 | 0x5E | winserver-2016 |
| Server 2019 | 0x5E | winserver-2019 |
| Server 2022 | 0x5E | winserver-2022 |
| Server 2025 | 0x5E | winserver-2025 |
Kernel module
Related APIs
Syscall stub
4C 8B D1 mov r10, rcx B8 5E 00 00 00 mov eax, 0x5E F6 04 25 08 03 FE 7F 01 test byte ptr [0x7FFE0308], 1 75 03 jne short +3 0F 05 syscall C3 ret CD 2E int 2Eh C3 ret
Undocumented notes
The kernel entry point behind every user-mode ETW *write*. `EventWrite`, `EventWriteEx`, `EventWriteTransfer`, `EtwEventWrite`, and the auto-generated TraceLogging emitters all funnel through `ntdll!EtwEventWriteFull` → `NtTraceEvent`. The TraceHandle is *not* a session handle — it is the per-provider registration handle returned by `EtwRegister`, and the kernel walks the provider's enable mask to decide which sessions receive the event. Its SSN has been `0x5E` from Windows 10 1507 all the way through Windows 11 24H2 and Server 2025, one of the most stable syscall numbers in the system, presumably because it is on the hot path of every Microsoft telemetry stack.
Common malware usage
Limited direct offensive value — calling it writes events that defenders *may* see. The interesting reverse is the opposite: implants that want a private logging channel will deliberately register a provider GUID nobody collects and call `NtTraceEvent` to scribble runtime debug into a session no SOC subscribes to. Some loaders also use it to emit decoy `Microsoft-Windows-*` events to dilute hunt queries. Far more attention goes to *suppressing* writes (see `NtTraceControl`) than to abusing this call directly. A handful of EDR-evasion notes mention overwriting `EtwEventWrite` with `ret` in ntdll, which collapses the user-mode side of this exact code path.
Detection opportunities
On its own, `NtTraceEvent` calls are pure noise — every Office app, browser tab, and Windows service emits thousands per second. What matters is the *absence* of expected events from a process whose ntdll has been patched (e.g. `EtwEventWrite` first byte = `0xC3`). Compare in-memory ntdll bytes against on-disk for the four classic ETW write stubs (`EtwEventWrite`, `EtwEventWriteFull`, `EtwEventWriteEx`, `EtwEventWriteTransfer`); any divergence is high-fidelity. At the session level, log gaps for the `Microsoft-Windows-Threat-Intelligence` and `Microsoft-Windows-Kernel-Process` providers correlated with new process creations are the classic ETW-bypass tell.
Direct syscall examples
cTraceLogging write via EventWriteTransfer
// Standard TraceLogging path — ultimately calls NtTraceEvent.
#include <windows.h>
#include <evntprov.h>
static const GUID kImplantProvider =
{ 0x11111111, 0x2222, 0x3333, { 0x44,0x55,0x66,0x77,0x88,0x99,0xAA,0xBB } };
REGHANDLE hReg = 0;
EventRegister(&kImplantProvider, NULL, NULL, &hReg);
EVENT_DESCRIPTOR ed = { 0 };
ed.Id = 1; ed.Level = 4; // Information
EVENT_DATA_DESCRIPTOR edd;
const wchar_t* msg = L"beacon-checkin";
EventDataDescCreate(&edd, msg, (ULONG)((wcslen(msg) + 1) * sizeof(wchar_t)));
// Funnels through ntdll!EtwEventWriteFull -> NtTraceEvent.
EventWriteTransfer(hReg, &ed, NULL, NULL, 1, &edd);asmx64 direct stub (SSN 0x5E, stable across all builds)
NtTraceEvent PROC
mov r10, rcx
mov eax, 5Eh
syscall
ret
NtTraceEvent ENDPrustStealth provider with windows-sys
// Cargo: windows-sys = { version = "0.59", features = ["Win32_System_Diagnostics_Etw"] }
use windows_sys::Win32::System::Diagnostics::Etw::*;
use windows_sys::core::GUID;
const PROVIDER: GUID = GUID::from_u128(0x11111111_2222_3333_4455_66778899AABB);
unsafe fn beacon_log(msg: &[u8]) {
let mut hreg: u64 = 0;
EventRegister(&PROVIDER, None, std::ptr::null_mut(), &mut hreg);
let ed = EVENT_DESCRIPTOR { Id: 1, Level: 4, ..core::mem::zeroed() };
let mut edd = EVENT_DATA_DESCRIPTOR { Ptr: msg.as_ptr() as u64, Size: msg.len() as u32, Reserved: 0 };
EventWrite(hreg, &ed, 1, &mut edd);
}MITRE ATT&CK mappings
Last verified: 2026-05-20