From bd3c4c8aa142da3fefd300a8b0b45ba5997728ec Mon Sep 17 00:00:00 2001 From: momo5502 Date: Sat, 10 Jun 2017 20:24:42 +0200 Subject: [PATCH] [AntiCheat] Prevent dll injection - Hook native LdrLoadDll to prevent injection - Hook native LdrpLoadDll to prevent injection - Hook NtCreateThreadEx to log threads created by this process and kill remote threads --- src/Components/Modules/AntiCheat.cpp | 204 +++++++++++++++++++++++++-- src/Components/Modules/AntiCheat.hpp | 14 +- src/Main.cpp | 10 ++ src/Utils/Utils.cpp | 31 ++++ src/Utils/Utils.hpp | 8 +- 5 files changed, 256 insertions(+), 11 deletions(-) diff --git a/src/Components/Modules/AntiCheat.cpp b/src/Components/Modules/AntiCheat.cpp index 76195560..a8228db2 100644 --- a/src/Components/Modules/AntiCheat.cpp +++ b/src/Components/Modules/AntiCheat.cpp @@ -4,10 +4,15 @@ namespace Components { Utils::Time::Interval AntiCheat::LastCheck; std::string AntiCheat::Hash; - Utils::Hook AntiCheat::LoadLibHook[4]; + Utils::Hook AntiCheat::CreateThreadHook; + Utils::Hook AntiCheat::LoadLibHook[6]; Utils::Hook AntiCheat::VirtualProtectHook[2]; unsigned long AntiCheat::Flags = NO_FLAG; + std::mutex AntiCheat::ThreadMutex; + std::vector AntiCheat::OwnThreadIds; + std::map> AntiCheat::ThreadHookMap; + // This function does nothing, it only adds the two passed variables and returns the value // The only important thing it does is to clean the first parameter, and then return // By returning, the crash procedure will be called, as it hasn't been cleaned from the stack @@ -70,9 +75,9 @@ namespace Components void AntiCheat::InitLoadLibHook() { - static uint8_t kernel32Str[] = {0xB4, 0x9A, 0x8D, 0xB1, 0x9A, 0x93, 0xCC, 0xCD, 0xD1, 0x9B, 0x93, 0x93}; // KerNel32.dll - static uint8_t loadLibAStr[] = {0xB3, 0x90, 0x9E, 0x9B, 0xB3, 0x96, 0x9D, 0x8D, 0x9E, 0x8D, 0x86, 0xBE}; // LoadLibraryA - static uint8_t loadLibWStr[] = {0xB3, 0x90, 0x9E, 0x9B, 0xB3, 0x96, 0x9D, 0x8D, 0x9E, 0x8D, 0x86, 0xA8}; // LoadLibraryW + static uint8_t kernel32Str[] = { 0xB4, 0x9A, 0x8D, 0xB1, 0x9A, 0x93, 0xCC, 0xCD, 0xD1, 0x9B, 0x93, 0x93 }; // KerNel32.dll + static uint8_t loadLibAStr[] = { 0xB3, 0x90, 0x9E, 0x9B, 0xB3, 0x96, 0x9D, 0x8D, 0x9E, 0x8D, 0x86, 0xBE }; // LoadLibraryA + static uint8_t loadLibWStr[] = { 0xB3, 0x90, 0x9E, 0x9B, 0xB3, 0x96, 0x9D, 0x8D, 0x9E, 0x8D, 0x86, 0xA8 }; // LoadLibraryW HMODULE kernel32 = GetModuleHandleA(Utils::String::XOR(std::string(reinterpret_cast(kernel32Str), sizeof kernel32Str), -1).data()); if (kernel32) @@ -109,6 +114,26 @@ namespace Components #endif } } + + static uint8_t ldrLoadDllStub[] = { 0x33, 0xC0, 0xC2, 0x10, 0x00 }; + static uint8_t ldrLoadDll[] = { 0xB3, 0x9B, 0x8D, 0xB3, 0x90, 0x9E, 0x9B, 0xBB, 0x93, 0x93 }; // LdrLoadDll + + HMODULE ntdll = Utils::GetNTDLL(); + AntiCheat::LoadLibHook[4].initialize(GetProcAddress(ntdll, Utils::String::XOR(std::string(reinterpret_cast(ldrLoadDll), sizeof ldrLoadDll), -1).data()), ldrLoadDllStub, HOOK_JUMP); + + // Patch LdrpLoadDll + Utils::Hook::Signature::Container container; + container.signature = "\x8B\xFF\x55\x8B\xEC\x83\xE4\xF8\x81\xEC\x00\x00\x00\x00\xA1\x00\x00\x00\x00\x33\xC4\x89\x84\x24\x00\x00\x00\x00\x53\x8B\x5D\x10\x56\x57"; + container.mask = "xxxxxxxxxx????x????xxxxx????xxxxxx"; + container.callback = [](char* addr) + { + static uint8_t ldrpLoadDllStub[] = { 0x33, 0xC0, 0xC2, 0x0C, 0x00 }; + AntiCheat::LoadLibHook[5].initialize(addr, ldrpLoadDllStub, HOOK_JUMP); + }; + + Utils::Hook::Signature signature(ntdll, Utils::GetModuleSize(ntdll)); + signature.add(container); + signature.process(); } void AntiCheat::ReadIntegrityCheck() @@ -254,10 +279,10 @@ namespace Components void AntiCheat::InstallLibHook() { - AntiCheat::LoadLibHook[0].install(); - AntiCheat::LoadLibHook[1].install(); - AntiCheat::LoadLibHook[2].install(); - AntiCheat::LoadLibHook[3].install(); + for(int i = 0; i < ARRAYSIZE(AntiCheat::LoadLibHook); ++i) + { + AntiCheat::LoadLibHook[i].install(); + } } void AntiCheat::PatchWinAPI() @@ -389,7 +414,7 @@ namespace Components BOOL WINAPI AntiCheat::VirtualProtectExStub(HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) { - if (GetCurrentProcess() == hProcess && !AntiCheat::IsPageChangeAllowed(_ReturnAddress(), lpAddress, dwSize)) return FALSE; + if (GetCurrentProcessId() == GetProcessId(hProcess) && !AntiCheat::IsPageChangeAllowed(_ReturnAddress(), lpAddress, dwSize)) return FALSE; AntiCheat::VirtualProtectHook[1].uninstall(false); BOOL result = VirtualProtectEx(hProcess, lpAddress, dwSize, flNewProtect, lpflOldProtect); @@ -562,6 +587,164 @@ namespace Components AntiCheat::VirtualProtectHook[0].initialize(vp, AntiCheat::VirtualProtectStub, HOOK_JUMP)->install(true, true); } + NTSTATUS NTAPI AntiCheat::NtCreateThreadExStub(PHANDLE phThread,ACCESS_MASK desiredAccess,LPVOID objectAttributes,HANDLE processHandle,LPTHREAD_START_ROUTINE startAddress,LPVOID parameter,BOOL createSuspended,DWORD stackZeroBits,DWORD sizeOfStackCommit,DWORD sizeOfStackReserve,LPVOID bytesBuffer) + { + HANDLE hThread = nullptr; + std::lock_guard _(AntiCheat::ThreadMutex); + + AntiCheat::CreateThreadHook.uninstall(); + NTSTATUS result = NtCreateThreadEx_t(AntiCheat::CreateThreadHook.getAddress())(&hThread, desiredAccess, objectAttributes, processHandle, startAddress, parameter, createSuspended, stackZeroBits, sizeOfStackCommit, sizeOfStackReserve, bytesBuffer); + AntiCheat::CreateThreadHook.install(); + + if (phThread) *phThread = hThread; + + if (GetProcessId(processHandle) == GetCurrentProcessId()) + { + AntiCheat::OwnThreadIds.push_back(GetThreadId(hThread)); + } + + return result; + } + + void AntiCheat::PatchThreadCreation() + { + HMODULE ntdll = Utils::GetNTDLL(); + if (ntdll) + { + static uint8_t ntCreateThreadEx[] = { 0xB1, 0x8B, 0xBC, 0x8D, 0x9A, 0x9E, 0x8B, 0x9A, 0xAB, 0x97, 0x8D, 0x9A, 0x9E, 0x9B, 0xBA, 0x87 }; // NtCreateThreadEx + FARPROC createThread = GetProcAddress(ntdll, Utils::String::XOR(std::string(reinterpret_cast(ntCreateThreadEx), sizeof ntCreateThreadEx), -1).data()); + if (createThread) + { + AntiCheat::CreateThreadHook.initialize(createThread, AntiCheat::NtCreateThreadExStub, HOOK_JUMP)->install(); + } + } + } + + int AntiCheat::ValidateThreadTermination(void* addr) + { + { + std::lock_guard _(AntiCheat::ThreadMutex); + + DWORD id = GetCurrentThreadId(); + auto threadHook = AntiCheat::ThreadHookMap.find(id); + if (threadHook != AntiCheat::ThreadHookMap.end()) + { + threadHook->second->uninstall(false); + AntiCheat::ThreadHookMap.erase(threadHook); // Uninstall and delete the hook + return 1; // Kill + } + } + + while(true) + { + std::lock_guard _(AntiCheat::ThreadMutex); + + // It would be better to wait for the thread + // but we don't know if there are multiple hooks at the same address + bool found = false; + for (auto threadHook : AntiCheat::ThreadHookMap) + { + if (threadHook.second->getAddress() == addr) + { + found = true; + break; + } + } + + if (!found) break; + std::this_thread::sleep_for(10ms); + } + + return 0; // Don't kill + } + + __declspec(naked) void AntiCheat::ThreadEntryPointStub() + { + __asm + { + push eax + push eax + pushad + + // Reinitialize the return address + mov eax, [esp + 28h] + sub eax, 5 + mov [esp + 28h], eax + + push eax + call AntiCheat::ValidateThreadTermination + add esp, 4h + + mov [esp + 20h], eax + + popad + + pop eax + + test eax, eax + jz dontKill + + pop eax + add esp, 4h // Remove return address (simulate a jump hook) + retn + + dontKill: + pop eax + retn + } + } + + void AntiCheat::VerifyThreadIntegrity() + { + bool kill = true; + { + std::lock_guard _(AntiCheat::ThreadMutex); + + auto threadHook = std::find(AntiCheat::OwnThreadIds.begin(), AntiCheat::OwnThreadIds.end(), GetCurrentThreadId()); + if (threadHook != AntiCheat::OwnThreadIds.end()) + { + AntiCheat::OwnThreadIds.erase(threadHook); + kill = false; + } + } + + if (kill) + { + static bool first = true; + if (first) first = false; // We can't control the main thread, as it's spawned externally + else + { + std::lock_guard _(AntiCheat::ThreadMutex); + + HMODULE ntdll = Utils::GetNTDLL(), targetModule; + if (!ntdll) return; // :( + + void* address = Utils::GetThreadStartAddress(GetCurrentThread()); + if (address) + { + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(address), &targetModule); + if (targetModule == ntdll) return; // Better not kill kernel threads + + DWORD id = GetCurrentThreadId(); + { + auto threadHook = AntiCheat::ThreadHookMap.find(id); + if (threadHook != AntiCheat::ThreadHookMap.end()) + { + threadHook->second->uninstall(false); + AntiCheat::ThreadHookMap.erase(threadHook); + } + } + + std::shared_ptr hook = std::make_shared(); + AntiCheat::ThreadHookMap[id] = hook; + + // Hook the entry point of the thread to properly terminate it + hook->initialize(address, AntiCheat::ThreadEntryPointStub, HOOK_CALL)->install(true, true); + } + } + } + } + AntiCheat::AntiCheat() { time(nullptr); @@ -611,6 +794,9 @@ namespace Components AntiCheat::Flags = NO_FLAG; AntiCheat::Hash.clear(); + AntiCheat::OwnThreadIds.clear(); + AntiCheat::ThreadHookMap.clear(); + for (int i = 0; i < ARRAYSIZE(AntiCheat::LoadLibHook); ++i) { AntiCheat::LoadLibHook[i].uninstall(); diff --git a/src/Components/Modules/AntiCheat.hpp b/src/Components/Modules/AntiCheat.hpp index 82149cc2..ce82edc3 100644 --- a/src/Components/Modules/AntiCheat.hpp +++ b/src/Components/Modules/AntiCheat.hpp @@ -27,6 +27,9 @@ namespace Components static unsigned long ProtectProcess(); static void PatchVirtualProtect(void* vp, void* vpex); + static void PatchThreadCreation(); + + static void VerifyThreadIntegrity(); private: enum IntergrityFlag @@ -79,7 +82,16 @@ namespace Components static void AcquireDebugPriviledge(HANDLE hToken); - static Utils::Hook LoadLibHook[4]; + static NTSTATUS NTAPI NtCreateThreadExStub(PHANDLE hThread, ACCESS_MASK desiredAccess, LPVOID objectAttributes, HANDLE processHandle, LPTHREAD_START_ROUTINE startAddress, LPVOID parameter, BOOL createSuspended, DWORD stackZeroBits, DWORD sizeOfStackCommit, DWORD sizeOfStackReserve, LPVOID bytesBuffer); + static int ValidateThreadTermination(void* addr); + static void ThreadEntryPointStub(); + + static std::mutex ThreadMutex; + static std::vector OwnThreadIds; + static std::map> ThreadHookMap; + + static Utils::Hook CreateThreadHook; + static Utils::Hook LoadLibHook[6]; static Utils::Hook VirtualProtectHook[2]; }; } diff --git a/src/Main.cpp b/src/Main.cpp index f0c535f2..899dc4c5 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -72,6 +72,7 @@ BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*l []() { Components::AntiCheat::ProtectProcess(); + Components::AntiCheat::PatchThreadCreation(); }(); #endif @@ -87,6 +88,15 @@ BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*l { Main::Uninitialize(); } + else if(ul_reason_for_call == DLL_THREAD_ATTACH) + { +#if !defined(DEBUG) && !defined(DISABLE_ANTICHEAT) + []() + { + Components::AntiCheat::VerifyThreadIntegrity(); + }(); +#endif + } return TRUE; } diff --git a/src/Utils/Utils.cpp b/src/Utils/Utils.cpp index c0cf82e3..6601ee8e 100644 --- a/src/Utils/Utils.cpp +++ b/src/Utils/Utils.cpp @@ -79,6 +79,37 @@ namespace Utils return ntHeader->OptionalHeader.SizeOfImage; } + void* GetThreadStartAddress(HANDLE hThread) + { + HMODULE ntdll = Utils::GetNTDLL(); + if (!ntdll) return nullptr; + + + static uint8_t ntQueryInformationThread[] = { 0xB1, 0x8B, 0xAE, 0x8A, 0x9A, 0x8D, 0x86, 0xB6, 0x91, 0x99, 0x90, 0x8D, 0x92, 0x9E, 0x8B, 0x96, 0x90, 0x91, 0xAB, 0x97, 0x8D, 0x9A, 0x9E, 0x9B }; // NtQueryInformationThread + NtQueryInformationThread_t NtQueryInformationThread = NtQueryInformationThread_t(GetProcAddress(ntdll, Utils::String::XOR(std::string(reinterpret_cast(ntQueryInformationThread), sizeof ntQueryInformationThread), -1).data())); + if (!NtQueryInformationThread) return nullptr; + + HANDLE dupHandle, currentProcess = GetCurrentProcess(); + if (!DuplicateHandle(currentProcess, hThread, currentProcess, &dupHandle, THREAD_QUERY_INFORMATION, FALSE, 0)) + { + SetLastError(ERROR_ACCESS_DENIED); + return nullptr; + } + + void* address = nullptr; + NTSTATUS status = NtQueryInformationThread(dupHandle, ThreadQuerySetWin32StartAddress, &address, sizeof(address), nullptr); + CloseHandle(dupHandle); + + if (status != 0) return nullptr; + return address; + } + + HMODULE GetNTDLL() + { + static uint8_t ntdll[] = { 0x91, 0x8B, 0x9B, 0x93, 0x93, 0xD1, 0x9B, 0x93, 0x93 }; // ntdll.dll + return GetModuleHandleA(Utils::String::XOR(std::string(reinterpret_cast(ntdll), sizeof ntdll), -1).data()); + } + bool HasIntercection(unsigned int base1, unsigned int len1, unsigned int base2, unsigned int len2) { return !(base1 + len1 <= base2 || base2 + len2 <= base1); diff --git a/src/Utils/Utils.hpp b/src/Utils/Utils.hpp index 6c0bcbe4..7ae60034 100644 --- a/src/Utils/Utils.hpp +++ b/src/Utils/Utils.hpp @@ -1,5 +1,10 @@ #pragma once +typedef LONG NTSTATUS; +typedef NTSTATUS(NTAPI *NtCreateThreadEx_t)(PHANDLE hThread, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, BOOL CreateSuspended, DWORD StackZeroBits, DWORD SizeOfStackCommit, DWORD SizeOfStackReserve, LPVOID lpBytesBuffer); +typedef NTSTATUS(NTAPI* NtQueryInformationThread_t)(HANDLE ThreadHandle, LONG ThreadInformationClass, PVOID ThreadInformation, ULONG ThreadInformationLength, PULONG ReturnLength); +#define ThreadQuerySetWin32StartAddress 9 + namespace Utils { std::string GetMimeType(std::string url); @@ -10,8 +15,9 @@ namespace Utils bool IsWineEnvironment(); unsigned long GetParentProcessId(); - size_t GetModuleSize(HMODULE module); + void* GetThreadStartAddress(HANDLE hThread); + HMODULE GetNTDLL(); bool HasIntercection(unsigned int base1, unsigned int len1, unsigned int base2, unsigned int len2);