#include "module.h" #include "ws.h" #include #include #include #pragma intrinsic(_ReturnAddress) #ifndef STATUS_UNSUCCESSFUL #define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) #endif namespace { static ModuleHookInitFault g_LastModuleHookInitFault{}; struct ModuleRange { std::uintptr_t Base = 0; std::uintptr_t End = 0; }; static void ResetModuleHookInitFault() noexcept { std::memset(&g_LastModuleHookInitFault, 0, sizeof(g_LastModuleHookInitFault)); g_LastModuleHookInitFault.Code = ModuleHookInitFaultCode::None; } static void CaptureFaultSample(const void *address, std::uint8_t sample[16]) noexcept { std::memset(sample, 0, 16); if (address == nullptr) { return; } __try { std::memcpy(sample, address, 16); } __except (EXCEPTION_EXECUTE_HANDLER) { std::memset(sample, 0, 16); } } static void SetModuleHookInitFault(ModuleHookInitFaultCode code, const wchar_t *moduleName, const char *exportName, void *address, void *redirectTarget = nullptr) noexcept { ResetModuleHookInitFault(); g_LastModuleHookInitFault.Code = code; g_LastModuleHookInitFault.ModuleName = moduleName; g_LastModuleHookInitFault.ExportName = exportName; g_LastModuleHookInitFault.Address = address; g_LastModuleHookInitFault.RedirectTarget = redirectTarget; CaptureFaultSample(address, g_LastModuleHookInitFault.Sample); } static bool TryResolveModuleImageRange(HMODULE module, ModuleRange &range) noexcept { auto *dos = reinterpret_cast(module); if (module == nullptr || dos->e_magic != IMAGE_DOS_SIGNATURE) { return false; } auto *nt = reinterpret_cast(reinterpret_cast(module) + dos->e_lfanew); if (nt->Signature != IMAGE_NT_SIGNATURE || nt->OptionalHeader.SizeOfImage == 0) { return false; } range.Base = reinterpret_cast(module); range.End = range.Base + nt->OptionalHeader.SizeOfImage; return true; } static bool AddressWithinRange(void *address, const ModuleRange &range) noexcept { std::uintptr_t value = reinterpret_cast(address); return value >= range.Base && value < range.End; } static bool TryDecodeAbsoluteTarget(void *entry, void *&target) noexcept { target = nullptr; if (entry == nullptr) { return false; } auto *bytes = static_cast(entry); __try { if (bytes[0] == 0xE9) { std::int32_t rel = *reinterpret_cast(&bytes[1]); target = bytes + 5 + rel; return true; } if (bytes[0] == 0xFF && bytes[1] == 0x25) { std::int32_t disp = *reinterpret_cast(&bytes[2]); auto **slot = reinterpret_cast(bytes + 6 + disp); target = *slot; return true; } if (bytes[0] == 0x48 && bytes[1] == 0xB8 && bytes[10] == 0xFF && bytes[11] == 0xE0) { target = *reinterpret_cast(&bytes[2]); return true; } } __except (EXCEPTION_EXECUTE_HANDLER) { target = nullptr; return false; } return false; } using LoadLibraryAFn = HMODULE(WINAPI *)(LPCSTR); using LoadLibraryWFn = HMODULE(WINAPI *)(LPCWSTR); using LoadLibraryExAFn = HMODULE(WINAPI *)(LPCSTR, HANDLE, DWORD); using LoadLibraryExWFn = HMODULE(WINAPI *)(LPCWSTR, HANDLE, DWORD); using LdrLoadDllFn = NTSTATUS(NTAPI *)(PWSTR, PULONG, PUNICODE_STRING, PHANDLE); using RtlAddFunctionTableFn = BOOLEAN(WINAPI *)(PRUNTIME_FUNCTION, DWORD, DWORD64); using RtlInstallFunctionTableCallbackFn = BOOLEAN(WINAPI *)(DWORD64, DWORD64, DWORD, PGET_RUNTIME_FUNCTION_CALLBACK, PVOID, PCWSTR); using RtlDeleteFunctionTableFn = BOOLEAN(WINAPI *)(PRUNTIME_FUNCTION); struct InlineHook { const wchar_t *ModuleName; const char *ExportName; const char *SourceModule; void *HookEntry; void **OriginalFunction; void *TargetAddress; void *Trampoline; std::uint8_t OriginalBytes[16]; bool Installed; }; static ModuleHookCallback g_ActiveCallback = nullptr; static __declspec(thread) bool g_InHook = false; static LoadLibraryAFn g_OriginalLoadLibraryA = nullptr; static LoadLibraryWFn g_OriginalLoadLibraryW = nullptr; static LoadLibraryExAFn g_OriginalLoadLibraryExA = nullptr; static LoadLibraryExWFn g_OriginalLoadLibraryExW = nullptr; static LdrLoadDllFn g_OriginalLdrLoadDll = nullptr; static RtlAddFunctionTableFn g_OriginalRtlAddFunctionTable = nullptr; static RtlInstallFunctionTableCallbackFn g_OriginalRtlInstallFunctionTableCallback = nullptr; static RtlDeleteFunctionTableFn g_OriginalRtlDeleteFunctionTable = nullptr; HMODULE WINAPI LoadLibraryAHook(LPCSTR lpLibFileName); HMODULE WINAPI LoadLibraryWHook(LPCWSTR lpLibFileName); HMODULE WINAPI LoadLibraryExAHook(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags); HMODULE WINAPI LoadLibraryExWHook(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags); NTSTATUS NTAPI LdrLoadDllHook(PWSTR searchPath, PULONG loadFlags, PUNICODE_STRING moduleFileName, PHANDLE moduleHandle); BOOLEAN WINAPI RtlAddFunctionTableHook(PRUNTIME_FUNCTION functionTable, DWORD entryCount, DWORD64 baseAddress); BOOLEAN WINAPI RtlInstallFunctionTableCallbackHook(DWORD64 tableIdentifier, DWORD64 baseAddress, DWORD length, PGET_RUNTIME_FUNCTION_CALLBACK callback, PVOID context, PCWSTR outOfProcessCallbackDll); BOOLEAN WINAPI RtlDeleteFunctionTableHook(PRUNTIME_FUNCTION functionTable); static InlineHook g_Hooks[] = { {L"KernelBase.dll", "LoadLibraryA", "KERNELBASE", reinterpret_cast(&LoadLibraryAHook), reinterpret_cast(&g_OriginalLoadLibraryA), nullptr, nullptr, {}, false}, {L"KernelBase.dll", "LoadLibraryW", "KERNELBASE", reinterpret_cast(&LoadLibraryWHook), reinterpret_cast(&g_OriginalLoadLibraryW), nullptr, nullptr, {}, false}, {L"KernelBase.dll", "LoadLibraryExA", "KERNELBASE", reinterpret_cast(&LoadLibraryExAHook), reinterpret_cast(&g_OriginalLoadLibraryExA), nullptr, nullptr, {}, false}, {L"KernelBase.dll", "LoadLibraryExW", "KERNELBASE", reinterpret_cast(&LoadLibraryExWHook), reinterpret_cast(&g_OriginalLoadLibraryExW), nullptr, nullptr, {}, false}, {L"ntdll.dll", "LdrLoadDll", "ntdll", reinterpret_cast(&LdrLoadDllHook), reinterpret_cast(&g_OriginalLdrLoadDll), nullptr, nullptr, {}, false}, {L"ntdll.dll", "RtlAddFunctionTable", "ntdll", reinterpret_cast(&RtlAddFunctionTableHook), reinterpret_cast(&g_OriginalRtlAddFunctionTable), nullptr, nullptr, {}, false}, {L"ntdll.dll", "RtlInstallFunctionTableCallback", "ntdll", reinterpret_cast(&RtlInstallFunctionTableCallbackHook), reinterpret_cast(&g_OriginalRtlInstallFunctionTableCallback), nullptr, nullptr, {}, false}, {L"ntdll.dll", "RtlDeleteFunctionTable", "ntdll", reinterpret_cast(&RtlDeleteFunctionTableHook), reinterpret_cast(&g_OriginalRtlDeleteFunctionTable), nullptr, nullptr, {}, false}, }; static bool InstallInlineHook(void *target, void *hook, std::uint8_t original[16], void **trampolineOut) noexcept { constexpr std::size_t kPatchSize = 16; constexpr std::size_t kTrampolineSize = 32; if (target == nullptr || hook == nullptr || original == nullptr || trampolineOut == nullptr) { return false; } void *trampoline = VirtualAlloc(nullptr, kTrampolineSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (trampoline == nullptr) { return false; } auto *dst = static_cast(target); auto *gate = static_cast(trampoline); DWORD oldProtect = 0; if (!VirtualProtect(dst, kPatchSize, PAGE_EXECUTE_READWRITE, &oldProtect)) { VirtualFree(trampoline, 0, MEM_RELEASE); return false; } std::memcpy(original, dst, kPatchSize); std::memcpy(gate, dst, kPatchSize); gate[16] = 0x48; gate[17] = 0xB8; *reinterpret_cast(&gate[18]) = dst + kPatchSize; gate[26] = 0xFF; gate[27] = 0xE0; for (std::size_t i = 28; i < kTrampolineSize; ++i) { gate[i] = 0xCC; } dst[0] = 0x48; dst[1] = 0xB8; *reinterpret_cast(&dst[2]) = hook; dst[10] = 0xFF; dst[11] = 0xE0; dst[12] = 0xCC; dst[13] = 0xCC; dst[14] = 0xCC; dst[15] = 0xCC; DWORD temp = 0; VirtualProtect(dst, kPatchSize, oldProtect, &temp); FlushInstructionCache(GetCurrentProcess(), dst, kPatchSize); FlushInstructionCache(GetCurrentProcess(), trampoline, kTrampolineSize); *trampolineOut = trampoline; return true; } static void RemoveInlineHook(void *target, const std::uint8_t original[16], void *trampoline) noexcept { if (target == nullptr || original == nullptr) { return; } DWORD oldProtect = 0; if (VirtualProtect(target, 16, PAGE_EXECUTE_READWRITE, &oldProtect)) { std::memcpy(target, original, 16); DWORD temp = 0; VirtualProtect(target, 16, oldProtect, &temp); FlushInstructionCache(GetCurrentProcess(), target, 16); } if (trampoline != nullptr) { VirtualFree(trampoline, 0, MEM_RELEASE); } } static void PublishModuleEvent(ModuleHookOperation operation, const char *functionName, const char *sourceModule, HMODULE moduleHandle, const void *nameBuffer, std::size_t nameLength, std::uint64_t arg0 = 0, std::uint64_t arg1 = 0, std::uint64_t arg2 = 0, std::uint64_t arg3 = 0) noexcept { if (g_InHook || g_ActiveCallback == nullptr) { return; } g_InHook = true; if (moduleHandle != nullptr) { (void)KeRefreshWinsockHooks(moduleHandle); } ModuleHookContext context{}; context.Operation = operation; context.FunctionName = functionName; context.SourceModule = sourceModule; context.Caller = _ReturnAddress(); context.ModuleHandle = moduleHandle; context.NameBuffer = nameBuffer; context.NameLength = nameLength; context.Args[0] = arg0; context.Args[1] = arg1; context.Args[2] = arg2; context.Args[3] = arg3; g_ActiveCallback(context); g_InHook = false; } static std::size_t CopyAnsiLength(LPCSTR value) noexcept { return (value != nullptr) ? strnlen_s(value, 31) : 0; } static std::size_t CopyWideLength(LPCWSTR value) noexcept { std::size_t chars = 0; if (value != nullptr) { while (value[chars] != L'\0' && chars < 31) { ++chars; } } return chars * sizeof(wchar_t); } HMODULE WINAPI LoadLibraryAHook(LPCSTR lpLibFileName) { if (g_OriginalLoadLibraryA == nullptr) { return nullptr; } HMODULE moduleHandle = g_OriginalLoadLibraryA(lpLibFileName); PublishModuleEvent(ModuleHookOperation::LoadLibraryA, "LoadLibraryA", "KERNELBASE", moduleHandle, lpLibFileName, CopyAnsiLength(lpLibFileName)); return moduleHandle; } HMODULE WINAPI LoadLibraryWHook(LPCWSTR lpLibFileName) { if (g_OriginalLoadLibraryW == nullptr) { return nullptr; } HMODULE moduleHandle = g_OriginalLoadLibraryW(lpLibFileName); PublishModuleEvent(ModuleHookOperation::LoadLibraryW, "LoadLibraryW", "KERNELBASE", moduleHandle, lpLibFileName, CopyWideLength(lpLibFileName)); return moduleHandle; } HMODULE WINAPI LoadLibraryExAHook(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) { if (g_OriginalLoadLibraryExA == nullptr) { return nullptr; } HMODULE moduleHandle = g_OriginalLoadLibraryExA(lpLibFileName, hFile, dwFlags); PublishModuleEvent(ModuleHookOperation::LoadLibraryExA, "LoadLibraryExA", "KERNELBASE", moduleHandle, lpLibFileName, CopyAnsiLength(lpLibFileName), static_cast(dwFlags), static_cast(reinterpret_cast(hFile))); return moduleHandle; } HMODULE WINAPI LoadLibraryExWHook(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) { if (g_OriginalLoadLibraryExW == nullptr) { return nullptr; } HMODULE moduleHandle = g_OriginalLoadLibraryExW(lpLibFileName, hFile, dwFlags); PublishModuleEvent(ModuleHookOperation::LoadLibraryExW, "LoadLibraryExW", "KERNELBASE", moduleHandle, lpLibFileName, CopyWideLength(lpLibFileName), static_cast(dwFlags), static_cast(reinterpret_cast(hFile))); return moduleHandle; } NTSTATUS NTAPI LdrLoadDllHook(PWSTR searchPath, PULONG loadFlags, PUNICODE_STRING moduleFileName, PHANDLE moduleHandle) { if (g_OriginalLdrLoadDll == nullptr) { return STATUS_UNSUCCESSFUL; } NTSTATUS status = g_OriginalLdrLoadDll(searchPath, loadFlags, moduleFileName, moduleHandle); HMODULE resolved = nullptr; if (NT_SUCCESS(status) && moduleHandle != nullptr) { resolved = reinterpret_cast(*moduleHandle); } PublishModuleEvent(ModuleHookOperation::LdrLoadDll, "LdrLoadDll", "ntdll", resolved, (moduleFileName != nullptr) ? moduleFileName->Buffer : nullptr, (moduleFileName != nullptr && moduleFileName->Length > 0) ? moduleFileName->Length : 0, static_cast((loadFlags != nullptr) ? *loadFlags : 0), static_cast(static_cast(status)), static_cast(reinterpret_cast(searchPath)), static_cast((moduleFileName != nullptr) ? moduleFileName->Length : 0)); return status; } BOOLEAN WINAPI RtlAddFunctionTableHook(PRUNTIME_FUNCTION functionTable, DWORD entryCount, DWORD64 baseAddress) { if (g_OriginalRtlAddFunctionTable == nullptr) { return FALSE; } BOOLEAN ok = g_OriginalRtlAddFunctionTable(functionTable, entryCount, baseAddress); if (ok) { PublishModuleEvent(ModuleHookOperation::RtlAddFunctionTable, "RtlAddFunctionTable", "ntdll", nullptr, nullptr, 0, static_cast(reinterpret_cast(functionTable)), static_cast(entryCount), static_cast(baseAddress), 0); } return ok; } BOOLEAN WINAPI RtlInstallFunctionTableCallbackHook(DWORD64 tableIdentifier, DWORD64 baseAddress, DWORD length, PGET_RUNTIME_FUNCTION_CALLBACK callback, PVOID context, PCWSTR outOfProcessCallbackDll) { if (g_OriginalRtlInstallFunctionTableCallback == nullptr) { return FALSE; } BOOLEAN ok = g_OriginalRtlInstallFunctionTableCallback(tableIdentifier, baseAddress, length, callback, context, outOfProcessCallbackDll); if (ok) { PublishModuleEvent(ModuleHookOperation::RtlInstallFunctionTableCallback, "RtlInstallFunctionTableCallback", "ntdll", nullptr, outOfProcessCallbackDll, CopyWideLength(outOfProcessCallbackDll), static_cast(tableIdentifier), static_cast(baseAddress), static_cast(length), static_cast(reinterpret_cast(callback))); } return ok; } BOOLEAN WINAPI RtlDeleteFunctionTableHook(PRUNTIME_FUNCTION functionTable) { if (g_OriginalRtlDeleteFunctionTable == nullptr) { return FALSE; } BOOLEAN ok = g_OriginalRtlDeleteFunctionTable(functionTable); if (ok) { PublishModuleEvent(ModuleHookOperation::RtlDeleteFunctionTable, "RtlDeleteFunctionTable", "ntdll", nullptr, nullptr, 0, static_cast(reinterpret_cast(functionTable)), 0, 0, 0); } return ok; } } // namespace bool KeSetModuleHook(ModuleHookCallback callback) noexcept { if (callback == nullptr) { return false; } g_ActiveCallback = callback; ResetModuleHookInitFault(); bool anyInstalled = false; for (auto &hook : g_Hooks) { if (hook.Installed) { anyInstalled = true; continue; } HMODULE moduleHandle = GetModuleHandleW(hook.ModuleName); if (moduleHandle == nullptr) { SetModuleHookInitFault(ModuleHookInitFaultCode::ModuleMissing, hook.ModuleName, hook.ExportName, nullptr); continue; } ModuleRange moduleRange{}; if (!TryResolveModuleImageRange(moduleHandle, moduleRange)) { SetModuleHookInitFault(ModuleHookInitFaultCode::ExportOutsideImage, hook.ModuleName, hook.ExportName, moduleHandle); continue; } FARPROC exportAddress = GetProcAddress(moduleHandle, hook.ExportName); if (exportAddress == nullptr) { SetModuleHookInitFault(ModuleHookInitFaultCode::ExportMissing, hook.ModuleName, hook.ExportName, nullptr); continue; } hook.TargetAddress = reinterpret_cast(exportAddress); if (!AddressWithinRange(hook.TargetAddress, moduleRange)) { SetModuleHookInitFault(ModuleHookInitFaultCode::ExportOutsideImage, hook.ModuleName, hook.ExportName, hook.TargetAddress); continue; } void *redirectTarget = nullptr; if (TryDecodeAbsoluteTarget(hook.TargetAddress, redirectTarget) && redirectTarget != nullptr && !AddressWithinRange(redirectTarget, moduleRange)) { SetModuleHookInitFault(ModuleHookInitFaultCode::ExportRedirectedOutsideImage, hook.ModuleName, hook.ExportName, hook.TargetAddress, redirectTarget); continue; } if (!InstallInlineHook(hook.TargetAddress, hook.HookEntry, hook.OriginalBytes, &hook.Trampoline)) { SetModuleHookInitFault(ModuleHookInitFaultCode::PatchInstallFailed, hook.ModuleName, hook.ExportName, hook.TargetAddress); continue; } *hook.OriginalFunction = hook.Trampoline; hook.Installed = true; anyInstalled = true; } if (!anyInstalled) { g_ActiveCallback = nullptr; } return anyInstalled; } void KeRemoveModuleHook() noexcept { for (auto &hook : g_Hooks) { if (!hook.Installed || hook.TargetAddress == nullptr) { continue; } RemoveInlineHook(hook.TargetAddress, hook.OriginalBytes, hook.Trampoline); hook.TargetAddress = nullptr; hook.Trampoline = nullptr; *hook.OriginalFunction = nullptr; hook.Installed = false; } g_ActiveCallback = nullptr; } bool KeCheckModuleHookIntegrity(std::uint32_t *mismatchCount) noexcept { std::uint32_t mismatches = 0; for (const auto &hook : g_Hooks) { if (!hook.Installed || hook.TargetAddress == nullptr) { continue; } const auto *bytes = static_cast(hook.TargetAddress); void *patchedTarget = nullptr; std::memcpy(&patchedTarget, &bytes[2], sizeof(patchedTarget)); bool intact = bytes[0] == 0x48 && bytes[1] == 0xB8 && patchedTarget == hook.HookEntry && bytes[10] == 0xFF && bytes[11] == 0xE0; if (!intact) { ++mismatches; } } if (mismatchCount != nullptr) { *mismatchCount = mismatches; } return mismatches == 0; } bool KeGetLastModuleHookInitFault(ModuleHookInitFault *faultOut) noexcept { if (faultOut == nullptr) { return false; } *faultOut = g_LastModuleHookInitFault; return faultOut->Code != ModuleHookInitFaultCode::None; }