python-3.xwinapictypesetw

ProcessTrace handle is not invoking the callback registered via OpenTraceA using python and ctypes


I have an ETL file (e.g. sample.etl) to parse using the Native WinAPI. Using cpp I am able to parse and process an existing windows etl log. Now, I'm trying to use python to call into the same windows API for easier post-processing. I can open the ETL file using OpenTraceA but ProcessTrace does not call into the Callback function registered. Could python masters check what I might be doing wrong? Thanks.

import ctypes
from ctypes.wintypes import *

advapidll = "C:\\Windows\\System32\\advapi32.dll"
advapi32 = ctypes.WinDLL(advapidll)

ERROR_SUCCESS = 0

PROCESS_TRACE_MODE_EVENT_RECORD = int(hex(10000000), 16)

class _GUID(ctypes.Structure):
    _fields_ = [
        ("Data1", ULONG),
        ("Data2", USHORT),
        ("Data3", USHORT),
        ("Data4", BYTE * 8),
    ]

class EVENT_TRACE_HEADER(ctypes.Structure):
    _fields_ = [
        # Define fields for EVENT_TRACE_HEADER here
    ]

class ETW_BUFFER_CONTEXT(ctypes.Structure):
    _fields_ = [
        ("ProcessorIndex", USHORT),
        ("LoggerId", USHORT),
    ]

class EVENT_TRACE(ctypes.Structure):
    _fields_ = [
        ("Header", EVENT_TRACE_HEADER),
        ("InstanceId", ULONG),
        ("ParentInstanceId", ULONG),
        ("ParentGuid", _GUID),  # Using UUID to represent GUID
        ("MofData", LPVOID),
        ("MofLength", ULONG),
        ("DUMMYUNIONNAME", ULONG),  # This can hold either ClientContext or BufferContext
    ]

class SYSTEMTIME(ctypes.Structure):
    _fields_ = [
        ("wYear", WORD),
        ("wMonth", WORD),
        ("wDayOfWeek", WORD),
        ("wDay", WORD),
        ("wHour", WORD),
        ("wMinute", WORD),
        ("wSecond", WORD),
        ("wMilliseconds", WORD),
    ]

class TIME_ZONE_INFORMATION(ctypes.Structure):
    _fields_ = [
        ("Bias", LONG),
        ("StandardName", WCHAR * 32),  # WCHAR array for StandardName
        ("StandardDate", SYSTEMTIME),
        ("StandardBias", LONG),
        ("DaylightName", WCHAR * 32),  # WCHAR array for DaylightName
        ("DaylightDate", SYSTEMTIME),
        ("DaylightBias", LONG),
    ]

class _TRACE_LOGFILE_HEADER(ctypes.Structure):
    _fields_ = [
        ("BufferSize", ULONG),
        ("Version", ULONG),  # Holds either Version or VersionDetail
        ("ProviderVersion", ULONG),
        ("NumberOfProcessors", ULONG),
        ("EndTime", LARGE_INTEGER),
        ("TimerResolution", ULONG),
        ("MaximumFileSize", ULONG),
        ("LogFileMode", ULONG),
        ("BuffersWritten", ULONG),
        ("LogInstanceGuid", _GUID),  # Holds either LogInstanceGuid or DUMMYSTRUCTNAME
        ("LoggerName", LPWSTR),  # Assuming wchar_t* for wide character string
        ("LogFileName", LPWSTR),  # Assuming wchar_t* for wide character string
        ("TimeZone", TIME_ZONE_INFORMATION),  # Use TIME_ZONE_INFORMATION as per your need
        ("BootTime", LARGE_INTEGER),
        ("PerfFreq", LARGE_INTEGER),
        ("StartTime", LARGE_INTEGER),
        ("ReservedFlags", ULONG),
        ("BuffersLost", ULONG),
    ]

class EVENT_DESCRIPTOR(ctypes.Structure):
    _fields_ = [
        ("Id", USHORT),      # USHORT
        ("Version", CHAR),  # UCHAR
        ("Channel", CHAR),  # UCHAR
        ("Level", CHAR),    # UCHAR
        ("Opcode", CHAR),    # UCHAR
        ("Task", USHORT),    # USHORT
        ("Keyword", ULARGE_INTEGER)  # ULONGLONG
    ]

