├── LICENSE ├── README.md └── dotnettracer.c /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2022, Paranoid Ninja 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DotNetTracer 2 | C code to enable ETW tracing for Dotnet Assemblies 3 | 4 | Compile with: 5 | ```x86_64-w64-mingw32-gcc dotnettracer.c -o dotnettracer.exe -ladvapi32``` 6 | -------------------------------------------------------------------------------- /dotnettracer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // compile with: 8 | // x86_64-w64-mingw32-gcc dotnettracer.c -o dotnettracer.exe -ladvapi32 9 | 10 | #define AssemblyDCStart_V1 155 11 | #define PROCESS_TRACE_MODE_REAL_TIME 0x00000100 12 | #define PROCESS_TRACE_MODE_EVENT_RECORD 0x10000000 13 | #define CLR_LOADER_KEYWORD 0x8 14 | #define CLR_STARTENUMERATION_KEYWORD 0x40 15 | // https://docs.microsoft.com/en-us/dotnet/framework/performance/clr-etw-providers 16 | GUID clrRuntimeProvider = { 0xe13c0d23, 0xccbc, 0x4e12, { 0x93, 0x1b, 0xd9, 0xcc, 0x2e, 0xee, 0x27, 0xe4 } }; 17 | extern ULONG EnableTraceEx(LPCGUID ProviderId, LPCGUID SourceId, TRACEHANDLE TraceHandle, ULONG IsEnabled, UCHAR Level, ULONGLONG MatchAnyKeyword, ULONGLONG MatchAllKeyword, ULONG EnableProperty, PEVENT_FILTER_DESCRIPTOR EnableFilterDesc); 18 | extern ULONG EnableTraceEx2(TRACEHANDLE TraceHandle, LPCGUID ProviderId, ULONG ControlCode, UCHAR Level, ULONGLONG MatchAnyKeyword, ULONGLONG MatchAllKeyword, ULONG Timeout, PENABLE_TRACE_PARAMETERS EnableParameters); 19 | 20 | typedef struct _EVENT_DESCRIPTOR { 21 | USHORT Id; 22 | UCHAR Version; 23 | UCHAR Channel; 24 | UCHAR Level; 25 | UCHAR Opcode; 26 | USHORT Task; 27 | ULONGLONG Keyword; 28 | } EVENT_DESCRIPTOR, *PEVENT_DESCRIPTOR; 29 | typedef const EVENT_DESCRIPTOR *PCEVENT_DESCRIPTOR; 30 | 31 | typedef struct _EVENT_HEADER { 32 | USHORT Size; 33 | USHORT HeaderType; 34 | USHORT Flags; 35 | USHORT EventProperty; 36 | ULONG ThreadId; 37 | ULONG ProcessId; 38 | LARGE_INTEGER TimeStamp; 39 | GUID ProviderId; 40 | EVENT_DESCRIPTOR EventDescriptor; 41 | union { 42 | struct { 43 | ULONG KernelTime; 44 | ULONG UserTime; 45 | } DUMMYSTRUCTNAME; 46 | ULONG64 ProcessorTime; 47 | } DUMMYUNIONNAME; 48 | GUID ActivityId; 49 | } EVENT_HEADER, *PEVENT_HEADER; 50 | 51 | typedef struct _EVENT_HEADER_EXTENDED_DATA_ITEM { 52 | USHORT Reserved1; 53 | USHORT ExtType; 54 | struct { 55 | USHORT Linkage : 1; 56 | USHORT Reserved2 : 15; 57 | }; 58 | USHORT DataSize; 59 | ULONGLONG DataPtr; 60 | } EVENT_HEADER_EXTENDED_DATA_ITEM, *PEVENT_HEADER_EXTENDED_DATA_ITEM; 61 | 62 | typedef struct _EVENT_RECORD_DUP { 63 | EVENT_HEADER EventHeader; 64 | ETW_BUFFER_CONTEXT BufferContext; 65 | USHORT ExtendedDataCount; 66 | USHORT UserDataLength; 67 | PEVENT_HEADER_EXTENDED_DATA_ITEM ExtendedData; 68 | PVOID UserData; 69 | PVOID UserContext; 70 | } EVENT_RECORD_DUP, *PEVENT_RECORD_DUP; 71 | 72 | #pragma pack(1) 73 | typedef struct _AssemblyLoadUnloadRundown_V1 74 | { 75 | ULONG64 AssemblyID; 76 | ULONG64 AppDomainID; 77 | ULONG64 BindingID; 78 | ULONG AssemblyFlags; 79 | WCHAR FullyQualifiedAssemblyName[1]; 80 | } AssemblyLoadUnloadRundown_V1, * PAssemblyLoadUnloadRundown_V1; 81 | #pragma pack() 82 | 83 | VOID eventHandler(PEVENT_RECORD_DUP EventRecord) { 84 | PEVENT_HEADER eventHeader = &EventRecord->EventHeader; 85 | PEVENT_DESCRIPTOR eventDescriptor = &eventHeader->EventDescriptor; 86 | AssemblyLoadUnloadRundown_V1* assemblyUserData; 87 | switch (eventDescriptor->Id) { 88 | case AssemblyDCStart_V1: 89 | assemblyUserData = (AssemblyLoadUnloadRundown_V1*)EventRecord->UserData; 90 | printf("[+] PID: %lu | TID: %lu | Assembly: %ls\n", eventHeader->ProcessId, eventHeader->ThreadId, assemblyUserData->FullyQualifiedAssemblyName); 91 | break; 92 | } 93 | } 94 | 95 | void startTracing(CHAR* traceName) { 96 | TRACEHANDLE hTrace = 0; 97 | ULONG errVal = 0; 98 | ULONG bufSize = 0; 99 | PEVENT_TRACE_PROPERTIES eventTracer = NULL; 100 | bufSize = sizeof(EVENT_TRACE_PROPERTIES) + strlen(traceName) + sizeof(WCHAR); 101 | eventTracer = (PEVENT_TRACE_PROPERTIES)calloc(bufSize, 1); 102 | eventTracer->Wnode.BufferSize = bufSize; 103 | eventTracer->Wnode.ClientContext = 2; 104 | eventTracer->Wnode.Flags = WNODE_FLAG_TRACED_GUID; 105 | eventTracer->LogFileMode = EVENT_TRACE_REAL_TIME_MODE | EVENT_TRACE_USE_PAGED_MEMORY; 106 | eventTracer->EnableFlags = EVENT_TRACE_FLAG_NO_SYSCONFIG; 107 | eventTracer->LogFileNameOffset = 0; 108 | eventTracer->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); 109 | 110 | errVal = StartTraceA(&hTrace, traceName, eventTracer); 111 | if (errVal) { 112 | if (errVal == 183) { 113 | printf("Collector Already exists. Use 'read' to read from the collector, or 'stop' to stop the collector\n"); 114 | ExitProcess(0); 115 | } 116 | printf("[-] E1: %lu\n", errVal); 117 | ExitProcess(0); 118 | } 119 | 120 | ENABLE_TRACE_PARAMETERS enableParameters = { 0 }; 121 | enableParameters.Version = ENABLE_TRACE_PARAMETERS_VERSION_2; 122 | // errVal = EnableTraceEx(&clrRuntimeProvider, NULL, hTrace, 1, TRACE_LEVEL_VERBOSE, 0x8, 0, 0, NULL); //Detailed diagnostic events 123 | errVal = EnableTraceEx2(hTrace, &clrRuntimeProvider, EVENT_CONTROL_CODE_ENABLE_PROVIDER, TRACE_LEVEL_INFORMATION, CLR_LOADER_KEYWORD | CLR_STARTENUMERATION_KEYWORD, 0, 0, &enableParameters); 124 | if (errVal) { 125 | printf("[-] E2: %lu\n", errVal); 126 | ExitProcess(0); 127 | } 128 | printf("[+] Tracing Enabled\n"); 129 | } 130 | 131 | void readTracing(CHAR* traceName) { 132 | EVENT_TRACE_LOGFILEA logTracer = { 0 }; 133 | logTracer.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD; 134 | logTracer.LoggerName = (LPSTR)traceName; 135 | logTracer.EventRecordCallback = (PEVENT_RECORD_CALLBACK)eventHandler; 136 | TRACEHANDLE hTrace = OpenTraceA(&logTracer); 137 | if (hTrace == INVALID_PROCESSTRACE_HANDLE) { 138 | printf("[-] E3: lu\n", GetLastError()); 139 | ExitProcess(0); 140 | } 141 | 142 | printf("[+] Reading ETW Events..\n"); 143 | ULONG errVal = ProcessTrace(&hTrace, 1, NULL, NULL); 144 | if (errVal) { 145 | printf("[-] E4: %lu\n", errVal); 146 | ExitProcess(0); 147 | } 148 | } 149 | 150 | void stopTracing(CHAR* traceName) { 151 | PEVENT_TRACE_PROPERTIES eventTracer = NULL; 152 | SIZE_T bufSize = sizeof(EVENT_TRACE_PROPERTIES) + strlen(traceName) + sizeof(WCHAR); 153 | eventTracer = (PEVENT_TRACE_PROPERTIES)calloc(bufSize, 1); 154 | eventTracer->Wnode.BufferSize = bufSize; 155 | eventTracer->LogFileNameOffset = 0; 156 | eventTracer->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); 157 | ULONG errVal = StopTraceA(0, traceName, eventTracer); 158 | if (errVal) { 159 | printf("[-] E5: %lu\n", errVal); 160 | ExitProcess(0); 161 | } 162 | printf("[+] Tracing stopped\n"); 163 | } 164 | 165 | int main(int argc, char* argv[]) { 166 | if (argc != 3) { 167 | printf("Usage:\n[+] Start tracing: %s start \n", argv[0]); 168 | printf("[+] Read exising trace: %s read \n", argv[0]); 169 | printf("[+] Stop tracing: %s stop \n", argv[0]); 170 | return 0; 171 | } 172 | 173 | CHAR *traceName = argv[2]; 174 | if (strcmp((argv[1]), "start") == 0) { 175 | startTracing(traceName); 176 | readTracing(traceName); 177 | } else if (strcmp((argv[1]), "read") == 0) { 178 | readTracing(traceName); 179 | } else if (strcmp((argv[1]), "stop") == 0) { 180 | stopTracing(traceName); 181 | } 182 | 183 | return 0; 184 | } 185 | --------------------------------------------------------------------------------