#pragma once #ifndef DIRECT_SYSCALL_HPP #define DIRECT_SYSCALL_HPP #include #include #include #ifndef SYSCALL_NO_FORCEINLINE #if defined(_MSC_VER) #define SYSCALL_FORCEINLINE __forceinline #endif #else #define SYSCALL_FORCEINLINE inline #endif #include #include #include #define SYSCALL_HASH_CT(str) \ []() [[msvc::forceinline]] { \ constexpr uint32_t hash_out{::syscall::fnv1a::hash_ctime(str)}; \ \ return hash_out; \ }() #define SYSCALL_HASH(str) ::syscall::fnv1a::hash_rtime(str) #define INVOKE_LAZY_FN(type, export_name, ...) \ [&]() [[msvc::forceinline]] { \ constexpr uint32_t export_hash{::syscall::fnv1a::hash_ctime(#export_name)}; \ \ return syscall::invoke_lazy_import(export_hash, __VA_ARGS__); \ }() #define INVOKE_SYSCALL(type, export_name, ...) \ [&]() [[msvc::forceinline]] { \ constexpr uint32_t export_hash{::syscall::fnv1a::hash_ctime(#export_name)}; \ \ return syscall::invoke_syscall(export_hash, __VA_ARGS__); \ }() namespace syscall { namespace nt { typedef struct _PEB_LDR_DATA { ULONG Length; BOOLEAN Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; } PEB_LDR_DATA, * PPEB_LDR_DATA; struct UNICODE_STRING { uint16_t Length; uint16_t MaximumLength; wchar_t* Buffer; }; typedef struct _LDR_MODULE { LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID BaseAddress; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; SHORT LoadCount; SHORT TlsIndex; LIST_ENTRY HashTableEntry; ULONG TimeDateStamp; } LDR_MODULE, * PLDR_MODULE; typedef struct _PEB_FREE_BLOCK { _PEB_FREE_BLOCK* Next; ULONG Size; } PEB_FREE_BLOCK, * PPEB_FREE_BLOCK; typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; PVOID Reserved2[2]; PVOID DllBase; PVOID EntryPoint; PVOID Reserved3; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; PVOID Reserved5[3]; union { ULONG CheckSum; PVOID Reserved6; }; ULONG TimeDateStamp; } LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY; typedef struct _RTL_DRIVE_LETTER_CURDIR { USHORT Flags; USHORT Length; ULONG TimeStamp; UNICODE_STRING DosPath; } RTL_DRIVE_LETTER_CURDIR, * PRTL_DRIVE_LETTER_CURDIR; typedef struct _RTL_USER_PROCESS_PARAMETERS { ULONG MaximumLength; ULONG Length; ULONG Flags; ULONG DebugFlags; PVOID ConsoleHandle; ULONG ConsoleFlags; HANDLE StdInputHandle; HANDLE StdOutputHandle; HANDLE StdErrorHandle; UNICODE_STRING CurrentDirectoryPath; HANDLE CurrentDirectoryHandle; UNICODE_STRING DllPath; UNICODE_STRING ImagePathName; UNICODE_STRING CommandLine; PVOID Environment; ULONG StartingPositionLeft; ULONG StartingPositionTop; ULONG Width; ULONG Height; ULONG CharWidth; ULONG CharHeight; ULONG ConsoleTextAttributes; ULONG WindowFlags; ULONG ShowWindowFlags; UNICODE_STRING WindowTitle; UNICODE_STRING DesktopName; UNICODE_STRING ShellInfo; UNICODE_STRING RuntimeData; RTL_DRIVE_LETTER_CURDIR DLCurrentDirectory[0x20]; } RTL_USER_PROCESS_PARAMETERS, * PRTL_USER_PROCESS_PARAMETERS; typedef struct _PEB { BOOLEAN InheritedAddressSpace; BOOLEAN ReadImageFileExecOptions; BOOLEAN BeingDebugged; BOOLEAN Spare; HANDLE Mutant; PVOID ImageBaseAddress; PPEB_LDR_DATA LoaderData; RTL_USER_PROCESS_PARAMETERS ProcessParameters; PVOID SubSystemData; PVOID ProcessHeap; PVOID FastPebLock; uintptr_t FastPebLockRoutine; uintptr_t FastPebUnlockRoutine; ULONG EnvironmentUpdateCount; uintptr_t KernelCallbackTable; PVOID EventLogSection; PVOID EventLog; PPEB_FREE_BLOCK FreeList; ULONG TlsExpansionCounter; PVOID TlsBitmap; ULONG TlsBitmapBits[0x2]; PVOID ReadOnlySharedMemoryBase; PVOID ReadOnlySharedMemoryHeap; uintptr_t ReadOnlyStaticServerData; PVOID AnsiCodePageData; PVOID OemCodePageData; PVOID UnicodeCaseTableData; ULONG NumberOfProcessors; ULONG NtGlobalFlag; BYTE Spare2[0x4]; LARGE_INTEGER CriticalSectionTimeout; ULONG HeapSegmentReserve; ULONG HeapSegmentCommit; ULONG HeapDeCommitTotalFreeThreshold; ULONG HeapDeCommitFreeBlockThreshold; ULONG NumberOfHeaps; ULONG MaximumNumberOfHeaps; uintptr_t* ProcessHeaps; PVOID GdiSharedHandleTable; PVOID ProcessStarterHelper; PVOID GdiDCAttributeList; PVOID LoaderLock; ULONG OSMajorVersion; ULONG OSMinorVersion; ULONG OSBuildNumber; ULONG OSPlatformId; ULONG ImageSubSystem; ULONG ImageSubSystemMajorVersion; ULONG ImageSubSystemMinorVersion; ULONG GdiHandleBuffer[0x22]; ULONG PostProcessInitRoutine; ULONG TlsExpansionBitmap; BYTE TlsExpansionBitmapBits[0x80]; ULONG SessionId; } PEB, * PPEB; }// namespace nt constexpr uint32_t xor_key_1 = __TIME__[2]; constexpr uint32_t xor_key_2 = __TIME__[4]; constexpr uint32_t xor_key_offset = (xor_key_1 ^ xor_key_2); namespace fnv1a { constexpr uint32_t fnv_prime_value = 0x01000193; SYSCALL_FORCEINLINE consteval uint32_t hash_ctime(const char* input, unsigned val = 0x811c9dc5 ^ ::syscall::xor_key_offset) noexcept { return input[0] == CS_XOR('\0') ? val : hash_ctime(input + 1, (val ^ *input) * fnv_prime_value); } SYSCALL_FORCEINLINE constexpr uint32_t hash_rtime(const char* input, unsigned val = 0x811c9dc5 ^ ::syscall::xor_key_offset) noexcept { return input[0] == CS_XOR('\0') ? val : hash_rtime(input + 1, (val ^ *input) * fnv_prime_value); } }// namespace fnv1a namespace utils { SYSCALL_FORCEINLINE std::string wide_to_string(wchar_t* buffer) noexcept { const auto out{ std::wstring(buffer) }; if (out.empty()) return ""; return std::string(out.begin(), out.end()); } }// namespace utils namespace win { SYSCALL_FORCEINLINE nt::PEB* get_peb() noexcept { #if defined(_M_IX86) || defined(__i386__) return reinterpret_cast<::syscall::nt::PEB*>(__readfsdword(0x30)); #else return reinterpret_cast<::syscall::nt::PEB*>(__readgsqword(0x60)); #endif } template static SYSCALL_FORCEINLINE T get_module_handle_from_hash(const uint32_t& module_hash) noexcept { auto peb = ::syscall::win::get_peb(); if (!peb) return NULL; auto head = &peb->LoaderData->InLoadOrderModuleList; for (auto it = head->Flink; it != head; it = it->Flink) { ::syscall::nt::_LDR_DATA_TABLE_ENTRY* ldr_entry = CONTAINING_RECORD(it, nt::LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); if (!ldr_entry->BaseDllName.Buffer) continue; auto name = ::syscall::utils::wide_to_string(ldr_entry->BaseDllName.Buffer); if (SYSCALL_HASH(name.data()) == module_hash) return reinterpret_cast(ldr_entry->DllBase); } return NULL; } template static SYSCALL_FORCEINLINE T get_module_export_from_table(uintptr_t module_address, const uint32_t& export_hash) noexcept { auto dos_headers = reinterpret_cast(module_address); if (dos_headers->e_magic != IMAGE_DOS_SIGNATURE) return NULL; PIMAGE_EXPORT_DIRECTORY export_directory = nullptr; auto nt_headers32 = reinterpret_cast(module_address + dos_headers->e_lfanew); auto nt_headers64 = reinterpret_cast(module_address + dos_headers->e_lfanew); PIMAGE_OPTIONAL_HEADER32 optional_header32 = &nt_headers32->OptionalHeader; PIMAGE_OPTIONAL_HEADER64 optional_header64 = &nt_headers64->OptionalHeader; // for 32bit modules. if (nt_headers32->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { // does not have a export table. if (optional_header32->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size <= 0U) return NULL; export_directory = reinterpret_cast(module_address + optional_header32->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); } // for 64bit modules. else if (nt_headers64->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { // does not have a export table. if (optional_header64->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size <= 0U) return NULL; export_directory = reinterpret_cast(module_address + optional_header64->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); } auto names_rva = reinterpret_cast(module_address + export_directory->AddressOfNames); auto functions_rva = reinterpret_cast(module_address + export_directory->AddressOfFunctions); auto name_ordinals = reinterpret_cast(module_address + export_directory->AddressOfNameOrdinals); uint32_t number_of_names = export_directory->NumberOfNames; for (size_t i = 0ul; i < number_of_names; i++) { const char* export_name = reinterpret_cast(module_address + names_rva[i]); if (export_hash == SYSCALL_HASH(export_name)) return static_cast(module_address + functions_rva[name_ordinals[i]]); } return NULL; } template SYSCALL_FORCEINLINE T force_find_export(const uint32_t& export_hash) noexcept { auto peb = ::syscall::win::get_peb(); if (!peb || !export_hash) return NULL; auto head = &peb->LoaderData->InLoadOrderModuleList; for (auto it = head->Flink; it != head; it = it->Flink) { ::syscall::nt::_LDR_DATA_TABLE_ENTRY* ldr_entry = CONTAINING_RECORD(it, nt::LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); if (!ldr_entry->BaseDllName.Buffer) continue; auto name = ::syscall::utils::wide_to_string(ldr_entry->BaseDllName.Buffer); auto export_address = ::syscall::win::get_module_export_from_table( reinterpret_cast(ldr_entry->DllBase), export_hash); if (!export_address) continue; return static_cast(export_address); } } }// namespace win SYSCALL_FORCEINLINE uint16_t get_return_code_from_export(uintptr_t export_address) noexcept { if (!export_address) return NULL; return *reinterpret_cast(static_cast(export_address + 12) + 1); } SYSCALL_FORCEINLINE int get_syscall_id_from_export(uintptr_t export_address) noexcept { if (!export_address) return NULL; #if defined(_M_IX86) || defined(__i386__) return *reinterpret_cast(static_cast(export_address) + 1); #else return *reinterpret_cast(static_cast(export_address + 3) + 1); #endif } struct create_function { void* _allocated_memory = nullptr; void* _function = nullptr; uint32_t _export_hash; public: SYSCALL_FORCEINLINE ~create_function() noexcept { if (this->_allocated_memory) { VirtualFree(this->_allocated_memory, 0, MEM_RELEASE); this->_allocated_memory = nullptr; } } SYSCALL_FORCEINLINE create_function(uint32_t export_hash) noexcept : _export_hash(export_hash) { static auto exported_address = ::syscall::win::force_find_export(this->_export_hash); static auto syscall_table_id = ::syscall::get_syscall_id_from_export(exported_address); if (!exported_address || !syscall_table_id) return; std::vector shellcode = { #if defined(_M_IX86) || defined(__i386__) 0xB8, 0x00, 0x10, 0x00, 0x00, // mov eax, 0x64, 0x8B, 0x15, 0xC0, 0x00, 0x00, 0x00,// mov edx, DWORD PTR fs:0xc0 ( 0xFF, 0xD2, // call edx 0xC2, 0x04, 0x00 // ret 4 #else 0x49, 0x89, 0xCA, // mov r10, rcx 0xB8, 0x3F, 0x10, 0x00, 0x00, // mov eax, 0x0F, 0x05, // syscall 0xC3 // ret #endif }; #if defined(_M_IX86) || defined(__i386__) // required for x86 ONLY! * reinterpret_cast(&shellcode[15]) = ::syscall::get_return_code_from_export(exported_address); *reinterpret_cast(&shellcode[1]) = syscall_table_id; #else * reinterpret_cast(&shellcode[4]) = syscall_table_id; #endif this->_allocated_memory = VirtualAlloc(nullptr, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (!this->_allocated_memory) { return; } memcpy(this->_allocated_memory, shellcode.data(), sizeof(shellcode)); *reinterpret_cast(&this->_function) = this->_allocated_memory; } SYSCALL_FORCEINLINE bool is_valid_address() noexcept { return this->_function != nullptr; } template SYSCALL_FORCEINLINE T invoke_call(Args... arguments) noexcept { return reinterpret_cast(this->_function)(arguments...); } }; template SYSCALL_FORCEINLINE T invoke_syscall(uint32_t export_hash, Args... arguments) noexcept { static auto syscall_function = ::syscall::create_function(export_hash); if (!syscall_function.is_valid_address()) { return NULL; } return syscall_function.invoke_call(arguments...); } template SYSCALL_FORCEINLINE T invoke_lazy_import(uint32_t export_hash, Args... arguments) noexcept { static auto exported_function = ::syscall::win::force_find_export(export_hash); if (exported_function) return reinterpret_cast(exported_function)(arguments...); } }// namespace syscall #endif// DIRECT_SYSCALL_HPP