class EVENT_HEADER(ctypes.Structure):
    _fields_ = [
        ("Size", USHORT),
        ("HeaderType", USHORT),
        ("Flags", USHORT),
        ("EventProperty", USHORT),
        ("ThreadId", ULONG),
        ("ProcessId", ULONG),
        ("TimeStamp", LARGE_INTEGER),
        ("ProviderId", _GUID),
        ("EventDescriptor", EVENT_DESCRIPTOR),
        ("ProcessorTime", ULARGE_INTEGER),
        ("ActivityId", _GUID),
    ]

class EVENT_RECORD(ctypes.Structure):
    _fields_ = [
        ("EventHeader", EVENT_HEADER),
        ("BufferContext", ETW_BUFFER_CONTEXT),
        ("ExtendedDataCount", USHORT),
        ("UserDataLength", USHORT),
        ("ExtendedData", ctypes.POINTER(ULONG)),  # Assuming PEVENT_HEADER_EXTENDED_DATA_ITEM is a pointer to ULONG
        ("UserData", LPVOID),
        ("UserContext", LPVOID),
    ]

PEVENT_RECORD_CALLBACK = ctypes.WINFUNCTYPE(None, ctypes.POINTER(EVENT_RECORD))

class _EVENT_TRACE_LOGFILEA(ctypes.Structure):
    _fields_ = [
        ("LogFileName", LPSTR),
        ("LoggerName", LPSTR),
        ("CurrentTime", LARGE_INTEGER),
        ("BuffersRead", ULONG),
        ("ProcessTraceMode", ULONG),
        ("CurrentEvent", EVENT_TRACE),
        ("LogfileHeader", _TRACE_LOGFILE_HEADER),
        ("BufferCallback", ctypes.WINFUNCTYPE(None)), 
        ("BufferSize", ULONG),
        ("Filled", ULONG),
        ("EventsLost", ULONG),
        ("EventRecordCallback", PEVENT_RECORD_CALLBACK),  # This can hold either EventCallback or EventRecordCallback
        ("IsKernelTrace", ULONG),
        ("Context", LPVOID),
    ]

@PEVENT_RECORD_CALLBACK
def handle_trace_event(event_record):
    print("hi")

OpenTraceA = advapi32.OpenTraceA
OpenTraceA.argtypes = [ctypes.POINTER(_EVENT_TRACE_LOGFILEA)]
OpenTraceA.restype = HANDLE

ProcessTrace = advapi32.ProcessTrace
ProcessTrace.argtypes = [ctypes.POINTER(HANDLE), ULONG, LPVOID, LPVOID]
ProcessTrace.restype = DWORD

def main():
    trace_logfile = _EVENT_TRACE_LOGFILEA()
    trace_logfile.LogFileName = b"sample.etl"
    trace_logfile.EventRecordCallback = handle_trace_event
    trace_logfile.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD

    trace_handle = OpenTraceA(ctypes.byref(trace_logfile))

    if trace_handle == HANDLE(0):
        print("Failed to open trace")
    else:
        print(f"Trace opened successfully: {trace_handle}")

    status = ProcessTrace(ctypes.byref(HANDLE(trace_handle)), 1, 0, 0)
    if (status != ERROR_SUCCESS):
        print(f"Problem occurred during ProcessTrace Status: {status}")

if __name__ == '__main__':
    main()

Solution

  • Listing [Python.Docs]: ctypes - A foreign function library for Python.

    Notes:

    One thing that's wrong

    According to (VStudion's, actually Windows Kits) evntcons.h:

    // ...
    
    #define PROCESS_TRACE_MODE_EVENT_RECORD             0x10000000
    
    // ...
    

    Your constant definition is not correct:

    >>> correct = 0x10000000
    >>> yours = int(hex(10000000), 16)
    >>>
    >>> correct, yours, correct == yours
    (268435456, 10000000, False)
    

    This alone is a good enough reason for your actual behavior (but there might be other errors as well). In other words, simply do:

    PROCESS_TRACE_MODE_EVENT_RECORD = 0x10000000