K/U-MDF 之 WPP
🛷

K/U-MDF 之 WPP

Tags
Windows NT
Reverse
Published
December 2, 2024
Author
Mason

缘起

在分析一些 Windows 系统组件时,不管是内核用户态驱动还是内置应用,经常能看到诸如下图所示的代码块
notion image

探索

初步搜索之后模糊了解到其是 Windows 开发模型中的一个类似于记录日志内置功能
由此类似这样的代码块在逆向分析中可以被视作非功能性用途的垃圾代码,遂多数时候我常因其侵占大片屏幕而将其代码块折叠。
恰逢近期分析某个组件时,载入 PDB 后发现 IDA 在 Disassembly 视图中出现了这样的注释
notion image
其中包含了 Pseudocode 视图中没有的一些信息
__annotation("TMF:", "3fda904f-19e4-31af-9e7b-c406dff68297 mykmdf // SRC=Driver.c MJ= MN=", "#typev Driver_c78 11 "%0KmdfHelloWorld: KmdfHelloWorldEvtDeviceAdd" // LEVEL=TRACE_LEVEL_INFORMATION FLAGS=MYDRIVER_ALL_INFO", "{", "}")
在对此一无所知的情况下,我们可以从中提取出一些有用的信息,诸如函数所在文件名称,日志的格式化字符串, Level 以及 Flags
回到函数列表,我们能看到大量的以 WPP_SF_* 为规则的函数
notion image
根据对照以及个人经验,这样的函数应对应于格式化字符串接收的参数类型
为了进一步理解,动手创建一个类似的工程会有助于我们更好的理解
使用 WPP 的方式参考官方文档:Adding WPP Software Tracing to a Windows Driver
🤔 这里有一个奇怪的点,在 Trace.h 中添加的 config 并未起到应有的作用 TraceEvents
有以下代码,
NTSTATUS DriverEntry( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath ) { WPP_INIT_TRACING(DriverObject, RegistryPath); // NTSTATUS variable to record success or failure NTSTATUS status = STATUS_SUCCESS; // Allocate the driver configuration object WDF_DRIVER_CONFIG config; // Print "Hello World" for DriverEntry KdPrint(("KmdfHelloWorld: DriverEntry\n")); // Initialize the driver configuration object to register the // entry point for the EvtDeviceAdd callback, KmdfHelloWorldEvtDeviceAdd WDF_DRIVER_CONFIG_INIT(&config, KmdfHelloWorldEvtDeviceAdd ); // Finally, create the driver object status = WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE ); if (!NT_SUCCESS(status)) { TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "WdfDriverCreate failed with status 0x%x\n", status); // // Cleanup tracing here because DriverContextCleanup will not be called // as we have failed to create WDFDRIVER object itself. // Please note that if you return failure from DriverEntry after the // WDFDRIVER object is created successfully, you don't have to // call WPP cleanup because in those cases DriverContextCleanup // will be executed when the framework deletes the DriverObject. // WPP_CLEANUP(DriverObject); } return status; }
在有参数的情况下,我们可以看到注释
__annotation("TMF:", "3fda904f-19e4-31af-9e7b-c406dff68297 mykmdf // SRC=Driver.c MJ= MN=", "#typev Driver_c46 10 "%0WdfDriverCreate failed with status 0x%10!x!" // LEVEL=TRACE_LEVEL_ERROR FLAGS=TRACE_DRIVER", "{", "status, ItemLong -- 10", "}")
且其调用函数名为 WPP_SF_D 合理猜测 D 其实意为 DWORD
格式化字符串为:%0WdfDriverCreate failed with status 0x%10!x!
%10!x! 对应于原始的 %x ,这里就会引出一个问题字符串头部的 %0 以及格式化字符串参数前的 %10 代表着什么?
再度仔细搜寻互联网上的内容后,可以找到一篇 RECON 2019 的议题:Using WPP and TraceLogging Tracing 以及其文字内容
其列举出了一些函数的命名规则,各个字母的含义:
q: pointer type (either 32-bit or 64-bit depending on the process architecture) P: pointer type (either 32-bit or 64-bit depending on the process architecture) D: an unsigned 32-bit integer d: a signed 32-bit integer I: an unsigned 64-bit integer S: a Unicode string
以及格式化字符串 %10 的含义:
The 10 and 11 refer to the format string index. 10 is the beginning of user-supplied event parameters. See default.tmf for more information.
具体参考
default.tmf
,这里 copy 一段:
// The #typev statement may be used to convert messages into user-readable forms. // Wherever possible, parameters are processed as their native format, // and the %x!x! style of FormatMessage should be used. // (The #type statement is obsolete) // // Note: Parameter %1 through %9 are predefined. // Parameter is #typev // %1 GUID Friendly Name string // %2 GUID SubType Name string // %3 Thread ID LONG // %4 System Time String // %5 Kernel Time or User Time String // %6 User Time or NULL String // %7 Sequence Number LONG // %8 Process Id LONG // %9 CPU Number LONG // %10 and above are user parameters. // %254 Is reserved // %255 Is reserved // // Note: These parameters are always present, but may not be valid depending on the source. // // User-defined messages always start at message number 10. // Messages 0 through 9 are reserved for system use. // Message number 255 is reserved. // // Available formats for user arguments are - // // Name Description Format // ItemChar CHAR // ItemUChar UCHAR // ItemCharShort USHORT // ItemCharSign SHORT // ItemShort Signed Short SHORT // ItemUShort Unsigned Short USHORT // ItemLong Signed Long, decoded as decimal LONG // ItemULong Unsigned Long, decoded as decimal ULONG // ItemULongX Unsigned Long, seen as hex ULONG // ItemLongLong Signed 64 Bit value LONGLONG // ItemULongLong Unsigned 64 Bit value ULONGLONG // ItemWString Unicode String, null-terminated String // ItemPString Counted ASCII String String // ItemPWString Counted Unicode String String // ItemUnknown String
除此之外,这篇议题详细介绍了 ETW 以及 WPP 在 PDB 符号文件缺失的情况下如何构造出 TMF 文件以实现 tracelog

想来大多时候对于漏洞研究 / 逆向分析,如果微软提供的 PDB 中不包含 TMF 元数据,此时 WPP 于我们了解组件构成的帮助有限,但这样大段 Tracing 代码相当影响审计效率和分析进度,所以接下来设想对使用了 WPP 且符号文件不包含 TMF 的组件:
利用 ida microcode 对 WPP Tracing 部分进行优化,提升可读性。
当然有一些包含了 TMF 信息的组件,我们也可以借此来提升反编译的伪 C 可读性。
将 WPP Tracing 的相关逻辑模板化,对其精简。
譬如将下面 TMF对应的伪C代码精简:
/** __annotation("TMF:", "3fda904f-19e4-31af-9e7b-c406dff68297 mykmdf // SRC=Driver.c MJ= MN=", "#typev Driver_c78 11 "%0KmdfHelloWorld: KmdfHelloWorldEvtDeviceAdd" // LEVEL=TRACE_LEVEL_INFORMATION FLAGS=MYDRIVER_ALL_INFO", "{", "}") **/
if ( WPP_GLOBAL_Control != (WPP_PROJECT_CONTROL_BLOCK *)&WPP_GLOBAL_Control && (WPP_GLOBAL_Control->Control.Flags[0] & 1) != 0 && WPP_GLOBAL_Control->Control.Level >= 4u ) { WPP_SF_(WPP_GLOBAL_Control->Control.Logger, 0xBu, WPP_Driver_c_Traceguids); }
TraceEvents(TRACE_LEVEL_INFORMATION, MYDRIVER_ALL_INFO, "KmdfHelloWorld: KmdfHelloWorldEvtDeviceAdd");