Repository: thefLink/Hunt-Weird-Syscalls Branch: main Commit: 166a45203b85 Files: 63 Total size: 305.7 KB Directory structure: gitextract_85lvkvnd/ ├── Hunt-Weird-Syscalls/ │ ├── Detectors.cpp │ ├── Detectors.h │ ├── Helpers.cpp │ ├── Helpers.h │ ├── Hunt-Weird-Syscalls.sln │ ├── Hunt-Weird-Syscalls.vcxproj │ ├── Hunt-Weird-Syscalls.vcxproj.filters │ ├── Hunt-Weird-Syscalls.vcxproj.user │ ├── Main.cpp │ ├── SetThreadContext_Direct/ │ │ ├── Main.c │ │ ├── SetThreadContext_Direct.vcxproj │ │ ├── SetThreadContext_Direct.vcxproj.filters │ │ ├── SetThreadContext_Direct.vcxproj.user │ │ ├── threadcontext_embedded.c │ │ ├── threadcontext_embedded.h │ │ └── threadcontext_embedded_-asm.x64.asm │ └── SetThreadContext_Indirect/ │ ├── Main.c │ ├── SetThreadContext_Indirect.vcxproj │ ├── SetThreadContext_Indirect.vcxproj.filters │ ├── SetThreadContext_Indirect.vcxproj.user │ ├── threadcontext_jumper_randomized.c │ ├── threadcontext_jumper_randomized.h │ └── threadcontext_jumper_randomized_-asm.x64.asm ├── README.md └── libs/ └── krabs/ ├── LICENSE ├── README.md ├── krabs/ │ ├── client.hpp │ ├── collection_view.hpp │ ├── compiler_check.hpp │ ├── errors.hpp │ ├── etw.hpp │ ├── filtering/ │ │ ├── comparers.hpp │ │ ├── event_filter.hpp │ │ ├── predicates.hpp │ │ └── view_adapters.hpp │ ├── guid.hpp │ ├── kernel_guids.hpp │ ├── kernel_providers.hpp │ ├── kt.hpp │ ├── parse_types.hpp │ ├── parser.hpp │ ├── perfinfo_groupmask.hpp │ ├── property.hpp │ ├── provider.hpp │ ├── schema.hpp │ ├── schema_locator.hpp │ ├── size_provider.hpp │ ├── tdh_helpers.hpp │ ├── testing/ │ │ ├── event_filter_proxy.hpp │ │ ├── extended_data_builder.hpp │ │ ├── filler.hpp │ │ ├── proxy.hpp │ │ ├── record_builder.hpp │ │ ├── record_property_thunk.hpp │ │ └── synth_record.hpp │ ├── trace.hpp │ ├── trace_context.hpp │ ├── ut.hpp │ ├── version_helpers.hpp │ └── wstring_convert.hpp ├── krabs.hpp ├── krabs.runsettings └── krabs.sln ================================================ FILE CONTENTS ================================================ ================================================ FILE: Hunt-Weird-Syscalls/Detectors.cpp ================================================ #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 ( ) ); } } ================================================ FILE: Hunt-Weird-Syscalls/Detectors.h ================================================ #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 ); } ================================================ FILE: Hunt-Weird-Syscalls/Helpers.cpp ================================================ #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; } } ================================================ FILE: Hunt-Weird-Syscalls/Helpers.h ================================================ #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 ); } ================================================ FILE: Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.sln ================================================  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 ================================================ FILE: Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.vcxproj ================================================ 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 ================================================ FILE: Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.vcxproj.filters ================================================  {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 ================================================ FILE: Hunt-Weird-Syscalls/Hunt-Weird-Syscalls.vcxproj.user ================================================  ================================================ FILE: Hunt-Weird-Syscalls/Main.cpp ================================================ #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; } ================================================ FILE: Hunt-Weird-Syscalls/SetThreadContext_Direct/Main.c ================================================ #include "windows.h" #include "threadcontext_embedded.h" int main(int argc, char** argv) { NtSetContextThread(-1, NULL); getchar(); return 0; } ================================================ FILE: Hunt-Weird-Syscalls/SetThreadContext_Direct/SetThreadContext_Direct.vcxproj ================================================ 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 ================================================ FILE: Hunt-Weird-Syscalls/SetThreadContext_Direct/SetThreadContext_Direct.vcxproj.filters ================================================  {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 ================================================ FILE: Hunt-Weird-Syscalls/SetThreadContext_Direct/SetThreadContext_Direct.vcxproj.user ================================================  ================================================ FILE: Hunt-Weird-Syscalls/SetThreadContext_Direct/threadcontext_embedded.c ================================================ #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; } ================================================ FILE: Hunt-Weird-Syscalls/SetThreadContext_Direct/threadcontext_embedded.h ================================================ #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 ================================================ FILE: Hunt-Weird-Syscalls/SetThreadContext_Direct/threadcontext_embedded_-asm.x64.asm ================================================ .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 ================================================ FILE: Hunt-Weird-Syscalls/SetThreadContext_Indirect/Main.c ================================================ #include "windows.h" #include "threadcontext_jumper_randomized.h" int main(int argc, char** argv) { NtSetContextThread(-1, NULL); getchar(); return 0; } ================================================ FILE: Hunt-Weird-Syscalls/SetThreadContext_Indirect/SetThreadContext_Indirect.vcxproj ================================================ 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 ================================================ FILE: Hunt-Weird-Syscalls/SetThreadContext_Indirect/SetThreadContext_Indirect.vcxproj.filters ================================================  {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 ================================================ FILE: Hunt-Weird-Syscalls/SetThreadContext_Indirect/SetThreadContext_Indirect.vcxproj.user ================================================  ================================================ FILE: Hunt-Weird-Syscalls/SetThreadContext_Indirect/threadcontext_jumper_randomized.c ================================================ #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; } ================================================ FILE: Hunt-Weird-Syscalls/SetThreadContext_Indirect/threadcontext_jumper_randomized.h ================================================ #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 ================================================ FILE: Hunt-Weird-Syscalls/SetThreadContext_Indirect/threadcontext_jumper_randomized_-asm.x64.asm ================================================ .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 ================================================ FILE: README.md ================================================ # Hunt-Weird-Syscalls This is a ETW based POC to monitor for abnormal syscalls. For now, the syscalls ``NtOpenThread`` and ``NtSetContextThread`` are monitored to identify IOCs indicating both **direct** and **indirect** syscalls. ## Description This project uses ``ETW``, more precisely kernel based ETW providers, to monitor for IOCs. ``ETW`` providers sitting in the kernel can effectively be leveraged, as the calltraces of emitted events contain the usermode address from where the syscall was conducted. This allows monitoring IOCs indicating direct and indirect syscalls, a technique often leveraged by threat actors: 1: A syscall was conducted from an untrusted module (=direct syscall) 2: The used syscall stub in ntdll does not match the conducted 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``. This is a POC, and only monitors two specific syscalls. It is of course possible to use other kernel based providers to enhance telemetry. ## Tests This project contains 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) **Tested on ``10.0.19044``.** ## 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) - [@OutflankNL](https://twitter.com/OutflankNL) for ``IsElevated()`` - [@trickster012](https://twitter.com/trickster012) for testing and support <3 ================================================ FILE: libs/krabs/LICENSE ================================================ 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. ================================================ FILE: libs/krabs/README.md ================================================ # 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. ================================================ FILE: libs/krabs/krabs/client.hpp ================================================ // 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; } ================================================ FILE: libs/krabs/krabs/collection_view.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/compiler_check.hpp ================================================ // 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 ================================================ FILE: libs/krabs/krabs/errors.hpp ================================================ // 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); } } } ================================================ FILE: libs/krabs/krabs/etw.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/filtering/comparers.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/filtering/event_filter.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/filtering/predicates.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/filtering/view_adapters.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/guid.hpp ================================================ // 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]); } }; } ================================================ FILE: libs/krabs/krabs/kernel_guids.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/kernel_providers.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/kt.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/parse_types.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/parser.hpp ================================================ // 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); } } ================================================ FILE: libs/krabs/krabs/perfinfo_groupmask.hpp ================================================ // 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 ================================================ FILE: libs/krabs/krabs/property.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/provider.hpp ================================================ // 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_; } } ================================================ FILE: libs/krabs/krabs/schema.hpp ================================================ // 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; } } ================================================ FILE: libs/krabs/krabs/schema_locator.hpp ================================================ // 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; } } ================================================ FILE: libs/krabs/krabs/size_provider.hpp ================================================ // 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; } } ================================================ FILE: libs/krabs/krabs/tdh_helpers.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/testing/event_filter_proxy.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/testing/extended_data_builder.hpp ================================================ // 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); } } } ================================================ FILE: libs/krabs/krabs/testing/filler.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/testing/proxy.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/testing/record_builder.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/testing/record_property_thunk.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/testing/synth_record.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/trace.hpp ================================================ // 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; } } ================================================ FILE: libs/krabs/krabs/trace_context.hpp ================================================ // 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. */ }; } ================================================ FILE: libs/krabs/krabs/ut.hpp ================================================ // 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 */ ================================================ FILE: libs/krabs/krabs/version_helpers.hpp ================================================ // 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_ ================================================ FILE: libs/krabs/krabs/wstring_convert.hpp ================================================ // 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; } } ================================================ FILE: libs/krabs/krabs.hpp ================================================ // 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) ================================================ FILE: libs/krabs/krabs.runsettings ================================================ False MTA x64 ================================================ FILE: libs/krabs/krabs.sln ================================================  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