/* * Process Hacker Extended Tools - * ETW monitoring * * Copyright (C) 2010-2015 wj32 * * This file is part of Process Hacker. * * Process Hacker is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Process Hacker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Process Hacker. If not, see . */ #include "exttools.h" #include "etwmon.h" ULONG NTAPI EtpEtwBufferCallback( _In_ PEVENT_TRACE_LOGFILE Buffer ); VOID NTAPI EtpEtwEventCallback( _In_ PEVENT_RECORD EventRecord ); NTSTATUS EtpEtwMonitorThreadStart( _In_ PVOID Parameter ); ULONG EtpStopEtwRundownSession( VOID ); ULONG NTAPI EtpRundownEtwBufferCallback( _In_ PEVENT_TRACE_LOGFILE Buffer ); VOID NTAPI EtpRundownEtwEventCallback( _In_ PEVENT_RECORD EventRecord ); NTSTATUS EtpRundownEtwMonitorThreadStart( _In_ PVOID Parameter ); static GUID ProcessHackerGuid = { 0x1288c53b, 0xaf35, 0x481b, { 0xb6, 0xb5, 0xa0, 0x5c, 0x39, 0x87, 0x2e, 0xd } }; static GUID SystemTraceControlGuid_I = { 0x9e814aad, 0x3204, 0x11d2, { 0x9a, 0x82, 0x00, 0x60, 0x08, 0xa8, 0x69, 0x39 } }; static GUID KernelRundownGuid_I = { 0x3b9c9951, 0x3480, 0x4220, { 0x93, 0x77, 0x9c, 0x8e, 0x51, 0x84, 0xf5, 0xcd } }; static GUID DiskIoGuid_I = { 0x3d6fa8d4, 0xfe05, 0x11d0, { 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c } }; static GUID FileIoGuid_I = { 0x90cbdc39, 0x4a3e, 0x11d1, { 0x84, 0xf4, 0x00, 0x00, 0xf8, 0x04, 0x64, 0xe3 } }; static GUID TcpIpGuid_I = { 0x9a280ac0, 0xc8e0, 0x11d1, { 0x84, 0xe2, 0x00, 0xc0, 0x4f, 0xb9, 0x98, 0xa2 } }; static GUID UdpIpGuid_I = { 0xbf3a50c5, 0xa9c9, 0x4988, { 0xa0, 0x05, 0x2d, 0xf0, 0xb7, 0xc8, 0x0f, 0x80 } }; // ETW tracing layer BOOLEAN EtEtwEnabled; static UNICODE_STRING EtpSharedKernelLoggerName = RTL_CONSTANT_STRING(KERNEL_LOGGER_NAME); static UNICODE_STRING EtpPrivateKernelLoggerName = RTL_CONSTANT_STRING(L"PhEtKernelLogger"); static TRACEHANDLE EtpSessionHandle; static PUNICODE_STRING EtpActualKernelLoggerName; static PGUID EtpActualSessionGuid; static PEVENT_TRACE_PROPERTIES EtpTraceProperties; static BOOLEAN EtpEtwActive; static BOOLEAN EtpStartedSession; static BOOLEAN EtpEtwExiting; static HANDLE EtpEtwMonitorThreadHandle; // ETW rundown layer static UNICODE_STRING EtpRundownLoggerName = RTL_CONSTANT_STRING(L"PhEtRundownLogger"); static TRACEHANDLE EtpRundownSessionHandle; static PEVENT_TRACE_PROPERTIES EtpRundownTraceProperties; static BOOLEAN EtpRundownActive; static HANDLE EtpRundownEtwMonitorThreadHandle; VOID EtEtwMonitorInitialization( VOID ) { if (PhGetOwnTokenAttributes().Elevated && PhGetIntegerSetting(SETTING_NAME_ENABLE_ETW_MONITOR)) { EtStartEtwSession(); if (EtEtwEnabled) EtpEtwMonitorThreadHandle = PhCreateThread(0, EtpEtwMonitorThreadStart, NULL); } } VOID EtEtwMonitorUninitialization( VOID ) { if (EtEtwEnabled) { EtpEtwExiting = TRUE; EtStopEtwSession(); } if (EtpRundownActive) { EtpStopEtwRundownSession(); } } VOID EtStartEtwSession( VOID ) { ULONG result; ULONG bufferSize; if (WindowsVersion >= WINDOWS_8) { EtpActualKernelLoggerName = &EtpPrivateKernelLoggerName; EtpActualSessionGuid = &ProcessHackerGuid; } else { EtpActualKernelLoggerName = &EtpSharedKernelLoggerName; EtpActualSessionGuid = &SystemTraceControlGuid_I; } bufferSize = sizeof(EVENT_TRACE_PROPERTIES) + EtpActualKernelLoggerName->Length + sizeof(WCHAR); if (!EtpTraceProperties) EtpTraceProperties = PhAllocate(bufferSize); memset(EtpTraceProperties, 0, sizeof(EVENT_TRACE_PROPERTIES)); EtpTraceProperties->Wnode.BufferSize = bufferSize; EtpTraceProperties->Wnode.Guid = *EtpActualSessionGuid; EtpTraceProperties->Wnode.ClientContext = 1; EtpTraceProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID; EtpTraceProperties->MinimumBuffers = 1; EtpTraceProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; EtpTraceProperties->FlushTimer = 1; EtpTraceProperties->EnableFlags = EVENT_TRACE_FLAG_DISK_IO | EVENT_TRACE_FLAG_DISK_FILE_IO | EVENT_TRACE_FLAG_NETWORK_TCPIP; EtpTraceProperties->LogFileNameOffset = 0; EtpTraceProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); if (WindowsVersion >= WINDOWS_8) EtpTraceProperties->LogFileMode |= EVENT_TRACE_SYSTEM_LOGGER_MODE; result = StartTrace(&EtpSessionHandle, EtpActualKernelLoggerName->Buffer, EtpTraceProperties); if (result == ERROR_SUCCESS) { EtEtwEnabled = TRUE; EtpEtwActive = TRUE; EtpStartedSession = TRUE; } else if (result == ERROR_ALREADY_EXISTS) { EtEtwEnabled = TRUE; EtpEtwActive = TRUE; EtpStartedSession = FALSE; // The session already exists. //result = ControlTrace(0, EtpActualKernelLoggerName->Buffer, EtpTraceProperties, EVENT_TRACE_CONTROL_UPDATE); } else { EtpEtwActive = FALSE; EtpStartedSession = FALSE; } } ULONG EtpControlEtwSession( _In_ ULONG ControlCode ) { // If we have a session handle, we use that instead of the logger name. EtpTraceProperties->LogFileNameOffset = 0; // make sure it is 0, otherwise ControlTrace crashes return ControlTrace( EtpStartedSession ? EtpSessionHandle : 0, EtpStartedSession ? NULL : EtpActualKernelLoggerName->Buffer, EtpTraceProperties, ControlCode ); } VOID EtStopEtwSession( VOID ) { if (EtEtwEnabled) EtpControlEtwSession(EVENT_TRACE_CONTROL_STOP); } VOID EtFlushEtwSession( VOID ) { if (EtEtwEnabled) EtpControlEtwSession(EVENT_TRACE_CONTROL_FLUSH); } ULONG NTAPI EtpEtwBufferCallback( _In_ PEVENT_TRACE_LOGFILE Buffer ) { return !EtpEtwExiting; } VOID NTAPI EtpEtwEventCallback( _In_ PEVENT_RECORD EventRecord ) { if (memcmp(&EventRecord->EventHeader.ProviderId, &DiskIoGuid_I, sizeof(GUID)) == 0) { // DiskIo ET_ETW_DISK_EVENT diskEvent; memset(&diskEvent, 0, sizeof(ET_ETW_DISK_EVENT)); diskEvent.Type = -1; switch (EventRecord->EventHeader.EventDescriptor.Opcode) { case EVENT_TRACE_TYPE_IO_READ: diskEvent.Type = EtEtwDiskReadType; break; case EVENT_TRACE_TYPE_IO_WRITE: diskEvent.Type = EtEtwDiskWriteType; break; default: break; } if (diskEvent.Type != -1) { DiskIo_TypeGroup1 *data = EventRecord->UserData; if (WindowsVersion >= WINDOWS_8) { diskEvent.ClientId.UniqueThread = UlongToHandle(data->IssuingThreadId); diskEvent.ClientId.UniqueProcess = EtThreadIdToProcessId(diskEvent.ClientId.UniqueThread); } else { if (EventRecord->EventHeader.ProcessId != -1) { diskEvent.ClientId.UniqueProcess = UlongToHandle(EventRecord->EventHeader.ProcessId); diskEvent.ClientId.UniqueThread = UlongToHandle(EventRecord->EventHeader.ThreadId); } } diskEvent.IrpFlags = data->IrpFlags; diskEvent.TransferSize = data->TransferSize; diskEvent.FileObject = (PVOID)data->FileObject; diskEvent.HighResResponseTime = data->HighResResponseTime; EtProcessDiskEvent(&diskEvent); EtDiskProcessDiskEvent(&diskEvent); } } else if (memcmp(&EventRecord->EventHeader.ProviderId, &FileIoGuid_I, sizeof(GUID)) == 0) { // FileIo ET_ETW_FILE_EVENT fileEvent; memset(&fileEvent, 0, sizeof(ET_ETW_FILE_EVENT)); fileEvent.Type = -1; switch (EventRecord->EventHeader.EventDescriptor.Opcode) { case 0: // Name fileEvent.Type = EtEtwFileNameType; break; case 32: // FileCreate fileEvent.Type = EtEtwFileCreateType; break; case 35: // FileDelete fileEvent.Type = EtEtwFileDeleteType; break; default: break; } if (fileEvent.Type != -1) { FileIo_Name *data = EventRecord->UserData; fileEvent.FileObject = (PVOID)data->FileObject; PhInitializeStringRef(&fileEvent.FileName, data->FileName); EtDiskProcessFileEvent(&fileEvent); } } else if ( memcmp(&EventRecord->EventHeader.ProviderId, &TcpIpGuid_I, sizeof(GUID)) == 0 || memcmp(&EventRecord->EventHeader.ProviderId, &UdpIpGuid_I, sizeof(GUID)) == 0 ) { // TcpIp/UdpIp ET_ETW_NETWORK_EVENT networkEvent; memset(&networkEvent, 0, sizeof(ET_ETW_NETWORK_EVENT)); networkEvent.Type = -1; switch (EventRecord->EventHeader.EventDescriptor.Opcode) { case EVENT_TRACE_TYPE_SEND: // send networkEvent.Type = EtEtwNetworkSendType; networkEvent.ProtocolType = PH_IPV4_NETWORK_TYPE; break; case EVENT_TRACE_TYPE_RECEIVE: // receive networkEvent.Type = EtEtwNetworkReceiveType; networkEvent.ProtocolType = PH_IPV4_NETWORK_TYPE; break; case EVENT_TRACE_TYPE_SEND + 16: // send ipv6 networkEvent.Type = EtEtwNetworkSendType; networkEvent.ProtocolType = PH_IPV6_NETWORK_TYPE; break; case EVENT_TRACE_TYPE_RECEIVE + 16: // receive ipv6 networkEvent.Type = EtEtwNetworkReceiveType; networkEvent.ProtocolType = PH_IPV6_NETWORK_TYPE; break; } if (memcmp(&EventRecord->EventHeader.ProviderId, &TcpIpGuid_I, sizeof(GUID)) == 0) networkEvent.ProtocolType |= PH_TCP_PROTOCOL_TYPE; else networkEvent.ProtocolType |= PH_UDP_PROTOCOL_TYPE; if (networkEvent.Type != -1) { PH_IP_ENDPOINT source; PH_IP_ENDPOINT destination; if (networkEvent.ProtocolType & PH_IPV4_NETWORK_TYPE) { TcpIpOrUdpIp_IPV4_Header *data = EventRecord->UserData; networkEvent.ClientId.UniqueProcess = UlongToHandle(data->PID); networkEvent.TransferSize = data->size; source.Address.Type = PH_IPV4_NETWORK_TYPE; source.Address.Ipv4 = data->saddr; source.Port = _byteswap_ushort(data->sport); destination.Address.Type = PH_IPV4_NETWORK_TYPE; destination.Address.Ipv4 = data->daddr; destination.Port = _byteswap_ushort(data->dport); } else if (networkEvent.ProtocolType & PH_IPV6_NETWORK_TYPE) { TcpIpOrUdpIp_IPV6_Header *data = EventRecord->UserData; networkEvent.ClientId.UniqueProcess = UlongToHandle(data->PID); networkEvent.TransferSize = data->size; source.Address.Type = PH_IPV6_NETWORK_TYPE; source.Address.In6Addr = data->saddr; source.Port = _byteswap_ushort(data->sport); destination.Address.Type = PH_IPV6_NETWORK_TYPE; destination.Address.In6Addr = data->daddr; destination.Port = _byteswap_ushort(data->dport); } networkEvent.LocalEndpoint = source; if (networkEvent.ProtocolType & PH_TCP_PROTOCOL_TYPE) networkEvent.RemoteEndpoint = destination; EtProcessNetworkEvent(&networkEvent); } } } NTSTATUS EtpEtwMonitorThreadStart( _In_ PVOID Parameter ) { ULONG result; EVENT_TRACE_LOGFILE logFile; TRACEHANDLE traceHandle; // See comment in EtEtwProcessesUpdatedCallback. if (WindowsVersion >= WINDOWS_8) EtUpdateProcessInformation(); memset(&logFile, 0, sizeof(EVENT_TRACE_LOGFILE)); logFile.LoggerName = EtpActualKernelLoggerName->Buffer; logFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD; logFile.BufferCallback = EtpEtwBufferCallback; logFile.EventRecordCallback = EtpEtwEventCallback; while (TRUE) { result = ERROR_SUCCESS; traceHandle = OpenTrace(&logFile); if (traceHandle != INVALID_PROCESSTRACE_HANDLE) { while (!EtpEtwExiting && (result = ProcessTrace(&traceHandle, 1, NULL, NULL)) == ERROR_SUCCESS) NOTHING; CloseTrace(traceHandle); } if (EtpEtwExiting) break; if (result == ERROR_WMI_INSTANCE_NOT_FOUND) { // The session was stopped by another program. Try to start it again. EtStartEtwSession(); } // Some error occurred, so sleep for a while before trying again. // Don't sleep if we just successfully started a session, though. if (!EtpEtwActive) Sleep(250); } return STATUS_SUCCESS; } ULONG EtStartEtwRundown( VOID ) { ULONG result; ULONG bufferSize; bufferSize = sizeof(EVENT_TRACE_PROPERTIES) + EtpRundownLoggerName.Length + sizeof(WCHAR); if (!EtpRundownTraceProperties) EtpRundownTraceProperties = PhAllocate(bufferSize); memset(EtpRundownTraceProperties, 0, sizeof(EVENT_TRACE_PROPERTIES)); EtpRundownTraceProperties->Wnode.BufferSize = bufferSize; EtpRundownTraceProperties->Wnode.ClientContext = 1; EtpRundownTraceProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID; EtpRundownTraceProperties->MinimumBuffers = 1; EtpRundownTraceProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; EtpRundownTraceProperties->FlushTimer = 1; EtpRundownTraceProperties->LogFileNameOffset = 0; EtpRundownTraceProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); result = StartTrace(&EtpRundownSessionHandle, EtpRundownLoggerName.Buffer, EtpRundownTraceProperties); if (result == ERROR_ALREADY_EXISTS) { EtpStopEtwRundownSession(); // ControlTrace (called from EtpStopEtwRundownSession) screws up the structure. EtpRundownTraceProperties->Wnode.BufferSize = bufferSize; EtpRundownTraceProperties->LogFileNameOffset = 0; EtpRundownTraceProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); result = StartTrace(&EtpRundownSessionHandle, EtpRundownLoggerName.Buffer, EtpRundownTraceProperties); } if (result != ERROR_SUCCESS) return result; result = EnableTraceEx(&KernelRundownGuid_I, NULL, EtpRundownSessionHandle, 1, 0, 0x10, 0, 0, NULL); if (result != ERROR_SUCCESS) { EtpStopEtwRundownSession(); return result; } EtpRundownActive = TRUE; EtpRundownEtwMonitorThreadHandle = PhCreateThread(0, EtpRundownEtwMonitorThreadStart, NULL); return result; } ULONG EtpStopEtwRundownSession( VOID ) { EtpRundownTraceProperties->LogFileNameOffset = 0; return ControlTrace(0, EtpRundownLoggerName.Buffer, EtpRundownTraceProperties, EVENT_TRACE_CONTROL_STOP); } ULONG NTAPI EtpRundownEtwBufferCallback( _In_ PEVENT_TRACE_LOGFILE Buffer ) { return !EtpEtwExiting; } VOID NTAPI EtpRundownEtwEventCallback( _In_ PEVENT_RECORD EventRecord ) { // TODO: Find a way to call CloseTrace when the enumeration finishes so we can // stop the trace cleanly. if (memcmp(&EventRecord->EventHeader.ProviderId, &FileIoGuid_I, sizeof(GUID)) == 0) { // FileIo ET_ETW_FILE_EVENT fileEvent; memset(&fileEvent, 0, sizeof(ET_ETW_FILE_EVENT)); fileEvent.Type = -1; switch (EventRecord->EventHeader.EventDescriptor.Opcode) { case 36: // FileRundown fileEvent.Type = EtEtwFileRundownType; break; default: break; } if (fileEvent.Type != -1) { FileIo_Name *data = EventRecord->UserData; fileEvent.FileObject = (PVOID)data->FileObject; PhInitializeStringRef(&fileEvent.FileName, data->FileName); EtDiskProcessFileEvent(&fileEvent); } } } NTSTATUS EtpRundownEtwMonitorThreadStart( _In_ PVOID Parameter ) { EVENT_TRACE_LOGFILE logFile; TRACEHANDLE traceHandle; memset(&logFile, 0, sizeof(EVENT_TRACE_LOGFILE)); logFile.LoggerName = EtpRundownLoggerName.Buffer; logFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD; logFile.BufferCallback = EtpRundownEtwBufferCallback; logFile.EventRecordCallback = EtpRundownEtwEventCallback; logFile.Context = &traceHandle; traceHandle = OpenTrace(&logFile); if (traceHandle != INVALID_PROCESSTRACE_HANDLE) { ProcessTrace(&traceHandle, 1, NULL, NULL); if (traceHandle != 0) CloseTrace(traceHandle); } NtClose(EtpRundownEtwMonitorThreadHandle); EtpRundownEtwMonitorThreadHandle = NULL; return STATUS_SUCCESS; }