From 54dce6332b745fed3231887ac40ab06f5a04f8fb Mon Sep 17 00:00:00 2001 From: thefLink Date: Tue, 18 Apr 2023 20:36:09 +0200 Subject: [PATCH] initial commit --- Hunt-Weird-Syscalls/Detectors.cpp | 76 ++ Hunt-Weird-Syscalls/Detectors.h | 17 + Hunt-Weird-Syscalls/Helpers.cpp | 65 ++ Hunt-Weird-Syscalls/Helpers.h | 12 + Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.sln | 51 ++ .../Hunt-Weird-Syscalls.vcxproj | 153 ++++ .../Hunt-Weird-Syscalls.vcxproj.filters | 36 + .../Hunt-Weird-Syscalls.vcxproj.user | 4 + Hunt-Weird-Syscalls/Main.cpp | 101 +++ .../SetThreadContext_Direct/Main.c | 11 + .../SetThreadContext_Direct.vcxproj | 158 +++++ .../SetThreadContext_Direct.vcxproj.filters | 35 + .../SetThreadContext_Direct.vcxproj.user | 4 + .../threadcontext_embedded.c | 278 ++++++++ .../threadcontext_embedded.h | 63 ++ .../threadcontext_embedded_-asm.x64.asm | 23 + .../SetThreadContext_Indirect/Main.c | 11 + .../SetThreadContext_Indirect.vcxproj | 158 +++++ .../SetThreadContext_Indirect.vcxproj.filters | 35 + .../SetThreadContext_Indirect.vcxproj.user | 4 + .../threadcontext_jumper_randomized.c | 278 ++++++++ .../threadcontext_jumper_randomized.h | 63 ++ ...readcontext_jumper_randomized_-asm.x64.asm | 27 + README.md | 33 + Screenshots/1.png | Bin 0 -> 14657 bytes libs/LICENSE | 25 + libs/krabs/README.md | 17 + libs/krabs/krabs.hpp | 61 ++ libs/krabs/krabs.runsettings | 9 + libs/krabs/krabs.sln | 161 +++++ libs/krabs/krabs/client.hpp | 26 + libs/krabs/krabs/collection_view.hpp | 88 +++ libs/krabs/krabs/compiler_check.hpp | 8 + libs/krabs/krabs/errors.hpp | 156 ++++ libs/krabs/krabs/etw.hpp | 403 +++++++++++ libs/krabs/krabs/filtering/comparers.hpp | 124 ++++ libs/krabs/krabs/filtering/event_filter.hpp | 234 ++++++ libs/krabs/krabs/filtering/predicates.hpp | 552 +++++++++++++++ libs/krabs/krabs/filtering/view_adapters.hpp | 50 ++ libs/krabs/krabs/guid.hpp | 345 +++++++++ libs/krabs/krabs/kernel_guids.hpp | 194 +++++ libs/krabs/krabs/kernel_providers.hpp | 243 +++++++ libs/krabs/krabs/kt.hpp | 206 ++++++ libs/krabs/krabs/parse_types.hpp | 330 +++++++++ libs/krabs/krabs/parser.hpp | 431 ++++++++++++ libs/krabs/krabs/perfinfo_groupmask.hpp | 210 ++++++ libs/krabs/krabs/property.hpp | 201 ++++++ libs/krabs/krabs/provider.hpp | 666 ++++++++++++++++++ libs/krabs/krabs/schema.hpp | 420 +++++++++++ libs/krabs/krabs/schema_locator.hpp | 163 +++++ libs/krabs/krabs/size_provider.hpp | 169 +++++ libs/krabs/krabs/tdh_helpers.hpp | 225 ++++++ .../krabs/testing/event_filter_proxy.hpp | 75 ++ .../krabs/testing/extended_data_builder.hpp | 168 +++++ libs/krabs/krabs/testing/filler.hpp | 176 +++++ libs/krabs/krabs/testing/proxy.hpp | 112 +++ libs/krabs/krabs/testing/record_builder.hpp | 373 ++++++++++ .../krabs/testing/record_property_thunk.hpp | 163 +++++ libs/krabs/krabs/testing/synth_record.hpp | 170 +++++ libs/krabs/krabs/trace.hpp | 446 ++++++++++++ libs/krabs/krabs/trace_context.hpp | 22 + libs/krabs/krabs/ut.hpp | 264 +++++++ libs/krabs/krabs/version_helpers.hpp | 142 ++++ libs/krabs/krabs/wstring_convert.hpp | 31 + 64 files changed, 9555 insertions(+) create mode 100644 Hunt-Weird-Syscalls/Detectors.cpp create mode 100644 Hunt-Weird-Syscalls/Detectors.h create mode 100644 Hunt-Weird-Syscalls/Helpers.cpp create mode 100644 Hunt-Weird-Syscalls/Helpers.h create mode 100644 Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.sln create mode 100644 Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.vcxproj create mode 100644 Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.vcxproj.filters create mode 100644 Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.vcxproj.user create mode 100644 Hunt-Weird-Syscalls/Main.cpp create mode 100644 Hunt-Weird-Syscalls/SetThreadContext_Direct/Main.c create mode 100644 Hunt-Weird-Syscalls/SetThreadContext_Direct/SetThreadContext_Direct.vcxproj create mode 100644 Hunt-Weird-Syscalls/SetThreadContext_Direct/SetThreadContext_Direct.vcxproj.filters create mode 100644 Hunt-Weird-Syscalls/SetThreadContext_Direct/SetThreadContext_Direct.vcxproj.user create mode 100644 Hunt-Weird-Syscalls/SetThreadContext_Direct/threadcontext_embedded.c create mode 100644 Hunt-Weird-Syscalls/SetThreadContext_Direct/threadcontext_embedded.h create mode 100644 Hunt-Weird-Syscalls/SetThreadContext_Direct/threadcontext_embedded_-asm.x64.asm create mode 100644 Hunt-Weird-Syscalls/SetThreadContext_Indirect/Main.c create mode 100644 Hunt-Weird-Syscalls/SetThreadContext_Indirect/SetThreadContext_Indirect.vcxproj create mode 100644 Hunt-Weird-Syscalls/SetThreadContext_Indirect/SetThreadContext_Indirect.vcxproj.filters create mode 100644 Hunt-Weird-Syscalls/SetThreadContext_Indirect/SetThreadContext_Indirect.vcxproj.user create mode 100644 Hunt-Weird-Syscalls/SetThreadContext_Indirect/threadcontext_jumper_randomized.c create mode 100644 Hunt-Weird-Syscalls/SetThreadContext_Indirect/threadcontext_jumper_randomized.h create mode 100644 Hunt-Weird-Syscalls/SetThreadContext_Indirect/threadcontext_jumper_randomized_-asm.x64.asm create mode 100644 README.md create mode 100644 Screenshots/1.png create mode 100644 libs/LICENSE create mode 100644 libs/krabs/README.md create mode 100644 libs/krabs/krabs.hpp create mode 100644 libs/krabs/krabs.runsettings create mode 100644 libs/krabs/krabs.sln create mode 100644 libs/krabs/krabs/client.hpp create mode 100644 libs/krabs/krabs/collection_view.hpp create mode 100644 libs/krabs/krabs/compiler_check.hpp create mode 100644 libs/krabs/krabs/errors.hpp create mode 100644 libs/krabs/krabs/etw.hpp create mode 100644 libs/krabs/krabs/filtering/comparers.hpp create mode 100644 libs/krabs/krabs/filtering/event_filter.hpp create mode 100644 libs/krabs/krabs/filtering/predicates.hpp create mode 100644 libs/krabs/krabs/filtering/view_adapters.hpp create mode 100644 libs/krabs/krabs/guid.hpp create mode 100644 libs/krabs/krabs/kernel_guids.hpp create mode 100644 libs/krabs/krabs/kernel_providers.hpp create mode 100644 libs/krabs/krabs/kt.hpp create mode 100644 libs/krabs/krabs/parse_types.hpp create mode 100644 libs/krabs/krabs/parser.hpp create mode 100644 libs/krabs/krabs/perfinfo_groupmask.hpp create mode 100644 libs/krabs/krabs/property.hpp create mode 100644 libs/krabs/krabs/provider.hpp create mode 100644 libs/krabs/krabs/schema.hpp create mode 100644 libs/krabs/krabs/schema_locator.hpp create mode 100644 libs/krabs/krabs/size_provider.hpp create mode 100644 libs/krabs/krabs/tdh_helpers.hpp create mode 100644 libs/krabs/krabs/testing/event_filter_proxy.hpp create mode 100644 libs/krabs/krabs/testing/extended_data_builder.hpp create mode 100644 libs/krabs/krabs/testing/filler.hpp create mode 100644 libs/krabs/krabs/testing/proxy.hpp create mode 100644 libs/krabs/krabs/testing/record_builder.hpp create mode 100644 libs/krabs/krabs/testing/record_property_thunk.hpp create mode 100644 libs/krabs/krabs/testing/synth_record.hpp create mode 100644 libs/krabs/krabs/trace.hpp create mode 100644 libs/krabs/krabs/trace_context.hpp create mode 100644 libs/krabs/krabs/ut.hpp create mode 100644 libs/krabs/krabs/version_helpers.hpp create mode 100644 libs/krabs/krabs/wstring_convert.hpp diff --git a/Hunt-Weird-Syscalls/Detectors.cpp b/Hunt-Weird-Syscalls/Detectors.cpp new file mode 100644 index 0000000..615c4fe --- /dev/null +++ b/Hunt-Weird-Syscalls/Detectors.cpp @@ -0,0 +1,76 @@ +#include "Detectors.h" + +namespace Detectors { + + std::vector allowedSyscallmodules = { + "ntdll.dll", + "win32u.dll", + "wow64win.dll" + }; + + PCSTR SyscallAllowOpenThread = "NtOpenThread"; + PCSTR SyscallAllowSetThreadContext = "NtSetContextThread"; + + VOID DirectSyscall ( DWORD pid, HANDLE hProcess, std::vector stack ) { + + std::string lastModule; + BOOL bSuccess = FALSE; + + bSuccess = Helpers::ModuleNameFromAddress ( hProcess, ( PVOID ) stack.front ( ), lastModule ); + if ( bSuccess == FALSE ) + return; + + for ( auto it = allowedSyscallmodules.begin ( ); it != allowedSyscallmodules.end ( ); ++it ) { + if ( !_stricmp ( *it, lastModule.c_str ( ) ) ) + return; + } + + printf ( "! Direct Syscall detected from process: %d\n", pid ); + printf ( "\t Syscall from: 0x%p (%s)\n", ( PVOID ) stack.front ( ), lastModule.c_str ( ) ); + + } + + VOID InDirectSyscall ( DWORD pid, HANDLE hProcess, std::vector stack, PCSTR allowedSyscall ) { + + MEMORY_BASIC_INFORMATION mbi = { 0 }; + + std::string lastModule; + HMODULE hNtdll = NULL; + + BOOL bSuccess = FALSE; + SIZE_T s = 0; + ULONG_PTR offsetIs = 0, offsetExpected = 0; + PVOID returnExpected = NULL; + + bSuccess = Helpers::ModuleNameFromAddress ( hProcess, ( PVOID ) stack.front ( ), lastModule ); + if ( bSuccess == FALSE ) + return; + + if ( _strcmpi ( lastModule.c_str ( ), "ntdll.dll")) + return; // Currently only verifying ntdll.dll syscalls + + s = VirtualQueryEx ( hProcess, ( LPCVOID ) stack.front ( ), &mbi, sizeof ( MEMORY_BASIC_INFORMATION ) ); + if ( s == 0 ) + return; + + offsetIs = stack.front ( ) - ( ULONG_PTR ) mbi.BaseAddress; + + hNtdll = GetModuleHandleA ( "ntdll.dll" ); + + returnExpected = GetProcAddress ( hNtdll, allowedSyscall); + s = VirtualQuery ( returnExpected, &mbi, sizeof ( MEMORY_BASIC_INFORMATION ) ); + if (s == 0) { + return; + } + + offsetExpected = ( ( PBYTE ) returnExpected - ( PBYTE ) mbi.BaseAddress ); + if ( offsetExpected < offsetIs && offsetIs <= offsetExpected + 23 ) { + return; + } + + printf ( "! Indirect Syscall detected from process: %d\n", pid ); + printf ( "\t Syscall %s expected from: stub at 0x%p but was: 0x%llx\n", allowedSyscall, returnExpected, stack.front ( ) ); + + } + +} \ No newline at end of file diff --git a/Hunt-Weird-Syscalls/Detectors.h b/Hunt-Weird-Syscalls/Detectors.h new file mode 100644 index 0000000..7e7fb32 --- /dev/null +++ b/Hunt-Weird-Syscalls/Detectors.h @@ -0,0 +1,17 @@ +#pragma once + +#include "windows.h" +#include + +#include "Helpers.h" + +namespace Detectors { + + extern std::vector SyscallsAllowOpenProcess; + extern PCSTR SyscallAllowOpenThread; + extern PCSTR SyscallAllowSetThreadContext; + + VOID DirectSyscall ( DWORD pid, HANDLE hProcess, std::vector stack ); + VOID InDirectSyscall ( DWORD pid, HANDLE hProcess, std::vector stack, PCSTR ); + +} \ No newline at end of file diff --git a/Hunt-Weird-Syscalls/Helpers.cpp b/Hunt-Weird-Syscalls/Helpers.cpp new file mode 100644 index 0000000..ebf9f73 --- /dev/null +++ b/Hunt-Weird-Syscalls/Helpers.cpp @@ -0,0 +1,65 @@ +#include "Helpers.h" + +namespace Helpers { + + 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 }; + + 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-Syscalls/Helpers.h b/Hunt-Weird-Syscalls/Helpers.h new file mode 100644 index 0000000..0ea0ed4 --- /dev/null +++ b/Hunt-Weird-Syscalls/Helpers.h @@ -0,0 +1,12 @@ +#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 ); +} \ No newline at end of file diff --git a/Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.sln b/Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.sln new file mode 100644 index 0000000..e5841b4 --- /dev/null +++ b/Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.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-Syscalls", "Hunt-Weird-Syscalls.vcxproj", "{B1446E95-C861-4AD9-8A49-D18B97C3108C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SetThreadContext_Direct", "SetThreadContext_Direct\SetThreadContext_Direct.vcxproj", "{C49CD6BF-0525-4270-BEF7-7DEC29630191}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SetThreadContext_Indirect", "SetThreadContext_Indirect\SetThreadContext_Indirect.vcxproj", "{77C9B594-A2E6-40AB-BA92-E23FC1EAE781}" +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 + {B1446E95-C861-4AD9-8A49-D18B97C3108C}.Debug|x64.ActiveCfg = Debug|x64 + {B1446E95-C861-4AD9-8A49-D18B97C3108C}.Debug|x64.Build.0 = Debug|x64 + {B1446E95-C861-4AD9-8A49-D18B97C3108C}.Debug|x86.ActiveCfg = Debug|Win32 + {B1446E95-C861-4AD9-8A49-D18B97C3108C}.Debug|x86.Build.0 = Debug|Win32 + {B1446E95-C861-4AD9-8A49-D18B97C3108C}.Release|x64.ActiveCfg = Release|x64 + {B1446E95-C861-4AD9-8A49-D18B97C3108C}.Release|x64.Build.0 = Release|x64 + {B1446E95-C861-4AD9-8A49-D18B97C3108C}.Release|x86.ActiveCfg = Release|Win32 + {B1446E95-C861-4AD9-8A49-D18B97C3108C}.Release|x86.Build.0 = Release|Win32 + {C49CD6BF-0525-4270-BEF7-7DEC29630191}.Debug|x64.ActiveCfg = Debug|x64 + {C49CD6BF-0525-4270-BEF7-7DEC29630191}.Debug|x64.Build.0 = Debug|x64 + {C49CD6BF-0525-4270-BEF7-7DEC29630191}.Debug|x86.ActiveCfg = Debug|Win32 + {C49CD6BF-0525-4270-BEF7-7DEC29630191}.Debug|x86.Build.0 = Debug|Win32 + {C49CD6BF-0525-4270-BEF7-7DEC29630191}.Release|x64.ActiveCfg = Release|x64 + {C49CD6BF-0525-4270-BEF7-7DEC29630191}.Release|x64.Build.0 = Release|x64 + {C49CD6BF-0525-4270-BEF7-7DEC29630191}.Release|x86.ActiveCfg = Release|Win32 + {C49CD6BF-0525-4270-BEF7-7DEC29630191}.Release|x86.Build.0 = Release|Win32 + {77C9B594-A2E6-40AB-BA92-E23FC1EAE781}.Debug|x64.ActiveCfg = Debug|x64 + {77C9B594-A2E6-40AB-BA92-E23FC1EAE781}.Debug|x64.Build.0 = Debug|x64 + {77C9B594-A2E6-40AB-BA92-E23FC1EAE781}.Debug|x86.ActiveCfg = Debug|Win32 + {77C9B594-A2E6-40AB-BA92-E23FC1EAE781}.Debug|x86.Build.0 = Debug|Win32 + {77C9B594-A2E6-40AB-BA92-E23FC1EAE781}.Release|x64.ActiveCfg = Release|x64 + {77C9B594-A2E6-40AB-BA92-E23FC1EAE781}.Release|x64.Build.0 = Release|x64 + {77C9B594-A2E6-40AB-BA92-E23FC1EAE781}.Release|x86.ActiveCfg = Release|Win32 + {77C9B594-A2E6-40AB-BA92-E23FC1EAE781}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D52D4FC8-D03B-44EE-95E7-EEF1749CA236} + EndGlobalSection +EndGlobal diff --git a/Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.vcxproj b/Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.vcxproj new file mode 100644 index 0000000..0f56344 --- /dev/null +++ b/Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.vcxproj @@ -0,0 +1,153 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {b1446e95-c861-4ad9-8a49-d18b97c3108c} + HuntWeirdSyscalls + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + 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-Syscalls/Hunt-Weird-Syscalls.vcxproj.filters b/Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.vcxproj.filters new file mode 100644 index 0000000..3eb41a7 --- /dev/null +++ b/Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.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-Syscalls/Hunt-Weird-Syscalls.vcxproj.user b/Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Hunt-Weird-Syscalls/Main.cpp b/Hunt-Weird-Syscalls/Main.cpp new file mode 100644 index 0000000..ddba7a7 --- /dev/null +++ b/Hunt-Weird-Syscalls/Main.cpp @@ -0,0 +1,101 @@ +#include "../libs/krabs/krabs.hpp" + +#include "Detectors.h" +#include "Helpers.h" + +#define EVENTID_SETTHTREADCONTEXT 4 +#define EVENTID_OPENTHREAD 6 + +VOID EnableAuditApiTracing ( krabs::user_trace& ); +VOID OnObservableSyscall ( const EVENT_RECORD&, const krabs::trace_context& ); + +VOID EnableAuditApiTracing ( krabs::user_trace& userTrace ) { + + krabs::provider<>* providerApiTracing = new krabs::provider<> ( L"Microsoft-Windows-Kernel-Audit-API-Calls" ); + + providerApiTracing->trace_flags ( providerApiTracing->trace_flags ( ) | EVENT_ENABLE_PROPERTY_STACK_TRACE ); + + krabs::event_filter* filterOpenThread = new krabs::event_filter ( krabs::predicates::id_is ( EVENTID_OPENTHREAD ) ); // OpenThread + krabs::event_filter* filterSetContextThread = new krabs::event_filter ( krabs::predicates::id_is ( EVENTID_SETTHTREADCONTEXT ) ); // OpenThread + + /* For now no distinction between events */ + filterOpenThread->add_on_event_callback ( OnObservableSyscall ); + filterSetContextThread->add_on_event_callback ( OnObservableSyscall ); + + providerApiTracing->add_filter ( *filterSetContextThread ); + providerApiTracing->add_filter ( *filterOpenThread ); + + userTrace.enable ( *providerApiTracing ); + +} + +VOID OnObservableSyscall ( const EVENT_RECORD& record, const krabs::trace_context& trace_context ) { + + BOOL bSuccess = FALSE; + HANDLE hProcess = NULL; + DWORD pid = 0; + + krabs::schema schema ( record, trace_context.schema_locator ); + krabs::parser parser ( schema ); + std::vector stack; + + pid = record.EventHeader.ProcessId; + stack = schema.stack_trace ( ); + + if ( pid == GetCurrentProcessId ( ) ) + return; + + Helpers::RemoveKernelAddrs ( stack ); + if ( stack.size ( ) == 0 ) + return; + + hProcess = OpenProcess ( PROCESS_ALL_ACCESS, FALSE, pid ); + if ( hProcess == NULL ) + return; + + Detectors::DirectSyscall ( pid, hProcess, stack ); + + if ( record.EventHeader.EventDescriptor.Id == EVENTID_OPENTHREAD ) + Detectors::InDirectSyscall ( pid, hProcess, stack, Detectors::SyscallAllowOpenThread); + else if ( record.EventHeader.EventDescriptor.Id == EVENTID_SETTHTREADCONTEXT ) + Detectors::InDirectSyscall ( pid, hProcess, stack, Detectors::SyscallAllowSetThreadContext ); + +Cleanup: + + if ( hProcess ) + CloseHandle ( hProcess ); + +} + +VOID Go ( krabs::user_trace* userTrace ) { + userTrace->start ( ); +} + +int main ( int argc, char** argv ) { + + HANDLE traceThread = NULL; + + krabs::user_trace userTrace ( L"Hunt-Weird-Syscalls" ); + + if ( !Helpers::IsElevated ( ) ) { + printf ( "- Not elevated\n" ); + return 0; + } + + printf ( "* Enabling trace, might take a bit ... \n" ); + + EnableAuditApiTracing ( 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-Syscalls/SetThreadContext_Direct/Main.c b/Hunt-Weird-Syscalls/SetThreadContext_Direct/Main.c new file mode 100644 index 0000000..a743914 --- /dev/null +++ b/Hunt-Weird-Syscalls/SetThreadContext_Direct/Main.c @@ -0,0 +1,11 @@ +#include "windows.h" +#include "threadcontext_embedded.h" + +int main(int argc, char** argv) { + + NtSetContextThread(-1, NULL); + + getchar(); + + return 0; +} \ No newline at end of file diff --git a/Hunt-Weird-Syscalls/SetThreadContext_Direct/SetThreadContext_Direct.vcxproj b/Hunt-Weird-Syscalls/SetThreadContext_Direct/SetThreadContext_Direct.vcxproj new file mode 100644 index 0000000..6e1b23d --- /dev/null +++ b/Hunt-Weird-Syscalls/SetThreadContext_Direct/SetThreadContext_Direct.vcxproj @@ -0,0 +1,158 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {c49cd6bf-0525-4270-bef7-7dec29630191} + SetThreadContextDirect + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + 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 + + + + + + + + + + + + Document + + + + + + + \ No newline at end of file diff --git a/Hunt-Weird-Syscalls/SetThreadContext_Direct/SetThreadContext_Direct.vcxproj.filters b/Hunt-Weird-Syscalls/SetThreadContext_Direct/SetThreadContext_Direct.vcxproj.filters new file mode 100644 index 0000000..34fb788 --- /dev/null +++ b/Hunt-Weird-Syscalls/SetThreadContext_Direct/SetThreadContext_Direct.vcxproj.filters @@ -0,0 +1,35 @@ + + + + + {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 + + + + + Header Files + + + + + Source Files + + + Source Files + + + + + Source Files + + + \ No newline at end of file diff --git a/Hunt-Weird-Syscalls/SetThreadContext_Direct/SetThreadContext_Direct.vcxproj.user b/Hunt-Weird-Syscalls/SetThreadContext_Direct/SetThreadContext_Direct.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/Hunt-Weird-Syscalls/SetThreadContext_Direct/SetThreadContext_Direct.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Hunt-Weird-Syscalls/SetThreadContext_Direct/threadcontext_embedded.c b/Hunt-Weird-Syscalls/SetThreadContext_Direct/threadcontext_embedded.c new file mode 100644 index 0000000..5f5a9c4 --- /dev/null +++ b/Hunt-Weird-Syscalls/SetThreadContext_Direct/threadcontext_embedded.c @@ -0,0 +1,278 @@ +#include "threadcontext_embedded.h" +#include + +//#define DEBUG + +// JUMPER + +#ifdef _M_IX86 + +EXTERN_C PVOID internal_cleancall_wow64_gate(VOID) { + return (PVOID)__readfsdword(0xC0); +} + +__declspec(naked) BOOL local_is_wow64(void) +{ + __asm { + mov eax, fs:[0xc0] + test eax, eax + jne wow64 + mov eax, 0 + ret + wow64: + mov eax, 1 + ret + } +} + + +#endif + +// Code below is adapted from @modexpblog. Read linked article for more details. +// https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams + +SW3_SYSCALL_LIST SW3_SyscallList; + +// SEARCH_AND_REPLACE +#ifdef SEARCH_AND_REPLACE +// THIS IS NOT DEFINED HERE; don't know if I'll add it in a future release +EXTERN void SearchAndReplace(unsigned char[], unsigned char[]); +#endif + +DWORD SW3_HashSyscall(PCSTR FunctionName) +{ + DWORD i = 0; + DWORD Hash = SW3_SEED; + + while (FunctionName[i]) + { + WORD PartialName = *(WORD*)((ULONG_PTR)FunctionName + i++); + Hash ^= PartialName + SW3_ROR8(Hash); + } + + return Hash; +} + +#ifndef JUMPER +PVOID SC_Address(PVOID NtApiAddress) +{ + return NULL; +} +#else +PVOID SC_Address(PVOID NtApiAddress) +{ + DWORD searchLimit = 512; + PVOID SyscallAddress; + + #ifdef _WIN64 + // If the process is 64-bit on a 64-bit OS, we need to search for syscall + BYTE syscall_code[] = { 0x0f, 0x05, 0xc3 }; + ULONG distance_to_syscall = 0x12; + #else + // If the process is 32-bit on a 32-bit OS, we need to search for sysenter + BYTE syscall_code[] = { 0x0f, 0x34, 0xc3 }; + ULONG distance_to_syscall = 0x0f; + #endif + + #ifdef _M_IX86 + // If the process is 32-bit on a 64-bit OS, we need to jump to WOW32Reserved + if (local_is_wow64()) + { + #ifdef DEBUG + printf("[+] Running 32-bit app on x64 (WOW64)\n"); + #endif + return NULL; + } + #endif + + // we don't really care if there is a 'jmp' between + // NtApiAddress and the 'syscall; ret' instructions + SyscallAddress = SW3_RVA2VA(PVOID, NtApiAddress, distance_to_syscall); + + if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code))) + { + // we can use the original code for this system call :) + #if defined(DEBUG) + printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress); + #endif + return SyscallAddress; + } + + // the 'syscall; ret' intructions have not been found, + // we will try to use one near it, similarly to HalosGate + + for (ULONG32 num_jumps = 1; num_jumps < searchLimit; num_jumps++) + { + // let's try with an Nt* API below our syscall + SyscallAddress = SW3_RVA2VA( + PVOID, + NtApiAddress, + distance_to_syscall + num_jumps * 0x20); + if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code))) + { + #if defined(DEBUG) + printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress); + #endif + return SyscallAddress; + } + + // let's try with an Nt* API above our syscall + SyscallAddress = SW3_RVA2VA( + PVOID, + NtApiAddress, + distance_to_syscall - num_jumps * 0x20); + if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code))) + { + #if defined(DEBUG) + printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress); + #endif + return SyscallAddress; + } + } + +#ifdef DEBUG + printf("Syscall Opcodes not found!\n"); +#endif + + return NULL; +} +#endif + + +BOOL SW3_PopulateSyscallList() +{ + // Return early if the list is already populated. + if (SW3_SyscallList.Count) return TRUE; + + #ifdef _WIN64 + PSW3_PEB Peb = (PSW3_PEB)__readgsqword(0x60); + #else + PSW3_PEB Peb = (PSW3_PEB)__readfsdword(0x30); + #endif + PSW3_PEB_LDR_DATA Ldr = Peb->Ldr; + PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL; + PVOID DllBase = NULL; + + // Get the DllBase address of NTDLL.dll. NTDLL is not guaranteed to be the second + // in the list, so it's safer to loop through the full list and find it. + PSW3_LDR_DATA_TABLE_ENTRY LdrEntry; + for (LdrEntry = (PSW3_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW3_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0]) + { + DllBase = LdrEntry->DllBase; + PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase; + PIMAGE_NT_HEADERS NtHeaders = SW3_RVA2VA(PIMAGE_NT_HEADERS, DllBase, DosHeader->e_lfanew); + PIMAGE_DATA_DIRECTORY DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders->OptionalHeader.DataDirectory; + DWORD VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + if (VirtualAddress == 0) continue; + + ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)SW3_RVA2VA(ULONG_PTR, DllBase, VirtualAddress); + + // If this is NTDLL.dll, exit loop. + PCHAR DllName = SW3_RVA2VA(PCHAR, DllBase, ExportDirectory->Name); + + if ((*(ULONG*)DllName | 0x20202020) != 0x6c64746e) continue; + if ((*(ULONG*)(DllName + 4) | 0x20202020) == 0x6c642e6c) break; + } + + if (!ExportDirectory) return FALSE; + + DWORD NumberOfNames = ExportDirectory->NumberOfNames; + PDWORD Functions = SW3_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions); + PDWORD Names = SW3_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames); + PWORD Ordinals = SW3_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals); + + // Populate SW3_SyscallList with unsorted Zw* entries. + DWORD i = 0; + PSW3_SYSCALL_ENTRY Entries = SW3_SyscallList.Entries; + do + { + PCHAR FunctionName = SW3_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]); + + // Is this a system call? + if (*(USHORT*)FunctionName == 0x775a) + { + Entries[i].Hash = SW3_HashSyscall(FunctionName); + Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]]; + Entries[i].SyscallAddress = SC_Address(SW3_RVA2VA(PVOID, DllBase, Entries[i].Address)); + + i++; + if (i == SW3_MAX_ENTRIES) break; + } + } while (--NumberOfNames); + + // Save total number of system calls found. + SW3_SyscallList.Count = i; + + // Sort the list by address in ascending order. + for (DWORD i = 0; i < SW3_SyscallList.Count - 1; i++) + { + for (DWORD j = 0; j < SW3_SyscallList.Count - i - 1; j++) + { + if (Entries[j].Address > Entries[j + 1].Address) + { + // Swap entries. + SW3_SYSCALL_ENTRY TempEntry; + + TempEntry.Hash = Entries[j].Hash; + TempEntry.Address = Entries[j].Address; + TempEntry.SyscallAddress = Entries[j].SyscallAddress; + + Entries[j].Hash = Entries[j + 1].Hash; + Entries[j].Address = Entries[j + 1].Address; + Entries[j].SyscallAddress = Entries[j + 1].SyscallAddress; + + Entries[j + 1].Hash = TempEntry.Hash; + Entries[j + 1].Address = TempEntry.Address; + Entries[j + 1].SyscallAddress = TempEntry.SyscallAddress; + } + } + } + + return TRUE; +} + +EXTERN_C DWORD SW3_GetSyscallNumber(DWORD FunctionHash) +{ + // Ensure SW3_SyscallList is populated. + if (!SW3_PopulateSyscallList()) return -1; + + for (DWORD i = 0; i < SW3_SyscallList.Count; i++) + { + if (FunctionHash == SW3_SyscallList.Entries[i].Hash) + { + return i; + } + } + + return -1; +} + +EXTERN_C PVOID SW3_GetSyscallAddress(DWORD FunctionHash) +{ + // Ensure SW3_SyscallList is populated. + if (!SW3_PopulateSyscallList()) return NULL; + + for (DWORD i = 0; i < SW3_SyscallList.Count; i++) + { + if (FunctionHash == SW3_SyscallList.Entries[i].Hash) + { + return SW3_SyscallList.Entries[i].SyscallAddress; + } + } + + return NULL; +} + +EXTERN_C PVOID SW3_GetRandomSyscallAddress(DWORD FunctionHash) +{ + // Ensure SW3_SyscallList is populated. + if (!SW3_PopulateSyscallList()) return NULL; + + DWORD index = ((DWORD) rand()) % SW3_SyscallList.Count; + + while (FunctionHash == SW3_SyscallList.Entries[index].Hash){ + // Spoofing the syscall return address + index = ((DWORD) rand()) % SW3_SyscallList.Count; + } + return SW3_SyscallList.Entries[index].SyscallAddress; +} diff --git a/Hunt-Weird-Syscalls/SetThreadContext_Direct/threadcontext_embedded.h b/Hunt-Weird-Syscalls/SetThreadContext_Direct/threadcontext_embedded.h new file mode 100644 index 0000000..ae703ee --- /dev/null +++ b/Hunt-Weird-Syscalls/SetThreadContext_Direct/threadcontext_embedded.h @@ -0,0 +1,63 @@ +#pragma once + +// Code below is adapted from @modexpblog. Read linked article for more details. +// https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams + +#ifndef SW3_HEADER_H_ +#define SW3_HEADER_H_ + +#include + +#define SW3_SEED 0x3A397AC2 +#define SW3_ROL8(v) (v << 8 | v >> 24) +#define SW3_ROR8(v) (v >> 8 | v << 24) +#define SW3_ROX8(v) ((SW3_SEED % 2) ? SW3_ROL8(v) : SW3_ROR8(v)) +#define SW3_MAX_ENTRIES 500 +#define SW3_RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva) + +// Typedefs are prefixed to avoid pollution. + +typedef struct _SW3_SYSCALL_ENTRY +{ + DWORD Hash; + DWORD Address; + PVOID SyscallAddress; +} SW3_SYSCALL_ENTRY, *PSW3_SYSCALL_ENTRY; + +typedef struct _SW3_SYSCALL_LIST +{ + DWORD Count; + SW3_SYSCALL_ENTRY Entries[SW3_MAX_ENTRIES]; +} SW3_SYSCALL_LIST, *PSW3_SYSCALL_LIST; + +typedef struct _SW3_PEB_LDR_DATA { + BYTE Reserved1[8]; + PVOID Reserved2[3]; + LIST_ENTRY InMemoryOrderModuleList; +} SW3_PEB_LDR_DATA, *PSW3_PEB_LDR_DATA; + +typedef struct _SW3_LDR_DATA_TABLE_ENTRY { + PVOID Reserved1[2]; + LIST_ENTRY InMemoryOrderLinks; + PVOID Reserved2[2]; + PVOID DllBase; +} SW3_LDR_DATA_TABLE_ENTRY, *PSW3_LDR_DATA_TABLE_ENTRY; + +typedef struct _SW3_PEB { + BYTE Reserved1[2]; + BYTE BeingDebugged; + BYTE Reserved2[1]; + PVOID Reserved3[2]; + PSW3_PEB_LDR_DATA Ldr; +} SW3_PEB, *PSW3_PEB; + +DWORD SW3_HashSyscall(PCSTR FunctionName); +BOOL SW3_PopulateSyscallList(); +EXTERN_C DWORD SW3_GetSyscallNumber(DWORD FunctionHash); +EXTERN_C PVOID SW3_GetSyscallAddress(DWORD FunctionHash); +EXTERN_C PVOID internal_cleancall_wow64_gate(VOID); +EXTERN_C NTSTATUS NtSetContextThread( + IN HANDLE ThreadHandle, + IN PCONTEXT Context); + +#endif diff --git a/Hunt-Weird-Syscalls/SetThreadContext_Direct/threadcontext_embedded_-asm.x64.asm b/Hunt-Weird-Syscalls/SetThreadContext_Direct/threadcontext_embedded_-asm.x64.asm new file mode 100644 index 0000000..15504d4 --- /dev/null +++ b/Hunt-Weird-Syscalls/SetThreadContext_Direct/threadcontext_embedded_-asm.x64.asm @@ -0,0 +1,23 @@ +.code + +EXTERN SW3_GetSyscallNumber: PROC + +NtSetContextThread PROC + mov [rsp +8], rcx ; Save registers. + mov [rsp+16], rdx + mov [rsp+24], r8 + mov [rsp+32], r9 + sub rsp, 28h + mov ecx, 0FA5F256Dh ; Load function hash into ECX. + call SW3_GetSyscallNumber ; Resolve function hash into syscall number. + add rsp, 28h + mov rcx, [rsp+8] ; Restore registers. + mov rdx, [rsp+16] + mov r8, [rsp+24] + mov r9, [rsp+32] + mov r10, rcx + syscall ; Invoke system call. + ret +NtSetContextThread ENDP + +end \ No newline at end of file diff --git a/Hunt-Weird-Syscalls/SetThreadContext_Indirect/Main.c b/Hunt-Weird-Syscalls/SetThreadContext_Indirect/Main.c new file mode 100644 index 0000000..fafd12c --- /dev/null +++ b/Hunt-Weird-Syscalls/SetThreadContext_Indirect/Main.c @@ -0,0 +1,11 @@ +#include "windows.h" +#include "threadcontext_jumper_randomized.h" + +int main(int argc, char** argv) { + + NtSetContextThread(-1, NULL); + + getchar(); + + return 0; +} \ No newline at end of file diff --git a/Hunt-Weird-Syscalls/SetThreadContext_Indirect/SetThreadContext_Indirect.vcxproj b/Hunt-Weird-Syscalls/SetThreadContext_Indirect/SetThreadContext_Indirect.vcxproj new file mode 100644 index 0000000..03a34d6 --- /dev/null +++ b/Hunt-Weird-Syscalls/SetThreadContext_Indirect/SetThreadContext_Indirect.vcxproj @@ -0,0 +1,158 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {77c9b594-a2e6-40ab-ba92-e23fc1eae781} + SetThreadContextIndirect + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + 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 + + + + + + + + + + + + Document + + + + + + + \ No newline at end of file diff --git a/Hunt-Weird-Syscalls/SetThreadContext_Indirect/SetThreadContext_Indirect.vcxproj.filters b/Hunt-Weird-Syscalls/SetThreadContext_Indirect/SetThreadContext_Indirect.vcxproj.filters new file mode 100644 index 0000000..28907cc --- /dev/null +++ b/Hunt-Weird-Syscalls/SetThreadContext_Indirect/SetThreadContext_Indirect.vcxproj.filters @@ -0,0 +1,35 @@ + + + + + {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 + + + + + Header Files + + + + + Source Files + + + Source Files + + + + + Source Files + + + \ No newline at end of file diff --git a/Hunt-Weird-Syscalls/SetThreadContext_Indirect/SetThreadContext_Indirect.vcxproj.user b/Hunt-Weird-Syscalls/SetThreadContext_Indirect/SetThreadContext_Indirect.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/Hunt-Weird-Syscalls/SetThreadContext_Indirect/SetThreadContext_Indirect.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Hunt-Weird-Syscalls/SetThreadContext_Indirect/threadcontext_jumper_randomized.c b/Hunt-Weird-Syscalls/SetThreadContext_Indirect/threadcontext_jumper_randomized.c new file mode 100644 index 0000000..cc4a9f7 --- /dev/null +++ b/Hunt-Weird-Syscalls/SetThreadContext_Indirect/threadcontext_jumper_randomized.c @@ -0,0 +1,278 @@ +#include "threadcontext_jumper_randomized.h" +#include + +//#define DEBUG + +#define JUMPER + +#ifdef _M_IX86 + +EXTERN_C PVOID internal_cleancall_wow64_gate(VOID) { + return (PVOID)__readfsdword(0xC0); +} + +__declspec(naked) BOOL local_is_wow64(void) +{ + __asm { + mov eax, fs:[0xc0] + test eax, eax + jne wow64 + mov eax, 0 + ret + wow64: + mov eax, 1 + ret + } +} + + +#endif + +// Code below is adapted from @modexpblog. Read linked article for more details. +// https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams + +SW3_SYSCALL_LIST SW3_SyscallList; + +// SEARCH_AND_REPLACE +#ifdef SEARCH_AND_REPLACE +// THIS IS NOT DEFINED HERE; don't know if I'll add it in a future release +EXTERN void SearchAndReplace(unsigned char[], unsigned char[]); +#endif + +DWORD SW3_HashSyscall(PCSTR FunctionName) +{ + DWORD i = 0; + DWORD Hash = SW3_SEED; + + while (FunctionName[i]) + { + WORD PartialName = *(WORD*)((ULONG_PTR)FunctionName + i++); + Hash ^= PartialName + SW3_ROR8(Hash); + } + + return Hash; +} + +#ifndef JUMPER +PVOID SC_Address(PVOID NtApiAddress) +{ + return NULL; +} +#else +PVOID SC_Address(PVOID NtApiAddress) +{ + DWORD searchLimit = 512; + PVOID SyscallAddress; + + #ifdef _WIN64 + // If the process is 64-bit on a 64-bit OS, we need to search for syscall + BYTE syscall_code[] = { 0x0f, 0x05, 0xc3 }; + ULONG distance_to_syscall = 0x12; + #else + // If the process is 32-bit on a 32-bit OS, we need to search for sysenter + BYTE syscall_code[] = { 0x0f, 0x34, 0xc3 }; + ULONG distance_to_syscall = 0x0f; + #endif + + #ifdef _M_IX86 + // If the process is 32-bit on a 64-bit OS, we need to jump to WOW32Reserved + if (local_is_wow64()) + { + #ifdef DEBUG + printf("[+] Running 32-bit app on x64 (WOW64)\n"); + #endif + return NULL; + } + #endif + + // we don't really care if there is a 'jmp' between + // NtApiAddress and the 'syscall; ret' instructions + SyscallAddress = SW3_RVA2VA(PVOID, NtApiAddress, distance_to_syscall); + + if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code))) + { + // we can use the original code for this system call :) + #if defined(DEBUG) + printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress); + #endif + return SyscallAddress; + } + + // the 'syscall; ret' intructions have not been found, + // we will try to use one near it, similarly to HalosGate + + for (ULONG32 num_jumps = 1; num_jumps < searchLimit; num_jumps++) + { + // let's try with an Nt* API below our syscall + SyscallAddress = SW3_RVA2VA( + PVOID, + NtApiAddress, + distance_to_syscall + num_jumps * 0x20); + if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code))) + { + #if defined(DEBUG) + printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress); + #endif + return SyscallAddress; + } + + // let's try with an Nt* API above our syscall + SyscallAddress = SW3_RVA2VA( + PVOID, + NtApiAddress, + distance_to_syscall - num_jumps * 0x20); + if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code))) + { + #if defined(DEBUG) + printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress); + #endif + return SyscallAddress; + } + } + +#ifdef DEBUG + printf("Syscall Opcodes not found!\n"); +#endif + + return NULL; +} +#endif + + +BOOL SW3_PopulateSyscallList() +{ + // Return early if the list is already populated. + if (SW3_SyscallList.Count) return TRUE; + + #ifdef _WIN64 + PSW3_PEB Peb = (PSW3_PEB)__readgsqword(0x60); + #else + PSW3_PEB Peb = (PSW3_PEB)__readfsdword(0x30); + #endif + PSW3_PEB_LDR_DATA Ldr = Peb->Ldr; + PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL; + PVOID DllBase = NULL; + + // Get the DllBase address of NTDLL.dll. NTDLL is not guaranteed to be the second + // in the list, so it's safer to loop through the full list and find it. + PSW3_LDR_DATA_TABLE_ENTRY LdrEntry; + for (LdrEntry = (PSW3_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW3_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0]) + { + DllBase = LdrEntry->DllBase; + PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase; + PIMAGE_NT_HEADERS NtHeaders = SW3_RVA2VA(PIMAGE_NT_HEADERS, DllBase, DosHeader->e_lfanew); + PIMAGE_DATA_DIRECTORY DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders->OptionalHeader.DataDirectory; + DWORD VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + if (VirtualAddress == 0) continue; + + ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)SW3_RVA2VA(ULONG_PTR, DllBase, VirtualAddress); + + // If this is NTDLL.dll, exit loop. + PCHAR DllName = SW3_RVA2VA(PCHAR, DllBase, ExportDirectory->Name); + + if ((*(ULONG*)DllName | 0x20202020) != 0x6c64746e) continue; + if ((*(ULONG*)(DllName + 4) | 0x20202020) == 0x6c642e6c) break; + } + + if (!ExportDirectory) return FALSE; + + DWORD NumberOfNames = ExportDirectory->NumberOfNames; + PDWORD Functions = SW3_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions); + PDWORD Names = SW3_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames); + PWORD Ordinals = SW3_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals); + + // Populate SW3_SyscallList with unsorted Zw* entries. + DWORD i = 0; + PSW3_SYSCALL_ENTRY Entries = SW3_SyscallList.Entries; + do + { + PCHAR FunctionName = SW3_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]); + + // Is this a system call? + if (*(USHORT*)FunctionName == 0x775a) + { + Entries[i].Hash = SW3_HashSyscall(FunctionName); + Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]]; + Entries[i].SyscallAddress = SC_Address(SW3_RVA2VA(PVOID, DllBase, Entries[i].Address)); + + i++; + if (i == SW3_MAX_ENTRIES) break; + } + } while (--NumberOfNames); + + // Save total number of system calls found. + SW3_SyscallList.Count = i; + + // Sort the list by address in ascending order. + for (DWORD i = 0; i < SW3_SyscallList.Count - 1; i++) + { + for (DWORD j = 0; j < SW3_SyscallList.Count - i - 1; j++) + { + if (Entries[j].Address > Entries[j + 1].Address) + { + // Swap entries. + SW3_SYSCALL_ENTRY TempEntry; + + TempEntry.Hash = Entries[j].Hash; + TempEntry.Address = Entries[j].Address; + TempEntry.SyscallAddress = Entries[j].SyscallAddress; + + Entries[j].Hash = Entries[j + 1].Hash; + Entries[j].Address = Entries[j + 1].Address; + Entries[j].SyscallAddress = Entries[j + 1].SyscallAddress; + + Entries[j + 1].Hash = TempEntry.Hash; + Entries[j + 1].Address = TempEntry.Address; + Entries[j + 1].SyscallAddress = TempEntry.SyscallAddress; + } + } + } + + return TRUE; +} + +EXTERN_C DWORD SW3_GetSyscallNumber(DWORD FunctionHash) +{ + // Ensure SW3_SyscallList is populated. + if (!SW3_PopulateSyscallList()) return -1; + + for (DWORD i = 0; i < SW3_SyscallList.Count; i++) + { + if (FunctionHash == SW3_SyscallList.Entries[i].Hash) + { + return i; + } + } + + return -1; +} + +EXTERN_C PVOID SW3_GetSyscallAddress(DWORD FunctionHash) +{ + // Ensure SW3_SyscallList is populated. + if (!SW3_PopulateSyscallList()) return NULL; + + for (DWORD i = 0; i < SW3_SyscallList.Count; i++) + { + if (FunctionHash == SW3_SyscallList.Entries[i].Hash) + { + return SW3_SyscallList.Entries[i].SyscallAddress; + } + } + + return NULL; +} + +EXTERN_C PVOID SW3_GetRandomSyscallAddress(DWORD FunctionHash) +{ + // Ensure SW3_SyscallList is populated. + if (!SW3_PopulateSyscallList()) return NULL; + + DWORD index = ((DWORD) rand()) % SW3_SyscallList.Count; + + while (FunctionHash == SW3_SyscallList.Entries[index].Hash){ + // Spoofing the syscall return address + index = ((DWORD) rand()) % SW3_SyscallList.Count; + } + return SW3_SyscallList.Entries[index].SyscallAddress; +} diff --git a/Hunt-Weird-Syscalls/SetThreadContext_Indirect/threadcontext_jumper_randomized.h b/Hunt-Weird-Syscalls/SetThreadContext_Indirect/threadcontext_jumper_randomized.h new file mode 100644 index 0000000..7b85e81 --- /dev/null +++ b/Hunt-Weird-Syscalls/SetThreadContext_Indirect/threadcontext_jumper_randomized.h @@ -0,0 +1,63 @@ +#pragma once + +// Code below is adapted from @modexpblog. Read linked article for more details. +// https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams + +#ifndef SW3_HEADER_H_ +#define SW3_HEADER_H_ + +#include + +#define SW3_SEED 0xE331CC7E +#define SW3_ROL8(v) (v << 8 | v >> 24) +#define SW3_ROR8(v) (v >> 8 | v << 24) +#define SW3_ROX8(v) ((SW3_SEED % 2) ? SW3_ROL8(v) : SW3_ROR8(v)) +#define SW3_MAX_ENTRIES 500 +#define SW3_RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva) + +// Typedefs are prefixed to avoid pollution. + +typedef struct _SW3_SYSCALL_ENTRY +{ + DWORD Hash; + DWORD Address; + PVOID SyscallAddress; +} SW3_SYSCALL_ENTRY, *PSW3_SYSCALL_ENTRY; + +typedef struct _SW3_SYSCALL_LIST +{ + DWORD Count; + SW3_SYSCALL_ENTRY Entries[SW3_MAX_ENTRIES]; +} SW3_SYSCALL_LIST, *PSW3_SYSCALL_LIST; + +typedef struct _SW3_PEB_LDR_DATA { + BYTE Reserved1[8]; + PVOID Reserved2[3]; + LIST_ENTRY InMemoryOrderModuleList; +} SW3_PEB_LDR_DATA, *PSW3_PEB_LDR_DATA; + +typedef struct _SW3_LDR_DATA_TABLE_ENTRY { + PVOID Reserved1[2]; + LIST_ENTRY InMemoryOrderLinks; + PVOID Reserved2[2]; + PVOID DllBase; +} SW3_LDR_DATA_TABLE_ENTRY, *PSW3_LDR_DATA_TABLE_ENTRY; + +typedef struct _SW3_PEB { + BYTE Reserved1[2]; + BYTE BeingDebugged; + BYTE Reserved2[1]; + PVOID Reserved3[2]; + PSW3_PEB_LDR_DATA Ldr; +} SW3_PEB, *PSW3_PEB; + +DWORD SW3_HashSyscall(PCSTR FunctionName); +BOOL SW3_PopulateSyscallList(); +EXTERN_C DWORD SW3_GetSyscallNumber(DWORD FunctionHash); +EXTERN_C PVOID SW3_GetSyscallAddress(DWORD FunctionHash); +EXTERN_C PVOID internal_cleancall_wow64_gate(VOID); +EXTERN_C NTSTATUS NtSetContextThread( + IN HANDLE ThreadHandle, + IN PCONTEXT Context); + +#endif diff --git a/Hunt-Weird-Syscalls/SetThreadContext_Indirect/threadcontext_jumper_randomized_-asm.x64.asm b/Hunt-Weird-Syscalls/SetThreadContext_Indirect/threadcontext_jumper_randomized_-asm.x64.asm new file mode 100644 index 0000000..ac73ef1 --- /dev/null +++ b/Hunt-Weird-Syscalls/SetThreadContext_Indirect/threadcontext_jumper_randomized_-asm.x64.asm @@ -0,0 +1,27 @@ +.code + +EXTERN SW3_GetSyscallNumber: PROC + +EXTERN SW3_GetRandomSyscallAddress: PROC + +NtSetContextThread PROC + mov [rsp +8], rcx ; Save registers. + mov [rsp+16], rdx + mov [rsp+24], r8 + mov [rsp+32], r9 + sub rsp, 28h + mov ecx, 0123E5E95h ; Load function hash into ECX. + call SW3_GetRandomSyscallAddress ; Get a syscall offset from a different api. + mov r15, rax ; Save the address of the syscall + mov ecx, 0123E5E95h ; Re-Load function hash into ECX (optional). + call SW3_GetSyscallNumber ; Resolve function hash into syscall number. + add rsp, 28h + mov rcx, [rsp+8] ; Restore registers. + mov rdx, [rsp+16] + mov r8, [rsp+24] + mov r9, [rsp+32] + mov r10, rcx + jmp r15 ; Jump to -> Invoke system call. +NtSetContextThread ENDP + +end \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7e8d02b --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Hunt-Weird-Syscalls + +This project aims to identify (some) abnormally conducted syscalls by leveraging ``ETW``. + +``ETW`` providers sitting in the kernel can effectively be leveraged, as the calltraces of the emitted events contain the address from where the syscall was conducted. + +This allows monitoring for two techniques often leveraged by threat actors: ``direct`` and ``indirect`` syscalls: + +1: Does the syscall come from an unusual module? (=direct syscall) +2: Does the used syscall stub in ntdll match the appropriate syscall? (=indirect syscall) + +This project uses the Provider: ``Microsoft-Windows-Kernel-Audit-API-Calls`` to monitor for ``OpenThread`` and ``SetContextThread`` events triggered by the syscalls ``NtSetContextThread`` or ``NtOpenThread`` respectively. +Calltraces are enabled, using the flag ``EVENT_ENABLE_PROPERTY_STACK_TRACE``. + +## Tests + +This project containts two sample programs using direct and indirect syscalls created using the amazing [SysWhispers3](https://github.com/klezVirus/SysWhispers3). +They were generated as follows: + +``` +python3 syswhispers.py -a x64 -m jumper_randomized --functions NtSetContextThread +python3 syswhispers.py -a x64 -m embedded --functions NtSetContextThread +``` + +Upon execution, abnormal syscalls should be identified: + +![Identification of Abnormal Syscalls](/Screenshots/1.png?raw=true + +## Credits + +- [KrabsETW](https://github.com/microsoft/krabsetw) +- [SysWhispers3](https://github.com/klezVirus/SysWhispers3) +- [etw provider docs by repnz](https://github.com/repnz/etw-providers-docs) \ No newline at end of file diff --git a/Screenshots/1.png b/Screenshots/1.png new file mode 100644 index 0000000000000000000000000000000000000000..c18f67eea5b31712c1c6d6cc554a0c0a466c8604 GIT binary patch literal 14657 zcmd6OXH-*Lw5|;i!9>9$AWgc8f`AHy5_&|DA_yXcD8(qfBt%MxiV%?!>OqhYiqZ{I zh0qk~AVo^3B1KB*Jp=-8!#Vf9@!lJEjCaTT^TvRkwv#>AT64~Ce&3oq*vJ5O>g4&8 zhYlS&rFTpF-l0QB(}3sr6UTw$cp)JLcsT5N52ksD+{Qlxyf}u`xT|sKP(Fcu=P?WL zn$7K&h3BC|pzr(thZ|fo?G7DM?A6oOFnw-4Pn0bXQ;JIReo>}9@|fJA@F4yP`h{$E z8bLiRmg10S%#;18M2$1>hIPRCypE#0-ratjpUNRZYh$Kgp8sg!i9;X^{^%PwyC*%{ zGC2BoA%C#2RSRN|L3Kqro3F%G@BG=UX3o2gI5B4oaSryH^ow??_|rAaoxHuR5&u2h z-X?H_?rr0CcGs)-sAdwc%_F}*Jt6%1v?g#EYYsx<+} zOdQS%Tm+ekRCBab+5na~zW5!NeLVrEcV%54~s0Xs9ri|!XFHEcFt^)0D)$=4>b5?J)3=dT*6K!&X?8!U7U6GOzrp zr5r2|o!26-ddC*ogzVm~$`OdTN`|SKWCVa&Rhszw=Obi7UskQKKh+8nODX^fzfcw5 zF4Ch)2d$+gCT#+(m1b+FMQ zw=SqQ5tD08^sdlf-j(b7sUUhgGZL)?W6Nc{a4O?(x=g7Ac`;Z#Zk!1swu{xqFh?je zP}}&vPaXIw`1`Mtc57GD{S*tRbrDCe%C14yyyLx~iib)ps}L+W)h|z;iVUz<*^#lAhZV< z{hj#aUF$Dz>zb)KlLv7iJk+vsty#l#Br4L$Lwa(7q@eOX|gPHg? z^Y!@Mo+^VNtmoh>ra|`6hW1_zbbb8ajg7Lo7&GD^{$Fbj)nUpQo}J>2xa&q`|~@ytV5$p@HsKZ?jTe8qg<3igZ8vpFOcEe$k$pdtbyct-xble ze`4Xpq9!O!CMWJqXX7`g^+j1IAHS{;Go5x@j{6^6r~oU4R`Kj*2}2zxrrtmY zM)CQiEw-tgMyr4DWJ&G6hLfNJ>^ZGjO61o)$ZmjDLk6HU=(U}zwHSu-o5e}P6Qtm| zy#4bk_*~IEe+a)uVy{#-_m=Op=PUc~ZcVvX?asU690wN@+55J*{8i{9gcQeZ>!C^+ z^4Rc(@4m|%8S>!y(~jKF<(48JUT1C>p64HXx=Ar#zHB+Ya{_Pt+tqo+S&!;9W%VW& zI@&NU08SgRMOf99&u<=B*!eid@Q0pRsn@)6%=9$UCPpb@cOPZ{R4m-DXg&w&94^L+ zEhL7VhIsH)66OxR+hqvBLP6H6nM$-;Pk?+EW>;e_G;jPt6m1_HN+^lH2Y$V`!yj zyZV!w5HZ$aNPK9{dvWMf3Pr8vop<%d{U4bhd zxC!}*H;=fxu&}WvtU!yCmAG@}_RcM@a<8?YlOC^b#(fvMFsj)bEya1Aaq%$=r-pdj z&jvK|j^26m0n?3IPNo>H7Voq5I@iuC`Mug&?5VYs_r6WfK$$QDRxsmE&`p}Ss6bM! z!%jg+=s*RQn?!_0m$~_NY#r^Muh${A2iiqo;N}-_@8oytkC&h>z7K~pODTc~+w3mJ z>EVa)CVh2lZ1LaA!}iy`$c^qg2A<#g;_-}7qlyX_R?4s%CI1f|;g=ll$o)NMrxSwp zLg@F0v9B$vKT8X}J16_3snIn8;jp?f{Lf&mzl}bQc^zaIrX4P(6EXK(PVq2YY;tXi zhCcygGpF``Yxj@boM69Ino0d4EOtL8w&+{ix$#`KD?YJbZ)fcBO%ieFjr2XH~3u=v@+lAFpDMRVNYH@Hn8ILdQj zlUe=&h?T;~Yj0JrK2|bPME-F>b@wDQVp-8q4b^AjMDrhcDFCuxn-Ua2|1hSGs#*U5 zXS!047v0^c1FfM@KGzM)!E6M^cNeRJ6q5G{a4!qdPGMoOlyUWkT%TY@9uLr;I7I~B z#wH$NMsC{i#+3TN?GsE>2Rpx54Pi>Mw7yV3MF=vclNdCi&2q80?F1+CPRe}e1ZwQR zHrmWl-|g0ffxd{Cz zr<`!HlTSs3KP34w%1-4MurOvsZTF4>a}SB~^%5g7Ga;p;=*S{EC^9S@({x+)g$< zcf;MwtmMUnVUyG&zV{AOf=mZ3A!=Ga`l8+kz{#l(tp0|C2~{t*4=#TUH>bdZD4<2w za(eDgd271v|-}HR_hmArBK1|8U$B|^)SIr;t;&zJ@g=qMxg+Q^E;`?@6mS%B*+h;1YEq( z#mq(B2BkGjOJ>(*Y@0D$e*GXFAlee?zlq9i8ET;`7}^Sw{{RvIYRIH7;XBjzc<yh3;F!V1aL|(zVlgdmBslIdQGYL|5aQV}M_9&j7 zl+n%){d;pko^_+PAMw@VO<&UBgT&uL{$9=|ba`<^wLjCVRF|9DLz3D0^_zQE$1&r5 zT5zkpO7`m8t%Q}!mhH7BYWc2Zcnlml>iy0#NqpC7)WXv&2z+Ht!T%*#exG`Q+n$(y>-F zO){*9y8)Y&Oh-80=lpT;8YlzN0PD5wdX!h9&hY@1BZZdzzPaXY+nLXr6tY6%k^-b;WD@h}Y_k{s4gO z_^Ipg*?w|X{;V}}p1uBr32!Fepn1&~kH|O}gsOKAkmR_{Ct|F8S9ApQ1kqxFVy_x6 z$nbJrSZhHtGEd#geJUE9daC22omo|rIk`J)j_vYh_o%}B^jBzeQ46=gi8AgR?t`Nc zidTK0^F)Op;!Sk(c~<(~RX1mtW>`nvQQ|yGsP<7jc!pfXvhNjgq2@@7(u1{$(?u5Lb?ZHCw43 z7GcW?4vQith_MZH3>IICyq9{F`BUeW1=wp%Sjja07`M_E$MmY)(;;ijIg|}p)vCOf z3m2Ty{Fcz-Fw_G>LZo3MZ78{Jp&ya&8u?}6e9=mQ!+yhMH@!*(`?&>H(vJE{=iGQL z(2QPuIu~VLUO$TG!G))1 z*YzFpW{jH0X6!xXdf68E>=J1S2JA=C}ki06KXNe%IS`_av7iB=P6U`+j>QTLX9l5d!eiNux*2DEZVJe z$AS4sYqMMG98KXc;!Pp-aYnD|Y@wZ(Cj@S5@P$=MIo@SN^~iXyBB6I8l0(e_v>J9G z(@BJa(!}hpjR~b=LPJR5A)X7^K{pHzV@+0<*89fudr?- z55^g%Cohu#){pr&eYUHSZm`w{Vy9TA{mLHGCHpAp?`$_)Z=^&OatFZ zKJYQ&3I(N5e@?~cE6#7LB1fyD*+=F7)m>{E#POYmul#Lp+5BRjz5crW7AWlitAyhI zS2}#~CU9^8DuRFDQ}e`^=>J;0-95~7_w8h+FoRormS7u;jPfkZK*CaI{qC7v{9t+S zyz)}^`l=Jh%=VybHIu2d0;sfKJV?QW(&}IH-aIu@Wga;D#i5o7qQ_*V)^^LX|1=QW+FNsam`Q2NpG!PY+Y zw%J6f@rptg`gV&<_0BL(&?V33kp52D+t{nc0x3#D$>`J_^>w{z<_pel|om z@FGLMB1&p~g-n z;n|?tjcL}d*Eq);o3T9`xo+Z&-fkHrWcq}UJ=k15ww6s=Q%lVd#dmUlpOAfw+g&A9 z-#u@Ct_Mc9Or}>_?f~wz#(I-Tqx{HK^dWlMCt^yT(>r!&^Q!&J8F5F23VPss1s8J)0MqtDiUI5wKsVoY#o znrx=ZFoKXva=1gW54qV~WvHAjSZ7?8ued!b<2kkQG0rg-`+9%XT^>7h_1?|4L$C)) zv4{T9u1v1p9Jl}W#8{bq0PT4AFy*sn(Z$GRonZjgHE>%}P?aYGF9cjL+(-?f$_KpF z4XzG%z(wc3JS3Ow=zvz@<0|Lqk}W1UG2iRbJ2eO+0kGkOg%Azl{KkRZ44gV#>UYXU z2=S-9<(q@mHH-Gpzi<&6+>p}6`r`3^MJ;Yp+v0HFDers(ubrriyqSkl0mTFtHlN3D-=~6QAE-7EmL7~_dSutV zk1()^M=<{!m40PqRZR0me?fM?^fv0d$vc0-gY}C6*Ex~>y=vipI!W>~@l&=hGQ>kb z^0W-0Upna8vUe&#f;E;c=4EKAL1+@Ea;Ymct*lSlaZEm`e)2I%CWmu{<@ZZ3vfw zb+8IF%Hh!PTVK}n*A1E;uho4%Va{cyko8EGo@$$tjE5`)x`Lu)uZj)yaZIOQ;hpH= zhBs1KWS(k~9k~EMrL}A6g^VZ&?{X>=(w6cU$2j^TRVPf%IF}AB*7CaK~O6~^&`oW(hLy1h|2W;!zdF8T49~eSw9R> zH>B3P;M_lklFoy%5mVM`!4%zg5xxS+vr{ux9YKW4j{H{lVdZ&3;^EVOw;r2oHvEw> zn8Z+dx!X5t?HP8}P*>6yxgQ#Jgy)%rNzE17(aRWpyCI>@e$UK|N{JSqCoh`O0%RxI zNrU6??wmO}IEONXBi;?<%JFTok<&J@So~=y<&4gM$ma91Wr5R~=Xd5}=eqQTKwb?Av=FXz$ zKydJyky;vTTvurQ_xN#IH`qIN`0roeF5i(c?@`s-*HLw9kK6q$^?Ltar}@=A3-%#< z3sm>BG_T|l1^Hyh9vFK?bV`zr#Vj$n_2fD-*T+cK%uUG|l;dv6c)qg{Kl6O)LdGK(KV zkxs&Cs&zeJyou^V_F97~V=s93%h%hTI_lFmC?~NI8u)YEObUw*c9=i*pLriDjGuVla!JCPn*UZFEr4{o zt8hUM?Kw^uq}A(HpKl-Tdp4U%(a2YNDA}c$H+D^cTTaTd2wl1`?4!04O`)}e`t!Q; zYc98~^vpU8!K=ra7Z*2_Rm%Ozp(%aqbvNaJ8n|QtvBJv-Of`21EMA557>QusU$8MO zZ}}=Zo>0Y}6c*pkvQa8&-8Z);I8O{tQntT*`BaB5`6VnaZKO_2?ag>owlmoE?N@4Bzt0wBDerguh;GNM;cqdwgz#8>e-^U zqbv0hv_rgWsP;^xk}@}JyV9em`n8jn^VqG)r@Lm5jBWOgm(mLvB}LU8hLxZqnnKTa z{%w`Q^apqRAEg`O2>}Jwo`g9lAT21QvE>GzW!EBUepX^~v3+}GBl@#GRU7=?YMy9~ z*SQ6C%70M&m2mFtdR3;fgq&0bwCbyiyy5g%uADOn{MTZ6fp{h3_wP3jd}{?}WqguM z2NjHM+^6BvL%xkds`LzZGyc-vl9|$p5UNtqr}`Tf?N-WAWt`XCj=P!r!kEdpTD~}M zEFqI3rEEDQ`n`BVnNKT!RvFG`J+*yD%@!!M7j2d~%BSS@Cu^v>+}ke9&uwj?v#sU7 z-@fYH##<0e~Q4_3qi)jkEGGfEYZZf&a!6B zUsdPTT#15C82^;<#;M>W`4ThKa@KZ;Z`?NwP37hb=&!{y(}r{XInz^Hublq?deF)Q zFPdeV*v7Knh}j-i8bG8LS|~*+B~-`m#54Z@xg$jBqm^w3r}X}v#1KqSF{a@uXP&( z99jE`W*A|OzFT7+H?wi+sa5-eK>Z6vcHttmapl>3c-sd>yA7)LrMc8Rav|~R%Tp76DmDtzJE^2z&}JXnyD#ic)k3elS07hV7ea0Y+1w`d zuGRuQt-)HbVj;(68KpF^Xrqw9Woce##J^rAv4L^4{Zd`p9JG*?k?V`^GeOuY)QyAb zfmIZYmuE_8D)>VKRUTc7cOT80te7%Mhk(sJmIu2)1#E_Q4mQw?}i zRJ%V3)gLbY72lXF#Jls@`Px)ZI_&{hqf(RxzWpQWrHyKS-Fd)M2O_e@N&9Qmvs(OW z8GsKIuw84FkC^~_T^tW-@S()b$Ox&cVRkzRB@mK{gr{u(qD^u2>sg46c$#;8eW$WZ zfte%_0xk9(I(%}iKX_H?0UVxVK1E6D%O9bejqbn-HFn?~ss4Wh1twA)BGCZyn6RvNt1I4v)K83T8Y@ zbo6_}Vw>q9W%>1Zw_h-=u;DzsZ^LWCQOHiY8nc^QU-zAOGeQN-Yey+ADP+lE?R2rS z`e{P0{ro%-RlqS`J3pBGYNa5s>G}FQfte< zpbrDec__rUEpR1-l#1FkZG&n@dW+lyt&q*VuxfroFyF0Kd0PLJaH%0$x@}S{hbjIP zdYeKhcy3hr&vxwXS5w@o%@>CSuyCIZmp4h75*KpEa{{?4NH|Nv3_z1PKu&MsA1J0c^COTSs{$+{+oG zX+x-n55@qkMl+iLr}bC1N(+&~-HBls?D_^ETEiwQlzHwR{IGc|HO9Q(M7_jp5J zry>SRmbM-dm#&QV`xLpX&GoLr==ihKkA|1@Q}42x;C{_=OEDshe9Dp{53c5BAfQ`b z!nU@q9jyJPme(|6!nzxJQby>7U2v@){voJTI!7$*rW1-43aSx%L^gpw%dJv`*L->9 z-kq;V5BkOMyj`XG(OOT`+(}+=Gb7yjhV+tRs^j|At8(Sb26ko}AUDEskHl2hBBh7TRh5fmx`q6%TK_5Rn`iI)LA3)LY+iDE zniiJdTfzppa@nl!lbnNPf2NS!y>tR7_Qf7Kqf7bUK~%LoJ{$AqlCap}nsBF{qx^b; z&SEjQ?ewn2wq73n7CvX3Q6QN)WCE+Xr3|^Hd8tbpfw}+;wAL9vJ3YY5T@tzK<0t&s z%wjw_+;?<$uAZeaM}`uSmw&EEm|7eRByXp2(cmk)Et1rxO4^&W=7g>pzxlPVfSzvS zD7PU1(3KFxWk=V$o8cg?qVc1)8(!p?;^So*;FY1447N25D5XOROV{4MulC)=Kx3nf z0#!p~chPke7NvMoZq)KP4Dv3SR|yr%KH0hl9{p=zW*l!^2e+SDS2X;m10!+Kiw7=# zxjWjb*C#p23tCI@=WO-)oCa=6X%;JOj9khFVXcp&QU_<-UKwD4`sR+xa@@oNd+`A2 z9Cvde;!G(&_F_kq5n}3-T%?kw=agPv{Q^YN^JMuJ#nXAt>25=nmOtuIyM1xcXRWdY zfiU=r168pI8M%q9kv=CtA9ECZbN7>PFEY;J$BavrsV?S=d98N+OcD;c?49L2Fj9MH zgh$dIYG;0(RC^D-rh{)TnA!ag^MF=ie z8}_4lvE@fYS$GZ4y(}3noetoDsH+M5&?0_m)}DNmcEbpL@BdMSzM&>ld}`2zggnW5 z5BgP^|L2x^ay>g!aLR>g@b44@-5Yl4cTnl5U{2P&H0?ki!y^K;XNv?;tgYI#noN2*X8dZ6{I98E0X?~ zig>=ZF?Kd5#$Y^8u4(knCI$;OJ+BT&I=d4%r~`|g&lea5o8eUK-`}TQ48*^eCQ|CX zeBCa4iYJ83P?Qp6)_%(@ulm>+ji8pe*0h|hBt;O3)c>#-!TPbJ->6wkPA?(F# zcgM<01a3d#_5HNEE*EbC1Re389U}QT#uyl2AlJNh%;NKf^R{{z-+-#Y{7Sd$k*aPc zf%tGUw>_%=#|$b=X;?6K&W?h`oFl~z=wg|F7uNF%-M|kkH2UcBzhnmM_weoOwbvk_ zQ56wJbD&$WjyP??YrGv^JCi1QM)I+-jn0uQxp}epA(Om`AT^rIn!HjJbRlw_hCkl) zn|1d$clYv)n|kVHUZ{$GN9m1o3!P0pe`2&9{eW#;sD>V8yO1xg{=458`+BV&T2M4X z=4V~cV1}I3G@+XQYHx)V9@SZDw(sc)(~+%(|AegkK|OTCdlmbM`0|CNN%K)zH6rX}gcI|Qs_w^N>z6gt9ub9qK?&$htJmNJ&3z;+8T?3vTXmZ%CBD+ZoRJ-(gbWBITo;H zY@bcSk{#wvRSasmNM}hd*q+_+7Nvx}*hQ}ReW%*#3&o}LS%aJ0qrNPOCVziQJ$E3r z`*Z9AwcFCfdv+kTU?sV0kRtyo%n<u3+CG{@UR!T3r$nFR0!kZST<=pe4XAt(zp!jD~v=r;M}FMd3c{H?Pq-`yl{*l=w0^XBPx)mvx^1bt z4f{ox{OGT#oU)p~ZA9L5IS6#M9c+9L@lHuSUR+7Em4Oxzv`&m(-nZPxct%nu#+#0N zU47~82z_uLwT?x}S%<<0VcH{dXZQ@&+x<_fc6!EfpktJdT@ZcH-bz~QAd8jPq#!RJ zU1^%nW}d`}9hI_F=N&E*HrY0`e~SEM+de*5lHLAustFV8Zbdx?`gUPW`rhu##?sI39kBvmx}h}De1|X*?PH7(sVH6l1d5oZxK#Qb zg7+!iR*(GlgeoB+#}&R-S{)j=d|6NUvAYE$CoJEaQ}o{|LjC7mKBmLqvAONh1fFre zWwUs%IALL(<0GbDx;!&NZKhXWx8Deg4ye)A-FU-V#b1mVZErg_{k5YXt6ksH*c^nWs2gZAS=iBJDq~ZNuKM0h3#PseKfqNP>6Q^ zF=&~U7nKs=D-^e3K5yC5L-OGn3}fmin%g$W?5rb?jB8uBXUdm&b*uF?NE*S5Vf@|4 z&D)f7e`T153mmlR>DT|cFuV8?n2!Cu6ArMq^+~uUONAXJ?y$@mT+?(NOklHy%hEd0 z=Wu6z3-@r0ct8asm3@n$$^CDUa)z#H6{CLoXzY58q*{zOn-rUE;_cd}TH)N}`d84< z{=u?)W;>+^HHl=3_h4lsAvtd>HUCjdG#d>b?zQuzH44x+?&vzgWLX3T#PigWJsPxw z%50bpr(Wc3N-_KUJ)W*UK*BWH|5j?WdBjxa>JjzM9e~#ZAB4vb0*f7L@#_Fl9lv! zth+eybdA8oHHOV|<0YGkz^}!3bf3pBN10g22*mxd*C19uzdW5MBZ>ONn6@8jGRor~ zIl~n%x|TYZDFX`|iRNrB_>_OgdcO8=IhiL-vq?T%$^IY@I1^-eKIWKg-1&f(X8*f? zrvpmFOr|?4!w4@umCNC!M=QqFUd~ToeDAZG484lDQ+yYg6}d*ABRQq)JIFkS8nM=` zxy^j;@=YtpokNJCkz;C{=6T;iJ{4pw*|`m2A7(t;Fwa?7ufiY3&^yLlH!>Z?z_V46 zF6Pybb*}@{lAPb7X8uLo%N^+OAC}K+X>W0>>&r_B z$r8kSO%DLAHUP7#WQ$TToZJaa=O%NiRJvK|l71eM0okc|vkW}f^M(hiHBPgLk;1Jv ze7uEZBjZ1J1>`lNfc^k1oBYj6yT5D?AW*83@Vg}D`ZXNPwu8UB>^-1(rKi3gjCM03 zHCymD*XqtDGSv%Mq$S(vTErG3h2lT?+0Hg1N9nEKQrlE_%LsHGULm;dxO=z%amKt~ zV2@`j*3BU|U#pUez6ivSWf=H^WkWKLH&F{{O!%cLPuTfj0Fdjk3xz)6FL&H{_XnQ- zDUEhKtc+|TOYHh8Po2d`w5x-&@rPF@9lUzaetI zUYd82>fY0r_bx=( zSTcx@s80}tp`(9%NBZ?O4FD4h5g}!XKDxLyKrNK zdTZ$Q%!@&2Lv@7>>0gaTi|DqtsWvn$F<)gt*8i#l0o{&Sg5UZ&#@)ffugmxM zw3cjsYqZ}vx+a$e#)%7594hPOz91vSI8o`OdU(w89_{%{N(HFocEl~Gr9hu3qn@6) zEWb!;@-Ma6_!l4s)yJ>KynnQ6JI0(Xb?nC-iw4L@7HS7T`r7Z09OH8g?V_QyEsBl+ES*4XfkRxCs*l zuTA8BmQ=w$vB8(65O<@N=@;u8J!CA2p<{58?PtF7zB+FOFRJixpAG^Gy*q_sVcu~0 zgrU6&w)-4FQs>Jzjo-+b9JXINin}ho$(n9Ul~38g2Lt`l@21^50{ytt09XmxhZUI% z);uap8Zx;CW^4feNayZ10Lutgz6q?}nNEvS@$mU}P;~whI0C>dWLuqx3Q2fVmRlcW zSgYw=5t9Ye-K3@Y!dT_)90y}!p<<_Ng-l^JFZ_smu}O=Y~95^$T-j z73a6(k!STMQGpEaJ;>%;ch>pZ9@NDx<7P5@#W~=%$3uEL2HIrJ HN3Z?|{FpFA literal 0 HcmV?d00001 diff --git a/libs/LICENSE b/libs/LICENSE new file mode 100644 index 0000000..a369f2b --- /dev/null +++ b/libs/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; + } +}