commit 10e24b5042b2df3a03852dcbb27622e0bef6f588 Author: thefLink Date: Sun May 14 19:31:05 2023 +0200 first commit diff --git a/Hunt-Weird-Imageloads/Detectors.cpp b/Hunt-Weird-Imageloads/Detectors.cpp new file mode 100644 index 0000000..8481ffa --- /dev/null +++ b/Hunt-Weird-Imageloads/Detectors.cpp @@ -0,0 +1,150 @@ +#include "Detectors.h" +#include + +namespace Detectors { + + VOID Detectors::PrivateRX::Check ( HANDLE hProcess, std::vector stack, DWORD dwPid, std::wstring imageName) { + + SIZE_T s = 0; + MEMORY_BASIC_INFORMATION mbi = { 0 }; + std::string processName; + + for ( ULONG_PTR pAddr : stack ) { + + s = VirtualQueryEx ( hProcess, ( LPCVOID ) pAddr, &mbi, sizeof ( MEMORY_BASIC_INFORMATION ) ); + if ( s == 0 ) + continue; + + if ( mbi.Type != MEM_PRIVATE ) + continue; + + if ( mbi.Protect == PAGE_EXECUTE_READ ) { + + Helpers::ModuleNameFromAddress ( hProcess, NULL, processName ); + + wprintf ( L"! Process %S ( %d ) loaded %s and callstack contains RX Page: 0x%p\n", processName.c_str ( ), dwPid, imageName.c_str ( ), ( PVOID ) pAddr ); + break; + + } + + } + + } + + VOID Detectors::PrivateRWX::Check ( HANDLE hProcess, std::vector stack, DWORD dwPid, std::wstring imageName ) { + + SIZE_T s = 0; + MEMORY_BASIC_INFORMATION mbi = { 0 }; + std::string processName; + + for ( ULONG_PTR pAddr : stack ) { + + s = VirtualQueryEx ( hProcess, ( LPCVOID ) pAddr, &mbi, sizeof ( MEMORY_BASIC_INFORMATION ) ); + if ( s == 0 ) + continue; + + if ( mbi.Type != MEM_PRIVATE ) + continue; + + if ( mbi.Protect == PAGE_EXECUTE_READ ) { + + Helpers::ModuleNameFromAddress ( hProcess, NULL, processName ); + + wprintf ( L"! Process %S ( %d ) loaded %s and callstack contains RWX Page: 0x%p\n", processName.c_str ( ), dwPid, imageName.c_str ( ), ( PVOID ) pAddr ); + break; + + } + + } + + } + + VOID Detectors::ModuleStomped::Check ( HANDLE hProcess, std::vector stack, DWORD dwPid, std::wstring imageName ) { + + std::string stompedModule; + std::string processName; + + for ( ULONG_PTR pAddr : stack ) { + + if ( Helpers::IsModuleStomped ( hProcess, ( PVOID ) pAddr ) ) { + + Helpers::ModuleNameFromAddress ( hProcess, ( PVOID ) pAddr, stompedModule ); + Helpers::ModuleNameFromAddress ( hProcess, NULL, processName ); + + wprintf ( L"! Process %S ( %d ) loaded %s and callstack contains stomped module: %S\n", processName.c_str ( ), dwPid, imageName.c_str ( ), stompedModule.c_str ( ) ); + break; + + } + } + + } + + // Was ist mit ZwMapViewOfSection ? + VOID Detectors::ModuleProxying::Check ( HANDLE hProcess, std::vector stack, DWORD dwPid, std::wstring imageName ) { + + BOOL bSuccess = FALSE; + + std::string callingmodule; + std::string processName; + std::string symbol; + + if ( stack.size ( ) < 2 ) + return; + + for ( auto it = stack.begin ( ); it != stack.end ( ); ++it ) { + + bSuccess = Helpers::SymbolNameFromAddress ( hProcess, ( PVOID ) *it, symbol ); + if ( bSuccess == FALSE ) + break; + + if ( symbol.find ( "LoadLibrary" ) != std::string::npos ) { + + bSuccess = Helpers::ModuleNameFromAddress ( hProcess, ( PVOID ) * ( it + 1 ), callingmodule ); + if ( bSuccess == FALSE ) + break; + + if ( !_stricmp ( callingmodule.c_str ( ), "ntdll.dll" ) ){ + + Helpers::ModuleNameFromAddress ( hProcess, NULL, processName ); + wprintf ( L"! Process %S ( %d ) loaded %s by proxying loadlibray through ntdll\n", processName.c_str ( ), dwPid, imageName.c_str ( ) ); + + } + + } + + } + + } + + VOID Detectors::DedicatedThread::Check ( HANDLE hProcess, std::vector stack, DWORD dwPid, PVOID baseAddr) { + + BOOL bSuccess = FALSE; + PVOID pLoadLibraryA = NULL, pLoadLibraryW = NULL, pLoadLibraryExA = NULL, pLoadLibraryExW = NULL; + + std::string baseModule; + std::string processName; + + bSuccess = Helpers::ModuleNameFromAddress ( hProcess, ( PVOID ) baseAddr, baseModule ); + if ( bSuccess == FALSE ) + return; + + pLoadLibraryA = GetProcAddress ( GetModuleHandleA ( "kernel32.dll" ), "LoadLibraryA" ); + pLoadLibraryW = GetProcAddress ( GetModuleHandleA ( "kernel32.dll" ), "LoadLibraryW" ); + pLoadLibraryExA = GetProcAddress ( GetModuleHandleA ( "kernel32.dll" ), "LoadLibraryExA" ); + pLoadLibraryExW = GetProcAddress ( GetModuleHandleA ( "kernel32.dll" ), "LoadLibraryExW" ); + + if ( !_stricmp ( baseModule.c_str ( ), "kernel32.dll" ) || !_stricmp ( baseModule.c_str ( ), "kernelbase.dll" ) ) { + + Helpers::ModuleNameFromAddress ( hProcess, NULL, processName ); + + wprintf ( L"! Process %S ( %d ) abnormally started a new thread in %S\n", processName.c_str ( ), dwPid, baseModule.c_str ( ) ); + + if ( baseAddr == pLoadLibraryA || baseAddr == pLoadLibraryW || baseAddr == pLoadLibraryExA || baseAddr == pLoadLibraryExW ) + wprintf ( L"\t !! Thread is abnormally started in a LoadLibrary function :o\n" ); + + } + + } + + +} \ No newline at end of file diff --git a/Hunt-Weird-Imageloads/Detectors.h b/Hunt-Weird-Imageloads/Detectors.h new file mode 100644 index 0000000..683894a --- /dev/null +++ b/Hunt-Weird-Imageloads/Detectors.h @@ -0,0 +1,27 @@ +#pragma once + +#include "windows.h" +#include + +#include "Helpers.h" + +namespace Detectors { + + class DetectorImageLoad { + public: + virtual VOID Check ( HANDLE, std::vector, DWORD, std::wstring ) = 0; + }; + + class DetectorThreadStart { + public: + virtual VOID Check ( HANDLE, std::vector, DWORD, PVOID ) = 0; + }; + + class PrivateRX : public DetectorImageLoad { public: VOID Check ( HANDLE, std::vector, DWORD, std::wstring ); }; + class PrivateRWX : public DetectorImageLoad { public: VOID Check ( HANDLE, std::vector, DWORD, std::wstring ); }; + class ModuleStomped : public DetectorImageLoad { public: VOID Check ( HANDLE, std::vector, DWORD, std::wstring ); }; + class ModuleProxying : public DetectorImageLoad { public: VOID Check ( HANDLE, std::vector, DWORD, std::wstring ); }; + + class DedicatedThread : public DetectorThreadStart { public: VOID Check ( HANDLE, std::vector, DWORD, PVOID); }; + +} \ No newline at end of file diff --git a/Hunt-Weird-Imageloads/Helpers.cpp b/Hunt-Weird-Imageloads/Helpers.cpp new file mode 100644 index 0000000..40ee375 --- /dev/null +++ b/Hunt-Weird-Imageloads/Helpers.cpp @@ -0,0 +1,132 @@ +#include "Helpers.h" +#include "Dbghelp.h" + +namespace Helpers { + + BOOL IsModuleStomped ( HANDLE hProcess, PVOID pAddr ) { + + SIZE_T s = 0; + MEMORY_BASIC_INFORMATION mbi = { 0 }; + PSAPI_WORKING_SET_EX_INFORMATION workingSets = { 0 }; + BOOL bSuccess = FALSE; + + s = VirtualQueryEx ( hProcess, ( LPCVOID ) pAddr, &mbi, sizeof ( MEMORY_BASIC_INFORMATION ) ); + if ( s == 0 ) + goto Cleanup; + + if ( mbi.Type != MEM_IMAGE || mbi.AllocationProtect == PAGE_NOACCESS ) + goto Cleanup; + + workingSets.VirtualAddress = mbi.BaseAddress; + + bSuccess = K32QueryWorkingSetEx ( hProcess, &workingSets, sizeof ( PSAPI_WORKING_SET_EX_INFORMATION ) ); + if ( bSuccess == FALSE ) + goto Cleanup; + + bSuccess = FALSE; + + if ( workingSets.VirtualAttributes.Shared != 0 ) + goto Cleanup; + + bSuccess = TRUE; + + Cleanup: + + return bSuccess; + + } + + BOOL SymbolNameFromAddress ( HANDLE hProcess, PVOID pAddr, std::string& symbol ) { + + BOOL bSuccess = FALSE; + DWORD64 dw64Displacement = 0; + CHAR cSymName [ 256 ] = { 0 }; + + PIMAGEHLP_SYMBOL64 pSymbol = NULL; + + pSymbol = ( PIMAGEHLP_SYMBOL64 ) HeapAlloc ( GetProcessHeap ( ), HEAP_ZERO_MEMORY, sizeof ( IMAGEHLP_SYMBOL64 ) + 256 * sizeof ( WCHAR ) ); + if ( pSymbol == NULL ) + goto Cleanup; + + pSymbol->SizeOfStruct = sizeof ( IMAGEHLP_SYMBOL64 ); + pSymbol->MaxNameLength = 255; + + bSuccess = SymGetSymFromAddr64 ( hProcess, ( ULONG64 ) pAddr, &dw64Displacement, pSymbol ); + if ( bSuccess == FALSE ) + goto Cleanup; + + UnDecorateSymbolName ( pSymbol->Name, cSymName, 256, UNDNAME_COMPLETE ); + symbol = std::string ( cSymName ); + + Cleanup: + + if ( pSymbol ) + HeapFree ( GetProcessHeap ( ), 0, pSymbol ); + + return bSuccess; + + } + + BOOL ModuleNameFromAddress ( HANDLE hProcess, PVOID pAddr, std::string& moduleName ) { + + BOOL bSuccess = FALSE; + SIZE_T s = 0; + MEMORY_BASIC_INFORMATION mbi = { 0 }; + CHAR cmoduleName [ MAX_PATH ] = { 0 }; + + moduleName.clear ( ); + + s = VirtualQueryEx ( hProcess, ( LPCVOID ) pAddr, &mbi, sizeof ( MEMORY_BASIC_INFORMATION ) ); + if ( s == 0 ) + goto Cleanup; + + bSuccess = K32GetModuleBaseNameA ( hProcess, ( HMODULE ) mbi.AllocationBase, ( LPSTR ) cmoduleName, MAX_PATH ); + if ( bSuccess == FALSE ) + goto Cleanup; + + moduleName = std::string ( cmoduleName ); + + bSuccess = TRUE; + + Cleanup: + + return bSuccess; + + } + + VOID RemoveKernelAddrs ( std::vector& stack ) { + + auto it = stack.begin ( ); + while ( it != stack.end ( ) ) { + + ULONG_PTR addr = *it; + if ( addr > 0xFFFF000000000000 ) { + it = stack.erase ( it ); + } + else { + ++it; + } + + } + + } + + //https://github.com/outflanknl/Dumpert/blob/master/Dumpert/Outflank-Dumpert/Dumpert.c Is Elevated() was taken from here :). + BOOL IsElevated ( VOID ) { + BOOL fRet = FALSE; + HANDLE hToken = NULL; + if ( OpenProcessToken ( GetCurrentProcess ( ), TOKEN_QUERY, &hToken ) ) { + TOKEN_ELEVATION Elevation = { 0 }; + DWORD cbSize = sizeof ( TOKEN_ELEVATION ); + if ( GetTokenInformation ( hToken, TokenElevation, &Elevation, sizeof ( Elevation ), &cbSize ) ) { + fRet = Elevation.TokenIsElevated; + } + } + if ( hToken ) { + CloseHandle ( hToken ); + } + return fRet; + } + + +} \ No newline at end of file diff --git a/Hunt-Weird-Imageloads/Helpers.h b/Hunt-Weird-Imageloads/Helpers.h new file mode 100644 index 0000000..2b52019 --- /dev/null +++ b/Hunt-Weird-Imageloads/Helpers.h @@ -0,0 +1,14 @@ +#pragma once +#include "windows.h" +#include "psapi.h" + +#include +#include + +namespace Helpers { + VOID RemoveKernelAddrs ( std::vector& ); + BOOL ModuleNameFromAddress ( HANDLE, PVOID, std::string& ); + BOOL IsElevated ( VOID ); + BOOL IsModuleStomped ( HANDLE, PVOID ); + BOOL SymbolNameFromAddress ( HANDLE, PVOID, std::string& ); +} \ No newline at end of file diff --git a/Hunt-Weird-Imageloads/Hunt-Weird-Imageloads.sln b/Hunt-Weird-Imageloads/Hunt-Weird-Imageloads.sln new file mode 100644 index 0000000..640b347 --- /dev/null +++ b/Hunt-Weird-Imageloads/Hunt-Weird-Imageloads.sln @@ -0,0 +1,51 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.32413.511 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Hunt-Weird-Imageloads", "Hunt-Weird-Imageloads.vcxproj", "{0020C8CD-76A4-4092-9CA5-AF05572CA661}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NewThreadLoadLibrary", "NewThreadLoadLibrary\NewThreadLoadLibrary.vcxproj", "{D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkItemLoadLibrary", "WorkItemLoadLibrary\WorkItemLoadLibrary.vcxproj", "{7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0020C8CD-76A4-4092-9CA5-AF05572CA661}.Debug|x64.ActiveCfg = Debug|x64 + {0020C8CD-76A4-4092-9CA5-AF05572CA661}.Debug|x64.Build.0 = Debug|x64 + {0020C8CD-76A4-4092-9CA5-AF05572CA661}.Debug|x86.ActiveCfg = Debug|Win32 + {0020C8CD-76A4-4092-9CA5-AF05572CA661}.Debug|x86.Build.0 = Debug|Win32 + {0020C8CD-76A4-4092-9CA5-AF05572CA661}.Release|x64.ActiveCfg = Release|x64 + {0020C8CD-76A4-4092-9CA5-AF05572CA661}.Release|x64.Build.0 = Release|x64 + {0020C8CD-76A4-4092-9CA5-AF05572CA661}.Release|x86.ActiveCfg = Release|Win32 + {0020C8CD-76A4-4092-9CA5-AF05572CA661}.Release|x86.Build.0 = Release|Win32 + {D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}.Debug|x64.ActiveCfg = Debug|x64 + {D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}.Debug|x64.Build.0 = Debug|x64 + {D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}.Debug|x86.ActiveCfg = Debug|Win32 + {D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}.Debug|x86.Build.0 = Debug|Win32 + {D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}.Release|x64.ActiveCfg = Release|x64 + {D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}.Release|x64.Build.0 = Release|x64 + {D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}.Release|x86.ActiveCfg = Release|Win32 + {D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}.Release|x86.Build.0 = Release|Win32 + {7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}.Debug|x64.ActiveCfg = Debug|x64 + {7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}.Debug|x64.Build.0 = Debug|x64 + {7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}.Debug|x86.ActiveCfg = Debug|Win32 + {7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}.Debug|x86.Build.0 = Debug|Win32 + {7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}.Release|x64.ActiveCfg = Release|x64 + {7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}.Release|x64.Build.0 = Release|x64 + {7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}.Release|x86.ActiveCfg = Release|Win32 + {7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BE1C43E1-9EF5-4D82-89DE-B7319B283FB1} + EndGlobalSection +EndGlobal diff --git a/Hunt-Weird-Imageloads/Hunt-Weird-Imageloads.vcxproj b/Hunt-Weird-Imageloads/Hunt-Weird-Imageloads.vcxproj new file mode 100644 index 0000000..52e3ab0 --- /dev/null +++ b/Hunt-Weird-Imageloads/Hunt-Weird-Imageloads.vcxproj @@ -0,0 +1,155 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {0020c8cd-76a4-4092-9ca5-af05572ca661} + HuntWeirdImageloads + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + dbghelp.lib;shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + dbghelp.lib;shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Hunt-Weird-Imageloads/Hunt-Weird-Imageloads.vcxproj.filters b/Hunt-Weird-Imageloads/Hunt-Weird-Imageloads.vcxproj.filters new file mode 100644 index 0000000..3eb41a7 --- /dev/null +++ b/Hunt-Weird-Imageloads/Hunt-Weird-Imageloads.vcxproj.filters @@ -0,0 +1,36 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/Hunt-Weird-Imageloads/Hunt-Weird-Imageloads.vcxproj.user b/Hunt-Weird-Imageloads/Hunt-Weird-Imageloads.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/Hunt-Weird-Imageloads/Hunt-Weird-Imageloads.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Hunt-Weird-Imageloads/Main.cpp b/Hunt-Weird-Imageloads/Main.cpp new file mode 100644 index 0000000..a2c6ded --- /dev/null +++ b/Hunt-Weird-Imageloads/Main.cpp @@ -0,0 +1,206 @@ +#include "../libs/krabs/krabs.hpp" +#include "dbghelp.h" +#include + +#include "Detectors.h" +#include "Helpers.h" + +#define EVENTID_THREADSTART 3 +#define EVENTID_IMAGELOAD 5 + +VOID EnableProcessTracing ( krabs::user_trace& ); +VOID Help ( VOID ); +VOID OnImageLoad ( const EVENT_RECORD&, const krabs::trace_context& ); +VOID OnThreadStart ( const EVENT_RECORD&, const krabs::trace_context& ); +VOID ParseArgs ( int, char** ); + +std::vector activeDetectorsImageLoad; +std::vector activeDetectorsThreadStart; + +VOID EnableProcessTracing ( krabs::user_trace& userTrace ) { + + krabs::provider<>* providerProcess = new krabs::provider<> ( L"Microsoft-Windows-Kernel-Process" ); + providerProcess->any ( 0x20 | 0x40 ); // WINEVENT_KEYWORD_THREAD | WINEVENT_KEYWORD_IMAGE + providerProcess->trace_flags ( providerProcess->trace_flags ( ) | EVENT_ENABLE_PROPERTY_STACK_TRACE ); + + krabs::event_filter* imageLoad = new krabs::event_filter ( krabs::predicates::id_is ( EVENTID_IMAGELOAD ) ); + krabs::event_filter* threadStart = new krabs::event_filter ( krabs::predicates::id_is ( EVENTID_THREADSTART ) ); + + imageLoad->add_on_event_callback ( OnImageLoad ); + threadStart->add_on_event_callback ( OnThreadStart ); + + providerProcess->add_filter ( *imageLoad ); + providerProcess->add_filter ( *threadStart ); + + userTrace.enable ( *providerProcess ); + +} + +VOID OnThreadStart ( const EVENT_RECORD& record, const krabs::trace_context& trace_context ) { + + krabs::schema schema ( record, trace_context.schema_locator ); + krabs::parser parser ( schema ); + + std::vector stack = schema.stack_trace ( ); + + DWORD pid = parser.parse ( L"ProcessID" ); + DWORD tid = parser.parse ( L"ThreadID" ); + + PVOID startAddr = parser.parse ( L"StartAddr" ); + + HANDLE hProcess = NULL; + BOOL bIs32Bit; + + hProcess = OpenProcess ( PROCESS_ALL_ACCESS, FALSE, pid ); + if ( hProcess == NULL ) + return; + + IsWow64Process ( hProcess, &bIs32Bit ); + if ( bIs32Bit ) + goto Cleanup; + + for ( Detectors::DetectorThreadStart* detector : activeDetectorsThreadStart ) + detector->Check ( hProcess, stack, pid, startAddr ); + +Cleanup: + + if ( hProcess ) + CloseHandle ( hProcess ); + +} + +VOID OnImageLoad ( const EVENT_RECORD& record, const krabs::trace_context& trace_context ) { + + krabs::schema schema ( record, trace_context.schema_locator ); + krabs::parser parser ( schema ); + + BOOL bIs32Bit = FALSE; + DWORD pid = parser.parse ( L"ProcessID" ); + PVOID imageBase = parser.parse ( L"ImageBase" ); + std::wstring imageName = parser.parse ( L"ImageName" ); + + std::vector stack = schema.stack_trace ( ); + Helpers::RemoveKernelAddrs ( stack ); + + HANDLE hProcess = NULL; + + hProcess = OpenProcess ( PROCESS_ALL_ACCESS, FALSE, pid ); + if ( hProcess == NULL ) + return; + + IsWow64Process ( hProcess, &bIs32Bit ); + if ( bIs32Bit ) + goto Cleanup; + + SymInitialize ( hProcess, NULL, TRUE ); + + for ( Detectors::DetectorImageLoad *detector : activeDetectorsImageLoad ) + detector->Check ( hProcess, stack, pid, imageName ); + +Cleanup: + + if ( hProcess ) { + + SymCleanup ( hProcess ); + CloseHandle ( hProcess ); + + } + +} + +VOID Go ( krabs::user_trace* userTrace ) { + userTrace->start ( ); +} + +VOID Help ( VOID ) { + printf ( "Hunt-Weird-ImageLoads.exe\n\n\t--all activates all alerts\n\t--rx alerts on private rx regions in callstack\n\t--rwx alerts on private rwx regions in callstack\n\t--stomped alerts on stomped modules in callstack\n\t--proxy alerts on abnormal calls to kernel32!loadlibrary from ntdll\n\t--dedicatedthread alerts on thread with baseaddr on loadlibrary*\n\n" ); + exit ( 1 ); +} + +VOID ParseArgs ( int argc, char** argv ) { + + if ( argc < 2 ) + Help ( ); + + for ( int i = 1; i < argc; i++ ) { + + if ( !_strcmpi ( argv [ i ], "--rx" ) ) { + printf ( "\t- Enabling detector RX\n" ); + activeDetectorsImageLoad.push_back ( new Detectors::PrivateRX ( ) ); + } + + else if ( !_strcmpi ( argv [ i ], "--rwx" ) ) { + printf ( "\t- Enabling detector RWX\n" ); + activeDetectorsImageLoad.push_back ( new Detectors::PrivateRWX ( ) ); + } + + else if ( !_strcmpi ( argv [ i ], "--stomped" ) ) { + printf ( "\t- Enabling detector stomped\n" ); + activeDetectorsImageLoad.push_back ( new Detectors::ModuleStomped ( ) ); + } + + else if ( !_strcmpi ( argv [ i ], "--proxy" ) ) { + printf ( "\t- Enabling detector for module proxying\n" ); + activeDetectorsImageLoad.push_back ( new Detectors::ModuleProxying ( ) ); + } + + else if ( !_strcmpi ( argv [ i ], "--dedicatedthread" ) ) { + printf ( "\t- Enabling detector dedicatedthread\n" ); + activeDetectorsThreadStart.push_back ( new Detectors::DedicatedThread ( ) ); + } + + else if ( !_strcmpi ( argv [ i ], "--all" ) ) { + + printf ( "\t- Enabling all detectors\n" ); + + activeDetectorsImageLoad.clear ( ); + + activeDetectorsImageLoad.push_back ( new Detectors::PrivateRX ( ) ); + activeDetectorsImageLoad.push_back ( new Detectors::PrivateRWX ( ) ); + activeDetectorsImageLoad.push_back ( new Detectors::ModuleStomped ( ) ); + activeDetectorsImageLoad.push_back ( new Detectors::ModuleProxying ( ) ); + + activeDetectorsThreadStart.push_back ( new Detectors::DedicatedThread ( ) ); + + } + + else + Help ( ); + + } + +} + +int main ( int argc, char** argv ) { + + HANDLE traceThread = NULL; + + krabs::user_trace userTrace ( L"Hunt-Weird-Imageloads" ); + + if ( !Helpers::IsElevated ( ) ) { + printf ( "- Not elevated\n" ); + return 0; + } + + printf ( "* Hunt-Weird-ImageLoads\n" ); + ParseArgs ( argc, argv ); + + printf ( "* Enabling trace, might take a bit ... \n" ); + + SymSetOptions ( SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS ); + + EnableProcessTracing ( userTrace ); + traceThread = CreateThread ( NULL, 0, ( LPTHREAD_START_ROUTINE ) Go, &userTrace, 0, NULL ); + if ( traceThread == NULL ) + return 0; // o.0 + + printf ( "* Started monitoring, press any key to exit ... \n" ); + + getchar ( ); + printf ( "* exiting ... \n" ); + userTrace.stop ( ); + WaitForSingleObject ( traceThread, INFINITE ); + + return 0; + +} \ No newline at end of file diff --git a/Hunt-Weird-Imageloads/NewThreadLoadLibrary/Main.c b/Hunt-Weird-Imageloads/NewThreadLoadLibrary/Main.c new file mode 100644 index 0000000..9326a96 --- /dev/null +++ b/Hunt-Weird-Imageloads/NewThreadLoadLibrary/Main.c @@ -0,0 +1,36 @@ +#include "windows.h" + +#include "stdio.h" + +HMODULE MyLoadLibrary ( PCSTR mName ) { + + HANDLE hThread = NULL; + + hThread = CreateThread ( NULL, 0, ( LPTHREAD_START_ROUTINE ) LoadLibraryA, ( LPVOID ) mName, 0, NULL ); + if ( hThread == NULL ) + return 0; + + WaitForSingleObject ( hThread, INFINITE ); + + return GetModuleHandleA ( mName ); + +} + +int main ( int arg, char** argv ) { + + HMODULE hModule = NULL; + + hModule = GetModuleHandleA ( "dbghelp.dll" ); + if ( hModule ) { + printf ( "Dll already loaded\n" ); + return 0; + } + + hModule = MyLoadLibrary ( "dbghelp.dll" ); + printf ( "Module at 0x%p\n", hModule ); + + getchar ( ); + + return 0; + +} \ No newline at end of file diff --git a/Hunt-Weird-Imageloads/NewThreadLoadLibrary/NewThreadLoadLibrary.vcxproj b/Hunt-Weird-Imageloads/NewThreadLoadLibrary/NewThreadLoadLibrary.vcxproj new file mode 100644 index 0000000..63c0f81 --- /dev/null +++ b/Hunt-Weird-Imageloads/NewThreadLoadLibrary/NewThreadLoadLibrary.vcxproj @@ -0,0 +1,147 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {d819fee1-5004-4abf-9b2d-6562fbbbe1c5} + NewThreadLoadLibrary + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + \ No newline at end of file diff --git a/Hunt-Weird-Imageloads/NewThreadLoadLibrary/NewThreadLoadLibrary.vcxproj.filters b/Hunt-Weird-Imageloads/NewThreadLoadLibrary/NewThreadLoadLibrary.vcxproj.filters new file mode 100644 index 0000000..35b62f7 --- /dev/null +++ b/Hunt-Weird-Imageloads/NewThreadLoadLibrary/NewThreadLoadLibrary.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/Hunt-Weird-Imageloads/NewThreadLoadLibrary/NewThreadLoadLibrary.vcxproj.user b/Hunt-Weird-Imageloads/NewThreadLoadLibrary/NewThreadLoadLibrary.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/Hunt-Weird-Imageloads/NewThreadLoadLibrary/NewThreadLoadLibrary.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Hunt-Weird-Imageloads/WorkItemLoadLibrary/Main.c b/Hunt-Weird-Imageloads/WorkItemLoadLibrary/Main.c new file mode 100644 index 0000000..2e23085 --- /dev/null +++ b/Hunt-Weird-Imageloads/WorkItemLoadLibrary/Main.c @@ -0,0 +1,43 @@ +#include "windows.h" +#include "stdio.h" + +/* + + Based on: https://github.com/rad9800/misc/blob/main/bypasses/WorkItemLoadLibrary.c + +*/ + +typedef NTSTATUS ( NTAPI* RtlQueueWorkItem )( PVOID, PVOID, ULONG ); + +HMODULE MyLoadLibrary ( PCSTR mName ) { + + HANDLE hThread = NULL; + RtlQueueWorkItem _RtlQueueWorkItem = ( RtlQueueWorkItem ) GetProcAddress ( GetModuleHandleA ( "ntdll.dll" ), "RtlQueueWorkItem" ); + _RtlQueueWorkItem ( LoadLibraryA, ( PVOID ) mName, WT_EXECUTEDEFAULT ); + + Sleep ( 1000 ); // Dirty :-) + + return GetModuleHandleA ( mName ); + +} + + +int +main ( int argc, char** argv ) { + + HMODULE hModule = NULL; + + hModule = GetModuleHandleA ( "wininet.dll" ); + if ( hModule ) { + printf ( "Dll already loaded\n" ); + return 0; + } + + hModule = MyLoadLibrary ( "wininet.dll" ); + printf ( "Module at 0x%p\n", hModule ); + + getchar ( ); + + return 0; + +} \ No newline at end of file diff --git a/Hunt-Weird-Imageloads/WorkItemLoadLibrary/WorkItemLoadLibrary.vcxproj b/Hunt-Weird-Imageloads/WorkItemLoadLibrary/WorkItemLoadLibrary.vcxproj new file mode 100644 index 0000000..3f6565b --- /dev/null +++ b/Hunt-Weird-Imageloads/WorkItemLoadLibrary/WorkItemLoadLibrary.vcxproj @@ -0,0 +1,147 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {7ec5d2d4-3e4c-418f-82d3-98c232f6cc9b} + WorkItemLoadLibrary + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + \ No newline at end of file diff --git a/Hunt-Weird-Imageloads/WorkItemLoadLibrary/WorkItemLoadLibrary.vcxproj.filters b/Hunt-Weird-Imageloads/WorkItemLoadLibrary/WorkItemLoadLibrary.vcxproj.filters new file mode 100644 index 0000000..35b62f7 --- /dev/null +++ b/Hunt-Weird-Imageloads/WorkItemLoadLibrary/WorkItemLoadLibrary.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/Hunt-Weird-Imageloads/WorkItemLoadLibrary/WorkItemLoadLibrary.vcxproj.user b/Hunt-Weird-Imageloads/WorkItemLoadLibrary/WorkItemLoadLibrary.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/Hunt-Weird-Imageloads/WorkItemLoadLibrary/WorkItemLoadLibrary.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..cae9bce --- /dev/null +++ b/Readme.md @@ -0,0 +1,34 @@ +# Hunt-Weird-ImageLoads + +This project was created to play with different IOCs caused by Imageload events. +It leverages ETW to monitor for ImageLoad events and walks the callstack to identify some possible IOCs, such as: + +- R(W)X page in callstack +- Stomped module in callstack +- Module proxying ( ntdll -> kernel32!LoadLibrary ) as described [here](https://github.com/rad9800/misc/blob/main/bypasses/WorkItemLoadLibrary.c) or [here](https://0xdarkvortex.dev/proxying-dll-loads-for-hiding-etwti-stack-tracing) +- New thread dedicated to load a library + +There are two sample programs for **module proxying** and **dedicated threads** in this repository. + +![In action](/screens/1.png?raw=true) + +## Conclusion + +In my tests, I had a lot of false positives monitoring for private or module stomped pages in the callstack and this is probably not a valid IOC. +However, it seems that both, **module proxying** and **dedicated threads** are quite abnormal, but see yourself. + +## Usage + +``` + --all activates all alerts + --rx alerts on private rx regions in callstack + --rwx alerts on private rwx regions in callstack + --stomped alerts on stomped modules in callstack + --proxy alerts on abnormal calls to kernel32!loadlibrary from ntdll + --dedicatedthread alerts on thread with baseaddr on loadlibrary* +``` + +## Credits + +- [@rad9800](https://twitter.com/rad9800) [For an example implementation of LoadLibray via RtlQueueWorkItem](https://github.com/rad9800/misc/blob/main/bypasses/WorkItemLoadLibrary.c) +- [@NinjaParanoid](https://twitter.com/NinjaParanoid) [For a super cool blogpost on this topic](https://0xdarkvortex.dev/proxying-dll-loads-for-hiding-etwti-stack-tracing/) \ No newline at end of file diff --git a/libs/krabs/LICENSE b/libs/krabs/LICENSE new file mode 100644 index 0000000..a369f2b --- /dev/null +++ b/libs/krabs/LICENSE @@ -0,0 +1,25 @@ +krabsetw + +Copyright (c) Microsoft Corporation + +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the ""Software""), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libs/krabs/README.md b/libs/krabs/README.md new file mode 100644 index 0000000..7894bf7 --- /dev/null +++ b/libs/krabs/README.md @@ -0,0 +1,17 @@ +# Krabs Readme + +Important Preprocessor Definitions: + +* `UNICODE` - krabsetw expects the `UNICODE` preprocessor definition to be + defined. The code will not successfully compile without this flag. There is + no plan to support compilation without `UNICODE` being set. + +* `NDEBUG` - Set this variable in release builds to disable runtime type + assertions. You'll still get a runtime error if the size type you're + requesting is not the same size as the property in the event schema. + +* `TYPEASSERT` - Set this variable only in debug builds (not `NDEBUG`) to enable + strict assertions. This means that if an explicit type check is not defined + for a requested type, a `static_assert` is thrown and the code will not + compile until one is added. This is mainly used for krabs development to + ensure that we don't miss asserts for types that are supported. diff --git a/libs/krabs/krabs.hpp b/libs/krabs/krabs.hpp new file mode 100644 index 0000000..1e1ae51 --- /dev/null +++ b/libs/krabs/krabs.hpp @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#pragma comment(lib, "advapi32.lib") +#pragma comment(lib, "ole32.lib") + + +// +// /\ +// ( / @ @ () +// \ __| |__ / +// -/ " \- +// /-| |-\ +// / /-\ /-\ \ +// / /-`---'-\ \ +// / \ +// +// Summary +// ---------------------------------------------------------------------------- +// Krabs is a wrapper around ETW because ETW is the worst API ever made. + +#pragma warning(push) +#pragma warning(disable: 4512) // stupid spurious "can't generate assignment error" warning +#pragma warning(disable: 4634) // DocXml comment warnings in native C++ +#pragma warning(disable: 4635) // DocXml comment warnings in native C++ + +#include "krabs/compiler_check.hpp" +#include "krabs/ut.hpp" +#include "krabs/kt.hpp" +#include "krabs/guid.hpp" +#include "krabs/trace.hpp" +#include "krabs/trace_context.hpp" +#include "krabs/client.hpp" +#include "krabs/errors.hpp" +#include "krabs/schema.hpp" +#include "krabs/schema_locator.hpp" +#include "krabs/parse_types.hpp" +#include "krabs/collection_view.hpp" +#include "krabs/size_provider.hpp" +#include "krabs/parser.hpp" +#include "krabs/property.hpp" +#include "krabs/provider.hpp" +#include "krabs/etw.hpp" +#include "krabs/tdh_helpers.hpp" +#include "krabs/kernel_providers.hpp" + +#include "krabs/testing/proxy.hpp" +#include "krabs/testing/filler.hpp" +#include "krabs/testing/synth_record.hpp" +#include "krabs/testing/record_builder.hpp" +#include "krabs/testing/event_filter_proxy.hpp" +#include "krabs/testing/record_property_thunk.hpp" + +#include "krabs/filtering/view_adapters.hpp" +#include "krabs/filtering/comparers.hpp" +#include "krabs/filtering/predicates.hpp" +#include "krabs/filtering/event_filter.hpp" + +#pragma warning(pop) diff --git a/libs/krabs/krabs.runsettings b/libs/krabs/krabs.runsettings new file mode 100644 index 0000000..184a772 --- /dev/null +++ b/libs/krabs/krabs.runsettings @@ -0,0 +1,9 @@ + + + + + False + MTA + x64 + + \ No newline at end of file diff --git a/libs/krabs/krabs.sln b/libs/krabs/krabs.sln new file mode 100644 index 0000000..70aa052 --- /dev/null +++ b/libs/krabs/krabs.sln @@ -0,0 +1,161 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32317.152 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{5C9E224B-AEB7-456B-B309-92292ACE1A0D}" + ProjectSection(SolutionItems) = preProject + .nuget\NuGet.Config = .nuget\NuGet.Config + .nuget\NuGet.exe = .nuget\NuGet.exe + .nuget\NuGet.targets = .nuget\NuGet.targets + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.O365.Security.Native.ETW", "..\Microsoft.O365.Security.Native.ETW\Microsoft.O365.Security.Native.ETW.vcxproj", "{ED4E6027-541F-440A-A5EE-15DBB7B89423}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "krabstests", "..\tests\krabstests\krabstests.vcxproj", "{880977B8-15CA-421B-BF48-D01626A530A2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E583602B-48AC-46A6-B0F0-343CE0B6AE33}" + ProjectSection(SolutionItems) = preProject + krabs.runsettings = krabs.runsettings + MTA.testsettings = MTA.testsettings + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "krabs headers", "krabs headers", "{1FD19105-D67C-492B-B98F-53E00A324269}" + ProjectSection(SolutionItems) = preProject + krabs.hpp = krabs.hpp + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "krabs", "krabs", "{371361C8-96EC-4D6D-B80B-2E47E3453264}" + ProjectSection(SolutionItems) = preProject + krabs\client.hpp = krabs\client.hpp + krabs\collection_view.hpp = krabs\collection_view.hpp + krabs\compiler_check.hpp = krabs\compiler_check.hpp + krabs\errors.hpp = krabs\errors.hpp + krabs\etw.hpp = krabs\etw.hpp + krabs\guid.hpp = krabs\guid.hpp + krabs\kernel_guids.hpp = krabs\kernel_guids.hpp + krabs\kernel_providers.hpp = krabs\kernel_providers.hpp + krabs\kt.hpp = krabs\kt.hpp + krabs\parser.hpp = krabs\parser.hpp + krabs\parse_types.hpp = krabs\parse_types.hpp + krabs\perfinfo_groupmask.hpp = krabs\perfinfo_groupmask.hpp + krabs\property.hpp = krabs\property.hpp + krabs\provider.hpp = krabs\provider.hpp + krabs\schema.hpp = krabs\schema.hpp + krabs\schema_locator.hpp = krabs\schema_locator.hpp + krabs\size_provider.hpp = krabs\size_provider.hpp + krabs\tdh_helpers.hpp = krabs\tdh_helpers.hpp + krabs\trace.hpp = krabs\trace.hpp + krabs\trace_context.hpp = krabs\trace_context.hpp + krabs\ut.hpp = krabs\ut.hpp + krabs\version_helpers.hpp = krabs\version_helpers.hpp + krabs\wstring_convert.hpp = krabs\wstring_convert.hpp + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "filtering", "filtering", "{96FA58B5-A1F6-4107-9FB4-226290F9D696}" + ProjectSection(SolutionItems) = preProject + krabs\filtering\comparers.hpp = krabs\filtering\comparers.hpp + krabs\filtering\event_filter.hpp = krabs\filtering\event_filter.hpp + krabs\filtering\predicates.hpp = krabs\filtering\predicates.hpp + krabs\filtering\view_adapters.hpp = krabs\filtering\view_adapters.hpp + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testing", "testing", "{9ED1AE76-2EAA-4CCF-8F01-458BDC8BCD53}" + ProjectSection(SolutionItems) = preProject + krabs\testing\event_filter_proxy.hpp = krabs\testing\event_filter_proxy.hpp + krabs\testing\extended_data_builder.hpp = krabs\testing\extended_data_builder.hpp + krabs\testing\filler.hpp = krabs\testing\filler.hpp + krabs\testing\proxy.hpp = krabs\testing\proxy.hpp + krabs\testing\record_builder.hpp = krabs\testing\record_builder.hpp + krabs\testing\record_property_thunk.hpp = krabs\testing\record_property_thunk.hpp + krabs\testing\synth_record.hpp = krabs\testing\synth_record.hpp + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EtwTestsCS", "..\tests\ManagedETWTests\EtwTestsCS.csproj", "{600CFE03-FD84-4323-9439-839D81C31972}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C4AB7F5F-2FB3-4C16-A1F3-F6700C655B02}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{2E00634C-7E8B-4656-9505-78FF2F5D0EDD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedExamples", "..\examples\ManagedExamples\ManagedExamples.csproj", "{32E71DD0-D11A-44DE-8CA8-572995AF2373}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NativeExamples", "..\examples\NativeExamples\NativeExamples.vcxproj", "{D31B1A4B-8282-4AED-99FC-9AA5974B9134}" + ProjectSection(ProjectDependencies) = postProject + {ED4E6027-541F-440A-A5EE-15DBB7B89423} = {ED4E6027-541F-440A-A5EE-15DBB7B89423} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.O365.Security.Native.ETW.NetCore", "..\Microsoft.O365.Security.Native.ETW.NetCore\Microsoft.O365.Security.Native.ETW.NetCore.vcxproj", "{9DE6788C-5759-4A75-B484-ABA4C7EF5F08}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + DebugSigning|x64 = DebugSigning|x64 + Release|x64 = Release|x64 + ReleaseSigning|x64 = ReleaseSigning|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ED4E6027-541F-440A-A5EE-15DBB7B89423}.Debug|x64.ActiveCfg = Debug|x64 + {ED4E6027-541F-440A-A5EE-15DBB7B89423}.Debug|x64.Build.0 = Debug|x64 + {ED4E6027-541F-440A-A5EE-15DBB7B89423}.DebugSigning|x64.ActiveCfg = DebugSigning|x64 + {ED4E6027-541F-440A-A5EE-15DBB7B89423}.DebugSigning|x64.Build.0 = DebugSigning|x64 + {ED4E6027-541F-440A-A5EE-15DBB7B89423}.Release|x64.ActiveCfg = Release|x64 + {ED4E6027-541F-440A-A5EE-15DBB7B89423}.Release|x64.Build.0 = Release|x64 + {ED4E6027-541F-440A-A5EE-15DBB7B89423}.ReleaseSigning|x64.ActiveCfg = ReleaseSigning|x64 + {ED4E6027-541F-440A-A5EE-15DBB7B89423}.ReleaseSigning|x64.Build.0 = ReleaseSigning|x64 + {880977B8-15CA-421B-BF48-D01626A530A2}.Debug|x64.ActiveCfg = Debug|x64 + {880977B8-15CA-421B-BF48-D01626A530A2}.Debug|x64.Build.0 = Debug|x64 + {880977B8-15CA-421B-BF48-D01626A530A2}.DebugSigning|x64.ActiveCfg = DebugSigning|x64 + {880977B8-15CA-421B-BF48-D01626A530A2}.DebugSigning|x64.Build.0 = DebugSigning|x64 + {880977B8-15CA-421B-BF48-D01626A530A2}.Release|x64.ActiveCfg = Release|x64 + {880977B8-15CA-421B-BF48-D01626A530A2}.Release|x64.Build.0 = Release|x64 + {880977B8-15CA-421B-BF48-D01626A530A2}.ReleaseSigning|x64.ActiveCfg = ReleaseSigning|x64 + {880977B8-15CA-421B-BF48-D01626A530A2}.ReleaseSigning|x64.Build.0 = ReleaseSigning|x64 + {600CFE03-FD84-4323-9439-839D81C31972}.Debug|x64.ActiveCfg = Debug|Any CPU + {600CFE03-FD84-4323-9439-839D81C31972}.Debug|x64.Build.0 = Debug|Any CPU + {600CFE03-FD84-4323-9439-839D81C31972}.DebugSigning|x64.ActiveCfg = Debug|Any CPU + {600CFE03-FD84-4323-9439-839D81C31972}.DebugSigning|x64.Build.0 = Debug|Any CPU + {600CFE03-FD84-4323-9439-839D81C31972}.Release|x64.ActiveCfg = Release|Any CPU + {600CFE03-FD84-4323-9439-839D81C31972}.Release|x64.Build.0 = Release|Any CPU + {600CFE03-FD84-4323-9439-839D81C31972}.ReleaseSigning|x64.ActiveCfg = Release|Any CPU + {600CFE03-FD84-4323-9439-839D81C31972}.ReleaseSigning|x64.Build.0 = Release|Any CPU + {32E71DD0-D11A-44DE-8CA8-572995AF2373}.Debug|x64.ActiveCfg = Debug|Any CPU + {32E71DD0-D11A-44DE-8CA8-572995AF2373}.Debug|x64.Build.0 = Debug|Any CPU + {32E71DD0-D11A-44DE-8CA8-572995AF2373}.DebugSigning|x64.ActiveCfg = DebugSigning|Any CPU + {32E71DD0-D11A-44DE-8CA8-572995AF2373}.DebugSigning|x64.Build.0 = DebugSigning|Any CPU + {32E71DD0-D11A-44DE-8CA8-572995AF2373}.Release|x64.ActiveCfg = Release|Any CPU + {32E71DD0-D11A-44DE-8CA8-572995AF2373}.Release|x64.Build.0 = Release|Any CPU + {32E71DD0-D11A-44DE-8CA8-572995AF2373}.ReleaseSigning|x64.ActiveCfg = ReleaseSigning|Any CPU + {32E71DD0-D11A-44DE-8CA8-572995AF2373}.ReleaseSigning|x64.Build.0 = ReleaseSigning|Any CPU + {D31B1A4B-8282-4AED-99FC-9AA5974B9134}.Debug|x64.ActiveCfg = Debug|x64 + {D31B1A4B-8282-4AED-99FC-9AA5974B9134}.Debug|x64.Build.0 = Debug|x64 + {D31B1A4B-8282-4AED-99FC-9AA5974B9134}.DebugSigning|x64.ActiveCfg = DebugSigning|x64 + {D31B1A4B-8282-4AED-99FC-9AA5974B9134}.DebugSigning|x64.Build.0 = DebugSigning|x64 + {D31B1A4B-8282-4AED-99FC-9AA5974B9134}.Release|x64.ActiveCfg = Release|x64 + {D31B1A4B-8282-4AED-99FC-9AA5974B9134}.Release|x64.Build.0 = Release|x64 + {D31B1A4B-8282-4AED-99FC-9AA5974B9134}.ReleaseSigning|x64.ActiveCfg = ReleaseSigning|x64 + {D31B1A4B-8282-4AED-99FC-9AA5974B9134}.ReleaseSigning|x64.Build.0 = ReleaseSigning|x64 + {9DE6788C-5759-4A75-B484-ABA4C7EF5F08}.Debug|x64.ActiveCfg = Debug|x64 + {9DE6788C-5759-4A75-B484-ABA4C7EF5F08}.Debug|x64.Build.0 = Debug|x64 + {9DE6788C-5759-4A75-B484-ABA4C7EF5F08}.DebugSigning|x64.ActiveCfg = DebugSigning|x64 + {9DE6788C-5759-4A75-B484-ABA4C7EF5F08}.DebugSigning|x64.Build.0 = DebugSigning|x64 + {9DE6788C-5759-4A75-B484-ABA4C7EF5F08}.Release|x64.ActiveCfg = Release|x64 + {9DE6788C-5759-4A75-B484-ABA4C7EF5F08}.Release|x64.Build.0 = Release|x64 + {9DE6788C-5759-4A75-B484-ABA4C7EF5F08}.ReleaseSigning|x64.ActiveCfg = ReleaseSigning|x64 + {9DE6788C-5759-4A75-B484-ABA4C7EF5F08}.ReleaseSigning|x64.Build.0 = ReleaseSigning|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {880977B8-15CA-421B-BF48-D01626A530A2} = {C4AB7F5F-2FB3-4C16-A1F3-F6700C655B02} + {371361C8-96EC-4D6D-B80B-2E47E3453264} = {1FD19105-D67C-492B-B98F-53E00A324269} + {96FA58B5-A1F6-4107-9FB4-226290F9D696} = {371361C8-96EC-4D6D-B80B-2E47E3453264} + {9ED1AE76-2EAA-4CCF-8F01-458BDC8BCD53} = {371361C8-96EC-4D6D-B80B-2E47E3453264} + {600CFE03-FD84-4323-9439-839D81C31972} = {C4AB7F5F-2FB3-4C16-A1F3-F6700C655B02} + {32E71DD0-D11A-44DE-8CA8-572995AF2373} = {2E00634C-7E8B-4656-9505-78FF2F5D0EDD} + {D31B1A4B-8282-4AED-99FC-9AA5974B9134} = {2E00634C-7E8B-4656-9505-78FF2F5D0EDD} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {82BAA012-2EF9-4303-A429-CDA3655D5009} + EndGlobalSection +EndGlobal diff --git a/libs/krabs/krabs/client.hpp b/libs/krabs/krabs/client.hpp new file mode 100644 index 0000000..010d9d3 --- /dev/null +++ b/libs/krabs/krabs/client.hpp @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include "compiler_check.hpp" +#include "ut.hpp" +#include "kt.hpp" +#include "trace.hpp" + +namespace krabs { + + /** + * + * Specialization of the base trace class for user traces. + * + */ + typedef krabs::trace user_trace; + + /** + * + * Specialization of the base trace class for kernel traces. + * + */ + typedef krabs::trace kernel_trace; +} diff --git a/libs/krabs/krabs/collection_view.hpp b/libs/krabs/krabs/collection_view.hpp new file mode 100644 index 0000000..be301a9 --- /dev/null +++ b/libs/krabs/krabs/collection_view.hpp @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include + +#include "compiler_check.hpp" + +namespace krabs { + + /** + * Wraps a range of a collection starting at the location + * specified by the begin iterator and ending a the location + * specified by the end iterator. The underlying items are + * left in-place and should be considered const + */ + template + struct collection_view + { + private: + const T beg_; + const T end_; + + public: + /** + * Construct a new view for the range specified by the + * iterators 'begin' and 'end' + */ + collection_view(const T begin, const T end) + : beg_(begin) + , end_(end) + { } + + /** + * Get the iterator for the beginning of the view range + */ + const T begin() const + { + return beg_; + } + + /** + * Get the iterator for the end of the view range + */ + const T end() const + { + return end_; + } + }; + + /** + * Create a view over the range specified by iterators 'begin' and 'end' + */ + template + inline collection_view view(const T& begin, const T& end) + { + return{ begin, end }; + } + + /** + * Create a const_iterator view over the specified string + */ + template + inline collection_view::const_iterator> view(const std::basic_string& string) + { + return{ string.cbegin(), string.cend() }; + } + + /** + * Create a const view over the range starting at 'begin' extending 'length' items + */ + template + inline collection_view view(const T* begin, size_t length) + { + return{ begin, begin + length }; + } + + /** + * Create a const view over the specified array + */ + template + inline collection_view view(const T(&arr)[n]) + { + return{ arr, arr + n }; + } + +} /* namespace krabs */ \ No newline at end of file diff --git a/libs/krabs/krabs/compiler_check.hpp b/libs/krabs/krabs/compiler_check.hpp new file mode 100644 index 0000000..85227a7 --- /dev/null +++ b/libs/krabs/krabs/compiler_check.hpp @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#if (_MSC_VER < 1900) +#error "krabsetw is only supported with Visual Studio 2015 and above (MSVC++ 14.0)" +#endif diff --git a/libs/krabs/krabs/errors.hpp b/libs/krabs/krabs/errors.hpp new file mode 100644 index 0000000..046fbb1 --- /dev/null +++ b/libs/krabs/krabs/errors.hpp @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include + +#include "compiler_check.hpp" + +namespace krabs { + + class trace_already_registered : public std::runtime_error { + public: + trace_already_registered() + : std::runtime_error("The trace session has already been registered") + {} + }; + + class invalid_parameter : public std::logic_error { + public: + invalid_parameter() + : std::logic_error("Invalid parameter given") + {} + }; + + class open_trace_failure : public std::runtime_error { + public: + open_trace_failure() + : std::runtime_error("Failure to open trace") + {} + }; + + class need_to_be_admin_failure : public std::runtime_error { + public: + need_to_be_admin_failure() + : std::runtime_error("Need to be an admin") + {} + }; + + class could_not_find_schema : public std::runtime_error { + public: + could_not_find_schema() + : std::runtime_error("Could not find the schema") + {} + + could_not_find_schema(const std::string& context) + : std::runtime_error(std::string("Could not find the schema: ") + context) + {} + }; + + class type_mismatch_assert : public std::runtime_error { + public: + type_mismatch_assert( + const char* property, + const char* actual, + const char* requested) + : std::runtime_error(std::string("Attempt to read property '") + + property + "' type " + actual + " as " + requested) + {} + }; + + class no_trace_sessions_remaining : public std::runtime_error { + public: + no_trace_sessions_remaining() + : std::runtime_error("No more trace sessions available.") + {} + }; + + class function_not_supported : public std::runtime_error { + public: + function_not_supported() + : std::runtime_error("This function is not supported on this system.") + {} + }; + + class unexpected_error : public std::runtime_error { + public: + unexpected_error(ULONG status) + : std::runtime_error(std::string("An unexpected error occurred: status_code=") + + std::to_string(status)) + {} + + unexpected_error(const std::string &context) + : std::runtime_error(std::string("An unexpected error occurred: ") + context) + {} + }; + + inline std::string get_status_and_record_context(ULONG status, const EVENT_RECORD& record) + { + std::stringstream message; + message << "status_code=" + << status + << " provider_id=" + << std::to_string(record.EventHeader.ProviderId) + << " event_id=" + << record.EventHeader.EventDescriptor.Id; + + return message.str(); + } + + /** + * Checks for common ETW API error codes. + */ + inline void error_check_common_conditions(ULONG status) + { + if (status == ERROR_SUCCESS) { + return; + } + + switch (status) { + case ERROR_ALREADY_EXISTS: + throw krabs::trace_already_registered(); + case ERROR_INVALID_PARAMETER: + throw krabs::invalid_parameter(); + case ERROR_ACCESS_DENIED: + throw krabs::need_to_be_admin_failure(); + case ERROR_NOT_FOUND: + throw krabs::could_not_find_schema(); + case ERROR_NO_SYSTEM_RESOURCES: + throw krabs::no_trace_sessions_remaining(); + case ERROR_NOT_SUPPORTED: + throw krabs::function_not_supported(); + default: + throw krabs::unexpected_error(status); + } + } + + /** + * Checks for common ETW API error codes and includes properties from the event record. + */ + inline void error_check_common_conditions(ULONG status, const EVENT_RECORD &record) + { + if (status == ERROR_SUCCESS) { + return; + } + + auto context = get_status_and_record_context(status, record); + + switch (status) { + case ERROR_ALREADY_EXISTS: + throw krabs::trace_already_registered(); + case ERROR_INVALID_PARAMETER: + throw krabs::invalid_parameter(); + case ERROR_ACCESS_DENIED: + throw krabs::need_to_be_admin_failure(); + case ERROR_NOT_FOUND: + throw krabs::could_not_find_schema(context); + case ERROR_NO_SYSTEM_RESOURCES: + throw krabs::no_trace_sessions_remaining(); + case ERROR_NOT_SUPPORTED: + throw krabs::function_not_supported(); + default: + throw krabs::unexpected_error(context); + } + } +} diff --git a/libs/krabs/krabs/etw.hpp b/libs/krabs/krabs/etw.hpp new file mode 100644 index 0000000..0bc2265 --- /dev/null +++ b/libs/krabs/krabs/etw.hpp @@ -0,0 +1,403 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// Interface for ETW. + +#pragma once + +#define INITGUID + +#include "compiler_check.hpp" +#include "trace.hpp" +#include "errors.hpp" + +#include +#include + +#include +#include + +namespace krabs { namespace details { + + // The ETW API requires that we reserve enough memory behind + // an EVENT_TRACE_PROPERTIES buffer in order to store an ETW trace name + // and an optional ETW log file name. The easiest way to do this is to + // use a struct to reserve this space -- the alternative is to malloc + // the bytes at runtime (ew). + class trace_info { + public: + EVENT_TRACE_PROPERTIES properties; + wchar_t traceName[MAX_PATH]; + wchar_t logfileName[MAX_PATH]; + }; + + /** + * + * Used to implement starting and stopping traces. + * + */ + template + class trace_manager { + public: + trace_manager(T &trace); + + /** + * + * Starts the ETW trace identified by the info in the trace type. + * + */ + void start(); + + /** + * + * Stops the ETW trace identified by the info in the trace type. + * + */ + void stop(); + + /** + * + * Opens the ETW trace identified by the info in the trace type. + * + */ + EVENT_TRACE_LOGFILE open(); + + /** + * + * Starts processing the ETW trace identified by the info in the trace type. + * open() needs to called for this to work first. + * + */ + void process(); + + /** + * + * Queries the ETW trace identified by the info in the trace type. + * + */ + EVENT_TRACE_PROPERTIES query(); + + /** + * + * Configures the ETW trace session settings. + * See https://docs.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-tracesetinformation. + * + */ + void set_trace_information( + TRACE_INFO_CLASS information_class, + PVOID trace_information, + ULONG information_length); + + /** + * + * Notifies the underlying trace of the buffers that were processed. + * + */ + void set_buffers_processed(size_t processed); + + /** + * + * Notifies the underlying trace that an event occurred. + * + */ + void on_event(const EVENT_RECORD &record); + + private: + trace_info fill_trace_info(); + EVENT_TRACE_LOGFILE fill_logfile(); + void close_trace(); + void register_trace(); + EVENT_TRACE_PROPERTIES query_trace(); + void stop_trace(); + EVENT_TRACE_LOGFILE open_trace(); + void process_trace(); + void enable_providers(); + + private: + T &trace_; + }; + + // Implementation + // ------------------------------------------------------------------------ + + /** + * + * Called by ETW when an event occurs, forwards calls to the + * appropriate instance. + * + * + * A pointer to the instance is stored in the UserContext + * field of the EVENT_RECORD. This is set via the Context field of the + * EVENT_TRACE_LOGFILE structure. + * + */ + template + static void __stdcall trace_callback_thunk(EVENT_RECORD *pRecord) + { + auto *pUserTrace = (T*)(pRecord->UserContext); + trace_manager trace(*pUserTrace); + trace.on_event(*pRecord); + } + + /** + * + * Called by ETW after the events for each buffer are delivered, gives + * statistics like the number of buffers processed and the number of + * events dropped. + * + * + * A pointer to the instance is stored in the UserContext + * field of the EVENT_RECORD. This is set via the Context field of the + * EVENT_TRACE_LOGFILE structure. + * + */ + template + static ULONG __stdcall trace_buffer_callback(EVENT_TRACE_LOGFILE *pLogFile) + { + auto *pTrace = (T*)(pLogFile->Context); + trace_manager trace(*pTrace); + + // NOTE: EventsLost is not set on this type + trace.set_buffers_processed(pLogFile->BuffersRead); + return TRUE; + } + + template + trace_manager::trace_manager(T &trace) + : trace_(trace) + {} + + template + void trace_manager::start() + { + if (trace_.sessionHandle_ == INVALID_PROCESSTRACE_HANDLE) { + (void)open(); + } + process_trace(); + } + + template + EVENT_TRACE_LOGFILE trace_manager::open() + { + register_trace(); + enable_providers(); + return open_trace(); + } + + template + void trace_manager::process() + { + process_trace(); + } + + template + EVENT_TRACE_PROPERTIES trace_manager::query() + { + return query_trace(); + } + + template + void trace_manager::set_trace_information( + TRACE_INFO_CLASS information_class, + PVOID trace_information, + ULONG information_length) + { + ULONG status = TraceSetInformation( + trace_.registrationHandle_, + information_class, + trace_information, + information_length); + + error_check_common_conditions(status); + } + + template + void trace_manager::stop() + { + stop_trace(); + close_trace(); + } + + template + void trace_manager::set_buffers_processed(size_t processed) + { + trace_.buffersRead_ = processed; + } + + template + void trace_manager::on_event(const EVENT_RECORD &record) + { + trace_.on_event(record); + } + + template + trace_info trace_manager::fill_trace_info() + { + trace_info info = {}; + info.properties.Wnode.BufferSize = sizeof(trace_info); + info.properties.Wnode.Guid = T::trace_type::get_trace_guid(); + info.properties.Wnode.Flags = WNODE_FLAG_TRACED_GUID; + info.properties.Wnode.ClientContext = 1; // QPC clock resolution + info.properties.BufferSize = trace_.properties_.BufferSize; + info.properties.MinimumBuffers = trace_.properties_.MinimumBuffers; + info.properties.MaximumBuffers = trace_.properties_.MaximumBuffers; + info.properties.FlushTimer = trace_.properties_.FlushTimer; + + if (trace_.properties_.LogFileMode) + info.properties.LogFileMode = trace_.properties_.LogFileMode; + else + info.properties.LogFileMode = EVENT_TRACE_REAL_TIME_MODE + | EVENT_TRACE_NO_PER_PROCESSOR_BUFFERING; + + info.properties.LogFileMode |= T::trace_type::augment_file_mode(); + info.properties.LoggerNameOffset = offsetof(trace_info, logfileName); + info.properties.EnableFlags = T::trace_type::construct_enable_flags(trace_); + assert(info.traceName[0] == '\0'); + assert(info.logfileName[0] == '\0'); + trace_.name_._Copy_s(info.traceName, ARRAYSIZE(info.traceName), trace_.name_.length()); + return info; + } + + template + EVENT_TRACE_LOGFILE trace_manager::fill_logfile() + { + EVENT_TRACE_LOGFILE file = {}; + + if (!trace_.logFilename_.empty()) + { + file.LogFileName = const_cast(trace_.logFilename_.c_str()); + file.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD; + } + else + { + file.LoggerName = const_cast(trace_.name_.c_str()); + file.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD | + PROCESS_TRACE_MODE_REAL_TIME; + } + file.Context = (void *)&trace_; + file.EventRecordCallback = trace_callback_thunk; + file.BufferCallback = trace_buffer_callback; + return file; + } + + template + void trace_manager::stop_trace() + { + trace_info info = fill_trace_info(); + ULONG status = ControlTrace( + NULL, + trace_.name_.c_str(), + &info.properties, + EVENT_TRACE_CONTROL_STOP); + + if (status != ERROR_WMI_INSTANCE_NOT_FOUND) { + error_check_common_conditions(status); + } + } + + template + EVENT_TRACE_PROPERTIES trace_manager::query_trace() + { + trace_info info = fill_trace_info(); + ULONG status = ControlTrace( + NULL, + trace_.name_.c_str(), + &info.properties, + EVENT_TRACE_CONTROL_QUERY); + + if (status != ERROR_WMI_INSTANCE_NOT_FOUND) { + error_check_common_conditions(status); + + return info.properties; + } + + return { }; + } + + template + void trace_manager::register_trace() + { + trace_info info = fill_trace_info(); + + ULONG status = StartTrace(&trace_.registrationHandle_, + trace_.name_.c_str(), + &info.properties); + if (status == ERROR_ALREADY_EXISTS) { + try { + stop_trace(); + status = StartTrace(&trace_.registrationHandle_, + trace_.name_.c_str(), + &info.properties); + } + catch (need_to_be_admin_failure) { + (void)open_trace(); + close_trace(); + // insufficient privilege to stop/configure + // but if open/close didn't throw also + // then we're okay to process events + status = ERROR_SUCCESS; + // we also invalidate the registrationHandle_ + // StartTrace() actually sets this to 0 on failure + trace_.registrationHandle_ = INVALID_PROCESSTRACE_HANDLE; + } + catch (invalid_parameter) { + // In some versions, the error code is 87 when using + // SystemTraceControlGuid session. If open/close doesn't + // throw, then we can continually processing events. + (void)open_trace(); + close_trace(); + status = ERROR_SUCCESS; + trace_.registrationHandle_ = INVALID_PROCESSTRACE_HANDLE; + } + } + + error_check_common_conditions(status); + } + + template + EVENT_TRACE_LOGFILE trace_manager::open_trace() + { + auto file = fill_logfile(); + trace_.sessionHandle_ = OpenTrace(&file); + if (trace_.sessionHandle_ == INVALID_PROCESSTRACE_HANDLE) { + throw open_trace_failure(); + } + return file; + } + + template + void trace_manager::process_trace() + { + if (trace_.sessionHandle_ == INVALID_PROCESSTRACE_HANDLE) { + throw open_trace_failure(); + } + + // Refactoring warning. + // During the testing of the (slower) C++/CLI implementation it became evident that + // EnableTraceEx2(EVENT_CONTROL_CODE_CAPTURE_STATE) must be called very shortly + // before ProcessTrace() in order for the rundown events to be generated. + T::trace_type::enable_rundown(trace_); + + ULONG status = ProcessTrace(&trace_.sessionHandle_, 1, NULL, NULL); + error_check_common_conditions(status); + } + + template + void trace_manager::close_trace() + { + if (trace_.sessionHandle_ != INVALID_PROCESSTRACE_HANDLE) { + ULONG status = CloseTrace(trace_.sessionHandle_); + trace_.sessionHandle_ = INVALID_PROCESSTRACE_HANDLE; + + if (status != ERROR_CTX_CLOSE_PENDING) { + error_check_common_conditions(status); + } + } + } + + template + void trace_manager::enable_providers() + { + T::trace_type::enable_providers(trace_); + } +} /* namespace details */ } /* namespace krabs */ diff --git a/libs/krabs/krabs/filtering/comparers.hpp b/libs/krabs/krabs/filtering/comparers.hpp new file mode 100644 index 0000000..31d106d --- /dev/null +++ b/libs/krabs/krabs/filtering/comparers.hpp @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include + +#include "../compiler_check.hpp" + +namespace krabs { namespace predicates { + + namespace comparers { + + // Algorithms + // -------------------------------------------------------------------- + + /** + * Iterator based equals + */ + template + struct equals + { + template + bool operator()(Iter1 first1, Iter1 last1, Iter2 first2, Iter2 last2) const + { + return std::equal(first1, last1, first2, last2, Comparer()); + } + }; + + /** + * Iterator based search + */ + template + struct contains + { + template + bool operator()(Iter1 first1, Iter1 last1, Iter2 first2, Iter2 last2) const + { + // empty test range always contained, even when input range empty + return first2 == last2 + || std::search(first1, last1, first2, last2, Comparer()) != last1; + } + }; + + /** + * Iterator based starts_with + */ + template + struct starts_with + { + template + bool operator()(Iter1 first1, Iter1 last1, Iter2 first2, Iter2 last2) const + { + const auto first_nonequal = std::mismatch(first1, last1, first2, last2, Comparer()); + return first_nonequal.second == last2; + } + }; + + /** + * Iterator based ends_with + */ + template + struct ends_with + { + template + bool operator()(Iter1 first1, Iter1 last1, Iter2 first2, Iter2 last2) const + { + const auto dist1 = std::distance(first1, last1); + const auto dist2 = std::distance(first2, last2); + + if (dist2 > dist1) + return false; + + const auto suffix_begin = std::next(first1, dist1 - dist2); + return std::equal(suffix_begin, last1, first2, last2, Comparer()); + } + }; + + // Custom Comparison + // -------------------------------------------------------------------- + + template + struct iequal_to + { + bool operator()(const T& a, const T& b) const + { + static_assert(sizeof(T) == 0, + "iequal_to needs a specialized overload for type"); + } + }; + + /** + * + * Binary predicate for comparing two wide characters case insensitively + * Does not handle all locales + * + */ + template <> + struct iequal_to + { + bool operator()(const wchar_t& a, const wchar_t& b) const + { + return towupper(a) == towupper(b); + } + }; + + /** + * + * Binary predicate for comparing two characters case insensitively + * Does not handle all locales + * + */ + template <> + struct iequal_to + { + bool operator()(const char& a, const char& b) const + { + return toupper(a) == toupper(b); + } + }; + + } /* namespace comparers */ + +} /* namespace predicates */ } /* namespace krabs */ \ No newline at end of file diff --git a/libs/krabs/krabs/filtering/event_filter.hpp b/libs/krabs/krabs/filtering/event_filter.hpp new file mode 100644 index 0000000..e24c505 --- /dev/null +++ b/libs/krabs/krabs/filtering/event_filter.hpp @@ -0,0 +1,234 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include +#include +#include +#include + +#include "../compiler_check.hpp" +#include "../trace_context.hpp" + +namespace krabs { namespace testing { + class event_filter_proxy; +} /* namespace testing */} /* namespace krabs */ + +namespace krabs { namespace details { + template class base_provider; +} /* namespace details */} /* namespace krabs */ + + +namespace krabs { + + typedef void(*c_provider_callback)(const EVENT_RECORD &, const krabs::trace_context &); + typedef void(*c_provider_error_callback)(const EVENT_RECORD&, const std::string&); + typedef std::function provider_event_callback; + typedef std::function provider_error_callback; + typedef std::function filter_predicate; + + template class provider; + + /** + * + * Use this to provide event filtering before an event bubbles to + * specific callbacks. + * + * + * Each event_filter has a single predicate (which can do complicated + * checks and logic on the event). All callbacks registered under the + * filter are invoked only if the predicate returns true for a given + * event. + * + */ + class event_filter { + public: + + /** + * + * Constructs an event_filter that applies the given predicate to all + * events. + * + */ + event_filter(filter_predicate predicate); + + /** + * + * Constructs an event_filter that applies event id filtering by event_id + * which will be added to list of filtered event ids in ETW API. + * This way is more effective from performance point of view. + * Given optional predicate will be applied to ETW API filtered results + * + */ + event_filter(unsigned short event_id, filter_predicate predicate=nullptr); + + /** + * + * Constructs an event_filter that applies event id filtering by event_id + * which will be added to list of filtered event ids in ETW API. + * This way is more effective from performance point of view. + * Given optional predicate will be applied to ETW API filtered results + * + */ + event_filter(std::vector event_ids, filter_predicate predicate = nullptr); + + /** + * + * Adds a function to call when an event for this filter is fired. + * + */ + void add_on_event_callback(c_provider_callback callback); + + template + void add_on_event_callback(U &callback); + + template + void add_on_event_callback(const U &callback); + + /** + * + * Adds a function to call when an error occurs. + * + */ + void add_on_error_callback(c_provider_error_callback callback); + + template + void add_on_error_callback(U& callback); + + template + void add_on_error_callback(const U& callback); + + const std::vector& provider_filter_event_ids() const + { + return provider_filter_event_ids_; + } + + private: + + /** + * + * Called when an event occurs, forwards to callbacks if the event + * satisfies the predicate. + * + */ + void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) const; + + /** + * + * Called when an error occurs, forwards to the error callback + * + */ + void on_error(const EVENT_RECORD& record, const std::string& error_message) const; + + private: + std::deque event_callbacks_; + std::deque error_callbacks_; + filter_predicate predicate_{ nullptr }; + std::vector provider_filter_event_ids_; + + private: + template + friend class details::base_provider; + + friend class krabs::testing::event_filter_proxy; + }; + + // Implementation + // ------------------------------------------------------------------------ + + inline event_filter::event_filter(filter_predicate predicate) + : predicate_(predicate) + {} + + inline event_filter::event_filter(std::vector event_ids, filter_predicate predicate/*=nullptr*/) + : provider_filter_event_ids_{ event_ids }, + predicate_(predicate) + {} + + inline event_filter::event_filter(unsigned short event_id, filter_predicate predicate/*=nullptr*/) + : provider_filter_event_ids_{ event_id }, + predicate_(predicate) + {} + + inline void event_filter::add_on_event_callback(c_provider_callback callback) + { + // C function pointers don't interact well with std::ref, so we + // overload to take care of this scenario. + event_callbacks_.push_back(callback); + } + + template + void event_filter::add_on_event_callback(U &callback) + { + // std::function copies its argument -- because our callbacks list + // is a list of std::function, this causes problems when a user + // intended for their particular instance to be called. + // std::ref lets us get around this and point to a specific instance + // that they handed us. + event_callbacks_.push_back(std::ref(callback)); + } + + template + void event_filter::add_on_event_callback(const U &callback) + { + // This is where temporaries bind to. Temporaries can't be wrapped in + // a std::ref because they'll go away very quickly. We are forced to + // actually copy these. + event_callbacks_.push_back(callback); + } + + inline void event_filter::add_on_error_callback(c_provider_error_callback callback) + { + // C function pointers don't interact well with std::ref, so we + // overload to take care of this scenario. + error_callbacks_.push_back(callback); + } + + template + void event_filter::add_on_error_callback(U& callback) + { + // std::function copies its argument -- because our callbacks list + // is a list of std::function, this causes problems when a user + // intended for their particular instance to be called. + // std::ref lets us get around this and point to a specific instance + // that they handed us. + error_callbacks_.push_back(std::ref(callback)); + } + + template + void event_filter::add_on_error_callback(const U& callback) + { + // This is where temporaries bind to. Temporaries can't be wrapped in + // a std::ref because they'll go away very quickly. We are forced to + // actually copy these. + error_callbacks_.push_back(callback); + } + + inline void event_filter::on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) const + { + if (event_callbacks_.empty()) { + return; + } + + try + { + if (predicate_ != nullptr && !predicate_(record, trace_context)) { + return; + } + + for (auto& callback : event_callbacks_) { + callback(record, trace_context); + } + } + catch (const krabs::could_not_find_schema& ex) + { + // this occurs when a predicate is applied to an event for which + // no schema exists. instead of allowing the exception to halt + // the entire trace, send a notification to the filter's error callback + for (auto& error_callback : error_callbacks_) { + error_callback(record, ex.what()); + } + } + } +} /* namespace krabs */ diff --git a/libs/krabs/krabs/filtering/predicates.hpp b/libs/krabs/krabs/filtering/predicates.hpp new file mode 100644 index 0000000..2f68030 --- /dev/null +++ b/libs/krabs/krabs/filtering/predicates.hpp @@ -0,0 +1,552 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include +#include +#include +#include + +#include "../compiler_check.hpp" +#include "comparers.hpp" +#include "../trace_context.hpp" +#include "view_adapters.hpp" + +using namespace krabs::predicates::adapters; +using namespace krabs::predicates::comparers; + +namespace krabs { namespace predicates { + + namespace details { + + /** + * + * The base predicate struct, use to create a vector or list of + * Arbitrary predicate types + * + */ + struct predicate_base + { + virtual bool operator()(const EVENT_RECORD&, const krabs::trace_context&) const = 0; + }; + + /** + * + * Returns true for any event. + * + */ + struct any_event : predicate_base { + bool operator()(const EVENT_RECORD &, const krabs::trace_context &) const + { + return true; + } + }; + + /** + * + * Returns false for any event. + * + */ + struct no_event : predicate_base { + bool operator()(const EVENT_RECORD &, const krabs::trace_context &) const + { + return false; + } + }; + + /** + * + * Performs a logical AND on two filters. + * + */ + template + struct and_filter : predicate_base { + and_filter(const T1 &t1, const T2 &t2) + : t1_(t1) + , t2_(t2) + {} + + bool operator()(const EVENT_RECORD &record, const krabs::trace_context &trace_context) const + { + return (t1_(record, trace_context) && t2_(record, trace_context)); + } + + private: + const T1 t1_; + const T2 t2_; + }; + + /** + * + * Performs a logical OR on two filters. + * + */ + template + struct or_filter : predicate_base { + or_filter(const T1 &t1, const T2 &t2) + : t1_(t1) + , t2_(t2) + {} + + bool operator()(const EVENT_RECORD &record, const krabs::trace_context &trace_context) const + { + return (t1_(record, trace_context) || t2_(record, trace_context)); + } + + private: + const T1 t1_; + const T2 t2_; + }; + + /** + * + * Performs a logical NOT on a filter. + * + */ + template + struct not_filter : predicate_base { + not_filter(const T1 &t1) + : t1_(t1) + {} + + bool operator()(const EVENT_RECORD &record, const krabs::trace_context &trace_context) const + { + return !t1_(record, trace_context); + } + + private: + const T1 t1_; + }; + + /** + * + * Returns true if the event property matches the expected value. + * + */ + template + struct property_is : predicate_base { + property_is(const std::wstring &property, const T &expected) + : property_(property) + , expected_(expected) + {} + + bool operator()(const EVENT_RECORD &record, const krabs::trace_context &trace_context) const + { + krabs::schema schema(record, trace_context.schema_locator); + krabs::parser parser(schema); + + try { + return (expected_ == parser.parse(property_)); + } + catch (...) { + return false; + } + } + + private: + const std::wstring property_; + const T expected_; + }; + + /** + * + * Gets a collection_view of a property using the specified adapter + * and executes the specified predicate against the view. + * This is used to provide type-specialization for properties + * that can be represented by the collection_view. + * + */ + template + struct property_view_predicate : details::predicate_base + { + property_view_predicate( + const std::wstring &property, + const T &expected, + Adapter adapter, + Predicate predicate) + : property_(property) + , expected_(expected) + , adapter_(adapter) + , predicate_(predicate) + { } + + //bool operator()(const EVENT_RECORD &record, const krabs::trace_context &trace_context) + bool operator()(const EVENT_RECORD& record, const krabs::trace_context& trace_context) const + { + krabs::schema schema(record, trace_context.schema_locator); + krabs::parser parser(schema); + + try { + auto view = parser.view_of(property_, adapter_); + return predicate_(view.begin(), view.end(), expected_.begin(), expected_.end()); + } + catch (...) { + return false; + } + } + + private: + const std::wstring property_; + const T expected_; + Adapter adapter_; + Predicate predicate_; + }; + } /* namespace details */ + + // Filter factory functions + // ------------------------------------------------------------------------ + + /** + * + * A simple filter that accepts any event. + * + */ + static details::any_event any_event; + + /** + * + * A simple filter that accepts no events. + * + */ + static details::no_event no_event; + + /** + * + * Accepts an event if its ID matches the expected value. + * + */ + struct id_is : details::predicate_base { + id_is(size_t expected) + : expected_(USHORT(expected)) + {} + + bool operator()(const EVENT_RECORD &record, const krabs::trace_context &) const + { + return (record.EventHeader.EventDescriptor.Id == expected_); + } + + private: + USHORT expected_; + }; + + /** + * + * Accepts an event if its opcode matches the expected value. + * + */ + struct opcode_is : details::predicate_base { + opcode_is(size_t expected) + : expected_(USHORT(expected)) + {} + + bool operator()(const EVENT_RECORD &record, const krabs::trace_context &) const + { + return (record.EventHeader.EventDescriptor.Opcode == expected_); + } + + private: + USHORT expected_; + }; + + /** + * + * Accepts an event if any of the predicates in the vector matches + * + */ + struct any_of : details::predicate_base { + any_of(std::vector list) + : list_(list) + {} + + bool operator()(const EVENT_RECORD &record, const krabs::trace_context &trace_context) const + { + for (auto &item : list_) { + if (item->operator()(record, trace_context)) { + return true; + }; + } + return false; + } + private: + std::vector list_; + }; + + /** + * + * Accepts an event if all of the predicates in the vector matches + * + */ + struct all_of : details::predicate_base { + all_of(std::vector list) + : list_(list) + {} + + bool operator()(const EVENT_RECORD& record, const krabs::trace_context& trace_context) const + { + if (list_.empty()) { + return false; + } + for (auto& item : list_) { + if (!item->operator()(record, trace_context)) { + return false; + }; + } + return true; + } + private: + std::vector list_; + }; + + /** + * + * Accepts an event only if none of the predicates in the vector match + * + */ + struct none_of : details::predicate_base { + none_of(std::vector list) + : list_(list) + {} + + bool operator()(const EVENT_RECORD& record, const krabs::trace_context& trace_context) const + { + for (auto& item : list_) { + if (item->operator()(record, trace_context)) { + return false; + }; + } + return true; + } + private: + std::vector list_; + }; + + /** + * + * Accepts an event if its version matches the expected value. + * + */ + struct version_is : details::predicate_base { + version_is(size_t expected) + : expected_(USHORT(expected)) + {} + + bool operator()(const EVENT_RECORD &record, const krabs::trace_context &) const + { + return (record.EventHeader.EventDescriptor.Version == expected_); + } + + private: + USHORT expected_; + }; + + /** + * + * Accepts an event if its PID matches the expected value. + * + */ + struct process_id_is : details::predicate_base { + process_id_is(size_t expected) + : expected_(ULONG(expected)) + {} + + bool operator()(const EVENT_RECORD &record, const krabs::trace_context &) const + { + return (record.EventHeader.ProcessId == expected_); + } + + private: + ULONG expected_; + }; + + /** + * + * Accepts an event if the named property matches the expected value. + * + */ + template + details::property_is property_is( + const std::wstring &prop, + const T &expected) + { + return details::property_is(prop, expected); + } + + /** + * + * Explicit specialization for string arrays, because C++. + * + */ + inline details::property_is property_is( + const std::wstring &prop, + const wchar_t *expected) + { + return details::property_is(prop, std::wstring(expected)); + } + + inline details::property_is property_is( + const std::wstring &prop, + const char *expected) + { + return details::property_is(prop, std::string(expected)); + } + + // View-based filters + // ------------------------------------------------------------------------ + + /** + * View based filters work on ranges of data in-place in the etw record. + * By default, these expect a null terminated string property in the + * record, but if an adapter is specified, any range of data can be + * compared. The comparison functor must support taking two iterator + * pairs that iterate the value_type specified by the adapter. Because + * comparisons use iterators, it's possible to compare various flavors + * of strings as well as arrays and binary data. + */ + + /** + * Accepts events if property exactly matches the expected value + */ + template < + typename Adapter = adapters::generic_string, + typename T, + typename Comparer = equals>> + details::property_view_predicate property_equals( + const std::wstring &prop, + const T& expected) + { + return { prop, expected, Adapter(), Comparer() }; + } + + /** + * Accepts events if property case insensitive matches the expected value + */ + template < + typename Adapter = adapters::generic_string, + typename T, + typename Comparer = equals>> + details::property_view_predicate property_iequals( + const std::wstring &prop, + const T& expected) + { + return{ prop, expected, Adapter(), Comparer() }; + } + + /** + * Accepts events if property contains expected value + */ + template < + typename Adapter = adapters::generic_string, + typename T, + typename Comparer = contains>> + details::property_view_predicate property_contains( + const std::wstring &prop, + const T& expected) + { + return {prop, expected, Adapter(), Comparer()}; + } + + /** + * Accepts events if property case insensitive contains expected value + */ + template < + typename Adapter = adapters::generic_string, + typename T, + typename Comparer = contains>> + details::property_view_predicate property_icontains( + const std::wstring &prop, + const T& expected) + { + return{ prop, expected, Adapter(), Comparer() }; + } + + /** + * Accepts events if property starts with expected value + */ + template < + typename Adapter = adapters::generic_string, + typename T, + typename Comparer = starts_with>> + details::property_view_predicate property_starts_with( + const std::wstring &prop, + const T& expected) + { + return{ prop, expected, Adapter(), Comparer() }; + } + + /** + * Accepts events if property case insensitive starts with expected value + */ + template < + typename Adapter = adapters::generic_string, + typename T, + typename Comparer = starts_with>> + details::property_view_predicate property_istarts_with( + const std::wstring &prop, + const T& expected) + { + return{ prop, expected, Adapter(), Comparer() }; + } + + /** + * Accepts events if property ends with expected value + */ + template < + typename Adapter = adapters::generic_string, + typename T, + typename Comparer = ends_with>> + details::property_view_predicate property_ends_with( + const std::wstring &prop, + const T& expected) + { + return{ prop, expected, Adapter(), Comparer() }; + } + + /** + * Accepts events if property case insensitive ends with expected value + */ + template < + typename Adapter = adapters::generic_string, + typename T, + typename Comparer = ends_with>> + details::property_view_predicate property_iends_with( + const std::wstring &prop, + const T& expected) + { + return{ prop, expected, Adapter(), Comparer() }; + } + + /** + * + * Accepts an event if its two component filters both accept the event. + * + */ + template + details::and_filter and_filter(const T1 &t1, const T2 &t2) + { + return details::and_filter(t1, t2); + } + + /** + * + * Accepts an event if either of its two component filters accept the event. + * + */ + template + details::or_filter or_filter(const T1 &t1, const T2 &t2) + { + return details::or_filter(t1, t2); + } + + /** + * + * Negates the filter that is given to it. + * + */ + template + details::not_filter not_filter(const T1 &t1) + { + return details::not_filter(t1); + } + +} /* namespace predicates */ } /* namespace krabs */ diff --git a/libs/krabs/krabs/filtering/view_adapters.hpp b/libs/krabs/krabs/filtering/view_adapters.hpp new file mode 100644 index 0000000..4580323 --- /dev/null +++ b/libs/krabs/krabs/filtering/view_adapters.hpp @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include + +#include "../compiler_check.hpp" +#include "../parser.hpp" + +namespace krabs { namespace predicates { + + namespace adapters { + + /** + * View adapter for counted_string strings + */ + struct counted_string + { + using value_type = krabs::counted_string::value_type; + using const_iterator = krabs::counted_string::const_iterator; + + collection_view operator()(const property_info& propInfo) const + { + auto cs_ptr = reinterpret_cast(propInfo.pPropertyIndex_); + return krabs::view(cs_ptr->string(), cs_ptr->length()); + } + }; + + /** + * View adapter for fixed width and null-terminated strings + */ + template + struct generic_string + { + using value_type = ElemT; + using const_iterator = const value_type*; + + collection_view operator()(const property_info& propInfo) const + { + auto pString = reinterpret_cast(propInfo.pPropertyIndex_); + auto length = get_string_content_length(pString, propInfo.length_); + + return krabs::view(pString, length); + } + }; + + } /* namespace adapters */ + +} /* namespace predicates */ } /* namespace krabs */ \ No newline at end of file diff --git a/libs/krabs/krabs/guid.hpp b/libs/krabs/krabs/guid.hpp new file mode 100644 index 0000000..1ecf207 --- /dev/null +++ b/libs/krabs/krabs/guid.hpp @@ -0,0 +1,345 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include +#include + +#include +#include +#include +#include +#include + +#include "compiler_check.hpp" + +namespace krabs { + + /** + * Represents a GUID, allowing simplified construction from a string or + * Windows GUID structure. + * + */ + class guid { + public: + guid(GUID guid); + guid(const std::wstring &guid); + + bool operator==(const guid &rhs) const; + bool operator==(const GUID &rhs) const; + + bool operator<(const guid &rhs) const; + bool operator<(const GUID &rhs) const; + + operator GUID() const; + operator const GUID*() const; + + /** + * Constructs a new random guid. + * + */ + static inline guid random_guid(); + + private: + GUID guid_; + + friend struct std::hash; + }; + + /** + * Helper functions for parsing GUID's. + * + */ + class guid_parser { + // Implementing in Krabs instead of Lobsters so that we can test it in unmanaged code. + private: + // Number of characters in the UUID's 8-4-4-4-12 string format. + static const size_t UUID_STRING_LENGTH = 36; + + static const unsigned char DELIMITER = '-'; + + // Expected character positions of runs of hex digits in 8-4-4-4-12 format, e.g. + // 00000000-0000-0000-0000-000000000000 + // Names correspond to struct members of GUID. + static const size_t STR_POSITION_DATA1 = 0; + static const size_t STR_POSITION_DATA2 = 8 + 1; + static const size_t STR_POSITION_DATA3 = STR_POSITION_DATA2 + 4 + 1; + static const size_t STR_POSITION_DATA4_PART1 = STR_POSITION_DATA3 + 4 + 1; + static const size_t STR_POSITION_DATA4_PART2 = STR_POSITION_DATA4_PART1 + 4 + 1; + + public: + // str_input must have at least 2 valid chars in allocated buffer + static bool hex_octet_to_byte(const char* str_input, unsigned char& byte_output); + // str_input must have at least 2*sizeof(T) valid chars in allocated buffer + template + static bool hex_string_to_number(const char* str_input, T& int_output); + // str_input must have at least 2*byte_count valid chars in allocated buffer, + // and byte_output must have at least byte_count bytes in allocated buffer + static bool hex_string_to_bytes(const char* str_input, unsigned char* byte_output, size_t byte_count); + + /** + * Parses GUID of "D" format. For example, the nil GUID would be "00000000-0000-0000-0000-000000000000". + * See: https://docs.microsoft.com/en-us/dotnet/api/system.guid.tostring?view=netframework-4.8 + * + * (str) must have at least (length) valid characters for memory safety. A null terminator is not + * required. Instead, (length) is used for the bounds check. + * + * Returns the parsed GUID. Throws a std::runtime_error if there is a bounds error or format error. + * + * This function is for performance, to help deal with container ID extended data, which has no null + * terminator, which would force us to clone the data to append a null terminator in order to use + * existing GUID parsing functions. + * + */ + static GUID parse_guid(const char* str, unsigned int length); + }; + + // Implementation + // ------------------------------------------------------------------------ + + inline guid::guid(GUID guid) + : guid_(guid) + {} + + inline guid::guid(const std::wstring &guid) + { + HRESULT hr = CLSIDFromString(guid.c_str(), &guid_); + if (FAILED(hr)) { +#pragma warning(push) +#pragma warning(disable: 4244) // narrowing guid wchar_t to char for this error message + std::string guidStr(guid.begin(), guid.end()); +#pragma warning(pop) + std::stringstream stream; + stream << "Error in constructing guid from string ("; + stream << guidStr; + stream << "), hr = 0x"; + stream << std::hex << hr; + throw std::runtime_error(stream.str()); + } + } + + inline bool guid::operator==(const guid &rhs) const + { + return (0 == memcmp(&guid_, &rhs.guid_, sizeof(GUID))); + } + + inline bool guid::operator==(const GUID &rhs) const + { + return (0 == memcmp(&guid_, &rhs, sizeof(GUID))); + } + + inline bool guid::operator<(const guid &rhs) const + { + return (memcmp(&guid_, &rhs.guid_, sizeof(guid_)) < 0); + } + + inline bool guid::operator<(const GUID &rhs) const + { + return (memcmp(&guid_, &rhs, sizeof(guid_)) < 0); + } + + inline guid::operator GUID() const + { + return guid_; + } + + inline guid::operator const GUID*() const + { + return &guid_; + } + + inline guid guid::random_guid() + { + GUID tmpGuid; + CoCreateGuid(&tmpGuid); + return guid(tmpGuid); + } + + struct CoTaskMemDeleter { + void operator()(wchar_t *mem) { + CoTaskMemFree(mem); + } + }; + + inline bool guid_parser::hex_octet_to_byte(const char* str_input, unsigned char& byte_output) + { + // Accepts chars '0' through '9' (0x30 to 0x39), + // 'A' through 'F' (0x41 to 0x46) + // 'a' through 'f' (0x61 to 0x66) + + // Narrow the value later, for safety checking. + auto value = 0; + // most significant digit in the octet + auto msd = str_input[0]; + // least significant digit in the octet + auto lsd = str_input[1]; + + if (msd >= '0' && msd <= '9') + { + value |= ((int)msd & 0x0F) << 4; + } + else if ((msd >= 'A' && msd <= 'F') || (msd >= 'a' && msd <= 'f')) + { + value |= (((int)msd & 0x0F) + 9) << 4; + } + else + { + return false; + } + + if (lsd >= '0' && lsd <= '9') + { + value |= ((int)lsd & 0x0F); + } + else if ((lsd >= 'A' && lsd <= 'F') || (lsd >= 'a' && lsd <= 'f')) + { + value |= (((int)lsd & 0x0F) + 9); + } + else + { + return false; + } + + assert(value >= 0 && value <= UCHAR_MAX); + byte_output = static_cast(value); + return true; + } + + template + bool guid_parser::hex_string_to_number(const char* str_input, T& int_output) + { + auto byte_count = sizeof(T); + T value = 0; + unsigned char byte = 0; + + for (size_t i = 0; i < byte_count; i++) + { + if (!guid_parser::hex_octet_to_byte(str_input + i * 2, byte)) + { + return false; + } + + value = (value << 8) | static_cast(byte); + } + + int_output = value; + return true; + } + + inline bool guid_parser::hex_string_to_bytes(const char* str_input, unsigned char* byte_output, size_t byte_count) + { + for (size_t i = 0; i < byte_count; i++) + { + if (!hex_octet_to_byte(str_input + (i * 2), byte_output[i])) + { + return false; + } + } + + return true; + } + + /** + * Parses GUID of "D" format. For example, the nil GUID would be "00000000-0000-0000-0000-000000000000". + * See: https://docs.microsoft.com/en-us/dotnet/api/system.guid.tostring?view=netframework-4.8 + * + * (str) must have at least (length) valid characters for memory safety. A null terminator is not + * required. Instead, (length) is used for the bounds check. + * + * Returns the parsed GUID. Throws a std::runtime_error if there is a bounds error or format error. + * + * This function is for performance, to help deal with container ID extended data, which has no null + * terminator, which would force us to clone the data to append a null terminator in order to use + * existing GUID parsing functions. + * + */ + inline GUID guid_parser::parse_guid(const char* str, unsigned int length) + { + if (length != UUID_STRING_LENGTH) + { + std::stringstream message; + message << "Input data has incorrect length. Expected " + << UUID_STRING_LENGTH + << ", got " + << length; + throw std::runtime_error(message.str()); + } + + GUID guid = { 0 }; + + // Check that hyphens are in expected places as a formatting issue. + if (str[STR_POSITION_DATA2 - 1] != DELIMITER || + str[STR_POSITION_DATA3 - 1] != DELIMITER || + str[STR_POSITION_DATA4_PART1 - 1] != DELIMITER || + str[STR_POSITION_DATA4_PART2 - 1] != DELIMITER) + { + throw std::runtime_error("Missing a hyphen where one was expected."); + } + + // Use from_hex_string for Data1, Data2, and Data3 because of endianness of the data + // Use hex_string_to_bytes for Data4's array elements because it's byte by byte instead + auto success = guid_parser::hex_string_to_number(str + STR_POSITION_DATA1, guid.Data1) + && guid_parser::hex_string_to_number(str + STR_POSITION_DATA2, guid.Data2) + && guid_parser::hex_string_to_number(str + STR_POSITION_DATA3, guid.Data3) + && guid_parser::hex_string_to_bytes(str + STR_POSITION_DATA4_PART1, reinterpret_cast(&guid.Data4[0]), 2) + && guid_parser::hex_string_to_bytes(str + STR_POSITION_DATA4_PART2, reinterpret_cast(&guid.Data4[2]), 6); + + if (!success) + { + throw std::runtime_error("GUID string contains non-hex digits where hex digits are expected."); + } + + return guid; + } +} + +namespace std +{ + /* + * Converts a krabs GUID to a wide string + */ + inline std::wstring to_wstring(const krabs::guid& guid) + { + wchar_t* guidString; + HRESULT hr = StringFromCLSID(guid, &guidString); + + if (FAILED(hr)) throw std::bad_alloc(); + + std::unique_ptr managed(guidString); + + return { managed.get() }; + } + + /* + * Converts a Windows GUID to a C string + */ + inline std::string to_string(const GUID& guid) + { + char guid_string[37]; // 32 hex chars + 4 hyphens + null terminator + snprintf( + guid_string, sizeof(guid_string), + "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + guid.Data1, guid.Data2, guid.Data3, + guid.Data4[0], guid.Data4[1], guid.Data4[2], + guid.Data4[3], guid.Data4[4], guid.Data4[5], + guid.Data4[6], guid.Data4[7]); + return guid_string; + } + + template<> + struct std::hash + { + size_t operator()(const krabs::guid& guid) const + { + // This algorithm comes from .NET's reference source for Guid.GetHashCode() + return guid.guid_.Data1 ^ + ((guid.guid_.Data2 << 16) | guid.guid_.Data3 ) ^ + ((guid.guid_.Data4[2] << 24) | guid.guid_.Data4[7]); + } + }; +} diff --git a/libs/krabs/krabs/kernel_guids.hpp b/libs/krabs/krabs/kernel_guids.hpp new file mode 100644 index 0000000..47b3ec5 --- /dev/null +++ b/libs/krabs/krabs/kernel_guids.hpp @@ -0,0 +1,194 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include + +#include "compiler_check.hpp" + +namespace krabs { namespace guids { + +DEFINE_GUID ( /* 45d8cccd-539f-4b72-a8b7-5c683142609a */ + alpc, + 0x45d8cccd, + 0x539f, + 0x4b72, + 0xa8, 0xb7, 0x5c, 0x68, 0x31, 0x42, 0x60, 0x9a + ); + +DEFINE_GUID ( /* 13976d09-a327-438c-950b-7f03192815c7 */ + debug, + 0x13976d09, + 0xa327, + 0x438c, + 0x95, 0x0b, 0x7f, 0x03, 0x19, 0x28, 0x15, 0xc7 + ); + +DEFINE_GUID ( /* 3d6fa8d4-fe05-11d0-9dda-00c04fd7ba7c */ + disk_io, + 0x3d6fa8d4, + 0xfe05, + 0x11d0, + 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c + ); + +DEFINE_GUID ( /* 01853a65-418f-4f36-aefc-dc0f1d2fd235 */ + event_trace_config, + 0x01853a65, + 0x418f, + 0x4f36, + 0xae, 0xfc, 0xdc, 0x0f, 0x1d, 0x2f, 0xd2, 0x35 + ); + +DEFINE_GUID ( /* 90cbdc39-4a3e-11d1-84f4-0000f80464e3 */ + file_io, + 0x90cbdc39, + 0x4a3e, + 0x11d1, + 0x84, 0xf4, 0x00, 0x00, 0xf8, 0x04, 0x64, 0xe3 + ); + +DEFINE_GUID ( /* 2cb15d1d-5fc1-11d2-abe1-00a0c911f518 */ + image_load, + 0x2cb15d1d, + 0x5fc1, + 0x11d2, + 0xab, 0xe1, 0x00, 0xa0, 0xc9, 0x11, 0xf5, 0x18 + ); + +DEFINE_GUID ( /* 3d6fa8d3-fe05-11d0-9dda-00c04fd7ba7c */ + page_fault, + 0x3d6fa8d3, + 0xfe05, + 0x11d0, + 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c + ); + +DEFINE_GUID ( /* ce1dbfb4-137e-4da6-87b0-3f59aa102cbc */ + perf_info, + 0xce1dbfb4, + 0x137e, + 0x4da6, + 0x87, 0xb0, 0x3f, 0x59, 0xaa, 0x10, 0x2c, 0xbc + ); + +DEFINE_GUID ( /* 3d6fa8d0-fe05-11d0-9dda-00c04fd7ba7c */ + process, + 0x3d6fa8d0, + 0xfe05, + 0x11d0, + 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c + ); + +DEFINE_GUID ( /* AE53722E-C863-11d2-8659-00C04FA321A1 */ + registry, + 0xae53722e, + 0xc863, + 0x11d2, + 0x86, 0x59, 0x0, 0xc0, 0x4f, 0xa3, 0x21, 0xa1 + ); + +DEFINE_GUID ( /* d837ca92-12b9-44a5-ad6a-3a65b3578aa8 */ + split_io, + 0xd837ca92, + 0x12b9, + 0x44a5, + 0xad, 0x6a, 0x3a, 0x65, 0xb3, 0x57, 0x8a, 0xa8 + ); + +DEFINE_GUID ( /* 9a280ac0-c8e0-11d1-84e2-00c04fb998a2 */ + tcp_ip, + 0x9a280ac0, + 0xc8e0, + 0x11d1, + 0x84, 0xe2, 0x00, 0xc0, 0x4f, 0xb9, 0x98, 0xa2 + ); + +DEFINE_GUID ( /* 3d6fa8d1-fe05-11d0-9dda-00c04fd7ba7c */ + thread, + 0x3d6fa8d1, + 0xfe05, + 0x11d0, + 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c + ); + +DEFINE_GUID ( /* bf3a50c5-a9c9-4988-a005-2df0b7c80f80 */ + udp_ip, + 0xbf3a50c5, + 0xa9c9, + 0x4988, + 0xa0, 0x05, 0x2d, 0xf0, 0xb7, 0xc8, 0x0f, 0x80 + ); + +DEFINE_GUID ( /* 9e814aad-3204-11d2-9a82-006008a86939 */ + system_trace, + 0x9e814aad, + 0x3204, + 0x11d2, + 0x9a, 0x82, 0x00, 0x60, 0x08, 0xa8, 0x69, 0x39); + +DEFINE_GUID( /* 89497f50-effe-4440-8cf2-ce6b1cdcaca7 */ + ob_trace, + 0x89497f50, + 0xeffe, + 0x4440, + 0x8c, 0xf2, 0xce, 0x6b, 0x1c, 0xdc, 0xac, 0xa7); + +DEFINE_GUID( /* 0268a8b6-74fd-4302-9dd0-6e8f1795c0cf */ + pool_trace, + 0x0268a8b6, + 0x74fd, + 0x4302, + 0x9d, 0xd0, 0x6e, 0x8f, 0x17, 0x95, 0xc0, 0xcf); + +DEFINE_GUID( /* 68fdd900-4a3e-11d1-84f4-0000f80464e3 */ + event_trace, + 0x68fdd900, + 0x4a3e, + 0x11d1, + 0x84, 0xf4, 0x00, 0x00, 0xf8, 0x04, 0x64, 0xe3); + +DEFINE_GUID( /* 6a399ae0-4bc6-4de9-870b-3657f8947e7e */ + lost_event, + 0x6a399ae0, + 0x4bc6, + 0x4de9, + 0x87, 0x0b, 0x36, 0x57, 0xf8, 0x94, 0x7e, 0x7e); + +DEFINE_GUID( /* 9aec974b-5b8e-4118-9b92-3186d8002ce5 */ + ums_event, + 0x9aec974b, + 0x5b8e, + 0x4118, + 0x9b, 0x92, 0x31, 0x86, 0xd8, 0x00, 0x2c, 0xe5); + +DEFINE_GUID( /* def2fe46-7bd6-4b80-bd94-f57fe20d0ce3 */ + stack_walk, + 0xdef2fe46, + 0x7bd6, + 0x4b80, + 0xbd, 0x94, 0xf5, 0x7f, 0xe2, 0x0d, 0x0c, 0xe3); + +DEFINE_GUID( /* e43445e0-0903-48c3-b878-ff0fccebdd04 */ + power, + 0xe43445e0, + 0x0903, + 0x48c3, + 0xb8, 0x78, 0xff, 0x0f, 0xcc, 0xeb, 0xdd, 0x04); + +DEFINE_GUID( /* f8f10121-b617-4a56-868b-9df1b27fe32c */ + mmcss_trace, + 0xf8f10121, + 0xb617, + 0x4a56, + 0x86, 0x8b, 0x9d, 0xf1, 0xb2, 0x7f, 0xe3, 0x2c); + +DEFINE_GUID( /* 3b9c9951-3480-4220-9377-9c8e5184f5cd */ + rundown, + 0x3b9c9951, + 0x3480, + 0x4220, + 0x93, 0x77, 0x9c, 0x8e, 0x51, 0x84, 0xf5, 0xcd); + +} /* namespace guids */ } /* namespace krabs */ diff --git a/libs/krabs/krabs/kernel_providers.hpp b/libs/krabs/krabs/kernel_providers.hpp new file mode 100644 index 0000000..d829048 --- /dev/null +++ b/libs/krabs/krabs/kernel_providers.hpp @@ -0,0 +1,243 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include "compiler_check.hpp" +#include "kernel_guids.hpp" +#include "perfinfo_groupmask.hpp" +#include "provider.hpp" + +#define INITGUID +#include + +namespace krabs { namespace kernel { + +#define CREATE_CONVENIENCE_KERNEL_PROVIDER(__name__, __value__, __guid__) \ + struct __name__ : public krabs::kernel_provider \ + { \ + __name__() \ + : krabs::kernel_provider(__value__, __guid__) \ + {} \ + }; + +#define CREATE_CONVENIENCE_KERNEL_PROVIDER_MASK(__name__, __guid__, __mask__) \ + struct __name__ : public krabs::kernel_provider \ + { \ + __name__() \ + : krabs::kernel_provider(__guid__, __mask__) \ + {} \ + }; + + /** + * A provider that enables ALPC events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + alpc_provider, + EVENT_TRACE_FLAG_ALPC, + krabs::guids::alpc); + + /** + * A provider that enables context switch events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + context_switch_provider, + EVENT_TRACE_FLAG_CSWITCH, + krabs::guids::thread); + + /** + * A provider that enables debug print events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + debug_print_provider, + EVENT_TRACE_FLAG_DBGPRINT, + krabs::guids::debug); + + /** + * A provider that enables file I/O name events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + disk_file_io_provider, + EVENT_TRACE_FLAG_DISK_FILE_IO, + krabs::guids::file_io); + + /** + * A provider that enables disk I/O completion events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + disk_io_provider, + EVENT_TRACE_FLAG_DISK_IO, + krabs::guids::disk_io); + + /** + * A provider that enables disk I/O start events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + disk_init_io_provider, + EVENT_TRACE_FLAG_DISK_IO_INIT, + krabs::guids::disk_io); + + /** + * A provider that enables file I/O completion events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + file_io_provider, + EVENT_TRACE_FLAG_FILE_IO, + krabs::guids::file_io); + + /** + * A provider that enables file I/O start events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + file_init_io_provider, + EVENT_TRACE_FLAG_FILE_IO_INIT, + krabs::guids::file_io); + + /** + * A provider that enables thread dispatch events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + thread_dispatch_provider, + EVENT_TRACE_FLAG_DISPATCHER, + krabs::guids::thread); + + /** + * A provider that enables device deferred procedure call events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + dpc_provider, + EVENT_TRACE_FLAG_DPC, + krabs::guids::perf_info); + + /** + * A provider that enables driver events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + driver_provider, + EVENT_TRACE_FLAG_DRIVER, + krabs::guids::disk_io); + + /** + * A provider that enables image load events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + image_load_provider, + EVENT_TRACE_FLAG_IMAGE_LOAD, + krabs::guids::image_load); + + /** + * A provider that enables interrupt events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + interrupt_provider, + EVENT_TRACE_FLAG_INTERRUPT, + krabs::guids::perf_info); + + /** + * A provider that enables memory hard fault events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + memory_hard_fault_provider, + EVENT_TRACE_FLAG_MEMORY_HARD_FAULTS, + krabs::guids::page_fault); + + /** + * A provider that enables memory page fault events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + memory_page_fault_provider, + EVENT_TRACE_FLAG_MEMORY_PAGE_FAULTS, + krabs::guids::page_fault); + + /** + * A provider that enables network tcp/ip events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + network_tcpip_provider, + EVENT_TRACE_FLAG_NETWORK_TCPIP, + krabs::guids::tcp_ip); + + /** + * A provider that enables process events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + process_provider, + EVENT_TRACE_FLAG_PROCESS, + krabs::guids::process); + + /** + * A provider that enables process counter events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + process_counter_provider, + EVENT_TRACE_FLAG_PROCESS_COUNTERS, + krabs::guids::process); + + /** + * A provider that enables profiling events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + profile_provider, + EVENT_TRACE_FLAG_PROFILE, + krabs::guids::perf_info); + + /** + * A provider that enables registry events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + registry_provider, + EVENT_TRACE_FLAG_REGISTRY, + krabs::guids::registry); + + /** + * A provider that enables split I/O events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + split_io_provider, + EVENT_TRACE_FLAG_SPLIT_IO, + krabs::guids::split_io); + + /** + * A provider that enables system call events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + system_call_provider, + EVENT_TRACE_FLAG_SYSTEMCALL, + krabs::guids::perf_info); + + /** + * A provider that enables thread start and stop events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + thread_provider, + EVENT_TRACE_FLAG_THREAD, + krabs::guids::thread); + + /** + * A provider that enables file map and unmap (excluding images) events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + vamap_provider, + EVENT_TRACE_FLAG_VAMAP, + krabs::guids::file_io); + + /** + * A provider that enables VirtualAlloc and VirtualFree events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER( + virtual_alloc_provider, + EVENT_TRACE_FLAG_VIRTUAL_ALLOC, + krabs::guids::page_fault); + + /** + * A provider that enables Object Manager events. + */ + CREATE_CONVENIENCE_KERNEL_PROVIDER_MASK( + object_manager_provider, + krabs::guids::ob_trace, + PERF_OB_HANDLE); + +#undef CREATE_CONVENIENCE_KERNEL_PROVIDER +#undef CREATE_CONVENIENCE_KERNEL_PROVIDER_MASK + +} /* namespace kernel */ } /* namespace krabs */ diff --git a/libs/krabs/krabs/kt.hpp b/libs/krabs/krabs/kt.hpp new file mode 100644 index 0000000..7826be3 --- /dev/null +++ b/libs/krabs/krabs/kt.hpp @@ -0,0 +1,206 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include "compiler_check.hpp" +#include "kernel_guids.hpp" +#include "perfinfo_groupmask.hpp" +#include "provider.hpp" +#include "trace.hpp" +#include "ut.hpp" +#include "version_helpers.hpp" + +#include + +namespace krabs { namespace details { + + /** + * + * Used as a template argument to a trace instance. This class implements + * code paths for kernel traces. Should never be used or seen by client + * code. + * + */ + struct kt { + + typedef krabs::kernel_provider provider_type; + + /** + * + * Used to assign a name to the trace instance that is being + * instantiated. + * + * + * In pre-Win8 days, there could only be a single kernel trace + * instance on an entire machine, and that instance had to be named + * a particular name. This restriction was loosened in Win8, but + * the trace still needs to do the right thing on older OSes. + * + */ + static const std::wstring enforce_name_policy( + const std::wstring &name); + + /** + * + * Generates a value that fills the EnableFlags field in an + * EVENT_TRACE_PROPERTIES structure. This controls the providers that + * get enabled for a kernel trace. + * + */ + static const unsigned long construct_enable_flags( + const krabs::trace &trace); + + /** + * + * Enables the providers that are attached to the given trace. + * + */ + static void enable_providers( + const krabs::trace &trace); + + /** + * + * Enables the configured kernel rundown flags. + * + * + * This ETW feature is undocumented and should be used with caution. + * + */ + static void enable_rundown( + const krabs::trace& trace); + + /** + * + * Decides to forward an event to any of the providers in the trace. + * + */ + static void forward_events( + const EVENT_RECORD &record, + const krabs::trace &trace); + + /** + * + * Sets the ETW trace log file mode. + * + */ + static unsigned long augment_file_mode(); + + /** + * + * Returns the GUID of the trace session. + * + */ + static krabs::guid get_trace_guid(); + + }; + + // Implementation + // ------------------------------------------------------------------------ + + inline const std::wstring kt::enforce_name_policy( + const std::wstring &name_hint) + { + if (IsWindows8OrGreater()) { + return krabs::details::ut::enforce_name_policy(name_hint); + } + + return KERNEL_LOGGER_NAME; + } + + inline const unsigned long kt::construct_enable_flags( + const krabs::trace &trace) + { + unsigned long flags = 0; + for (auto &provider : trace.providers_) { + flags |= provider.get().flags(); + } + + return flags; + } + + inline void kt::enable_providers( + const krabs::trace &trace) + { + EVENT_TRACE_GROUPMASK_INFORMATION gmi = { 0 }; + gmi.EventTraceInformationClass = EventTraceGroupMaskInformation; + gmi.TraceHandle = trace.registrationHandle_; + + // initialise EventTraceGroupMasks to the values that have been enabled via the trace flags + ULONG status = NtQuerySystemInformation(SystemPerformanceTraceInformation, &gmi, sizeof(gmi), nullptr); + error_check_common_conditions(status); + + auto group_mask_set = false; + for (auto& provider : trace.providers_) { + auto group = provider.get().group_mask(); + PERFINFO_OR_GROUP_WITH_GROUPMASK(group, &(gmi.EventTraceGroupMasks)); + group_mask_set |= (group != 0); + } + + if (group_mask_set) { + // This will fail on Windows 7, so only call it if truly neccessary + status = NtSetSystemInformation(SystemPerformanceTraceInformation, &gmi, sizeof(gmi)); + error_check_common_conditions(status); + } + + return; + } + + inline void kt::enable_rundown( + const krabs::trace& trace) + { + bool rundown_enabled = false; + ULONG rundown_flags = 0; + for (auto& provider : trace.providers_) { + rundown_enabled |= provider.get().rundown_enabled(); + rundown_flags |= provider.get().rundown_flags(); + } + + if (rundown_enabled) { + ULONG status = EnableTraceEx2(trace.registrationHandle_, + &krabs::guids::rundown, + EVENT_CONTROL_CODE_ENABLE_PROVIDER, + 0, + rundown_flags, + 0, + 0, + NULL); + error_check_common_conditions(status); + } + } + + + inline void kt::forward_events( + const EVENT_RECORD &record, + const krabs::trace &trace) + { + for (auto &provider : trace.providers_) { + if (provider.get().id() == record.EventHeader.ProviderId) { + provider.get().on_event(record, trace.context_); + return; + } + } + + if (trace.default_callback_ != nullptr) + trace.default_callback_(record, trace.context_); + } + + inline unsigned long kt::augment_file_mode() + { + if (IsWindows8OrGreater()) { + return EVENT_TRACE_SYSTEM_LOGGER_MODE; + } + + return 0; + } + + inline krabs::guid kt::get_trace_guid() + { + if (IsWindows8OrGreater()) { + return krabs::guid::random_guid(); + } + + return krabs::guid(SystemTraceControlGuid); + } + +} /* namespace details */ } /* namespace krabs */ diff --git a/libs/krabs/krabs/parse_types.hpp b/libs/krabs/krabs/parse_types.hpp new file mode 100644 index 0000000..cb2c7b7 --- /dev/null +++ b/libs/krabs/krabs/parse_types.hpp @@ -0,0 +1,330 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include +#include +#include +#include + +#include +#include + +#include "compiler_check.hpp" + +namespace krabs { + + /** + * + * Provided entirely for code clarity purposes. + * Indicates that the number is intended to be used as an ID + * + * + * This should be turned into an _id user defined literal when our + * compiler decides to catch up to the times. + * + * + * id(1000); + * + */ + template + T id(T n) + { + return n; + } + + /** + * + * Provided entirely for code clarity purposes. + * Indicates that the number is intended to be used as a version + * + * + * This should be turned into a _vers user defined literal when our + * compiler decides to catch up to the times. + * + * + * id(1000); + * + */ + template + T version(T n) + { + return n; + } + + /** + * + * Provided entirely for code clarity purposes. + * Indicates that the number is intended to be used as an opcode + * + * + * This should be turned into a _opcode user defined literal when our + * compiler decides to catch up to the times. + * + * + * opcode(1000); + * + */ + template + T opcode(T n) + { + return n; + } + + + /** + * + * Used to discriminate between hex ints and regular ints in ETW events. + * + * + * Q: Why in the world? I can't even. + * A: ETW differentiates between hexints and regular ints. When + * record_builder validates that the input type matches the type + * specified in the schema, getting this wrong will cause an + * exception. A quick little type wrapper like this lets us + * discriminate based on the type and everything turns out better. + * + */ + struct hexint32 { + hexint32(int v) + : value(v) + {} + + int value; + }; + + struct hexint64 { + hexint64(long long v) + : value(v) + {} + + long long value; + }; + + /** + * + * Used to support parsing and creation of binary ETW fields. + * + */ + struct binary { + public: + binary() : bytes_() { } + + binary(const BYTE* start, size_t n) + : bytes_(start, start + n) + { } + + const std::vector& bytes() const + { + return bytes_; + } + + private: + std::vector bytes_; + }; + + template + binary make_binary(const T& value, size_t n) + { + const auto start = (BYTE*)&value; + return binary(start, n); + } + + /** + * + * Used to handle parsing of IPv4 and IPv6 fields in an ETW record. + * This is used in the parser class in a template specialization. + * + */ + struct ip_address { + union { + DWORD v4; + BYTE v6[16]; + }; + bool is_ipv6; + + static ip_address from_ipv6(const BYTE* bytes) + { + ip_address addr; + addr.is_ipv6 = true; + memcpy_s(addr.v6, 16, bytes, 16); + return addr; + } + + static ip_address from_ipv4(DWORD val) + { + ip_address addr; + addr.is_ipv6 = false; + addr.v4 = val; + return addr; + } + + ip_address() {} + }; + + /** + * + * Used to handle parsing of socket addresses in + * network order. This union is a convenient wrapper + * around the type IPv4 and IPv6 types provided by + * the Winsock (v2) APIs. + * + */ + struct socket_address { + union { + struct sockaddr sa; + struct sockaddr_in sa_in; + struct sockaddr_in6 sa_in6; + struct sockaddr_storage sa_stor; + }; + size_t size; + + static socket_address from_bytes(const BYTE* bytes, size_t size_in_bytes) + { + socket_address sa; + memcpy_s(&(sa.sa_stor), sizeof sa.sa_stor, bytes, size_in_bytes); + sa.size = size_in_bytes; + return sa; + } + }; + + /** + * + * Holds information about an property extracted from the etw schema + * + */ + struct property_info { + const BYTE *pPropertyIndex_; + const EVENT_PROPERTY_INFO *pEventPropertyInfo_; + ULONG length_; + + property_info( + const BYTE *offset, + const EVENT_PROPERTY_INFO &evtPropInfo, + ULONG length) + : pPropertyIndex_(offset) + , pEventPropertyInfo_(&evtPropInfo) + , length_(length) + { } + + property_info() + : pPropertyIndex_(nullptr) + , pEventPropertyInfo_(nullptr) + , length_(0) + { } + + inline bool found() const + { + return pPropertyIndex_ != nullptr; + } + }; + + /** + * + * Used to handle parsing of SIDs from either a + * SID or WBEMSID property + * + */ + struct sid { + // SIDs are variable-length + // So the 'best' way to store them is to convert to a string + // The other-end can either print the string or call ConvertStringSidToSidA + // to get the SID back + std::string sid_string; + + static sid from_bytes(const BYTE* bytes, size_t size_in_bytes) + { + sid ws; + LPSTR temp_sid_string; + UNREFERENCED_PARAMETER(size_in_bytes); + + if (!ConvertSidToStringSidA((PSID)bytes, &temp_sid_string)) { + throw std::runtime_error( + "Failed to get a SID from a property"); + } + ws.sid_string = temp_sid_string; + LocalFree(temp_sid_string); + return ws; + } + + private: + }; + + /** + * + * Used to handle parsing of Pointer Address types. + * + */ + struct pointer { + /** + * We store the pointer as an uint64_t, as it is highly unlikley + * to be pointing to somewhere accessible to our process + */ + uint64_t address; + + static pointer from_bytes(const BYTE* bytes, size_t size_in_bytes) + { + pointer pt; + + // If 32-Bit, first parse as a uint32 + // Then we can 'cast' that to our uint64_t + if (size_in_bytes == sizeof(uint32_t)) { + pt.address = *reinterpret_cast(bytes); + } + else if (size_in_bytes == sizeof(uint64_t)) { + pt.address = *reinterpret_cast(bytes); + } + else { + throw std::runtime_error( + "Failed to get a POINTER from a property"); + } + + return pt; + } + + private: + }; + + + /** + * + * Used to handle parsing of CountedStrings in an ETW Record. + * This is used in the parser class in a template specialization. + * + */ +#pragma pack(push,1) + struct counted_string { + using value_type = wchar_t; + using reference = value_type&; + using pointer = value_type*; + using const_reference = const value_type&; + using const_pointer = const value_type*; + using iterator = value_type*; + using const_iterator = const value_type*; + + /** + * size of the string in bytes + */ + uint16_t size_; + wchar_t string_[1]; + + const_pointer string() const + { + return string_; + } + + size_t length() const + { + return size_ / sizeof(value_type); + } + }; +#pragma pack(pop) + + static_assert(std::is_trivial::value && std::is_standard_layout::value , "Do not modify counted_string"); + +} /* namespace krabs */ diff --git a/libs/krabs/krabs/parser.hpp b/libs/krabs/krabs/parser.hpp new file mode 100644 index 0000000..2f916c2 --- /dev/null +++ b/libs/krabs/krabs/parser.hpp @@ -0,0 +1,431 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include +#include +#include +#include + +#include + +#include "compiler_check.hpp" +#include "collection_view.hpp" +#include "property.hpp" +#include "parse_types.hpp" +#include "size_provider.hpp" +#include "tdh_helpers.hpp" + +namespace krabs { + + class schema; + + /** + * + * Used to parse specific properties out of an event schema. + * + * + * The parser class dodges the task of trying to validate that the expected + * type of a field matches the actual type of a field -- the onus is on + * client code to get this right. + * + */ + class parser { + public: + + /** + * + * Constructs an event parser from an event schema. + * + * + * void on_event(const EVENT_RECORD &record) + * { + * krabs::schema schema(record); + * krabs::parser parser(schema); + * } + * + */ + parser(const schema &); + + /** + * + * Returns an iterator that returns each property in the event. + * + * + * void on_event(const EVENT_RECORD &record) + * { + * krabs::schema schema(record); + * krabs::parser parser(schema); + * for (property &property : parser.properties()) + * { + * // ... + * } + * } + * + */ + property_iterator properties() const; + + /** + * + * Attempts to retrieve the given property by name and type. + * + * + * Type hinting here is taken as the authoritative source. There is no + * validation that the request for type is correct. + * + */ + template + bool try_parse(const std::wstring &name, T &out); + + /** + * + * Attempts to parse the given property by name and type. If the + * property does not exist, an exception is thrown. + * + */ + template + T parse(const std::wstring &name); + + template + auto view_of(const std::wstring &name, Adapter &adapter) -> collection_view; + + private: + property_info find_property(const std::wstring &name); + void cache_property(const wchar_t *name, property_info info); + + private: + const schema &schema_; + const BYTE *pEndBuffer_; + BYTE *pBufferIndex_; + ULONG lastPropertyIndex_; + + // Maintain a mapping from property name to blob data index. + std::deque> propertyCache_; + }; + + // Implementation + // ------------------------------------------------------------------------ + + inline parser::parser(const schema &s) + : schema_(s) + , pEndBuffer_((BYTE*)s.record_.UserData + s.record_.UserDataLength) + , pBufferIndex_((BYTE*)s.record_.UserData) + , lastPropertyIndex_(0) + {} + + inline property_iterator parser::properties() const + { + return property_iterator(schema_); + } + + inline property_info parser::find_property(const std::wstring &name) + { + // A schema contains a collection of properties that are keyed by name. + // These properties are stored in a blob of bytes that needs to be + // interpreted according to information that is packaged up in the + // schema and that can be retrieved using the Tdh* APIs. This format + // requires a linear traversal over the blob, incrementing according to + // the contents within it. This is janky, so our strategy is to + // minimize this as much as possible via caching. + + // The first step is to use our cache for the property to see if we've + // discovered it already. + for (auto &item : propertyCache_) { + if (name == item.first) { + return item.second; + } + } + + const ULONG totalPropCount = schema_.pSchema_->PropertyCount; + + assert((pBufferIndex_ <= pEndBuffer_ && pBufferIndex_ >= schema_.record_.UserData) && + "invariant: we should've already thrown for falling off the edge"); + + // accept that last property can be omitted from buffer. this happens if last property + // is string but empty and the provider strips the null terminator + assert((pBufferIndex_ == pEndBuffer_ ? ((totalPropCount - lastPropertyIndex_) <= 1) + : true) + && "invariant: if we've exhausted our buffer, then we must've" + "exhausted the properties as well"); + + // We've not looked up this property before, so we have to do the work + // to find it. While we're going through the blob to find it, we'll + // remember what we've seen to save time later. + // + // Question: Why don't we just populate the cache before looking up any + // properties and simplify our code (less state, etc)? + // + // Answer: Doing that introduces overhead in the case that only a + // subset of properties are needed. While this code is a bit + // more complicated, we introduce no additional performance + // overhead at runtime. + for (auto &i = lastPropertyIndex_; i < totalPropCount; ++i) { + + auto ¤tPropInfo = schema_.pSchema_->EventPropertyInfoArray[i]; + const wchar_t *pName = reinterpret_cast( + reinterpret_cast(schema_.pSchema_) + + currentPropInfo.NameOffset); + + ULONG propertyLength = size_provider::get_property_size( + pBufferIndex_, + pName, + schema_.record_, + currentPropInfo); + + // verify that the length of the property doesn't exceed the buffer + if (pBufferIndex_ + propertyLength > pEndBuffer_) { + throw std::out_of_range("Property length past end of property buffer"); + } + + property_info propInfo(pBufferIndex_, currentPropInfo, propertyLength); + cache_property(pName, propInfo); + + // advance the buffer index since we've already processed this property + pBufferIndex_ += propertyLength; + + // The property was found, return it + if (name == pName) { + // advance the index since we've already processed this property + ++i; + return propInfo; + } + } + + // property wasn't found, return an empty propInfo + return property_info(); + } + + inline void parser::cache_property(const wchar_t *name, property_info propInfo) + { + propertyCache_.push_front(std::make_pair(name, propInfo)); + } + + inline void throw_if_property_not_found(const property_info &propInfo) + { + if (!propInfo.found()) { + throw std::runtime_error("Property with the given name does not exist"); + } + } + + template + size_t get_string_content_length(const T* string, size_t lengthBytes) + { + // for some string types the length includes the null terminator + // so we need to find the length of just the content part + + T nullChar {0}; + auto length = lengthBytes / sizeof(T); + + for (auto i = length; i >= 1; --i) + if (string[i - 1] != nullChar) return i; + + return 0; + } + + // try_parse + // ------------------------------------------------------------------------ + + template + bool parser::try_parse(const std::wstring &name, T &out) + { + try { + out = parse(name); + return true; + } + +#ifndef NDEBUG + // in debug builds we want any mismatch asserts + // to get back to the caller. This is removed + // in release builds. + catch (const krabs::type_mismatch_assert&) { + throw; + } +#endif // NDEBUG + + catch (...) { + return false; + } + } + + // parse + // ------------------------------------------------------------------------ + + template + T parser::parse(const std::wstring &name) + { + auto propInfo = find_property(name); + throw_if_property_not_found(propInfo); + + krabs::debug::assert_valid_assignment(name, propInfo); + + // ensure that size of the type we are requesting is + // the same size of the property in the event + if (sizeof(T) != propInfo.length_) + throw std::runtime_error("Property size doesn't match requested size"); + + return *(T*)propInfo.pPropertyIndex_; + } + + template<> + inline bool parser::parse(const std::wstring& name) + { + auto propInfo = find_property(name); + throw_if_property_not_found(propInfo); + + krabs::debug::assert_valid_assignment(name, propInfo); + + // Boolean in ETW is 4 bytes long + return static_cast(*(unsigned*)propInfo.pPropertyIndex_); + } + + template <> + inline std::wstring parser::parse(const std::wstring &name) + { + auto propInfo = find_property(name); + throw_if_property_not_found(propInfo); + + krabs::debug::assert_valid_assignment(name, propInfo); + + auto string = reinterpret_cast(propInfo.pPropertyIndex_); + auto length = get_string_content_length(string, propInfo.length_); + + return std::wstring(string, length); + } + + template <> + inline std::string parser::parse(const std::wstring &name) + { + auto propInfo = find_property(name); + throw_if_property_not_found(propInfo); + + krabs::debug::assert_valid_assignment(name, propInfo); + + auto string = reinterpret_cast(propInfo.pPropertyIndex_); + auto length = get_string_content_length(string, propInfo.length_); + + return std::string(string, length); + } + + template<> + inline const counted_string* parser::parse(const std::wstring &name) + { + auto propInfo = find_property(name); + throw_if_property_not_found(propInfo); + + krabs::debug::assert_valid_assignment(name, propInfo); + + return reinterpret_cast(propInfo.pPropertyIndex_); + } + + template<> + inline binary parser::parse(const std::wstring &name) + { + auto propInfo = find_property(name); + throw_if_property_not_found(propInfo); + + // no type asserts for binary - anything can be read as binary + + return binary(propInfo.pPropertyIndex_, propInfo.length_); + } + + template<> + inline ip_address parser::parse( + const std::wstring &name) + { + auto propInfo = find_property(name); + throw_if_property_not_found(propInfo); + + krabs::debug::assert_valid_assignment(name, propInfo); + + auto outType = propInfo.pEventPropertyInfo_->nonStructType.OutType; + + switch (outType) { + case TDH_OUTTYPE_IPV6: + return ip_address::from_ipv6(propInfo.pPropertyIndex_); + + case TDH_OUTTYPE_IPV4: + return ip_address::from_ipv4(*(DWORD*)propInfo.pPropertyIndex_); + + default: + throw std::runtime_error("IP Address was not IPV4 or IPV6"); + } + } + + template<> + inline socket_address parser::parse( + const std::wstring &name) + { + auto propInfo = find_property(name); + throw_if_property_not_found(propInfo); + + krabs::debug::assert_valid_assignment(name, propInfo); + + return socket_address::from_bytes(propInfo.pPropertyIndex_, propInfo.length_); + } + + template<> + inline sid parser::parse( + const std::wstring& name) + { + auto propInfo = find_property(name); + throw_if_property_not_found(propInfo); + + krabs::debug::assert_valid_assignment(name, propInfo); + auto InType = propInfo.pEventPropertyInfo_->nonStructType.InType; + + // A WBEMSID is actually a TOKEN_USER structure followed by the SID. + // We only care about the SID. From MSDN: + // + // The size of the TOKEN_USER structure differs + // depending on whether the events were generated on a 32 - bit + // or 64 - bit architecture. Also the structure is aligned + // on an 8 - byte boundary, so its size is 8 bytes on a + // 32 - bit computer and 16 bytes on a 64 - bit computer. + // Doubling the pointer size handles both cases. + ULONG sid_start = 16; + if (EVENT_HEADER_FLAG_32_BIT_HEADER == (schema_.record_.EventHeader.Flags & EVENT_HEADER_FLAG_32_BIT_HEADER)) { + sid_start = 8; + } + switch (InType) { + case TDH_INTYPE_SID: + return sid::from_bytes(propInfo.pPropertyIndex_, propInfo.length_); + case TDH_INTYPE_WBEMSID: + // Safety measure to make sure we don't overflow + if (propInfo.length_ <= sid_start) { + throw std::runtime_error( + "Requested a WBEMSID property but data is too small"); + } + return sid::from_bytes(propInfo.pPropertyIndex_ + sid_start, propInfo.length_ - sid_start); + + default: + throw std::runtime_error("SID was not a SID or WBEMSID"); + } + } + + template<> + inline pointer parser::parse(const std::wstring& name) + { + auto propInfo = find_property(name); + throw_if_property_not_found(propInfo); + + krabs::debug::assert_valid_assignment(name, propInfo); + + return pointer::from_bytes(propInfo.pPropertyIndex_, propInfo.length_); + } + + // view_of + // ------------------------------------------------------------------------ + + template + auto parser::view_of(const std::wstring &name, Adapter &adapter) + -> collection_view + { + auto propInfo = find_property(name); + throw_if_property_not_found(propInfo); + + // TODO: type asserts? + + return adapter(propInfo); + } +} diff --git a/libs/krabs/krabs/perfinfo_groupmask.hpp b/libs/krabs/krabs/perfinfo_groupmask.hpp new file mode 100644 index 0000000..18a6ab4 --- /dev/null +++ b/libs/krabs/krabs/perfinfo_groupmask.hpp @@ -0,0 +1,210 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#ifndef PERFINFO_GROUPMASK_HPP +#define PERFINFO_GROUPMASK_HPP + +#include +#pragma comment(lib, "ntdll") + +// https://geoffchappell.com/studies/windows/km/ntoskrnl/api/etw/tracesup/perfinfo_groupmask.htm + +#define PERF_MASK_INDEX (0xe0000000) +#define PERF_MASK_GROUP (~PERF_MASK_INDEX) +#define PERF_NUM_MASKS 8 + +typedef ULONG PERFINFO_MASK; +typedef struct _PERFINFO_GROUPMASK { + ULONG Masks[PERF_NUM_MASKS]; +} PERFINFO_GROUPMASK, *PPERFINFO_GROUPMASK; + +#define PERF_GET_MASK_INDEX(GM) (((GM) & PERF_MASK_INDEX) >> 29) +#define PERF_GET_MASK_GROUP(GM) ((GM) & PERF_MASK_GROUP) +#define PERFINFO_OR_GROUP_WITH_GROUPMASK(Group, pGroupMask) \ + (pGroupMask)->Masks[PERF_GET_MASK_INDEX(Group)] |= PERF_GET_MASK_GROUP(Group); + +// Masks[0] +#define PERF_PROCESS EVENT_TRACE_FLAG_PROCESS +#define PERF_THREAD EVENT_TRACE_FLAG_THREAD +#define PERF_PROC_THREAD EVENT_TRACE_FLAG_PROCESS | EVENT_TRACE_FLAG_THREAD +#define PERF_LOADER EVENT_TRACE_FLAG_IMAGE_LOAD +#define PERF_PERF_COUNTER EVENT_TRACE_FLAG_PROCESS_COUNTERS +#define PERF_FILENAME EVENT_TRACE_FLAG_DISK_FILE_IO +#define PERF_DISK_IO EVENT_TRACE_FLAG_DISK_FILE_IO | EVENT_TRACE_FLAG_DISK_IO +#define PERF_DISK_IO_INIT EVENT_TRACE_FLAG_DISK_IO_INIT +#define PERF_ALL_FAULTS EVENT_TRACE_FLAG_MEMORY_PAGE_FAULTS +#define PERF_HARD_FAULTS EVENT_TRACE_FLAG_MEMORY_HARD_FAULTS +#define PERF_VAMAP EVENT_TRACE_FLAG_VAMAP +#define PERF_NETWORK EVENT_TRACE_FLAG_NETWORK_TCPIP +#define PERF_REGISTRY EVENT_TRACE_FLAG_REGISTRY +#define PERF_DBGPRINT EVENT_TRACE_FLAG_DBGPRINT +#define PERF_JOB EVENT_TRACE_FLAG_JOB +#define PERF_ALPC EVENT_TRACE_FLAG_ALPC +#define PERF_SPLIT_IO EVENT_TRACE_FLAG_SPLIT_IO +#define PERF_DEBUG_EVENTS EVENT_TRACE_FLAG_DEBUG_EVENTS +#define PERF_FILE_IO EVENT_TRACE_FLAG_FILE_IO +#define PERF_FILE_IO_INIT EVENT_TRACE_FLAG_FILE_IO_INIT +#define PERF_NO_SYSCONFIG EVENT_TRACE_FLAG_NO_SYSCONFIG + +// Masks[1] +#define PERF_MEMORY 0x20000001 +#define PERF_PROFILE 0x20000002 // equivalent to EVENT_TRACE_FLAG_PROFILE +#define PERF_CONTEXT_SWITCH 0x20000004 // equivalent to EVENT_TRACE_FLAG_CSWITCH +#define PERF_FOOTPRINT 0x20000008 +#define PERF_DRIVERS 0x20000010 // equivalent to EVENT_TRACE_FLAG_DRIVER +#define PERF_REFSET 0x20000020 +#define PERF_POOL 0x20000040 +#define PERF_POOLTRACE 0x20000041 +#define PERF_DPC 0x20000080 // equivalent to EVENT_TRACE_FLAG_DPC +#define PERF_COMPACT_CSWITCH 0x20000100 +#define PERF_DISPATCHER 0x20000200 // equivalent to EVENT_TRACE_FLAG_DISPATCHER +#define PERF_PMC_PROFILE 0x20000400 +#define PERF_PROFILING 0x20000402 +#define PERF_PROCESS_INSWAP 0x20000800 +#define PERF_AFFINITY 0x20001000 +#define PERF_PRIORITY 0x20002000 +#define PERF_INTERRUPT 0x20004000 // equivalent to EVENT_TRACE_FLAG_INTERRUPT +#define PERF_VIRTUAL_ALLOC 0x20008000 // equivalent to EVENT_TRACE_FLAG_VIRTUAL_ALLOC +#define PERF_SPINLOCK 0x20010000 +#define PERF_SYNC_OBJECTS 0x20020000 +#define PERF_DPC_QUEUE 0x20040000 +#define PERF_MEMINFO 0x20080000 +#define PERF_CONTMEM_GEN 0x20100000 +#define PERF_SPINLOCK_CNTRS 0x20200000 +#define PERF_SPININSTR 0x20210000 +#define PERF_SESSION 0x20400000 +#define PERF_PFSECTION 0x20400000 +#define PERF_MEMINFO_WS 0x20800000 +#define PERF_KERNEL_QUEUE 0x21000000 +#define PERF_INTERRUPT_STEER 0x22000000 +#define PERF_SHOULD_YIELD 0x24000000 +#define PERF_WS 0x28000000 + +// Masks[2] +#define PERF_ANTI_STARVATION 0x40000001 +#define PERF_PROCESS_FREEZE 0x40000002 +#define PERF_PFN_LIST 0x40000004 +#define PERF_WS_DETAIL 0x40000008 +#define PERF_WS_ENTRY 0x40000010 +#define PERF_HEAP 0x40000020 +#define PERF_SYSCALL 0x40000040 // equivalent to EVENT_TRACE_FLAG_SYSTEMCALL +#define PERF_UMS 0x40000080 +#define PERF_BACKTRACE 0x40000100 +#define PERF_VULCAN 0x40000200 +#define PERF_OBJECTS 0x40000400 +#define PERF_EVENTS 0x40000800 +#define PERF_FULLTRACE 0x40001000 +#define PERF_DFSS 0x40002000 +#define PERF_PREFETCH 0x40004000 +#define PERF_PROCESSOR_IDLE 0x40008000 +#define PERF_CPU_CONFIG 0x40010000 +#define PERF_TIMER 0x40020000 +#define PERF_CLOCK_INTERRUPT 0x40040000 +#define PERF_LOAD_BALANCER 0x40080000 +#define PERF_CLOCK_TIMER 0x40100000 +#define PERF_IDLE_SELECTION 0x40200000 +#define PERF_IPI 0x40400000 +#define PERF_IO_TIMER 0x40800000 +#define PERF_REG_HIVE 0x41000000 +#define PERF_REG_NOTIF 0x42000000 +#define PERF_PPM_EXIT_LATENCY 0x44000000 +#define PERF_WORKER_THREAD 0x48000000 + +// Masks[4] +#define PERF_OPTICAL_IO 0x80000001 +#define PERF_OPTICAL_IO_INIT 0x80000002 +#define PERF_DLL_INFO 0x80000008 +#define PERF_DLL_FLUSH_WS 0x80000010 +#define PERF_OB_HANDLE 0x80000040 +#define PERF_OB_OBJECT 0x80000080 +#define PERF_WAKE_DROP 0x80000200 +#define PERF_WAKE_EVENT 0x80000400 +#define PERF_DEBUGGER 0x80000800 +#define PERF_PROC_ATTACH 0x80001000 +#define PERF_WAKE_COUNTER 0x80002000 +#define PERF_POWER 0x80008000 +#define PERF_SOFT_TRIM 0x80010000 +#define PERF_CC 0x80020000 +#define PERF_FLT_IO_INIT 0x80080000 +#define PERF_FLT_IO 0x80100000 +#define PERF_FLT_FASTIO 0x80200000 +#define PERF_FLT_IO_FAILURE 0x80400000 +#define PERF_HV_PROFILE 0x80800000 +#define PERF_WDF_DPC 0x81000000 +#define PERF_WDF_INTERRUPT 0x82000000 +#define PERF_CACHE_FLUSH 0x84000000 + +// Masks[5] +#define PERF_HIBER_RUNDOWN 0xA0000001 + +// Masks[6] +#define PERF_SYSCFG_SYSTEM 0xC0000001 +#define PERF_SYSCFG_GRAPHICS 0xC0000002 +#define PERF_SYSCFG_STORAGE 0xC0000004 +#define PERF_SYSCFG_NETWORK 0xC0000008 +#define PERF_SYSCFG_SERVICES 0xC0000010 +#define PERF_SYSCFG_PNP 0xC0000020 +#define PERF_SYSCFG_OPTICAL 0xC0000040 +#define PERF_SYSCFG_ALL 0xDFFFFFFF + +// Masks[7] - Control Mask. All flags that change system behavior go here. +#define PERF_CLUSTER_OFF 0xE0000001 +#define PERF_MEMORY_CONTROL 0xE0000002 + +// TraceQueryInformation wasn't introduced until Windows 8, so we need to use +// NtQuerySystemInformation instead in order to maintain support for Windows 7. +// This requires the below additional definitions. + +typedef enum _EVENT_TRACE_INFORMATION_CLASS { + EventTraceKernelVersionInformation, + EventTraceGroupMaskInformation, + EventTracePerformanceInformation, + EventTraceTimeProfileInformation, + EventTraceSessionSecurityInformation, + EventTraceSpinlockInformation, + EventTraceStackTracingInformation, + EventTraceExecutiveResourceInformation, + EventTraceHeapTracingInformation, + EventTraceHeapSummaryTracingInformation, + EventTracePoolTagFilterInformation, + EventTracePebsTracingInformation, + EventTraceProfileConfigInformation, + EventTraceProfileSourceListInformation, + EventTraceProfileEventListInformation, + EventTraceProfileCounterListInformation, + EventTraceStackCachingInformation, + EventTraceObjectTypeFilterInformation, + MaxEventTraceInfoClass +} EVENT_TRACE_INFORMATION_CLASS; + +typedef struct _EVENT_TRACE_GROUPMASK_INFORMATION { + EVENT_TRACE_INFORMATION_CLASS EventTraceInformationClass; + TRACEHANDLE TraceHandle; + PERFINFO_GROUPMASK EventTraceGroupMasks; +} EVENT_TRACE_GROUPMASK_INFORMATION, * PEVENT_TRACE_GROUPMASK_INFORMATION; + +#ifndef _WINTERNL_ + +typedef enum _SYSTEM_INFORMATION_CLASS { +} SYSTEM_INFORMATION_CLASS; + +typedef LONG NTSTATUS; + +extern "C" NTSTATUS NTAPI NtQuerySystemInformation( + _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, + _Out_writes_bytes_to_opt_(SystemInformationLength, *ReturnLength) PVOID SystemInformation, + _In_ ULONG SystemInformationLength, + _Out_opt_ PULONG ReturnLength +); + +#endif // _WINTERNL_ + +extern "C" NTSTATUS NTAPI NtSetSystemInformation( + _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, + _In_reads_bytes_opt_(SystemInformationLength) PVOID SystemInformation, + _In_ ULONG SystemInformationLength +); + +constexpr auto SystemPerformanceTraceInformation{ static_cast(0x1f) }; + +#endif // PERFINFO_GROUPMASK_HPP \ No newline at end of file diff --git a/libs/krabs/krabs/property.hpp b/libs/krabs/krabs/property.hpp new file mode 100644 index 0000000..3e8e957 --- /dev/null +++ b/libs/krabs/krabs/property.hpp @@ -0,0 +1,201 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#define INITGUID + +#include +#include + +#include "compiler_check.hpp" +#include "schema.hpp" +#include "errors.hpp" + +#include +#include +#include + +#pragma comment(lib, "tdh.lib") + +namespace krabs { + + /** + * + * Represents a single property of the record schema. + * + * + * Noticeably absent from this property is the ability to ask what its + * value is. The reason for this is that this property instance is + * intended to work with synth_records, which don't always have data to + * correspond with properties. This class *cannot* return a value because + * there isn't always a value to return. + * + */ + class property { + public: + + /** + * + * Constructs a property. + * + * + * This should be instantiated by client code -- let the parser + * object do this for you with its `properties` method. + * + */ + property(const std::wstring &name, _TDH_IN_TYPE type); + + /** + * + * Retrieves the name of the property. + * + */ + const std::wstring &name() const; + + /** + * + * Retrieves the Tdh type of the property. + * + */ + _TDH_IN_TYPE type() const; + + private: + std::wstring name_; + _TDH_IN_TYPE type_; + }; + + + /** + * + * Iterates the properties in a given event record. + * + */ + class property_iterator { + public: + + /** + * + * Constructs a new iterator that lazily retrieves the properties of + * the given event record. + * + * + * Don't construct this yourself. Let the `parser` class do it for you. + * + */ + property_iterator(const schema &s); + + /** + * + * Returns an iterator that hasn't yielded any properties yet. + * + */ + std::vector::iterator begin(); + + /** + * + * Returns an iterator that has yielded all properties. + * + */ + std::vector::iterator end(); + + private: + + /** + * + * Constructs a property instance out of the raw data of the + * given property. + * + */ + property get_property(size_t index) const; + + /** + * + * Collects the names of the properties in the schema. + * + * + * This is a little lazy of us, as we end up iterating the properties + * entirely before allowing enumeration by the client. Because this + * code is most likely called in a non-critical path, there's not + * much to worry about here. + */ + std::vector enum_properties() const; + + + private: + const krabs::schema &schema_; + size_t numProperties_; + std::vector properties_; + std::vector::iterator beg_; + std::vector::iterator end_; + std::vector::iterator curr_; + }; + + // Implementation + // ------------------------------------------------------------------------ + + inline property::property(const std::wstring &name, _TDH_IN_TYPE type) + : name_(name) + , type_(type) + {} + + inline const std::wstring &property::name() const + { + return name_; + } + + inline _TDH_IN_TYPE property::type() const + { + return type_; + } + + // ------------------------------------------------------------------------ + + inline property_iterator::property_iterator(const schema &s) + : schema_(s) + , numProperties_(s.pSchema_->TopLevelPropertyCount) + , properties_(enum_properties()) + , beg_(properties_.begin()) + , end_(properties_.end()) + , curr_(properties_.begin()) + {} + + inline std::vector::iterator property_iterator::begin() + { + return beg_; + } + + inline std::vector::iterator property_iterator::end() + { + return end_; + } + + inline property property_iterator::get_property(size_t index) const + { + const auto &curr_prop = schema_.pSchema_->EventPropertyInfoArray[index]; + + const wchar_t *pName = reinterpret_cast( + reinterpret_cast(schema_.pSchema_) + + curr_prop.NameOffset); + + auto tdh_type = (_TDH_IN_TYPE)curr_prop.nonStructType.InType; + + return property(pName, tdh_type); + } + + inline std::vector property_iterator::enum_properties() const + { + std::vector props; + for (size_t i = 0; i < numProperties_; ++i) { + props.emplace_back(get_property(i)); + } + + return props; + } + + +} /* namespace krabs */ diff --git a/libs/krabs/krabs/provider.hpp b/libs/krabs/krabs/provider.hpp new file mode 100644 index 0000000..e4d0390 --- /dev/null +++ b/libs/krabs/krabs/provider.hpp @@ -0,0 +1,666 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#define INITGUID + +#include +#include + +#include "compiler_check.hpp" +#include "filtering/event_filter.hpp" +#include "perfinfo_groupmask.hpp" +#include "trace_context.hpp" +#include "wstring_convert.hpp" + +#include +#include +#include +#include + +#ifdef _DEBUG +#pragma comment(lib, "comsuppwd.lib") +#else +#pragma comment(lib, "comsuppw.lib") +#endif + +namespace krabs { namespace details { + template class trace_manager; + + struct kt; + struct ut; + +} /* namespace details */ } /* namespace krabs */ + +namespace krabs { + + template + class trace; + + typedef void(*c_provider_callback)(const EVENT_RECORD &, const krabs::trace_context &); + typedef void(*c_provider_error_callback)(const EVENT_RECORD&, const std::string&); + typedef std::function provider_callback; + typedef std::function provider_error_callback; + + namespace details { + + /** + * + * Serves as a base for providers and kernel_providers. Handles event + * registration and forwarding. + * + */ + template + class base_provider { + public: + + /** + * + * Adds a function to call when an event for this provider is fired. + * + * + * the function to call into + * + * void my_fun(const EVENT_RECORD &record, const krabs::trace_context &trace_context) { ... } + * // ... + * krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * provider<> powershell(id); + * provider.add_on_event_callback(my_fun); + * + * + * + * auto fun = [&](const EVENT_RECORD &record, const krabs::trace_context &trace_context) {...} + * krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * provider<> powershell(id); + * provider.add_on_event_callback(fun); + * + */ + void add_on_event_callback(c_provider_callback callback); + + template + void add_on_event_callback(U &callback); + + template + void add_on_event_callback(const U &callback); + + /** + * + * Adds a function to call when an error occurs when this provider handles an event. + * + */ + void add_on_error_callback(c_provider_error_callback callback); + + template + void add_on_error_callback(U& callback); + + template + void add_on_error_callback(const U& callback); + + /** + * + * Adds a new filter to a provider, where the filter is expected + * to have callbacks attached to it. + * + * + * krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * krabs::provider<> powershell(id); + * krabs::event_filter filter(krabs::filtering::any_event); + * filter.add_on_event_callback([&](const EVENT_RECORD &record, const krabs::trace_context &trace_context) {...}); + * powershell.add_filter(filter); + * + */ + void add_filter(const event_filter &f); + + protected: + + /** + * + * Called when an event occurs, forwards to callbacks and filters. + * + */ + void on_event(const EVENT_RECORD &record, const krabs::trace_context &context) const; + + protected: + std::deque callbacks_; + std::deque error_callbacks_; + std::deque filters_; + + private: + template + friend class details::trace_manager; + + template + friend class krabs::trace; + + template + friend class base_provider; + }; + + } // namespace details + + /** + * + * Used to enable specific types of events from specific event sources in + * ETW. Corresponds tightly with the concept of an ETW provider. Used for + * user trace instances (not kernel trace instances). + * + * + * + * The type of flags to use when filtering event types via any and all. + * There is an implicitly requirement that T can be downcasted to a + * ULONGLONG. + * + */ + template + class provider : public details::base_provider { + public: + + /** + * + * Constructs a provider with the given guid identifier. + * + * + * the GUID that identifies the provider. + * + * krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * provider<> powershell(id); + * + */ + provider(GUID id); + + /** + * + * Constructs a provider with the given a provider name. + * + * + * the provider name. + * + * krabs::guid id(L"Microsoft-Windows-WinINet"); + * provider<> winINet(id); + * + */ + provider(const std::wstring &providerName); + + /** + * + * Sets the "any" flag of the provider. + * + * + * the value to set any to. + * + * krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * provider<> powershell(id); + * powershell.any(0xFF00); + * + */ + void any(T any); + + /** + * + * Sets the "all" flag of the provider. + * + * + * the value to set all to. + * + * krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * provider<> powershell(id); + * powershell.all(0xFF00); + * + */ + void all(T all); + + /** + * + * Sets the "level" flag of the provider. Valid values are 0~255 (0xFF). + * + * + * the value to set level to. + * + * krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * provider<> powershell(id); + * powershell.level(0x00); + * + */ + void level(T level); + + /** + * + * Sets the "EnableProperty" flag on the ENABLE_TRACE_PARAMETER struct. + * These properties configure behaviours for a specified user-mode provider. + * Valid values can be found here: + * https://msdn.microsoft.com/en-us/library/windows/desktop/dd392306(v=vs.85).aspx + * + * + * the value to set + * + * krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * provider<> powershell(id); + * powershell.trace_flags(EVENT_ENABLE_PROPERTY_STACK_TRACE); + * + */ + void trace_flags(T trace_flags); + + /** + * + * Gets the configured value for the "EnableProperty" flag on the + * ENABLE_TRACE_PARAMETER struct. See void trace_flags(T trace_flags) + * for details on what the values mean. + * + * The value to set when the provider is enabled. + * + * krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * provider<> powershell(id); + * powershell.trace_flags(EVENT_ENABLE_PROPERTY_STACK_TRACE); + * auto flags = powershell.get_trace_flags(); + * assert(flags == EVENT_ENABLE_PROPERTY_STACK_TRACE); + * + */ + T trace_flags() const; + + /** + * + * Requests that the provider log its state information. See: + * https://docs.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-enabletraceex2 + * + * + * + * krabs::provider<> process_provider(L"Microsoft-Windows-Kernel-Process"); + * process_provider.any(0x10); // WINEVENT_KEYWORD_PROCESS + * process_provider.enable_rundown_events(); + * + */ + void enable_rundown_events(); + + /** + * + * Turns a strongly typed provider to provider<> (useful for + * creating collections of providers). + * + * + * + * krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * provider powershell(id); + * provider<> blah(powershell); + * + */ + operator provider<>() const; + + private: + GUID guid_; + T any_; + T all_; + T level_; + T trace_flags_; + bool rundown_enabled_; + + private: + template + friend class details::trace_manager; + + template + friend class krabs::trace; + + template + friend class base_provider; + + friend struct details::ut; + + }; + + /** + * + * Used to enable specific types of event sources from an ETW kernel + * trace. + * + */ + class kernel_provider : public details::base_provider { + public: + + /** + * + * Constructs a kernel_provider that enables events of the given + * flags. + * + */ + kernel_provider(unsigned long flags, const GUID &id) + : p_(flags) + , id_(id) + , gm_(0) + , r_(0) + , rundown_enabled_(false) + {} + + /** + * + * Constructs a kernel_provider that enables events of the given + * group mask. + * + * + * Only supported on Windows 8 and newer. + * + */ + kernel_provider(const GUID& id, PERFINFO_MASK group_mask) + : p_(0) + , id_(id) + , gm_(group_mask) + , r_(0) + , rundown_enabled_(false) + {} + + /** + * + * Retrieves the GUID associated with this provider. + * + */ + const krabs::guid &id() const; + + /** + * + * Sets flags to be enabled for the kernel rundown GUID. + * + * + * This ETW feature is undocumented and should be used with caution. + * + */ + void set_rundown_flags(unsigned long rundown_flags) { + r_ = rundown_flags; + rundown_enabled_ = true; + }; + + private: + + /** + * + * Retrieves the flag value associated with this provider. + * + */ + unsigned long flags() const { return p_; } + + /** + * + * Retrieves the group mask value associated with this provider. + * + */ + PERFINFO_MASK group_mask() const { return gm_; } + + /** + * + * Retrieves the rundown flag value associated with this provider. + * + */ + unsigned long rundown_flags() const { return r_; } + + /** + * + * Have rundown flags been set for this this provider? + * + */ + bool rundown_enabled() const { return rundown_enabled_; } + + private: + unsigned long p_; + const krabs::guid id_; + PERFINFO_MASK gm_; + unsigned long r_; + bool rundown_enabled_; + + private: + friend struct details::kt; + }; + + + // Implementation + // ------------------------------------------------------------------------ + + namespace details { + + template + void base_provider::add_on_event_callback(c_provider_callback callback) + { + // C function pointers don't interact well with std::ref, so we + // overload to take care of this scenario. + callbacks_.push_back(callback); + } + + template + template + void base_provider::add_on_event_callback(U &callback) + { + // std::function copies its argument -- because our callbacks list + // is a list of std::function, this causes problems when a user + // intended for their particular instance to be called. + // std::ref lets us get around this and point to a specific instance + // that they handed us. + callbacks_.push_back(std::ref(callback)); + } + + template + template + void base_provider::add_on_event_callback(const U &callback) + { + // This is where temporaries bind to. Temporaries can't be wrapped in + // a std::ref because they'll go away very quickly. We are forced to + // actually copy these. + callbacks_.push_back(callback); + } + + template + void base_provider::add_on_error_callback(c_provider_error_callback callback) + { + // C function pointers don't interact well with std::ref, so we + // overload to take care of this scenario. + error_callbacks_.push_back(callback); + } + + template + template + void base_provider::add_on_error_callback(U& callback) + { + // std::function copies its argument -- because our callbacks list + // is a list of std::function, this causes problems when a user + // intended for their particular instance to be called. + // std::ref lets us get around this and point to a specific instance + // that they handed us. + error_callbacks_.push_back(std::ref(callback)); + } + + template + template + void base_provider::add_on_error_callback(const U& callback) + { + // This is where temporaries bind to. Temporaries can't be wrapped in + // a std::ref because they'll go away very quickly. We are forced to + // actually copy these. + error_callbacks_.push_back(callback); + } + + template + void base_provider::add_filter(const event_filter &f) + { + filters_.push_back(f); + } + + template + void base_provider::on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) const + { + try + { + for (auto& callback : callbacks_) { + callback(record, trace_context); + } + + for (auto& filter : filters_) { + filter.on_event(record, trace_context); + } + } + catch (krabs::could_not_find_schema& ex) + { + for (auto& error_callback : error_callbacks_) { + error_callback(record, ex.what()); + } + } + } + } // namespace details + + // ------------------------------------------------------------------------ + + static const GUID emptyGuid = { 0 }; + + template + provider::provider(GUID id) + : guid_(id) + , any_(0) + , all_(0) + , level_(5) + , trace_flags_(0) + , rundown_enabled_(false) + {} + + + inline void check_com_hr(HRESULT hr) { + if (FAILED(hr)) { + std::stringstream stream; + stream << "Error in creating instance of trace providers"; + stream << ", hr = 0x"; + stream << std::hex << hr; + throw std::runtime_error(stream.str()); + } + } + + inline void check_provider_hr(HRESULT hr, const std::wstring &providerName) { + if (FAILED(hr)) { + std::stringstream stream; + stream << "Error in constructing guid from provider name ("; + stream << from_wstring(providerName); + stream << "), hr = 0x"; + stream << std::hex << hr; + throw std::runtime_error(stream.str()); + } + } + + template + provider::provider(const std::wstring &providerName) + { + ITraceDataProviderCollection *allProviders; + + HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + check_com_hr(hr); + { + hr = CoCreateInstance( + CLSID_TraceDataProviderCollection, + NULL, + CLSCTX_SERVER, + IID_ITraceDataProviderCollection, + (void**)&allProviders); + check_com_hr(hr); + + auto release_ptr = [](IUnknown* ptr) { ptr->Release(); }; + std::unique_ptr allProvidersPtr(allProviders, release_ptr); + + hr = allProviders->GetTraceDataProviders(NULL); + check_provider_hr(hr, providerName); + + ULONG count; + hr = allProviders->get_Count((long*)&count); + check_provider_hr(hr, providerName); + + VARIANT index; + index.vt = VT_UI4; + + GUID providerGuid = { 0 }; + + for (index.ulVal = 0; index.ulVal < count; index.ulVal++){ + ITraceDataProvider *provider; + hr = allProviders->get_Item(index, &provider); + check_provider_hr(hr, providerName); + + std::unique_ptr providerPtr(provider, release_ptr); + + _bstr_t name; + hr = provider->get_DisplayName(name.GetAddress()); + check_provider_hr(hr, providerName); + + if (wcscmp(name, providerName.c_str()) == 0){ + hr = provider->get_Guid(&providerGuid); + check_provider_hr(hr, providerName); + break; + } + } + + if (memcmp((void*)&providerGuid, (void*)&emptyGuid, sizeof(emptyGuid)) == 0) + { + std::stringstream stream; + stream << "Provider name does not exist. ("; + stream << from_wstring(providerName); + stream << "), hr = 0x"; + stream << std::hex << hr; + throw std::runtime_error(stream.str()); + } + + guid_ = providerGuid; + any_ = 0; + all_ = 0; + level_ = 5; + trace_flags_ = 0; + rundown_enabled_ = false; + } + + CoUninitialize(); + } + + template + void provider::any(T any) + { + any_ = any; + } + + template + void provider::all(T all) + { + all_ = all; + } + + template + void provider::level(T level) + { + level_ = level; + } + + template + void provider::trace_flags(T trace_flags) + { + trace_flags_ = trace_flags; + } + + template + T provider::trace_flags() const + { + return static_cast(trace_flags_); + } + + template + void provider::enable_rundown_events() + { + rundown_enabled_ = true; + } + + template + provider::operator provider<>() const + { + provider<> tmp(guid_); + tmp.any_ = static_cast(any_); + tmp.all_ = static_cast(all_); + tmp.level_ = static_cast(level_); + tmp.trace_flags_ = static_cast(trace_flags_); + tmp.callbacks_ = this.callbacks_; + + return tmp; + } + + inline const krabs::guid &kernel_provider::id() const + { + return id_; + } + +} diff --git a/libs/krabs/krabs/schema.hpp b/libs/krabs/krabs/schema.hpp new file mode 100644 index 0000000..c4994b5 --- /dev/null +++ b/libs/krabs/krabs/schema.hpp @@ -0,0 +1,420 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#define INITGUID + +#include + +#include +#include +#include + +#include "compiler_check.hpp" +#include "schema_locator.hpp" + +#pragma comment(lib, "tdh.lib") + + +namespace krabs { namespace testing { + class record_builder; +} /* namespace testing */ } /* namespace krabs */ + + +namespace krabs { + + class schema; + class parser; + + /** + * + * Used to query events for detailed information. Creation is rather + * costly, so client code should try hard to delay creation of this. + * + */ + class schema { + public: + + /** + * + * Constructs a schema from an event record instance + * using the provided schema_locator. + * + * + * + * void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) + * { + * krabs::schema schema(record, trace_context.schema_locator); + * } + * + */ + schema(const EVENT_RECORD &, const krabs::schema_locator &); + + /** + * Compares two schemas for equality. + * + * + * schema1 == schema2; + * schema1 != schema2; + * + */ + bool operator==(const schema &other) const; + bool operator!=(const schema &other) const; + + /* + * + * Returns the name of an event via its schema. + * + * + * void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) + * { + * krabs::schema schema(record, trace_context.schema_locator); + * std::wstring name = krabs::event_name(schema); + * } + * + */ + const wchar_t *event_name() const; + + /* + * + * Returns the name of an opcode via its schema. + * + * + * void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) + * { + * krabs::schema schema(record, trace_context.schema_locator); + * std::wstring name = krabs::opcode_name(schema); + * } + * + */ + const wchar_t* opcode_name() const; + + /* + * + * Returns the taskname of an event via its schema. + * + * + * void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) + * { + * krabs::schema schema(record, trace_context.schema_locator); + * std::wstring name = krabs::task_name(schema); + * } + * + */ + const wchar_t *task_name() const; + + /* + * + * Returns the DECODING_SOURCE of an event via its schema. + * + * + * void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) + * { + * krabs::schema schema(record, trace_context.schema_locator); + * DECODING_SOURCE source = krabs::decoding_source(schema); + * } + * + */ + DECODING_SOURCE decoding_source() const; + + /** + * + * Returns the event ID via its schema. + * + * + * void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) + * { + * krabs::schema schema(record, trace_context.schema_locator); + * int id = schema.event_id(); + * } + * + */ + int event_id() const; + + /** + * + * Returns the event opcode. + * + * + * void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) + * { + * krabs::schema schema(record, trace_context.schema_locator); + * int opcode = schema.event_opcode(); + * } + * + */ + int event_opcode() const; + + /** + * + * Returns the version of the event. + * + */ + unsigned int event_version() const; + + /** + * + * Returns the flags of the event. + * + */ + unsigned int event_flags() const; + + /** + * + * Returns the provider name of an event via its schema. + * + * + * void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) + * { + * krabs::schema schema(record, trace_context.schema_locator); + * std::wstring name = krabs::provider_name(schema); + * } + * + */ + const wchar_t *provider_name() const; + + /** + * + * Returns the PID associated with the event via its schema. + * + * + * void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) + * { + * krabs::schema schema(record, trace_context.schema_locator); + * unsigned int name = krabs::process_id(schema); + * } + * + */ + unsigned int process_id() const; + + /** + * + * Returns the Thread ID associated with the event via its schema. + * + * + * void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) + * { + * krabs::schema schema(record, trace_context.schema_locator); + * unsigned int name = krabs::thread_id(schema); + * } + * + */ + unsigned int thread_id() const; + + /** + * + * Returns the timestamp associated with the event via its schema. + * + * + * void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) + * { + * krabs::schema schema(record, trace_context.schema_locator); + * LARGE_INTEGER time = krabs::timestamp(schema); + * } + * + */ + LARGE_INTEGER timestamp() const; + + /** + * + * Returns the Activity ID associated with the event via its schema. + * + * + * void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) + * { + * krabs::schema schema(record, trace_context.schema_locator); + * GUID activity_id = krabs::activity_id(schema); + * } + * + */ + GUID activity_id() const; + + /** + * + * Retrieves the call stack associated with the record, if enabled. + * + * + * void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) + * { + * krabs::schema schema(record, trace_context.schema_locator); + * std::vector stack_trace = schema.stack_trace(); + * } + * + */ + std::vector stack_trace() const; + + private: + const EVENT_RECORD &record_; + TRACE_EVENT_INFO *pSchema_; + + private: + friend std::wstring event_name(const schema &); + friend std::wstring opcode_name(const schema &); + friend std::wstring task_name(const schema &); + friend DECODING_SOURCE decoding_source(const schema &); + friend std::wstring provider_name(const schema &); + friend unsigned int process_id(const schema &); + friend LARGE_INTEGER timestamp(const schema &); + friend GUID activity_id(const schema&); + friend int event_id(const EVENT_RECORD &); + friend int event_id(const schema &); + friend std::vector stack_trace(const schema&); + friend std::vector stack_trace(const EVENT_RECORD&); + + friend class parser; + friend class property_iterator; + friend class record_builder; + }; + + + // Implementation + // ------------------------------------------------------------------------ + + inline schema::schema(const EVENT_RECORD &record, const krabs::schema_locator &schema_locator) + : record_(record) + , pSchema_(schema_locator.get_event_schema(record)) + { } + + inline bool schema::operator==(const schema &other) const + { + return (pSchema_->ProviderGuid == other.pSchema_->ProviderGuid && + pSchema_->EventDescriptor.Id == other.pSchema_->EventDescriptor.Id && + pSchema_->EventDescriptor.Version == other.pSchema_->EventDescriptor.Version); + } + + inline bool schema::operator!=(const schema &other) const + { + return !(*this == other); + } + + inline const wchar_t *schema::event_name() const + { + /* + EventNameOffset will be 0 if the event does not have an assigned name or + if this event is decoded on a system that does not support decoding + manifest event names. Event name decoding is supported on Windows + 10 Fall Creators Update (2017) and later. + */ + if (pSchema_->EventNameOffset != 0) { + return reinterpret_cast( + reinterpret_cast(pSchema_) + + pSchema_->EventNameOffset); + } + else { + return L""; + } + } + + inline const wchar_t* schema::opcode_name() const + { + /* + In WPP Traces OpcodeName is not used + */ + if (pSchema_->OpcodeNameOffset != 0) { + return reinterpret_cast( + reinterpret_cast(pSchema_) + + pSchema_->OpcodeNameOffset); + } + else { + return L""; + } + } + + inline const wchar_t *schema::task_name() const + { + if (pSchema_->TaskNameOffset != 0) { + return reinterpret_cast( + reinterpret_cast(pSchema_) + + pSchema_->TaskNameOffset); + } + else { + return L""; + } + } + + inline DECODING_SOURCE schema::decoding_source() const + { + return pSchema_->DecodingSource; + } + + inline int schema::event_id() const + { + return record_.EventHeader.EventDescriptor.Id; + } + + inline int schema::event_opcode() const + { + return record_.EventHeader.EventDescriptor.Opcode; + } + + inline unsigned int schema::event_version() const + { + return record_.EventHeader.EventDescriptor.Version; + } + + inline unsigned int schema::event_flags() const + { + return record_.EventHeader.Flags; + } + + inline const wchar_t *schema::provider_name() const + { + return reinterpret_cast( + reinterpret_cast(pSchema_) + + pSchema_->ProviderNameOffset); + } + + inline unsigned int schema::process_id() const + { + return record_.EventHeader.ProcessId; + } + + inline unsigned int schema::thread_id() const + { + return record_.EventHeader.ThreadId; + } + + inline LARGE_INTEGER schema::timestamp() const + { + return record_.EventHeader.TimeStamp; + } + + inline GUID schema::activity_id() const + { + return record_.EventHeader.ActivityId; + } + + inline std::vector schema::stack_trace() const + { + std::vector call_stack; + if (record_.ExtendedDataCount != 0) { + for (USHORT i = 0; i < record_.ExtendedDataCount; i++) + { + auto item = record_.ExtendedData[i]; + if (item.ExtType == EVENT_HEADER_EXT_TYPE_STACK_TRACE64) { + auto stacktrace = reinterpret_cast(item.DataPtr); + auto stack_length = (item.DataSize - sizeof(ULONG64)) / sizeof(ULONG64); + for (size_t j = 0; j < stack_length; j++) + { + call_stack.push_back(stacktrace->Address[j]); + } + } + else if (item.ExtType == EVENT_HEADER_EXT_TYPE_STACK_TRACE32) { + auto stacktrace = reinterpret_cast(item.DataPtr); + auto stack_length = (item.DataSize - sizeof(ULONG64)) / sizeof(ULONG); + for (size_t j = 0; j < stack_length; j++) + { + call_stack.push_back(stacktrace->Address[j]); + } + } + } + } + + return call_stack; + } +} diff --git a/libs/krabs/krabs/schema_locator.hpp b/libs/krabs/krabs/schema_locator.hpp new file mode 100644 index 0000000..e3ec591 --- /dev/null +++ b/libs/krabs/krabs/schema_locator.hpp @@ -0,0 +1,163 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#define INITGUID + +#include +#include +#include + +#include +#include + +#include "compiler_check.hpp" +#include "errors.hpp" +#include "guid.hpp" + +#pragma comment(lib, "tdh.lib") + +namespace krabs { + + /** + * + * Type used as the key for cache lookup in a schema_locator. + * + */ + struct schema_key + { + guid provider; + uint16_t id; + uint8_t opcode; + uint8_t version; + uint8_t level; + + schema_key(const EVENT_RECORD &record) + : provider(record.EventHeader.ProviderId) + , id(record.EventHeader.EventDescriptor.Id) + , opcode(record.EventHeader.EventDescriptor.Opcode) + , level(record.EventHeader.EventDescriptor.Level) + , version(record.EventHeader.EventDescriptor.Version) { } + + bool operator==(const schema_key &rhs) const + { + return provider == rhs.provider && + id == rhs.id && + opcode == rhs.opcode && + level == rhs.level && + version == rhs.version; + } + + bool operator!=(const schema_key &rhs) const { return !(*this == rhs); } + }; +} + +namespace std { + + /** + * + * Builds a hash code for a schema_key + * + */ + template<> + struct std::hash + { + size_t operator()(const krabs::schema_key &key) const + { + // Shift-Add-XOR hash - good enough for the small sets we deal with + size_t h = 2166136261; + + h ^= (h << 5) + (h >> 2) + std::hash()(key.provider); + h ^= (h << 5) + (h >> 2) + key.id; + h ^= (h << 5) + (h >> 2) + key.opcode; + h ^= (h << 5) + (h >> 2) + key.version; + h ^= (h << 5) + (h >> 2) + key.level; + + return h; + } + }; +} + +namespace krabs { + + /** + * + * Get event schema from TDH. + * + */ + std::unique_ptr get_event_schema_from_tdh(const EVENT_RECORD &); + + /** + * + * Fetches and caches schemas from TDH. + * NOTE: this cache also reduces the number of managed to native transitions + * when krabs is compiled into a managed assembly. + * + */ + class schema_locator { + public: + + /** + * + * Retrieves the event schema from the cache or falls back to + * TDH to load the schema. + * + */ + const PTRACE_EVENT_INFO get_event_schema(const EVENT_RECORD &record) const; + + private: + mutable std::unordered_map> cache_; + }; + + // Implementation + // ------------------------------------------------------------------------ + + inline const PTRACE_EVENT_INFO schema_locator::get_event_schema(const EVENT_RECORD &record) const + { + // check the cache + auto key = schema_key(record); + auto& buffer = cache_[key]; + + if (!buffer) { + auto temp = get_event_schema_from_tdh(record); + buffer.swap(temp); + } + + return (PTRACE_EVENT_INFO)(buffer.get()); + } + + inline std::unique_ptr get_event_schema_from_tdh(const EVENT_RECORD &record) + { + // get required size + ULONG bufferSize = 0; + ULONG status = TdhGetEventInformation( + (PEVENT_RECORD)&record, + 0, + NULL, + NULL, + &bufferSize); + + if (status != ERROR_INSUFFICIENT_BUFFER) { + error_check_common_conditions(status, record); + } + + // allocate and fill the schema from TDH + auto buffer = std::unique_ptr(new char[bufferSize]); + + error_check_common_conditions( + TdhGetEventInformation( + (PEVENT_RECORD)&record, + 0, + NULL, + (PTRACE_EVENT_INFO)buffer.get(), + &bufferSize), + record); + + return buffer; + } +} diff --git a/libs/krabs/krabs/size_provider.hpp b/libs/krabs/krabs/size_provider.hpp new file mode 100644 index 0000000..e9734d5 --- /dev/null +++ b/libs/krabs/krabs/size_provider.hpp @@ -0,0 +1,169 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include +#include +#include + +#include "compiler_check.hpp" + +namespace krabs { + + // TODO: I don't like this interface - it's too tightly + // coupled to parser.hpp mainly because the code was + // lifted directly out of parser::find_property. + + class size_provider { + public: + /** + * + * Get the size of the specified property from the specified record. + * + * BYTE* offset into the user data buffer where the property starts + * wchar_t* name of the property to query + * EVENT_RECORD& record to query + * EVENT_PROPERTY_INFO& property info for the property to query + */ + static ULONG get_property_size( + const BYTE*, + const wchar_t*, + const EVENT_RECORD&, + const EVENT_PROPERTY_INFO&); + + private: + static ULONG get_heuristic_size( + const BYTE*, + const EVENT_PROPERTY_INFO&, + const EVENT_RECORD&); + + static ULONG get_tdh_size( + const wchar_t*, + const EVENT_RECORD&); + }; + + // Implementation + // ------------------------------------------------------------------------ + + inline ULONG size_provider::get_property_size( + const BYTE* propertyStart, + const wchar_t* propertyName, + const EVENT_RECORD& record, + const EVENT_PROPERTY_INFO& propertyInfo) + { + // The values of the event are essentially stored as an ad-hoc + // variant. In order to determine how far we need to advance the + // seeking pointer, we need to know the size of the property that + // we've just looked at. For certain variable-sized types (like a + // string), we need to ask Tdh* to determine the length of the + // property. For others, the size is immediately accessible in + // the schema structure. + + if ((propertyInfo.Flags & PropertyParamLength) == 0 && + propertyInfo.length > 0) + { + // length is a union that may refer to another field for a length + // value. In that case, defer to TDH for the value otherwise + // use the length value directly. + + // For pointers check header instead of size, see PointerSize at + // https://docs.microsoft.com/en-us/windows/win32/api/tdh/nf-tdh-tdhformatproperty + // for details + if (propertyInfo.nonStructType.InType == TDH_INTYPE_POINTER) + { + return record.EventHeader.Flags & EVENT_HEADER_FLAG_32_BIT_HEADER ? 4 : 8; + } + + return propertyInfo.length; + } + + ULONG propertyLength = 0; + + // If no flags are set on the property, attempt to use the length + // field. If that field is 0, try using our heuristic. + if (propertyInfo.Flags == 0) + { + if (propertyInfo.length > 0) + propertyLength = propertyInfo.length; + else + propertyLength = get_heuristic_size(propertyStart, propertyInfo, record); + } + + // Couldn't get the length from the 'length' field or + // the heuristic for size failed -> ask Tdh. + if (propertyLength == 0) + propertyLength = get_tdh_size(propertyName, record); + + return propertyLength; + } + + inline ULONG size_provider::get_heuristic_size( + const BYTE* propertyStart, + const EVENT_PROPERTY_INFO& propertyInfo, + const EVENT_RECORD& record) + { + ULONG propertyLength = 0; + PBYTE pRecordEnd = (PBYTE)record.UserData + record.UserDataLength; + + // The calls to Tdh are kind of expensive, especially when krabs is + // included in a managed assembly as this call will be a thunk. + // The following _very_ common property types can be short-circuited + // to prevent the expensive call. + + // Be careful! Check IN and OUT types before making an assumption. + + // Strings that appear at the end of a record may not be null-terminated. + // If a string is null-terminated, propertyLength includes the null character. + // If a string is not-null terminated, propertyLength includes all bytes up + // to the end of the record buffer. + + if (propertyInfo.nonStructType.OutType == TDH_OUTTYPE_STRING) + { + if (propertyInfo.nonStructType.InType == TDH_INTYPE_UNICODESTRING) + { + auto p = (const wchar_t*)propertyStart; + auto pEnd = (const wchar_t*)pRecordEnd; + while (p < pEnd) { + if (!*p++) { + break; + } + } + propertyLength = static_cast(((PBYTE)p) - propertyStart); + } + else if (propertyInfo.nonStructType.InType == TDH_INTYPE_ANSISTRING) + { + auto p = (const char*)propertyStart; + auto pEnd = (const char*)pRecordEnd; + while (p < pEnd) { + if (!*p++) { + break; + } + + } + propertyLength = static_cast(((PBYTE)p) - propertyStart); + } + } + + return propertyLength; + } + + inline ULONG size_provider::get_tdh_size( + const wchar_t* propertyName, + const EVENT_RECORD& record) + { + ULONG propertyLength = 0; + + PROPERTY_DATA_DESCRIPTOR desc; + desc.PropertyName = (ULONGLONG)propertyName; + desc.ArrayIndex = ULONG_MAX; + + TdhGetPropertySize((PEVENT_RECORD)&record, 0, NULL, 1, &desc, &propertyLength); + + return propertyLength; + } +} \ No newline at end of file diff --git a/libs/krabs/krabs/tdh_helpers.hpp b/libs/krabs/krabs/tdh_helpers.hpp new file mode 100644 index 0000000..2c5a80e --- /dev/null +++ b/libs/krabs/krabs/tdh_helpers.hpp @@ -0,0 +1,225 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#define INITGUID + +#include +#include +#include + +#include "compiler_check.hpp" +#include "parse_types.hpp" + +namespace krabs { + +#define CASE_TYPE(enum) case TDH_INTYPE_##enum: return #enum + + inline const char* in_type_to_string(_TDH_IN_TYPE type) + { + switch (type) + { + CASE_TYPE(NULL); + CASE_TYPE(UNICODESTRING); + CASE_TYPE(ANSISTRING); + CASE_TYPE(INT8); + CASE_TYPE(UINT8); + CASE_TYPE(INT16); + CASE_TYPE(UINT16); + CASE_TYPE(INT32); + CASE_TYPE(UINT32); + CASE_TYPE(INT64); + CASE_TYPE(UINT64); + CASE_TYPE(FLOAT); + CASE_TYPE(DOUBLE); + CASE_TYPE(BOOLEAN); + CASE_TYPE(BINARY); + CASE_TYPE(GUID); + CASE_TYPE(POINTER); + CASE_TYPE(FILETIME); + CASE_TYPE(SYSTEMTIME); + CASE_TYPE(SID); + CASE_TYPE(HEXINT32); + CASE_TYPE(HEXINT64); + CASE_TYPE(COUNTEDSTRING); + CASE_TYPE(COUNTEDANSISTRING); + CASE_TYPE(REVERSEDCOUNTEDSTRING); + CASE_TYPE(REVERSEDCOUNTEDANSISTRING); + CASE_TYPE(NONNULLTERMINATEDSTRING); + CASE_TYPE(NONNULLTERMINATEDANSISTRING); + CASE_TYPE(UNICODECHAR); + CASE_TYPE(ANSICHAR); + CASE_TYPE(SIZET); + CASE_TYPE(HEXDUMP); + CASE_TYPE(WBEMSID); + default: return ""; + } + } + +#undef CASE_TYPE + + namespace debug { + + // this function provides a user-friendly compiler error + // which shows the type in question in the error message. + template + inline void missing_assert_specialization_for() + { + static_assert(sizeof(T) == 0, __FUNCSIG__); + } + + // The "catch-all" implementation of assert_valid_assignment just + // throws in debug to let us know that we are trying to parse a + // type that does not have any assignment validation. This compiles + // to a no-op in release. + template + inline void assert_valid_assignment(const std::wstring&, const property_info&) + { +#ifndef NDEBUG + + // NOTE: if you want compile time assignment assertion define TYPEASSERT + // in the preprocessor or undefine it to disable compilation errors + +#ifdef TYPEASSERT + missing_assert_specialization_for(); +#endif // TYPEASSERT +#endif // NDEBUG + } + +#ifndef NDEBUG + + // These specializations will be removed in release builds and compilation + // will fall back to the unspecialized version which is a no-op in release. + + inline void throw_if_invalid( + const std::wstring& name, + const property_info& info, + _TDH_IN_TYPE requested) + { + auto actual = (_TDH_IN_TYPE)info.pEventPropertyInfo_->nonStructType.InType; + + if (requested == actual) return; + +#pragma warning(push) +#pragma warning(disable: 4244) // narrowing property name wchar_t to char for this error message + std::string ansiName(name.begin(), name.end()); +#pragma warning(pop) + + throw type_mismatch_assert( + ansiName.c_str(), + in_type_to_string(actual), + in_type_to_string(requested)); + } + + // The macro below generates a specialized version of assert_valid_assignment + // only in debug builds. The specialized overload will be selected instead + // of the unspecialized version defined above. This allows us to have + // type-driven assertions only in debug builds. + +#define BUILD_ASSERT(type, tdh_type) \ + template <> \ + inline void assert_valid_assignment( \ + const std::wstring& name, const property_info& info) \ + { \ + throw_if_invalid(name, info, tdh_type); \ + } + + // NOTE: don't just blindly add assertions here, some types + // that seem trivial (e.g. bool) are not because of differences + // between the representation in C++ and the representation in ETW. + // Ensure that type sizes match and that the ETW form isn't + // a variant or variable length. A type that requires a specialized + // assertion will also require a specialized parser. + + // strings + BUILD_ASSERT(std::wstring, TDH_INTYPE_UNICODESTRING); + BUILD_ASSERT(std::string, TDH_INTYPE_ANSISTRING); + BUILD_ASSERT(const counted_string*, TDH_INTYPE_COUNTEDSTRING); + + // integers + BUILD_ASSERT(int8_t, TDH_INTYPE_INT8); + BUILD_ASSERT(uint8_t, TDH_INTYPE_UINT8); + BUILD_ASSERT(int16_t, TDH_INTYPE_INT16); + BUILD_ASSERT(uint16_t, TDH_INTYPE_UINT16); + BUILD_ASSERT(int32_t, TDH_INTYPE_INT32); + BUILD_ASSERT(uint32_t, TDH_INTYPE_UINT32); + BUILD_ASSERT(int64_t, TDH_INTYPE_INT64); + BUILD_ASSERT(uint64_t, TDH_INTYPE_UINT64); + + // floating + BUILD_ASSERT(float, TDH_INTYPE_FLOAT); + BUILD_ASSERT(double, TDH_INTYPE_DOUBLE); + + // FILETIME + BUILD_ASSERT(::FILETIME, TDH_INTYPE_FILETIME); + BUILD_ASSERT(::SYSTEMTIME, TDH_INTYPE_SYSTEMTIME); + +#undef BUILD_ASSERT + + template <> + inline void assert_valid_assignment( + const std::wstring&, const property_info& info) + { + auto outType = info.pEventPropertyInfo_->nonStructType.OutType; + + if (outType != TDH_OUTTYPE_IPV6 && + outType != TDH_OUTTYPE_IPV4) { + throw std::runtime_error( + "Requested an IP address from non-IP address property"); + } + } + + template <> + inline void assert_valid_assignment( + const std::wstring&, const property_info& info) + { + auto outType = info.pEventPropertyInfo_->nonStructType.OutType; + + if (outType != TDH_OUTTYPE_SOCKETADDRESS) { + throw std::runtime_error( + "Requested a socket address from property that does not contain a socket address"); + } + } + + template <> + inline void assert_valid_assignment( + const std::wstring&, const property_info& info) + { + auto inType = info.pEventPropertyInfo_->nonStructType.InType; + + if (inType != TDH_INTYPE_WBEMSID && inType != TDH_INTYPE_SID) { + throw std::runtime_error( + "Requested a SID but was neither a SID nor WBEMSID"); + } + } + + template <> + inline void assert_valid_assignment( + const std::wstring&, const property_info& info) + { + auto inType = info.pEventPropertyInfo_->nonStructType.InType; + + if (inType != TDH_INTYPE_POINTER) { + throw std::runtime_error( + "Requested a POINTER from property that is not one"); + } + } + + template <> + inline void assert_valid_assignment( + const std::wstring&, const property_info& info) + { + auto inType = info.pEventPropertyInfo_->nonStructType.InType; + + if (inType != TDH_INTYPE_BOOLEAN) { + throw std::runtime_error( + "Requested a BOOLEAN from property that is not one"); + } + } + +#endif // NDEBUG + + } /* namespace debug */ + +} /* namespace krabs */ diff --git a/libs/krabs/krabs/testing/event_filter_proxy.hpp b/libs/krabs/krabs/testing/event_filter_proxy.hpp new file mode 100644 index 0000000..76ee05e --- /dev/null +++ b/libs/krabs/krabs/testing/event_filter_proxy.hpp @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#define INITGUID + + +#include "../compiler_check.hpp" +#include "../filtering/event_filter.hpp" +#include "synth_record.hpp" + +namespace krabs { namespace testing { + + /** + * + * Serves as a fill-in for the event_filter class for testing purposes. + * It acts as a liason for the actual filter instance and allows for forced event + * testing. + * + */ + class event_filter_proxy { + public: + + /** + * + * Constructs a proxy for the given event_filter. + * + * + * krabs::event_filter event_filter; + * krabs::testing::event_filter_proxy proxy(event_filter); + * + */ + event_filter_proxy(krabs::event_filter &filter); + + /** + * + * Pushes an event through to the proxied filter instance. + * + * + * krabs::event_filter event_filter; + * krabs::testing::event_filter_proxy proxy(event_filter); + * + * krabs::guid powershell(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * krabs::testing::record_builder builder(powershell, krabs::id(7942), krabs::version(1)); + * + * builder.add_properties() + * (L"ClassName", L"FakeETWEventForRealz") + * (L"Message", L"This message is completely faked"); + * + * auto record = builder.pack_incomplete(); + * proxy.push_event(record); + * + */ + void push_event(const synth_record &record); + + private: + krabs::event_filter &event_filter_; + krabs::trace_context trace_context_; + }; + + // Implementation + // ------------------------------------------------------------------------ + + inline event_filter_proxy::event_filter_proxy(krabs::event_filter &event_filter) + : event_filter_(event_filter) + { + } + + inline void event_filter_proxy::push_event(const synth_record &record) + { + event_filter_.on_event(record, trace_context_); + } + +} /* namespace testing */ } /* namespace krabs */ diff --git a/libs/krabs/krabs/testing/extended_data_builder.hpp b/libs/krabs/krabs/testing/extended_data_builder.hpp new file mode 100644 index 0000000..ad74a7a --- /dev/null +++ b/libs/krabs/krabs/testing/extended_data_builder.hpp @@ -0,0 +1,168 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include +#include +#include + +#include +#include +#include + +// TODO: Remove this #define once Krabs starts using Windows SDK v. 10.0.19041.0 or later. +// From evntcons.h starting in Windows SDK v. 10.0.19041.0. +#ifndef EVENT_HEADER_EXT_TYPE_CONTAINER_ID + #define EVENT_HEADER_EXT_TYPE_CONTAINER_ID 16 +#endif + +namespace krabs { namespace testing { + class extended_data_builder; + + /** + * + * Since extended data items have to be packed later, we have to hold onto the data + * until we're ready to pack it. + * + */ + class extended_data_thunk + { + public: + extended_data_thunk(USHORT ext_type, BYTE* data, size_t data_length); + + private: + // Intentionally not defined. + extended_data_thunk(); + + USHORT ext_type_; + std::vector bytes_; + + friend class extended_data_builder; + }; + + /** + * + * Generates fake packed EVENT_HEADER_EXTENDED_DATA_ITEM structures to later add into test + * synth_record objects. These are not guaranteed to be indistinguishable from the real + * thing, just good enough to unit test code that reads/interprets extended data. + * + * Note for testing: this builder just appends extended data structures, it won't stop you + * from breaking any API invariants, such as only one of a specific extended data item type. + * + */ + class extended_data_builder + { + public: + static constexpr size_t GUID_STRING_LENGTH_NO_BRACES = 36; + static constexpr size_t GUID_STRING_LENGTH_WITH_BRACES = GUID_STRING_LENGTH_NO_BRACES + 2; + + + extended_data_builder() + : items_() + {} + + // Mocks a container ID type extended data item. + void add_container_id(const GUID& container_id); + + // This generates a contiguous buffer holding all of the data for + // the extended data items. Non-trivial because the actual structs + // have to be a contiguous array, and they each contain pointers, + // not offsets, to dynamically sized data buffers. + std::pair, size_t> pack() const; + + // Returns the value that should correspond with EVENT_RECORD.ExtendedDataCount + inline size_t count() const { return items_.size(); } + + private: + std::vector items_; + }; + + // Implementation + // ------------------------------------------------------------------------ + + inline extended_data_thunk::extended_data_thunk(USHORT ext_type, BYTE* data, size_t data_length) + : ext_type_(ext_type) + , bytes_() + { + bytes_.assign(data, data + data_length); + } + + inline void extended_data_builder::add_container_id(const GUID& container_id) + { + // With null terminator + wchar_t wide_guid_buffer[GUID_STRING_LENGTH_WITH_BRACES + 1] = {}; + + // No null terminator + BYTE guid_data[GUID_STRING_LENGTH_NO_BRACES] = {}; + + StringFromGUID2(container_id, wide_guid_buffer, GUID_STRING_LENGTH_WITH_BRACES + 1); + + for (int i = 0; i < GUID_STRING_LENGTH_NO_BRACES; i++) + { + // Offset by 1 to ignore the wrapping braces. + guid_data[i] = static_cast(wide_guid_buffer[i + 1]); + } + + items_.emplace_back(static_cast(EVENT_HEADER_EXT_TYPE_CONTAINER_ID), guid_data, GUID_STRING_LENGTH_NO_BRACES); + } + + inline std::pair, size_t> extended_data_builder::pack() const + { + // Return null for buffer if there are no extended data items. + if (items_.size() == 0) + { + return std::make_pair(std::shared_ptr(nullptr), 0); + } + + BYTE* data_buffer = nullptr; + size_t data_buffer_size = 0; + + // Step 1: compute the required buffer size + size_t array_part_size = sizeof(EVENT_HEADER_EXTENDED_DATA_ITEM) * items_.size(); + size_t data_part_size = 0; + + for (const extended_data_thunk& item : items_) + { + data_part_size += item.bytes_.size(); + } + + // Allocate the buffer and zero it + data_buffer = new BYTE[array_part_size + data_part_size]; + data_buffer_size = array_part_size + data_part_size; + ZeroMemory(data_buffer, data_buffer_size); + + // Step 2: Fill the buffer. For each extended data item, write the object into the buffer at the back. + auto array_ptr = reinterpret_cast(data_buffer); + auto data_ptr = data_buffer + array_part_size; + + for (int i = 0; i < items_.size(); i++) + { + // 2a: write the struct + auto& destination = array_ptr[i]; + const auto& thunk = items_[i]; + const size_t thunk_size = thunk.bytes_.size(); + + destination.ExtType = thunk.ext_type_; + destination.DataSize = static_cast(thunk_size); + // Assert that the conversion did not truncate thunk_size. + assert(static_cast(destination.DataSize) == thunk_size); + + // 2b: Write the data + assert((data_buffer + data_buffer_size) > data_ptr); // prevent wraparound with unsigned int math + size_t remaining = (data_buffer + data_buffer_size) - (data_ptr); + // Assert that we will not truncate the data due to not allocating enough space in the buffer. + assert(remaining >= thunk_size); + // Make sure we rather not copy all of the data than overrun the buffer. + memcpy_s(data_ptr, std::min(remaining, thunk_size), thunk.bytes_.data(), thunk_size); + + // 2c: point the DataPtr field at the data + destination.DataPtr = reinterpret_cast(data_ptr); + + // 2d: increment the pointer for where to write the next piece of data + data_ptr += destination.DataSize; + } + + return std::make_pair(std::shared_ptr(data_buffer), data_buffer_size); + } +} } diff --git a/libs/krabs/krabs/testing/filler.hpp b/libs/krabs/krabs/testing/filler.hpp new file mode 100644 index 0000000..01f2f5d --- /dev/null +++ b/libs/krabs/krabs/testing/filler.hpp @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include + +#include "../compiler_check.hpp" +#include "../parse_types.hpp" + +namespace krabs { namespace testing { namespace details { + + /** + * + * Defines how much padding to inject into a synth_record when a property + * is not filled by calling code. + * + */ + inline size_t how_many_bytes_to_fill(_TDH_IN_TYPE type) + { + static_assert(sizeof(float) == 4, "sizeof(float) must be 4, defined on MSDN"); + + switch (type) { + case TDH_INTYPE_NULL: throw std::runtime_error("supposed to be unusued -- something horrible is happening"); + case TDH_INTYPE_UNICODESTRING: return sizeof(wchar_t); + case TDH_INTYPE_ANSISTRING: return sizeof(char); + case TDH_INTYPE_INT8: return sizeof(int8_t); + case TDH_INTYPE_UINT8: return sizeof(uint8_t); + case TDH_INTYPE_INT16: return sizeof(int16_t); + case TDH_INTYPE_UINT16: return sizeof(uint16_t); + case TDH_INTYPE_INT32: return sizeof(int32_t); + case TDH_INTYPE_UINT32: return sizeof(uint32_t); + case TDH_INTYPE_INT64: return sizeof(int64_t); + case TDH_INTYPE_UINT64: return sizeof(int64_t); + case TDH_INTYPE_FLOAT: return sizeof(float); + case TDH_INTYPE_DOUBLE: return sizeof(double); + case TDH_INTYPE_BOOLEAN: return sizeof(uint32_t); // 4-byte bool, defined on MSDN + case TDH_INTYPE_BINARY: return sizeof(char); + case TDH_INTYPE_GUID: return sizeof(GUID); + case TDH_INTYPE_POINTER: return sizeof(char*); + case TDH_INTYPE_FILETIME: return sizeof(FILETIME); + case TDH_INTYPE_SYSTEMTIME: return sizeof(SYSTEMTIME); + case TDH_INTYPE_SID: return sizeof(PSID); + case TDH_INTYPE_HEXINT32: return sizeof(uint32_t); + case TDH_INTYPE_HEXINT64: return sizeof(uint64_t); + default: break; + }; + + throw std::runtime_error("Unexpected fill type"); + } + + /** + * + * Maps C++ types to TDH types. Used to do runtime type checking of packed + * synthetic properties. + * + */ + + template + struct tdh_morphism { + // This doesn't have a value field, so compilation will fail when we + // try to use a type in our record_builder that isn't recognized. + }; + + template + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_POINTER; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_UNICODESTRING; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_ANSISTRING; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_INT8; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_UINT8; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_INT16; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_UINT16; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_INT32; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_UINT32; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_INT64; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_UINT64; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_FLOAT; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_DOUBLE; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_BOOLEAN; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_GUID; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_GUID; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_FILETIME; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_SYSTEMTIME; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_HEXINT32; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_HEXINT64; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_SID; + }; + + template <> + struct tdh_morphism { + static const _TDH_IN_TYPE value = TDH_INTYPE_BINARY; + }; + + +} /* namespace details */ } /* namespace testing */ } /* namespace krabs */ diff --git a/libs/krabs/krabs/testing/proxy.hpp b/libs/krabs/krabs/testing/proxy.hpp new file mode 100644 index 0000000..4b59e84 --- /dev/null +++ b/libs/krabs/krabs/testing/proxy.hpp @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#define INITGUID + +#include "../compiler_check.hpp" +#include "../trace.hpp" +#include "../client.hpp" +#include "../testing/synth_record.hpp" + +namespace krabs { namespace testing { + + /** + * + * Serves as a fill-in for the trace class for testing purposes. It acts + * as a liason for the actual trace instance and allows for forced event + * testing. + * + */ + template + class trace_proxy { + public: + + /** + * + * Constructs a proxy for the given trace. + * + * + * krabs::user_trace trace; + * krabs::testing::trace_proxy proxy(trace); + * + */ + + trace_proxy(T &trace); + + /** + * + * Mocks starting the underlying trace. + * + * + * krabs::user_trace trace; + * krabs::testing::trace_proxy proxy(trace); + * proxy.start(); // do not call trace.start() + * + */ + void start(); + + /** + * + * Pushes an event through to the proxied trace instance. + * + * + * This is the primary mechanism for testing providers and their + * callbacks. Create a fake event with an record_builder instance + * and then push the created synth_record through the object + * graph. + * + * + * krabs::user_trace trace; + * krabs::testing::trace_proxy proxy(trace); + * proxy.start(); // do not call trace.start() + * + * krabs::guid powershell(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * krabs::testing::record_builder builder(powershell, krabs::id(7942), krabs::version(1)); + * + * builder.add_properties() + * (L"ClassName", L"FakeETWEventForRealz") + * (L"Message", L"This message is completely faked"); + * + * auto record = builder.pack_incomplete(); + * proxy.push_event(record); + * + */ + void push_event(const synth_record &record); + + private: + T &trace_; + }; + + /** + * Specific instantiation for user traces. + */ + typedef trace_proxy user_trace_proxy; + + /** + * Specific instantiation for kernel traces. + */ + typedef trace_proxy kernel_trace_proxy; + + // Implementation + // ------------------------------------------------------------------------ + + template + trace_proxy::trace_proxy(T &trace) + : trace_(trace) + { + } + + template + void trace_proxy::start() + { + } + + template + void trace_proxy::push_event(const synth_record &record) + { + trace_.on_event(record); + } + +} /* namespace testing */ } /* namespace krabs */ diff --git a/libs/krabs/krabs/testing/record_builder.hpp b/libs/krabs/krabs/testing/record_builder.hpp new file mode 100644 index 0000000..eee7a43 --- /dev/null +++ b/libs/krabs/krabs/testing/record_builder.hpp @@ -0,0 +1,373 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#define INITGUID +#include +#include +#include +#include +#include +#include + +#include "../compiler_check.hpp" +#include "../guid.hpp" +#include "../schema.hpp" +#include "../parser.hpp" +#include "../tdh_helpers.hpp" +#include "filler.hpp" +#include "synth_record.hpp" +#include "record_property_thunk.hpp" +#include "extended_data_builder.hpp" + +namespace krabs { namespace testing { + + class record_builder; + + namespace details { + + /** + * + * Provides a convenient syntax for adding properties to a + * record_builder. + * + * + * Really shouldn't be used by client code. An instance of this should + * be handed out by a specific record_builder's `add_properties` + * method. + * + */ + struct property_adder { + public: + property_adder(record_builder &builder); + + /** + * + * Allows chaining of property addition. + * + * + * record_builder builder; + * builder.add_properties() + * (L"Name", L"Bjarne Stroustrup") + * (L"Level", 9001); + * + */ + template + property_adder &operator()(const std::wstring &name, T &&value); + + private: + record_builder &builder_; + }; + } + + /** + * + * Enables creation of synthetic events in order to test client code. + * + * + * This beast of a class enables the creation of EVENT_RECORD events + * for testing. The class accepts a collection of keyed pairs that are + * then packed into the event according to the schema on the local + * machine. Because a lot of this is Dark Arts kind of stuff, there + * really isn't a guarantee that this code works perfectly. Please + * file bugs. + * + */ + class record_builder { + private: + public: + record_builder( + const krabs::guid &providerId, + size_t id, + size_t version, + size_t opcode = 0, + size_t level = 0, + bool trim_string_null_terminator = false); + + /** + * Enables adding new properties to the builder. + * + * record_builder builder; + * builder.add_properties() + * (L"Name", L"Bjarne Stroustrup") + * (L"Level", 9001); + * + */ + details::property_adder add_properties(); + + /** + * Packs the event properties into an EVENT_RECORD. + * + * record_builder builder; + * builder.add_properties() + * (L"Name", L"Bjarne Stroustrup") + * (L"Level", 9001); + * auto event = builder.pack(); + * + */ + krabs::testing::synth_record pack() const; + + /** + * + * Packs the event properties into an EVENT_RECORD, but + * doesn't throw when the properties are not complete. + * + * + * record_builder builder; + * builder.add_properties() + * (L"Name", L"Grumpy Gills"); + * auto event = builder.pack_incomplete(); + * + */ + krabs::testing::synth_record pack_incomplete() const; + + /** + * + * Provides access to the properties that have been added. + * + * + * record_builder builder; + * builder.add_properties()(L"Foo", 10); + * for (auto &prop : builder.properties()) { + * // ... + * } + * + */ + const std::vector &properties() const; + + /** + * + * Adds extended data representing a GUID for an Windows container ID + * + */ + void add_container_id_extended_data(const GUID& container_id); + + /** + * + * Gives direct access to the EVENT_HEADER that will be packed into + * the faked record. + * + */ + EVENT_HEADER &header(); + + /** + * + * Fills an EVENT_RECORD with the info necessary to grab its schema + * via Tdh. + * + */ + EVENT_RECORD create_stub_record() const; + + private: + + /** + * + * Does the dirty work of packing up an event record's user data. + * + * + * A pair, where the first item is the packed user data and + * the second is the properties that were not filled (because the + * user never specified them). + * + */ + std::pair, std::vector> + pack_impl(const EVENT_RECORD &record) const; + + private: + const krabs::guid &providerId_; + const size_t id_; + const size_t version_; + const size_t opcode_; + const size_t level_; + EVENT_HEADER header_; + std::vector properties_; + bool trim_string_null_terminator_; + extended_data_builder extended_data_; + + friend struct details::property_adder; + }; + + + // Implementation + // ------------------------------------------------------------------------ + + inline details::property_adder::property_adder(record_builder &builder) + : builder_(builder) + { + } + + template + details::property_adder &details::property_adder::operator()( + const std::wstring &name, + T &&value) + { + builder_.properties_.emplace_back(name, value); + return *this; + } + + // ------------------------------------------------------------------------ + + inline record_builder::record_builder( + const krabs::guid &providerId, + size_t id, + size_t version, + size_t opcode, + size_t level, + bool trim_string_null_terminator) + : providerId_(providerId) + , id_(id) + , version_(version) + , opcode_(opcode) + , level_(level) + , trim_string_null_terminator_(trim_string_null_terminator) + { + ZeroMemory(&header_, sizeof(EVENT_HEADER)); + header_.EventDescriptor.Id = static_cast(id_); + header_.EventDescriptor.Version = static_cast(version_); + header_.EventDescriptor.Opcode = static_cast(opcode_); + header_.EventDescriptor.Level = static_cast(level_); + memcpy(&header_.ProviderId, (const GUID *)&providerId_, sizeof(GUID)); + } + + inline EVENT_HEADER &record_builder::header() + { + return header_; + } + + inline details::property_adder record_builder::add_properties() + { + return details::property_adder(*this); + } + + inline synth_record record_builder::pack() const + { + EVENT_RECORD record = create_stub_record(); + + auto results = pack_impl(record); + if (!results.second.empty()) { + std::string msg = "Not all the properties of the event were filled:"; + + for (auto& s : results.second) { +#pragma warning(push) +#pragma warning(disable: 4244) // narrowing property name wchar_t to char for this error message + msg += " " + std::string(s.begin(), s.end()); +#pragma warning(pop) + } + + throw std::invalid_argument(msg); + } + + // If it's a size 0 list, pack() will return (nullptr, 0) and no buffer is allocated. + auto extended_data_buffer = extended_data_.pack(); + record.ExtendedData = reinterpret_cast(extended_data_buffer.first.get()); + record.ExtendedDataCount = static_cast(extended_data_.count()); + + // Pass shared_ptr of the extended data buffer to make sure the buffer isn't deleted before the synth_record is. + return krabs::testing::synth_record(record, results.first, extended_data_buffer.first); + } + + inline synth_record record_builder::pack_incomplete() const + { + EVENT_RECORD record = create_stub_record(); + auto results = pack_impl(record); + + // If it's a size 0 list, pack() will return (nullptr, 0) and no buffer is allocated. + auto extended_data_buffer = extended_data_.pack(); + record.ExtendedData = reinterpret_cast(extended_data_buffer.first.get()); + record.ExtendedDataCount = static_cast(extended_data_.count()); + + // Pass shared_ptr of the extended data buffer to make sure the buffer isn't deleted before the synth_record is. + return krabs::testing::synth_record(record, results.first, extended_data_buffer.first); + } + + inline EVENT_RECORD record_builder::create_stub_record() const + { + EVENT_RECORD record = {0}; + memcpy(&record.EventHeader, &header_, sizeof(EVENT_HEADER)); + if (record.EventHeader.Size == 0) { + record.EventHeader.Size = sizeof(record.EventHeader); + } + + return record; + } + + inline const std::vector & + record_builder::properties() const + { + return properties_; + } + + inline void record_builder::add_container_id_extended_data(const GUID& container_id) + { + extended_data_.add_container_id(container_id); + } + + inline std::pair, std::vector> + record_builder::pack_impl(const EVENT_RECORD &record) const + { + std::pair, std::vector> results; + krabs::schema_locator schema_locator; + krabs::schema event_schema(record, schema_locator); + krabs::parser event_parser(event_schema); + + // When the last property in a record is of string type (ansi or unicode), + // ETW may omit the string NULL terminator. bytes_to_trim below will eventually be + // set to the number of bytes that can be trimmed from the generated buffer. + + auto bytes_to_trim = 0; + for (auto prop : event_parser.properties()) { + bytes_to_trim = 0; + + auto found_prop = std::find_if(properties_.begin(), + properties_.end(), + [&](const record_property_thunk &thunk) { + return prop.name() == thunk.name(); + }); + + if (found_prop != properties_.end()) { + + // Verify that the user-provided property data matches the type + // that the schema expects. + if (found_prop->type() != prop.type()) { + std::string ansi(prop.name().begin(), prop.name().end()); + auto msg = std::string( + "Invalid property type given for property " + ansi + + " Expected: " + krabs::in_type_to_string(prop.type()) + + " Received: " + krabs::in_type_to_string(found_prop->type())); + + throw std::invalid_argument(msg.c_str()); + } + + // if this is a string type, we could trim the null terminator + // (assuming that there are no other properties after this one) + if (prop.type() == TDH_INTYPE_UNICODESTRING) { + bytes_to_trim = sizeof(L'\0'); + } + else if (prop.type() == TDH_INTYPE_ANSISTRING) { + bytes_to_trim = sizeof('\0'); + } + + std::copy(found_prop->bytes().begin(), + found_prop->bytes().end(), + std::back_inserter(results.first)); + } else { + + // If the property wasn't filled by the user's tests, we fill + // it with empty data that is the size that is expected + // according to the schema. We also remember these properties, + // because it may be considered an error to not fill all + // properties manually. + results.second.emplace_back(prop.name()); + std::fill_n(std::back_inserter(results.first), + details::how_many_bytes_to_fill(prop.type()), static_cast(0)); + } + } + + if (trim_string_null_terminator_) { + results.first.resize(results.first.size() - bytes_to_trim); + } + + return results; + } +} /* namespace testing */ } /* namespace krabs */ diff --git a/libs/krabs/krabs/testing/record_property_thunk.hpp b/libs/krabs/krabs/testing/record_property_thunk.hpp new file mode 100644 index 0000000..d447f57 --- /dev/null +++ b/libs/krabs/krabs/testing/record_property_thunk.hpp @@ -0,0 +1,163 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include "../compiler_check.hpp" +#include "filler.hpp" + +namespace krabs { namespace testing { + + class record_builder; + + /** + * + * Takes any value and turns it into a sequence of serialized bytes. + * + * + * When we're composing an event, we need to store heterogeneous types in + * a collection while we wait until we know exactly how to pack the actual + * event. Because the actual EVENT_RECORD structure properties are packed + * into a byte collection, we take our cue from that and do similarly. We + * keep all of the random property byte blobs separate until we know the + * particular order to stash them in so we have less futzing to do later. + * + */ + class record_property_thunk { + public: + + template + record_property_thunk(const std::wstring &property, const T &value); + + record_property_thunk(const std::wstring &property, const wchar_t *value); + record_property_thunk(const std::wstring &property, const char *value); + record_property_thunk(const std::wstring &property, bool value); + + const std::wstring &name() const; + const std::vector &bytes() const; + const _TDH_IN_TYPE type() const; + + private: + + // We need this because we don't have delegating constructors in VS 2012. + template + void common_string_init(const std::wstring &property, const T &value); + + template + void common_init(const std::wstring &property, const T &value); + + private: + std::wstring name_; + std::vector bytes_; + _TDH_IN_TYPE type_; + + friend class record_builder; + }; + + // Implementation + // ------------------------------------------------------------------------ + template + inline record_property_thunk::record_property_thunk( + const std::wstring &property, + const T &value) + { + common_init(property, value); + } + + // Specialization for wstrings + template <> + inline record_property_thunk::record_property_thunk( + const std::wstring &property, + const std::wstring &value) + { + common_string_init(property, value); + } + + // Specialization for strings + template <> + inline record_property_thunk::record_property_thunk( + const std::wstring &property, + const std::string &value) + { + common_string_init(property, value); + } + + // Overload for wchar_t strings. + inline record_property_thunk::record_property_thunk( + const std::wstring &property, + const wchar_t *value) + { + common_string_init(property, std::move(std::wstring(value))); + } + + // Overload for char strings. + inline record_property_thunk::record_property_thunk( + const std::wstring &property, + const char *value) + { + common_string_init(property, std::move(std::string(value))); + } + + // Specialization for binary blobs + template <> + inline record_property_thunk::record_property_thunk( + const std::wstring &property, + const krabs::binary &bin) + : name_(property) + , bytes_(bin.bytes()) + , type_(krabs::testing::details::tdh_morphism::value) + { + } + + // Overload for booleans + inline record_property_thunk::record_property_thunk( + const std::wstring &property, + bool value) + { + common_init(property, (int)value); + type_ = krabs::testing::details::tdh_morphism::value; + } + + template + void record_property_thunk::common_init( + const std::wstring &property, + const T &value) + { + name_ = property; + bytes_ = std::move(std::vector((BYTE*)&value, (BYTE*)&value + sizeof(T))); + type_ = krabs::testing::details::tdh_morphism::value; + } + + template + void record_property_thunk::common_string_init( + const std::wstring &property, + const T &value) + { + name_ = property; + + const size_t size = value.size() * sizeof(typename T::value_type); + bytes_ = std::move(std::vector((BYTE*)&value[0], (BYTE*)&value[0] + size)); + type_ = krabs::testing::details::tdh_morphism::value; + + // Null terminate the string + for (size_t i = 0; i < sizeof(typename T::value_type); ++i) { + bytes_.push_back('\0'); + } + } + + inline const std::wstring &record_property_thunk::name() const + { + return name_; + } + + inline const std::vector &record_property_thunk::bytes() const + { + return bytes_; + } + + inline const _TDH_IN_TYPE record_property_thunk::type() const + { + return type_; + } + +} /* namespace testing */ } /* namespace krabs */ diff --git a/libs/krabs/krabs/testing/synth_record.hpp b/libs/krabs/krabs/testing/synth_record.hpp new file mode 100644 index 0000000..29548c7 --- /dev/null +++ b/libs/krabs/krabs/testing/synth_record.hpp @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#define INITGUID + +#include +#include + +#include "../compiler_check.hpp" +#include "../guid.hpp" +#include "../schema.hpp" +#include "../parser.hpp" + +#include +#include +#include + +namespace krabs { namespace testing { + + /** + * + * Represents a property that is faked -- one that is built by hand for the + * purpose of testing event reaction code. + * + */ + class synth_record { + public: + + /** + * + * Constructs a synthetic property, given a partially filled + * EVENT_RECORD and a packed sequence of bytes that represent the + * event's user data. + * + * + * This class should not be directly instantiated -- an record_builder + * should return this with its `pack` methods. + * + */ + synth_record(const EVENT_RECORD& record, + const std::vector& user_data); + + /** + * + * Constructs a synthetic property, given a partially filled + * EVENT_RECORD and a packed sequence of bytes that represent the + * event's user data. + * + * + * This class should not be directly instantiated -- an record_builder + * should return this with its `pack` methods. + * + */ + synth_record(const EVENT_RECORD &record, + const std::vector &user_data, + const std::shared_ptr &extended_data); + + /** + * + * Copies a synth_record and updates the pointers + * in the EVENT_RECORD appropriately. + * + */ + synth_record(const synth_record& other); + + /** + * + * Moves a synth_record into a new instance. + * + */ + synth_record(synth_record&& other); + + /** + * + * Assigns a synth_record to another. + * + * by value to take advantage of move ctor + */ + synth_record& operator=(synth_record); + + /** + * + * Allows implicit casts to an EVENT_RECORD. + * + */ + operator const EVENT_RECORD&() const; + + /** + * + * Swaps two synth_records. + * + */ + friend void swap(synth_record& left, synth_record& right) + { + using std::swap; // ADL + + swap(left.record_, right.record_); + swap(left.data_, right.data_); + swap(left.extended_data_, right.extended_data_); + } + + private: + synth_record() + : record_() + , data_() { } + + EVENT_RECORD record_; + std::vector data_; + + // extended_data shared PTR is passed around to make sure that the data + // buffer is only deleted after all dependent synth_records are deleted. + // since the extended data structure uses direct pointers to data + // instead of offsets, we can't pass around a vector unless we + // also want to redo the pointers every time the buffer is copied. + std::shared_ptr extended_data_; + }; + + // Implementation + // ------------------------------------------------------------------------ + + inline synth_record::synth_record(const EVENT_RECORD& record, + const std::vector& user_data) + : synth_record(record, user_data, std::shared_ptr()) + { + // Empty shared_ptr is fine here because there's no concern + // about managing lifetime of an extended data buffer if there + // is no extended data buffer. + } + + inline synth_record::synth_record( + const EVENT_RECORD &record, + const std::vector &user_data, + const std::shared_ptr &extended_data) + : record_(record) + , data_(user_data) + , extended_data_(extended_data) + { + if (data_.size() > 0) { + record_.UserData = &data_[0]; + } else { + record_.UserData = 0; + } + + record_.UserDataLength = static_cast(data_.size()); + } + + inline synth_record::synth_record(const synth_record& other) + : synth_record(other.record_, other.data_, other.extended_data_) + { } + + inline synth_record::synth_record(synth_record&& other) + : synth_record() + { + swap(*this, other); + } + + inline synth_record& synth_record::operator=(synth_record other) + { + swap(*this, other); + return *this; + } + + inline synth_record::operator const EVENT_RECORD&() const + { + return record_; + } + +} /* namespace testing */ } /* namespace krabs */ diff --git a/libs/krabs/krabs/trace.hpp b/libs/krabs/krabs/trace.hpp new file mode 100644 index 0000000..81b076e --- /dev/null +++ b/libs/krabs/krabs/trace.hpp @@ -0,0 +1,446 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include + +#include "compiler_check.hpp" +#include "guid.hpp" +#include "provider.hpp" +#include "trace_context.hpp" +#include "etw.hpp" + + +namespace krabs { namespace details { + template class trace_manager; +} /* namespace details */ } /* namespace krabs */ + +namespace krabs { namespace testing { + template class trace_proxy; +} /* namespace testing */ } /* namespace krabs */ + + +namespace krabs { + + template + class provider; + + /** + * + * Selected statistics about an ETW trace + * + */ + class trace_stats + { + public: + const uint32_t buffersCount; + const uint32_t buffersFree; + const uint32_t buffersWritten; + const uint32_t buffersLost; + const uint64_t eventsTotal; + const uint64_t eventsHandled; + const uint32_t eventsLost; + + trace_stats(uint64_t eventsHandled, const EVENT_TRACE_PROPERTIES& props) + : buffersCount(props.NumberOfBuffers) + , buffersFree(props.FreeBuffers) + , buffersWritten(props.BuffersWritten) + , buffersLost(props.RealTimeBuffersLost) + , eventsTotal(eventsHandled + props.EventsLost) + , eventsHandled(eventsHandled) + , eventsLost(props.EventsLost) + { } + }; + + /** + * + * Represents a single trace session that can have multiple + * enabled providers. Ideally, there should only need to be a + * single trace instance for all ETW user traces. + * + */ + template + class trace { + public: + + typedef T trace_type; + + /** + * + * Constructs a trace with an optional trace name, which can be + * any arbitrary, unique name. + * + * + * + * trace trace; + * trace namedTrace(L"Some special name"); + * + */ + trace(const std::wstring &name); + trace(const wchar_t *name = L""); + + /** + * + * Destructs the trace session and unregisters the session, if + * applicable. + * + * + * + * trace trace; + * // ~trace implicitly called + * + */ + ~trace(); + + /** + * + * Sets the trace properties for a session. + * Must be called before open()/start(). + * See https://docs.microsoft.com/en-us/windows/win32/etw/event-trace-properties + * for important details and restrictions. + * Configurable properties are -> + * - BufferSize. In KB. The maximum buffer size is 1024 KB. + * - MinimumBuffers. Minimum number of buffers is two per processor*. + * - MaximumBuffers. + * - FlushTimer. How often, in seconds, the trace buffers are forcibly flushed. + * - LogFileMode. EVENT_TRACE_NO_PER_PROCESSOR_BUFFERING simulates a *single* sequential processor. + * + * + * krabs::trace trace; + * EVENT_TRACE_PROPERTIES properties = { 0 }; + * properties.BufferSize = 256; + * properties.MinimumBuffers = 12; + * properties.MaximumBuffers = 48; + * properties.FlushTimer = 1; + * properties.LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + * trace.set_trace_properties(&properties); + * krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * provider<> powershell(id); + * trace.enable(powershell); + * trace.start(); + * + */ + void set_trace_properties(const PEVENT_TRACE_PROPERTIES properties); + + /** + * + * Configures trace session settings. + * Must be called after open(). + * See https://docs.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-tracesetinformation + * for more information. + * + * + * krabs::trace trace; + * // Adjust SE_SYSTEM_PROFILE_NAME token privilege through AdjustTokenPrivileges(...) + * // to enable stack tracing (not done in this example). Then: + * STACK_TRACING_EVENT_ID event_id = {0}; + * event_id.EventGuid = krabs::guids::perf_info; + * event_id.Type = 46; // SampleProfile + * trace.open(); + * trace.set_trace_information(TraceStackTracingInfo, &event_id, sizeof(STACK_TRACING_EVENT_ID)); + * krabs::kernel_provider stack_walk_provider(EVENT_TRACE_FLAG_PROFILE, krabs::guids::stack_walk); + * trace.enable(stack_walk_provider); + * trace.process(); + * + */ + void set_trace_information( + TRACE_INFO_CLASS information_class, + PVOID trace_information, + ULONG information_length); + + /** + * + * Configures trace to read from a file instead of realtime + * Must be called before open(). + * See https://docs.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-tracesetinformation + * for more information. + * + * + * krabs::trace trace; + * trace.set_trace_filename(L"C:\merged.etl"); + * trace.process(); + * + */ + void set_trace_filename(const std::wstring& filename); + + /** + * + * Enables the provider on the given user trace. + * + * + * krabs::trace trace; + * krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * provider<> powershell(id); + * trace.enable(powershell); + * + */ + void enable(const typename T::provider_type &p); + + /** + * + * Starts a trace session. + * + * + * krabs::trace trace; + * krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * provider<> powershell(id); + * trace.enable(powershell); + * trace.start(); + * + */ + void start(); + + /** + * + * Closes a trace session. + * + * + * krabs::trace trace; + * krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * provider<> powershell(id); + * trace.enable(powershell); + * trace.start(); + * trace.stop(); + * + */ + void stop(); + + /** + * + * Opens a trace session. + * This is an optional call before start() if you need the trace + * registered with the ETW subsystem before you start processing events. + * + * + * krabs::trace trace; + * krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * provider<> powershell(id); + * trace.enable(powershell); + * auto logfile = trace.open(); + * + */ + EVENT_TRACE_LOGFILE open(); + + /** + * + * Start processing events for an already opened session. + * + * + * krabs::trace trace; + * krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * provider<> powershell(id); + * trace.enable(powershell); + * trace.open(); + * trace.process(); + * + */ + void process(); + + /** + * + * Queries the trace session to get stats about + * events lost and buffers handled. + * + */ + trace_stats query_stats(); + + /** + * + * Returns the number of buffers that were processed. + * + * + * krabs::trace trace; + * krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}"); + * provider<> powershell(id); + * trace.enable(powershell); + * trace.start(); + * trace.stop(); + * std::wcout << trace.buffers_processed() << std::endl; + * + */ + size_t buffers_processed() const; + + /** + * + * Adds a function to call when an event is fired which has no corresponding provider. + * + * + * the function to call into + * + * void my_fun(const EVENT_RECORD &record) { ... } + * // ... + * krabs::trace trace; + * trace.set_default_event_callback(my_fun); + * + * + * + * auto fun = [&](const EVENT_RECORD &record) {...} + * krabs::trace trace; + * trace.set_default_event_callback(fun); + * + */ + void set_default_event_callback(c_provider_callback callback); + + private: + + /** + * + * Invoked when an event occurs in the underlying ETW session. + * + */ + void on_event(const EVENT_RECORD &); + + private: + std::wstring name_; + std::wstring logFilename_; + std::deque> providers_; + + TRACEHANDLE registrationHandle_; + TRACEHANDLE sessionHandle_; + + size_t buffersRead_; + uint64_t eventsHandled_; + + EVENT_TRACE_PROPERTIES properties_; + + const trace_context context_; + + provider_callback default_callback_ = nullptr; + + private: + template + friend class details::trace_manager; + + template + friend class testing::trace_proxy; + + friend typename T; + }; + + // Implementation + // ------------------------------------------------------------------------ + + template + trace::trace(const std::wstring &name) + : registrationHandle_(INVALID_PROCESSTRACE_HANDLE) + , sessionHandle_(INVALID_PROCESSTRACE_HANDLE) + , eventsHandled_(0) + , buffersRead_(0) + , context_() + { + name_ = T::enforce_name_policy(name); + ZeroMemory(&properties_, sizeof(EVENT_TRACE_PROPERTIES)); + } + + template + trace::trace(const wchar_t *name) + : registrationHandle_(INVALID_PROCESSTRACE_HANDLE) + , sessionHandle_(INVALID_PROCESSTRACE_HANDLE) + , eventsHandled_(0) + , buffersRead_(0) + , context_() + { + name_ = T::enforce_name_policy(name); + ZeroMemory(&properties_, sizeof(EVENT_TRACE_PROPERTIES)); + } + + template + trace::~trace() + { + stop(); + } + + template + void trace::set_trace_properties(const PEVENT_TRACE_PROPERTIES properties) + { + properties_.BufferSize = properties->BufferSize; + properties_.MinimumBuffers = properties->MinimumBuffers; + properties_.MaximumBuffers = properties->MaximumBuffers; + properties_.FlushTimer = properties->FlushTimer; + properties_.LogFileMode = properties->LogFileMode; + } + + template + void trace::set_trace_information( + TRACE_INFO_CLASS information_class, + PVOID trace_information, + ULONG information_length) + { + details::trace_manager manager(*this); + manager.set_trace_information(information_class, trace_information, information_length); + } + + template + void trace::set_trace_filename(const std::wstring& filename) + { + logFilename_ = filename; + } + + template + void trace::on_event(const EVENT_RECORD &record) + { + ++eventsHandled_; + T::forward_events(record, *this); + } + + template + void trace::enable(const typename T::provider_type &p) + { + providers_.push_back(std::ref(p)); + } + + template + void trace::start() + { + eventsHandled_ = 0; + + details::trace_manager manager(*this); + manager.start(); + } + + template + void trace::stop() + { + details::trace_manager manager(*this); + manager.stop(); + } + + template + EVENT_TRACE_LOGFILE trace::open() + { + eventsHandled_ = 0; + + details::trace_manager manager(*this); + return manager.open(); + } + + template + void trace::process() + { + eventsHandled_ = 0; + + details::trace_manager manager(*this); + manager.process(); + } + + template + trace_stats trace::query_stats() + { + details::trace_manager manager(*this); + return { eventsHandled_, manager.query() }; + } + + template + size_t trace::buffers_processed() const + { + return buffersRead_; + } + + template + void trace::set_default_event_callback(c_provider_callback callback) + { + default_callback_ = callback; + } + +} diff --git a/libs/krabs/krabs/trace_context.hpp b/libs/krabs/krabs/trace_context.hpp new file mode 100644 index 0000000..3561d96 --- /dev/null +++ b/libs/krabs/krabs/trace_context.hpp @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include "schema_locator.hpp" + +namespace krabs { + + /** + * + * Additional ETW trace context passed to event callbacks + * to enable processing. + * + */ + struct trace_context + { + const schema_locator schema_locator; + /* Add additional trace context here. */ + }; + +} diff --git a/libs/krabs/krabs/ut.hpp b/libs/krabs/krabs/ut.hpp new file mode 100644 index 0000000..b331195 --- /dev/null +++ b/libs/krabs/krabs/ut.hpp @@ -0,0 +1,264 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include + +#include "compiler_check.hpp" +#include "trace.hpp" +#include "provider.hpp" + +namespace krabs { namespace details { + + /** + * + * Used as a template argument to a trace instance. This class implements + * code paths for user traces. Should never be used or seen by client + * code. + * + */ + struct ut { + + typedef krabs::provider<> provider_type; + + struct filter_flags { + UCHAR level_; + ULONGLONG any_; + ULONGLONG all_; + ULONG trace_flags_; + }; + + struct filter_settings{ + std::set provider_filter_event_ids_; + filter_flags filter_flags_{}; + bool rundown_enabled_ = false; + }; + + typedef std::map provider_filter_settings; + /** + * + * Used to assign a name to the trace instance that is being + * instantiated. + * + * + * There really isn't a name policy to enforce with user traces, but + * kernel traces do have specific naming requirements. + * + */ + static const std::wstring enforce_name_policy( + const std::wstring &name); + + /** + * + * Generates a value that fills the EnableFlags field in an + * EVENT_TRACE_PROPERTIES structure. This controls the providers that + * get enabled for a kernel trace. For a user trace, it doesn't do + * much of anything. + * + */ + static const unsigned long construct_enable_flags( + const krabs::trace &trace); + + /** + * + * Enables the providers that are attached to the given trace. + * + */ + static void enable_providers( + const krabs::trace &trace); + + /** + * + * Enables the configured rundown events for each provider. + * Should be called immediately prior to ProcessTrace. + * + */ + static void enable_rundown( + const krabs::trace& trace); + + /** + * + * Decides to forward an event to any of the providers in the trace. + * + */ + static void forward_events( + const EVENT_RECORD &record, + const krabs::trace &trace); + + /** + * + * Sets the ETW trace log file mode. + * + */ + static unsigned long augment_file_mode(); + + /** + * + * Returns the GUID of the trace session. + * + */ + static krabs::guid get_trace_guid(); + }; + + + // Implementation + // ------------------------------------------------------------------------ + + inline const std::wstring ut::enforce_name_policy( + const std::wstring &name_hint) + { + if (name_hint.empty()) { + return std::to_wstring(krabs::guid::random_guid()); + } + + return name_hint; + } + + inline const unsigned long ut::construct_enable_flags( + const krabs::trace &) + { + return 0; + } + + inline void ut::enable_providers( + const krabs::trace &trace) + { + if (trace.registrationHandle_ == INVALID_PROCESSTRACE_HANDLE) + return; + + provider_filter_settings provider_flags; + + // This function essentially takes the union of all the provider flags + // for a given provider GUID. This comes about when multiple providers + // for the same GUID are provided and request different provider flags. + // TODO: Only forward the calls that are requested to each provider. + for (auto &provider : trace.providers_) { + auto& settings = provider_flags[provider.get().guid_]; + settings.filter_flags_.level_ |= provider.get().level_; + settings.filter_flags_.any_ |= provider.get().any_; + settings.filter_flags_.all_ |= provider.get().all_; + settings.filter_flags_.trace_flags_ |= provider.get().trace_flags_; + settings.rundown_enabled_ |= provider.get().rundown_enabled_; + + for (const auto& filter : provider.get().filters_) { + settings.provider_filter_event_ids_.insert( + filter.provider_filter_event_ids().begin(), + filter.provider_filter_event_ids().end()); + } + } + + for (auto &provider : provider_flags) { + ENABLE_TRACE_PARAMETERS parameters; + parameters.ControlFlags = 0; + parameters.Version = ENABLE_TRACE_PARAMETERS_VERSION_2; + parameters.SourceId = provider.first; + + GUID guid = provider.first; + auto& settings = provider.second; + + parameters.EnableProperty = settings.filter_flags_.trace_flags_; + parameters.EnableFilterDesc = nullptr; + parameters.FilterDescCount = 0; + EVENT_FILTER_DESCRIPTOR filterDesc{}; + std::vector filterEventIdBuffer; + auto filterEventIdCount = settings.provider_filter_event_ids_.size(); + + if (filterEventIdCount > 0) { + //event filters existing, set native filters using API + parameters.FilterDescCount = 1; + filterDesc.Type = EVENT_FILTER_TYPE_EVENT_ID; + + //allocate + size of expected events in filter + DWORD size = FIELD_OFFSET(EVENT_FILTER_EVENT_ID, Events[filterEventIdCount]); + filterEventIdBuffer.resize(size, 0); + + auto filterEventIds = reinterpret_cast(&(filterEventIdBuffer[0])); + filterEventIds->FilterIn = TRUE; + filterEventIds->Count = static_cast(filterEventIdCount); + + auto index = 0; + for (auto filter : settings.provider_filter_event_ids_) { + filterEventIds->Events[index] = filter; + index++; + } + + filterDesc.Ptr = reinterpret_cast(filterEventIds); + filterDesc.Size = size; + + parameters.EnableFilterDesc = &filterDesc; + } + + ULONG status = EnableTraceEx2(trace.registrationHandle_, + &guid, + EVENT_CONTROL_CODE_ENABLE_PROVIDER, + settings.filter_flags_.level_, + settings.filter_flags_.any_, + settings.filter_flags_.all_, + 0, + ¶meters); + error_check_common_conditions(status); + } + } + + inline void ut::enable_rundown( + const krabs::trace& trace) + { + if (trace.registrationHandle_ == INVALID_PROCESSTRACE_HANDLE) + return; + + for (auto& provider : trace.providers_) { + if (!provider.get().rundown_enabled_) + continue; + + ULONG status = EnableTraceEx2(trace.registrationHandle_, + &provider.get().guid_, + EVENT_CONTROL_CODE_CAPTURE_STATE, + 0, + 0, + 0, + 0, + NULL); + error_check_common_conditions(status); + } + } + + inline void ut::forward_events( + const EVENT_RECORD &record, + const krabs::trace &trace) + { + // for manifest providers, EventHeader.ProviderId is the Provider GUID + for (auto& provider : trace.providers_) { + if (record.EventHeader.ProviderId == provider.get().guid_) { + provider.get().on_event(record, trace.context_); + return; + } + } + + // for MOF providers, EventHeader.Provider is the *Message* GUID + // we need to ask TDH for event information in order to determine the + // correct provider to pass this event to + auto schema = get_event_schema_from_tdh(record); + auto eventInfo = reinterpret_cast(schema.get()); + for (auto& provider : trace.providers_) { + if (eventInfo->ProviderGuid == provider.get().guid_) { + provider.get().on_event(record, trace.context_); + return; + } + } + + if (trace.default_callback_ != nullptr) + trace.default_callback_(record, trace.context_); + } + + inline unsigned long ut::augment_file_mode() + { + return 0; + } + + inline krabs::guid ut::get_trace_guid() + { + return krabs::guid::random_guid(); + } + +} /* namespace details */ } /* namespace krabs */ diff --git a/libs/krabs/krabs/version_helpers.hpp b/libs/krabs/krabs/version_helpers.hpp new file mode 100644 index 0000000..198f6c9 --- /dev/null +++ b/libs/krabs/krabs/version_helpers.hpp @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// We manually include this file because it doesn't exist for VS2012. + +#ifndef _versionhelpers_H_INCLUDED_ +#define _versionhelpers_H_INCLUDED_ + +#include + +#ifdef _MSC_VER +#pragma once +#endif // _MSC_VER + +#pragma region Application Family +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + +#include // for _In_, etc. + +#if !defined(__midl) && !defined(SORTPP_PASS) + +#if (NTDDI_VERSION >= NTDDI_WINXP) + +#ifdef __cplusplus + +#define VERSIONHELPERAPI inline bool + +#else // __cplusplus + +#define VERSIONHELPERAPI FORCEINLINE BOOL + +#endif // __cplusplus + +#ifndef NTDDI_WINBLUE +#define NTDDI_WINBLUE 0x06030000 +#endif + +#ifndef _WIN32_WINNT_WINBLUE +#define _WIN32_WINNT_WINBLUE 0x0602 +#endif + +VERSIONHELPERAPI +IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor) +{ + OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 }; + DWORDLONG const dwlConditionMask = VerSetConditionMask( + VerSetConditionMask( + VerSetConditionMask( + 0, VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL), + VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + + osvi.dwMajorVersion = wMajorVersion; + osvi.dwMinorVersion = wMinorVersion; + osvi.wServicePackMajor = wServicePackMajor; + + return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE; +} + +VERSIONHELPERAPI +IsWindowsXPOrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 0); +} + +VERSIONHELPERAPI +IsWindowsXPSP1OrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 1); +} + +VERSIONHELPERAPI +IsWindowsXPSP2OrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 2); +} + +VERSIONHELPERAPI +IsWindowsXPSP3OrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 3); +} + +VERSIONHELPERAPI +IsWindowsVistaOrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 0); +} + +VERSIONHELPERAPI +IsWindowsVistaSP1OrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 1); +} + +VERSIONHELPERAPI +IsWindowsVistaSP2OrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 2); +} + +VERSIONHELPERAPI +IsWindows7OrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), LOBYTE(_WIN32_WINNT_WIN7), 0); +} + +VERSIONHELPERAPI +IsWindows7SP1OrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), LOBYTE(_WIN32_WINNT_WIN7), 1); +} + +VERSIONHELPERAPI +IsWindows8OrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), LOBYTE(_WIN32_WINNT_WIN8), 0); +} + +VERSIONHELPERAPI +IsWindows8Point1OrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINBLUE), LOBYTE(_WIN32_WINNT_WINBLUE), 0); +} + +VERSIONHELPERAPI +IsWindowsServer() +{ + OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0, 0, VER_NT_WORKSTATION }; + DWORDLONG const dwlConditionMask = VerSetConditionMask( 0, VER_PRODUCT_TYPE, VER_EQUAL ); + + return !VerifyVersionInfoW(&osvi, VER_PRODUCT_TYPE, dwlConditionMask); +} + +#endif // NTDDI_VERSION + +#endif // defined(__midl) + +#endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) */ +#pragma endregion + +#endif // _VERSIONHELPERS_H_INCLUDED_ diff --git a/libs/krabs/krabs/wstring_convert.hpp b/libs/krabs/krabs/wstring_convert.hpp new file mode 100644 index 0000000..84c9d7b --- /dev/null +++ b/libs/krabs/krabs/wstring_convert.hpp @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include + +namespace krabs { + + /** + * Converts std::wstring argument to std::string using UTF-8 codepage + * Returns empty string if translation fails or input string is empty + * + */ + inline std::string from_wstring(const std::wstring& wstr, UINT codePage = CP_UTF8) + { + if (wstr.empty()) + return {}; + + const auto requiredLen = WideCharToMultiByte(codePage, 0, wstr.data(), static_cast(wstr.size()), + nullptr, 0, nullptr, nullptr); + if (0 == requiredLen) + return {}; + + std::string result(requiredLen, 0); + const auto convertedLen = WideCharToMultiByte(codePage, 0, wstr.data(), static_cast(wstr.size()), + &result[0], requiredLen, nullptr, nullptr); + if (0 == convertedLen) + return {}; + + return result; + } +} diff --git a/screens/1.png b/screens/1.png new file mode 100644 index 0000000..1e5f50d Binary files /dev/null and b/screens/1.png differ