Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DllBlock: hook VirtualAlloc/VirtualProtect for dynamic code exclusions #419

Merged
merged 1 commit into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 168 additions & 21 deletions NanaZip.Shared/DllBlock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,51 @@

#include <Detours.h>

#include <mutex>
#include <array>
#include <vector>
#include <intrin.h>

#include "DllBlock.h"
#include "Mitigations.h"

#ifdef NDEBUG

namespace
{
enum DllFlags : unsigned int {
UnknownDll = 0,
DllNeedsBlocking = 1,
DllNeedsDynamicCodeOptout = 2,
};

static std::vector<std::pair<UINT_PTR, SIZE_T>> dynamic_code_ranges;
static std::mutex dynamic_code_range_lock;

using DllType = std::pair<const char*, DllFlags>;
static const std::array<DllType, 4> 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) {
Expand Down Expand Up @@ -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,
Expand All @@ -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 };
Expand All @@ -116,11 +144,117 @@ namespace
if (!GetDllExportName(dllName, reinterpret_cast<const char*>(*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<std::mutex> lock(dynamic_code_range_lock);
dynamic_code_ranges.push_back(std::make_pair(reinterpret_cast<UINT_PTR>(*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<UINT_PTR>(BaseAddress);
{
std::lock_guard<std::mutex> 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<UINT_PTR>(pCaller);
std::lock_guard<std::mutex> 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;
}
}
Expand All @@ -129,13 +263,26 @@ EXTERN_C BOOL WINAPI NanaZipBlockDlls()
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());

RealNtMapViewOfSection = static_cast<decltype(NtMapViewOfSection)*>(DetourFindFunction("ntdll.dll", "NtMapViewOfSection"));
RealNtQuerySection = static_cast<decltype(NtQuerySection)*>(DetourFindFunction("ntdll.dll", "NtQuerySection"));
RealNtUnmapViewOfSection = static_cast<decltype(NtUnmapViewOfSection)*>(DetourFindFunction("ntdll.dll", "NtUnmapViewOfSection"));
if (DetourAttach(&RealNtMapViewOfSection, MyNtMapViewOfSection) != NO_ERROR) {

RealVirtualAlloc = static_cast<decltype(VirtualAlloc)*>(DetourFindFunction("kernel32.dll", "VirtualAlloc"));
RealVirtualAllocEx = static_cast<decltype(VirtualAllocEx)*>(DetourFindFunction("kernel32.dll", "VirtualAllocEx"));
RealVirtualProtect = static_cast<decltype(VirtualProtect)*>(DetourFindFunction("kernel32.dll", "VirtualProtect"));
RealVirtualProtectEx = static_cast<decltype(VirtualProtectEx)*>(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;
}
Expand Down
48 changes: 48 additions & 0 deletions NanaZip.Shared/Mitigations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@

#include <Detours.h>

#include <atomic>

namespace
{
#ifdef NDEBUG
static std::atomic<decltype(::SetThreadInformation)*> SetThreadInformationPtr = nullptr;
#endif

static HMODULE GetKernel32ModuleHandle()
{
static HMODULE CachedResult = ::GetModuleHandleW(L"kernel32.dll");
Expand Down Expand Up @@ -105,13 +111,26 @@ EXTERN_C BOOL WINAPI NanaZipEnableMitigations()
{
PROCESS_MITIGATION_DYNAMIC_CODE_POLICY Policy = { 0 };
Policy.ProhibitDynamicCode = 1;
Policy.AllowThreadOptOut = 1;
if (!::SetProcessMitigationPolicyWrapper(
ProcessDynamicCodePolicy,
&Policy,
sizeof(PROCESS_MITIGATION_DYNAMIC_CODE_POLICY)))
{
return FALSE;
}

HMODULE ModuleHandle = ::GetKernel32ModuleHandle();
if (ModuleHandle)
{
using ProcType = decltype(::SetThreadInformation)*;
FARPROC ProcAddress = ::GetProcAddress(
ModuleHandle,
"SetThreadInformation");
SetThreadInformationPtr.store(
reinterpret_cast<ProcType>(ProcAddress),
std::memory_order_relaxed);
}
}
#endif // NDEBUG

Expand Down Expand Up @@ -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
1 change: 1 addition & 0 deletions NanaZip.Shared/Mitigations.h
Original file line number Diff line number Diff line change
Expand Up @@ -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