Process Hacker
etwmon.c
Go to the documentation of this file.
1 /*
2  * Process Hacker Extended Tools -
3  * ETW monitoring
4  *
5  * Copyright (C) 2010-2015 wj32
6  *
7  * This file is part of Process Hacker.
8  *
9  * Process Hacker is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Process Hacker is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include "exttools.h"
24 #include "etwmon.h"
25 
26 ULONG NTAPI EtpEtwBufferCallback(
27  _In_ PEVENT_TRACE_LOGFILE Buffer
28  );
29 
31  _In_ PEVENT_RECORD EventRecord
32  );
33 
35  _In_ PVOID Parameter
36  );
37 
39  VOID
40  );
41 
42 ULONG NTAPI EtpRundownEtwBufferCallback(
43  _In_ PEVENT_TRACE_LOGFILE Buffer
44  );
45 
47  _In_ PEVENT_RECORD EventRecord
48  );
49 
51  _In_ PVOID Parameter
52  );
53 
54 static GUID ProcessHackerGuid = { 0x1288c53b, 0xaf35, 0x481b, { 0xb6, 0xb5, 0xa0, 0x5c, 0x39, 0x87, 0x2e, 0xd } };
55 static GUID SystemTraceControlGuid_I = { 0x9e814aad, 0x3204, 0x11d2, { 0x9a, 0x82, 0x00, 0x60, 0x08, 0xa8, 0x69, 0x39 } };
56 static GUID KernelRundownGuid_I = { 0x3b9c9951, 0x3480, 0x4220, { 0x93, 0x77, 0x9c, 0x8e, 0x51, 0x84, 0xf5, 0xcd } };
57 static GUID DiskIoGuid_I = { 0x3d6fa8d4, 0xfe05, 0x11d0, { 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c } };
58 static GUID FileIoGuid_I = { 0x90cbdc39, 0x4a3e, 0x11d1, { 0x84, 0xf4, 0x00, 0x00, 0xf8, 0x04, 0x64, 0xe3 } };
59 static GUID TcpIpGuid_I = { 0x9a280ac0, 0xc8e0, 0x11d1, { 0x84, 0xe2, 0x00, 0xc0, 0x4f, 0xb9, 0x98, 0xa2 } };
60 static GUID UdpIpGuid_I = { 0xbf3a50c5, 0xa9c9, 0x4988, { 0xa0, 0x05, 0x2d, 0xf0, 0xb7, 0xc8, 0x0f, 0x80 } };
61 
62 // ETW tracing layer
63 
64 BOOLEAN EtEtwEnabled;
65 static UNICODE_STRING EtpSharedKernelLoggerName = RTL_CONSTANT_STRING(KERNEL_LOGGER_NAME);
66 static UNICODE_STRING EtpPrivateKernelLoggerName = RTL_CONSTANT_STRING(L"PhEtKernelLogger");
67 static TRACEHANDLE EtpSessionHandle;
68 static PUNICODE_STRING EtpActualKernelLoggerName;
69 static PGUID EtpActualSessionGuid;
70 static PEVENT_TRACE_PROPERTIES EtpTraceProperties;
71 static BOOLEAN EtpEtwActive;
72 static BOOLEAN EtpStartedSession;
73 static BOOLEAN EtpEtwExiting;
74 static HANDLE EtpEtwMonitorThreadHandle;
75 
76 // ETW rundown layer
77 
78 static UNICODE_STRING EtpRundownLoggerName = RTL_CONSTANT_STRING(L"PhEtRundownLogger");
79 static TRACEHANDLE EtpRundownSessionHandle;
80 static PEVENT_TRACE_PROPERTIES EtpRundownTraceProperties;
81 static BOOLEAN EtpRundownActive;
82 static HANDLE EtpRundownEtwMonitorThreadHandle;
83 
85  VOID
86  )
87 {
89  {
91 
92  if (EtEtwEnabled)
93  EtpEtwMonitorThreadHandle = PhCreateThread(0, EtpEtwMonitorThreadStart, NULL);
94  }
95 }
96 
98  VOID
99  )
100 {
101  if (EtEtwEnabled)
102  {
103  EtpEtwExiting = TRUE;
105  }
106 
107  if (EtpRundownActive)
108  {
110  }
111 }
112 
114  VOID
115  )
116 {
117  ULONG result;
118  ULONG bufferSize;
119 
120  if (WindowsVersion >= WINDOWS_8)
121  {
122  EtpActualKernelLoggerName = &EtpPrivateKernelLoggerName;
123  EtpActualSessionGuid = &ProcessHackerGuid;
124  }
125  else
126  {
127  EtpActualKernelLoggerName = &EtpSharedKernelLoggerName;
128  EtpActualSessionGuid = &SystemTraceControlGuid_I;
129  }
130 
131  bufferSize = sizeof(EVENT_TRACE_PROPERTIES) + EtpActualKernelLoggerName->Length + sizeof(WCHAR);
132 
133  if (!EtpTraceProperties)
134  EtpTraceProperties = PhAllocate(bufferSize);
135 
136  memset(EtpTraceProperties, 0, sizeof(EVENT_TRACE_PROPERTIES));
137 
138  EtpTraceProperties->Wnode.BufferSize = bufferSize;
139  EtpTraceProperties->Wnode.Guid = *EtpActualSessionGuid;
140  EtpTraceProperties->Wnode.ClientContext = 1;
141  EtpTraceProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
142  EtpTraceProperties->MinimumBuffers = 1;
143  EtpTraceProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
144  EtpTraceProperties->FlushTimer = 1;
145  EtpTraceProperties->EnableFlags = EVENT_TRACE_FLAG_DISK_IO | EVENT_TRACE_FLAG_DISK_FILE_IO | EVENT_TRACE_FLAG_NETWORK_TCPIP;
146  EtpTraceProperties->LogFileNameOffset = 0;
147  EtpTraceProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
148 
149  if (WindowsVersion >= WINDOWS_8)
150  EtpTraceProperties->LogFileMode |= EVENT_TRACE_SYSTEM_LOGGER_MODE;
151 
152  result = StartTrace(&EtpSessionHandle, EtpActualKernelLoggerName->Buffer, EtpTraceProperties);
153 
154  if (result == ERROR_SUCCESS)
155  {
156  EtEtwEnabled = TRUE;
157  EtpEtwActive = TRUE;
158  EtpStartedSession = TRUE;
159  }
160  else if (result == ERROR_ALREADY_EXISTS)
161  {
162  EtEtwEnabled = TRUE;
163  EtpEtwActive = TRUE;
164  EtpStartedSession = FALSE;
165  // The session already exists.
166  //result = ControlTrace(0, EtpActualKernelLoggerName->Buffer, EtpTraceProperties, EVENT_TRACE_CONTROL_UPDATE);
167  }
168  else
169  {
170  EtpEtwActive = FALSE;
171  EtpStartedSession = FALSE;
172  }
173 }
174 
176  _In_ ULONG ControlCode
177  )
178 {
179  // If we have a session handle, we use that instead of the logger name.
180 
181  EtpTraceProperties->LogFileNameOffset = 0; // make sure it is 0, otherwise ControlTrace crashes
182 
183  return ControlTrace(
184  EtpStartedSession ? EtpSessionHandle : 0,
185  EtpStartedSession ? NULL : EtpActualKernelLoggerName->Buffer,
186  EtpTraceProperties,
187  ControlCode
188  );
189 }
190 
192  VOID
193  )
194 {
195  if (EtEtwEnabled)
196  EtpControlEtwSession(EVENT_TRACE_CONTROL_STOP);
197 }
198 
200  VOID
201  )
202 {
203  if (EtEtwEnabled)
204  EtpControlEtwSession(EVENT_TRACE_CONTROL_FLUSH);
205 }
206 
208  _In_ PEVENT_TRACE_LOGFILE Buffer
209  )
210 {
211  return !EtpEtwExiting;
212 }
213 
215  _In_ PEVENT_RECORD EventRecord
216  )
217 {
218  if (memcmp(&EventRecord->EventHeader.ProviderId, &DiskIoGuid_I, sizeof(GUID)) == 0)
219  {
220  // DiskIo
221 
222  ET_ETW_DISK_EVENT diskEvent;
223 
224  memset(&diskEvent, 0, sizeof(ET_ETW_DISK_EVENT));
225  diskEvent.Type = -1;
226 
227  switch (EventRecord->EventHeader.EventDescriptor.Opcode)
228  {
229  case EVENT_TRACE_TYPE_IO_READ:
230  diskEvent.Type = EtEtwDiskReadType;
231  break;
232  case EVENT_TRACE_TYPE_IO_WRITE:
233  diskEvent.Type = EtEtwDiskWriteType;
234  break;
235  default:
236  break;
237  }
238 
239  if (diskEvent.Type != -1)
240  {
241  DiskIo_TypeGroup1 *data = EventRecord->UserData;
242 
243  if (WindowsVersion >= WINDOWS_8)
244  {
245  diskEvent.ClientId.UniqueThread = UlongToHandle(data->IssuingThreadId);
247  }
248  else
249  {
250  if (EventRecord->EventHeader.ProcessId != -1)
251  {
252  diskEvent.ClientId.UniqueProcess = UlongToHandle(EventRecord->EventHeader.ProcessId);
253  diskEvent.ClientId.UniqueThread = UlongToHandle(EventRecord->EventHeader.ThreadId);
254  }
255  }
256 
257  diskEvent.IrpFlags = data->IrpFlags;
258  diskEvent.TransferSize = data->TransferSize;
259  diskEvent.FileObject = (PVOID)data->FileObject;
260  diskEvent.HighResResponseTime = data->HighResResponseTime;
261 
262  EtProcessDiskEvent(&diskEvent);
263  EtDiskProcessDiskEvent(&diskEvent);
264  }
265  }
266  else if (memcmp(&EventRecord->EventHeader.ProviderId, &FileIoGuid_I, sizeof(GUID)) == 0)
267  {
268  // FileIo
269 
270  ET_ETW_FILE_EVENT fileEvent;
271 
272  memset(&fileEvent, 0, sizeof(ET_ETW_FILE_EVENT));
273  fileEvent.Type = -1;
274 
275  switch (EventRecord->EventHeader.EventDescriptor.Opcode)
276  {
277  case 0: // Name
278  fileEvent.Type = EtEtwFileNameType;
279  break;
280  case 32: // FileCreate
281  fileEvent.Type = EtEtwFileCreateType;
282  break;
283  case 35: // FileDelete
284  fileEvent.Type = EtEtwFileDeleteType;
285  break;
286  default:
287  break;
288  }
289 
290  if (fileEvent.Type != -1)
291  {
292  FileIo_Name *data = EventRecord->UserData;
293 
294  fileEvent.FileObject = (PVOID)data->FileObject;
295  PhInitializeStringRef(&fileEvent.FileName, data->FileName);
296 
297  EtDiskProcessFileEvent(&fileEvent);
298  }
299  }
300  else if (
301  memcmp(&EventRecord->EventHeader.ProviderId, &TcpIpGuid_I, sizeof(GUID)) == 0 ||
302  memcmp(&EventRecord->EventHeader.ProviderId, &UdpIpGuid_I, sizeof(GUID)) == 0
303  )
304  {
305  // TcpIp/UdpIp
306 
307  ET_ETW_NETWORK_EVENT networkEvent;
308 
309  memset(&networkEvent, 0, sizeof(ET_ETW_NETWORK_EVENT));
310  networkEvent.Type = -1;
311 
312  switch (EventRecord->EventHeader.EventDescriptor.Opcode)
313  {
314  case EVENT_TRACE_TYPE_SEND: // send
315  networkEvent.Type = EtEtwNetworkSendType;
316  networkEvent.ProtocolType = PH_IPV4_NETWORK_TYPE;
317  break;
318  case EVENT_TRACE_TYPE_RECEIVE: // receive
319  networkEvent.Type = EtEtwNetworkReceiveType;
320  networkEvent.ProtocolType = PH_IPV4_NETWORK_TYPE;
321  break;
322  case EVENT_TRACE_TYPE_SEND + 16: // send ipv6
323  networkEvent.Type = EtEtwNetworkSendType;
324  networkEvent.ProtocolType = PH_IPV6_NETWORK_TYPE;
325  break;
326  case EVENT_TRACE_TYPE_RECEIVE + 16: // receive ipv6
327  networkEvent.Type = EtEtwNetworkReceiveType;
328  networkEvent.ProtocolType = PH_IPV6_NETWORK_TYPE;
329  break;
330  }
331 
332  if (memcmp(&EventRecord->EventHeader.ProviderId, &TcpIpGuid_I, sizeof(GUID)) == 0)
333  networkEvent.ProtocolType |= PH_TCP_PROTOCOL_TYPE;
334  else
335  networkEvent.ProtocolType |= PH_UDP_PROTOCOL_TYPE;
336 
337  if (networkEvent.Type != -1)
338  {
339  PH_IP_ENDPOINT source;
340  PH_IP_ENDPOINT destination;
341 
342  if (networkEvent.ProtocolType & PH_IPV4_NETWORK_TYPE)
343  {
344  TcpIpOrUdpIp_IPV4_Header *data = EventRecord->UserData;
345 
346  networkEvent.ClientId.UniqueProcess = UlongToHandle(data->PID);
347  networkEvent.TransferSize = data->size;
348 
350  source.Address.Ipv4 = data->saddr;
351  source.Port = _byteswap_ushort(data->sport);
352  destination.Address.Type = PH_IPV4_NETWORK_TYPE;
353  destination.Address.Ipv4 = data->daddr;
354  destination.Port = _byteswap_ushort(data->dport);
355  }
356  else if (networkEvent.ProtocolType & PH_IPV6_NETWORK_TYPE)
357  {
358  TcpIpOrUdpIp_IPV6_Header *data = EventRecord->UserData;
359 
360  networkEvent.ClientId.UniqueProcess = UlongToHandle(data->PID);
361  networkEvent.TransferSize = data->size;
362 
364  source.Address.In6Addr = data->saddr;
365  source.Port = _byteswap_ushort(data->sport);
366  destination.Address.Type = PH_IPV6_NETWORK_TYPE;
367  destination.Address.In6Addr = data->daddr;
368  destination.Port = _byteswap_ushort(data->dport);
369  }
370 
371  networkEvent.LocalEndpoint = source;
372 
373  if (networkEvent.ProtocolType & PH_TCP_PROTOCOL_TYPE)
374  networkEvent.RemoteEndpoint = destination;
375 
376  EtProcessNetworkEvent(&networkEvent);
377  }
378  }
379 }
380 
382  _In_ PVOID Parameter
383  )
384 {
385  ULONG result;
386  EVENT_TRACE_LOGFILE logFile;
387  TRACEHANDLE traceHandle;
388 
389  memset(&logFile, 0, sizeof(EVENT_TRACE_LOGFILE));
390  logFile.LoggerName = EtpActualKernelLoggerName->Buffer;
391  logFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD;
392  logFile.BufferCallback = EtpEtwBufferCallback;
393  logFile.EventRecordCallback = EtpEtwEventCallback;
394 
395  while (TRUE)
396  {
397  result = ERROR_SUCCESS;
398  traceHandle = OpenTrace(&logFile);
399 
400  if (traceHandle != INVALID_PROCESSTRACE_HANDLE)
401  {
402  while (!EtpEtwExiting && (result = ProcessTrace(&traceHandle, 1, NULL, NULL)) == ERROR_SUCCESS)
403  NOTHING;
404 
405  CloseTrace(traceHandle);
406  }
407 
408  if (EtpEtwExiting)
409  break;
410 
411  if (result == ERROR_WMI_INSTANCE_NOT_FOUND)
412  {
413  // The session was stopped by another program. Try to start it again.
415  }
416 
417  // Some error occurred, so sleep for a while before trying again.
418  // Don't sleep if we just successfully started a session, though.
419  if (!EtpEtwActive)
420  Sleep(250);
421  }
422 
423  return STATUS_SUCCESS;
424 }
425 
427  VOID
428  )
429 {
430  ULONG result;
431  ULONG bufferSize;
432 
433  bufferSize = sizeof(EVENT_TRACE_PROPERTIES) + EtpRundownLoggerName.Length + sizeof(WCHAR);
434 
435  if (!EtpRundownTraceProperties)
436  EtpRundownTraceProperties = PhAllocate(bufferSize);
437 
438  memset(EtpRundownTraceProperties, 0, sizeof(EVENT_TRACE_PROPERTIES));
439 
440  EtpRundownTraceProperties->Wnode.BufferSize = bufferSize;
441  EtpRundownTraceProperties->Wnode.ClientContext = 1;
442  EtpRundownTraceProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
443  EtpRundownTraceProperties->MinimumBuffers = 1;
444  EtpRundownTraceProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
445  EtpRundownTraceProperties->FlushTimer = 1;
446  EtpRundownTraceProperties->LogFileNameOffset = 0;
447  EtpRundownTraceProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
448 
449  result = StartTrace(&EtpRundownSessionHandle, EtpRundownLoggerName.Buffer, EtpRundownTraceProperties);
450 
451  if (result == ERROR_ALREADY_EXISTS)
452  {
454  // ControlTrace (called from EtpStopEtwRundownSession) screws up the structure.
455  EtpRundownTraceProperties->Wnode.BufferSize = bufferSize;
456  EtpRundownTraceProperties->LogFileNameOffset = 0;
457  EtpRundownTraceProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
458  result = StartTrace(&EtpRundownSessionHandle, EtpRundownLoggerName.Buffer, EtpRundownTraceProperties);
459  }
460 
461  if (result != ERROR_SUCCESS)
462  return result;
463 
464  result = EnableTraceEx(&KernelRundownGuid_I, NULL, EtpRundownSessionHandle, 1, 0, 0x10, 0, 0, NULL);
465 
466  if (result != ERROR_SUCCESS)
467  {
469  return result;
470  }
471 
472  EtpRundownActive = TRUE;
473  EtpRundownEtwMonitorThreadHandle = PhCreateThread(0, EtpRundownEtwMonitorThreadStart, NULL);
474 
475  return result;
476 }
477 
479  VOID
480  )
481 {
482  EtpRundownTraceProperties->LogFileNameOffset = 0;
483  return ControlTrace(0, EtpRundownLoggerName.Buffer, EtpRundownTraceProperties, EVENT_TRACE_CONTROL_STOP);
484 }
485 
487  _In_ PEVENT_TRACE_LOGFILE Buffer
488  )
489 {
490  return !EtpEtwExiting;
491 }
492 
494  _In_ PEVENT_RECORD EventRecord
495  )
496 {
497  // TODO: Find a way to call CloseTrace when the enumeration finishes so we can
498  // stop the trace cleanly.
499 
500  if (memcmp(&EventRecord->EventHeader.ProviderId, &FileIoGuid_I, sizeof(GUID)) == 0)
501  {
502  // FileIo
503 
504  ET_ETW_FILE_EVENT fileEvent;
505 
506  memset(&fileEvent, 0, sizeof(ET_ETW_FILE_EVENT));
507  fileEvent.Type = -1;
508 
509  switch (EventRecord->EventHeader.EventDescriptor.Opcode)
510  {
511  case 36: // FileRundown
512  fileEvent.Type = EtEtwFileRundownType;
513  break;
514  default:
515  break;
516  }
517 
518  if (fileEvent.Type != -1)
519  {
520  FileIo_Name *data = EventRecord->UserData;
521 
522  fileEvent.FileObject = (PVOID)data->FileObject;
523  PhInitializeStringRef(&fileEvent.FileName, data->FileName);
524 
525  EtDiskProcessFileEvent(&fileEvent);
526  }
527  }
528 }
529 
531  _In_ PVOID Parameter
532  )
533 {
534  EVENT_TRACE_LOGFILE logFile;
535  TRACEHANDLE traceHandle;
536 
537  memset(&logFile, 0, sizeof(EVENT_TRACE_LOGFILE));
538  logFile.LoggerName = EtpRundownLoggerName.Buffer;
539  logFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD;
540  logFile.BufferCallback = EtpRundownEtwBufferCallback;
541  logFile.EventRecordCallback = EtpRundownEtwEventCallback;
542  logFile.Context = &traceHandle;
543 
544  traceHandle = OpenTrace(&logFile);
545 
546  if (traceHandle != INVALID_PROCESSTRACE_HANDLE)
547  {
548  ProcessTrace(&traceHandle, 1, NULL, NULL);
549 
550  if (traceHandle != 0)
551  CloseTrace(traceHandle);
552  }
553 
554  NtClose(EtpRundownEtwMonitorThreadHandle);
555  EtpRundownEtwMonitorThreadHandle = NULL;
556 
557  return STATUS_SUCCESS;
558 }