From db696263e62414bcd495ad1a9dd9f0efc9d2cf0a Mon Sep 17 00:00:00 2001 From: dinhngtu <1257909+dinhngtu@users.noreply.github.com> Date: Sun, 26 May 2024 03:52:35 +0200 Subject: [PATCH] DllBlock: hook VirtualAlloc/VirtualProtect for dynamic code exclusions (#419) --- NanaZip.Shared/DllBlock.cpp | 189 +++++++++++++++++++++++++++++---- NanaZip.Shared/Mitigations.cpp | 48 +++++++++ NanaZip.Shared/Mitigations.h | 1 + 3 files changed, 217 insertions(+), 21 deletions(-) diff --git a/NanaZip.Shared/DllBlock.cpp b/NanaZip.Shared/DllBlock.cpp index e54dcf8b9..fd2ee1e12 100644 --- a/NanaZip.Shared/DllBlock.cpp +++ b/NanaZip.Shared/DllBlock.cpp @@ -13,28 +13,51 @@ #include +#include +#include +#include +#include + #include "DllBlock.h" +#include "Mitigations.h" #ifdef NDEBUG namespace { + enum DllFlags : unsigned int { + UnknownDll = 0, + DllNeedsBlocking = 1, + DllNeedsDynamicCodeOptout = 2, + }; + + static std::vector> dynamic_code_ranges; + static std::mutex dynamic_code_range_lock; + + using DllType = std::pair; + static const std::array DllList = { + std::make_pair("BaseGUI.dll", DllNeedsDynamicCodeOptout), + std::make_pair("ExplorerPatcher.amd64.dll", DllNeedsBlocking), + std::make_pair("ExplorerPatcher.IA-32.dll", DllNeedsBlocking), + std::make_pair("TFMain64.dll", DllNeedsBlocking), + }; + static decltype(NtMapViewOfSection)* RealNtMapViewOfSection = nullptr; static decltype(NtQuerySection)* RealNtQuerySection = nullptr; static decltype(NtUnmapViewOfSection)* RealNtUnmapViewOfSection = nullptr; + static decltype(VirtualAlloc)* RealVirtualAlloc = nullptr; + static decltype(VirtualAllocEx)* RealVirtualAllocEx = nullptr; + static decltype(VirtualProtect)* RealVirtualProtect = nullptr; + static decltype(VirtualProtectEx)* RealVirtualProtectEx = nullptr; - static bool DllNeedsBlocking(const char* dllName) { - if (!::_stricmp(dllName, "ExplorerPatcher.amd64.dll")) { - return true; - } - else if (!::_stricmp(dllName, "ExplorerPatcher.IA-32.dll")) { - return true; - } - else if (!::_stricmp(dllName, "TFMain64.dll")) { - // Translucent Flyouts - return true; + static DllFlags FindDll(const char* dllName) + { + for (auto it = DllList.begin(); it != DllList.end(); it++) { + if (!_stricmp(it->first, dllName)) { + return it->second; + } } - return false; + return UnknownDll; } static inline bool CheckExtents(size_t viewSize, size_t offset, size_t size) { @@ -72,6 +95,17 @@ namespace return true; } + static bool HandleIsCurrentProcess(HANDLE ProcessHandle) { + return ProcessHandle == GetCurrentProcess() || GetProcessId(ProcessHandle) == GetCurrentProcessId(); + } + + static bool ProtectionIsExecute(DWORD Protect) { + return Protect == PAGE_EXECUTE || + Protect == PAGE_EXECUTE_READ || + Protect == PAGE_EXECUTE_READWRITE || + Protect == PAGE_EXECUTE_WRITECOPY; + } + static NTSTATUS NTAPI MyNtMapViewOfSection( _In_ HANDLE SectionHandle, _In_ HANDLE ProcessHandle, @@ -98,13 +132,7 @@ namespace InheritDisposition, AllocationType, Win32Protect); - if (ret < 0) { - return ret; - } - if (!(Win32Protect == PAGE_EXECUTE || - Win32Protect == PAGE_EXECUTE_READ || - Win32Protect == PAGE_EXECUTE_READWRITE || - Win32Protect == PAGE_EXECUTE_WRITECOPY)) { + if (ret < 0 || !HandleIsCurrentProcess(ProcessHandle) || !ProtectionIsExecute(Win32Protect)) { return ret; } SECTION_BASIC_INFORMATION sbi = { 0 }; @@ -116,11 +144,117 @@ namespace if (!GetDllExportName(dllName, reinterpret_cast(*BaseAddress), *ViewSize)) { return ret; } - bool needsBlocking = DllNeedsBlocking(dllName); - if (needsBlocking) { + DllFlags dllType = FindDll(dllName); + if (dllType & DllNeedsBlocking) { RealNtUnmapViewOfSection(ProcessHandle, *BaseAddress); return STATUS_ACCESS_DENIED; } + else if (dllType & DllNeedsDynamicCodeOptout) { + std::lock_guard lock(dynamic_code_range_lock); + dynamic_code_ranges.push_back(std::make_pair(reinterpret_cast(*BaseAddress), *ViewSize)); + } + return ret; + } + + static NTSTATUS NTAPI MyNtUnmapViewOfSection( + _In_ HANDLE ProcessHandle, + _In_opt_ PVOID BaseAddress) + { + NTSTATUS ret = RealNtUnmapViewOfSection(ProcessHandle, BaseAddress); + if (ret < 0 || !HandleIsCurrentProcess(ProcessHandle)) { + return ret; + } + UINT_PTR ptr = reinterpret_cast(BaseAddress); + { + std::lock_guard lock(dynamic_code_range_lock); + for (auto it = dynamic_code_ranges.begin(); it != dynamic_code_ranges.end(); it++) { + if (ptr >= it->first && ptr < it->first + it->second) { + dynamic_code_ranges.erase(it); + break; + } + } + } + return ret; + } + + static bool CallerUsesDynamicCode(void* pCaller) + { + UINT_PTR caller = reinterpret_cast(pCaller); + std::lock_guard lock(dynamic_code_range_lock); + for (auto it = dynamic_code_ranges.begin(); it != dynamic_code_ranges.end(); it++) { + if (caller >= it->first && caller < it->first + it->second) { + return true; + } + } + return false; + } + + static _Ret_maybenull_ _Post_writable_byte_size_(dwSize) LPVOID WINAPI MyVirtualAlloc( + _In_opt_ LPVOID lpAddress, + _In_ SIZE_T dwSize, + _In_ DWORD flAllocationType, + _In_ DWORD flProtect) + { + if (!ProtectionIsExecute(flProtect) || + !CallerUsesDynamicCode(_ReturnAddress())) { + return RealVirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect); + } + // what do we even do if it fails? so, no error checking. + NanaZipSetThreadDynamicCodeOptout(TRUE); + LPVOID ret = RealVirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect); + NanaZipSetThreadDynamicCodeOptout(FALSE); + return ret; + } + + static _Ret_maybenull_ _Post_writable_byte_size_(dwSize) LPVOID WINAPI MyVirtualAllocEx( + _In_ HANDLE hProcess, + _In_opt_ LPVOID lpAddress, + _In_ SIZE_T dwSize, + _In_ DWORD flAllocationType, + _In_ DWORD flProtect) + { + if (!HandleIsCurrentProcess(hProcess) || + !ProtectionIsExecute(flProtect) || + !CallerUsesDynamicCode(_ReturnAddress())) { + return RealVirtualAllocEx(hProcess, lpAddress, dwSize, flAllocationType, flProtect); + } + NanaZipSetThreadDynamicCodeOptout(TRUE); + LPVOID ret = RealVirtualAllocEx(hProcess, lpAddress, dwSize, flAllocationType, flProtect); + NanaZipSetThreadDynamicCodeOptout(FALSE); + return ret; + } + + static _Success_(return != FALSE) BOOL WINAPI MyVirtualProtect( + _In_ LPVOID lpAddress, + _In_ SIZE_T dwSize, + _In_ DWORD flNewProtect, + _Out_ PDWORD lpflOldProtect) + { + if (!ProtectionIsExecute(flNewProtect) || + !CallerUsesDynamicCode(_ReturnAddress())) { + return RealVirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect); + } + NanaZipSetThreadDynamicCodeOptout(TRUE); + BOOL ret = RealVirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect); + NanaZipSetThreadDynamicCodeOptout(FALSE); + return ret; + } + + static _Success_(return != FALSE) BOOL WINAPI MyVirtualProtectEx( + _In_ HANDLE hProcess, + _In_ LPVOID lpAddress, + _In_ SIZE_T dwSize, + _In_ DWORD flNewProtect, + _Out_ PDWORD lpflOldProtect) + { + if (!HandleIsCurrentProcess(hProcess) || + !ProtectionIsExecute(flNewProtect) || + !CallerUsesDynamicCode(_ReturnAddress())) { + return RealVirtualProtectEx(hProcess, lpAddress, dwSize, flNewProtect, lpflOldProtect); + } + NanaZipSetThreadDynamicCodeOptout(TRUE); + BOOL ret = RealVirtualProtectEx(hProcess, lpAddress, dwSize, flNewProtect, lpflOldProtect); + NanaZipSetThreadDynamicCodeOptout(FALSE); return ret; } } @@ -129,13 +263,26 @@ EXTERN_C BOOL WINAPI NanaZipBlockDlls() { DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); + RealNtMapViewOfSection = static_cast(DetourFindFunction("ntdll.dll", "NtMapViewOfSection")); RealNtQuerySection = static_cast(DetourFindFunction("ntdll.dll", "NtQuerySection")); RealNtUnmapViewOfSection = static_cast(DetourFindFunction("ntdll.dll", "NtUnmapViewOfSection")); - if (DetourAttach(&RealNtMapViewOfSection, MyNtMapViewOfSection) != NO_ERROR) { + + RealVirtualAlloc = static_cast(DetourFindFunction("kernel32.dll", "VirtualAlloc")); + RealVirtualAllocEx = static_cast(DetourFindFunction("kernel32.dll", "VirtualAllocEx")); + RealVirtualProtect = static_cast(DetourFindFunction("kernel32.dll", "VirtualProtect")); + RealVirtualProtectEx = static_cast(DetourFindFunction("kernel32.dll", "VirtualProtectEx")); + + if (DetourAttach(&RealNtMapViewOfSection, MyNtMapViewOfSection) != NO_ERROR || + DetourAttach(&RealNtUnmapViewOfSection, MyNtUnmapViewOfSection) != NO_ERROR || + DetourAttach(&RealVirtualAlloc, MyVirtualAlloc) != NO_ERROR || + DetourAttach(&RealVirtualAllocEx, MyVirtualAllocEx) != NO_ERROR || + DetourAttach(&RealVirtualProtect, MyVirtualProtect) != NO_ERROR || + DetourAttach(&RealVirtualProtectEx, MyVirtualProtectEx) != NO_ERROR) { DetourTransactionAbort(); return FALSE; } + DetourTransactionCommit(); return TRUE; } diff --git a/NanaZip.Shared/Mitigations.cpp b/NanaZip.Shared/Mitigations.cpp index cd0992259..69c2dbaff 100644 --- a/NanaZip.Shared/Mitigations.cpp +++ b/NanaZip.Shared/Mitigations.cpp @@ -15,8 +15,14 @@ #include +#include + namespace { +#ifdef NDEBUG + static std::atomic SetThreadInformationPtr = nullptr; +#endif + static HMODULE GetKernel32ModuleHandle() { static HMODULE CachedResult = ::GetModuleHandleW(L"kernel32.dll"); @@ -105,6 +111,7 @@ EXTERN_C BOOL WINAPI NanaZipEnableMitigations() { PROCESS_MITIGATION_DYNAMIC_CODE_POLICY Policy = { 0 }; Policy.ProhibitDynamicCode = 1; + Policy.AllowThreadOptOut = 1; if (!::SetProcessMitigationPolicyWrapper( ProcessDynamicCodePolicy, &Policy, @@ -112,6 +119,18 @@ EXTERN_C BOOL WINAPI NanaZipEnableMitigations() { return FALSE; } + + HMODULE ModuleHandle = ::GetKernel32ModuleHandle(); + if (ModuleHandle) + { + using ProcType = decltype(::SetThreadInformation)*; + FARPROC ProcAddress = ::GetProcAddress( + ModuleHandle, + "SetThreadInformation"); + SetThreadInformationPtr.store( + reinterpret_cast(ProcAddress), + std::memory_order_relaxed); + } } #endif // NDEBUG @@ -151,3 +170,32 @@ EXTERN_C BOOL WINAPI NanaZipDisableChildProcesses() return TRUE; } + +#ifdef NDEBUG + +EXTERN_C BOOL WINAPI NanaZipSetThreadDynamicCodeOptout(BOOL optout) +{ + using ProcType = decltype(::SetThreadInformation)*; + ProcType SetThreadInformationWrapper = SetThreadInformationPtr.load(std::memory_order_relaxed); + if (SetThreadInformationWrapper) { + DWORD ThreadPolicy = optout ? THREAD_DYNAMIC_CODE_ALLOW : 0; + return SetThreadInformationWrapper( + ::GetCurrentThread(), + ThreadDynamicCodePolicy, + &ThreadPolicy, + sizeof(DWORD)); + } + else { + return TRUE; + } +} + +#else + +EXTERN_C BOOL WINAPI NanaZipSetThreadDynamicCodeOptout(BOOL optout) +{ + UNREFERENCED_PARAMETER(optout); + return TRUE; +} + +#endif diff --git a/NanaZip.Shared/Mitigations.h b/NanaZip.Shared/Mitigations.h index 41444289a..b7de248ae 100644 --- a/NanaZip.Shared/Mitigations.h +++ b/NanaZip.Shared/Mitigations.h @@ -16,5 +16,6 @@ EXTERN_C BOOL WINAPI NanaZipEnableMitigations(); EXTERN_C BOOL WINAPI NanaZipDisableChildProcesses(); +EXTERN_C BOOL WINAPI NanaZipSetThreadDynamicCodeOptout(BOOL optout); #endif // !NANAZIP_SHARED_MITIGATIONS