From 1ca490d953ffaf40708447e6afccc63b79aca21d Mon Sep 17 00:00:00 2001 From: momo5502 Date: Mon, 8 Aug 2016 14:49:53 +0200 Subject: [PATCH] Protect OpenProcess and co. --- deps/mongoose | 2 +- src/Components/Modules/AntiCheat.cpp | 257 ++++++++++++++++++++++++++- src/Components/Modules/AntiCheat.hpp | 6 + src/STDInclude.hpp | 2 + 4 files changed, 263 insertions(+), 4 deletions(-) diff --git a/deps/mongoose b/deps/mongoose index 7b48859f..7cace648 160000 --- a/deps/mongoose +++ b/deps/mongoose @@ -1 +1 @@ -Subproject commit 7b48859f8d7bfa8d7370f4b25136eb866f04d294 +Subproject commit 7cace648c0adba4272fdf820f9ee8a471fb788fc diff --git a/src/Components/Modules/AntiCheat.cpp b/src/Components/Modules/AntiCheat.cpp index 8aacbd97..9a355541 100644 --- a/src/Components/Modules/AntiCheat.cpp +++ b/src/Components/Modules/AntiCheat.cpp @@ -97,8 +97,15 @@ namespace Components GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(callee), &hModuleTarget); GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(AntiCheat::AssertCalleeModule), &hModuleSelf); - if (!hModuleSelf || !hModuleTarget || !hModuleProcess || (hModuleTarget != hModuleSelf &&hModuleTarget != hModuleProcess)) + if (!hModuleSelf || !hModuleTarget || !hModuleProcess || (hModuleTarget != hModuleSelf && hModuleTarget != hModuleProcess)) { +#ifdef DEBUG_DETECTIONS + char buffer[MAX_PATH] = { 0 }; + GetModuleFileNameA(hModuleTarget, buffer, sizeof buffer); + + OutputDebugStringA(Utils::String::VA("AntiCheat: Callee assertion failed: %X %s", reinterpret_cast(callee), buffer)); +#endif + //AntiCheat::CrashClient(); AntiCheat::Hash.append("\0", 1); } @@ -139,14 +146,22 @@ namespace Components void AntiCheat::IntegrityCheck() { + static int count = 0; int lastCheck = AntiCheat::LastCheck; int milliseconds = Game::Sys_Milliseconds(); + if (lastCheck) count = 0; + else ++count; + if (milliseconds < 1000 * 40) return; - // If there was no check within the last 90 seconds, crash! - if ((milliseconds - lastCheck) > 1000 * 90) + // If there was no check within the last 120 seconds, crash! + if ((lastCheck && (milliseconds - lastCheck) > 1000 * 120) || count > 1) { +#ifdef DEBUG_DETECTIONS + OutputDebugStringA("AntiCheat: Integrity check failed"); +#endif + AntiCheat::CrashClient(); } } @@ -167,6 +182,10 @@ namespace Components // Crash if the hashes don't match else if (AntiCheat::Hash != hash) { +#ifdef DEBUG_DETECTIONS + OutputDebugStringA("AntiCheat: Memory scan failed"); +#endif + AntiCheat::CrashClient(); } } @@ -307,6 +326,235 @@ namespace Components } } + DWORD AntiCheat::ProtectProcess() + { + // Returned to caller + DWORD dwResult = (DWORD)-1; + + // Released on exit + HANDLE hToken = NULL; + PVOID pTokenInfo = NULL; + + PSID psidEveryone = NULL; + PSID psidSystem = NULL; + PSID psidAdmins = NULL; + + PACL pDacl = NULL; + PSECURITY_DESCRIPTOR pSecDesc = NULL; + + __try + { + // Scratch + DWORD dwSize = 0; + BOOL bResult = FALSE; + + // If this fails, you can try to fallback to OpenThreadToken + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hToken)) + { + dwResult = GetLastError(); + assert(FALSE); + __leave; /*failed*/ + } + + bResult = GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize); + dwResult = GetLastError(); + assert(bResult == FALSE && ERROR_INSUFFICIENT_BUFFER == dwResult); + if (!(bResult == FALSE && ERROR_INSUFFICIENT_BUFFER == dwResult)) { __leave; /*failed*/ } + + if (dwSize) + { + pTokenInfo = HeapAlloc(GetProcessHeap(), 0, dwSize); + dwResult = GetLastError(); + assert(NULL != pTokenInfo); + if (NULL == pTokenInfo) { __leave; /*failed*/ } + } + + bResult = GetTokenInformation(hToken, TokenUser, pTokenInfo, dwSize, &dwSize); + dwResult = GetLastError(); + assert(bResult && pTokenInfo); + if (!(bResult && pTokenInfo)) { __leave; /*failed*/ } + + PSID psidCurUser = ((TOKEN_USER*)pTokenInfo)->User.Sid; + + SID_IDENTIFIER_AUTHORITY sidEveryone = SECURITY_WORLD_SID_AUTHORITY; + bResult = AllocateAndInitializeSid(&sidEveryone, 1, + SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &psidEveryone); + dwResult = GetLastError(); + assert(bResult && psidEveryone); + if (!(bResult && psidEveryone)) { __leave; /*failed*/ } + + SID_IDENTIFIER_AUTHORITY sidSystem = SECURITY_NT_AUTHORITY; + bResult = AllocateAndInitializeSid(&sidSystem, 1, + SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &psidSystem); + dwResult = GetLastError(); + + assert(bResult && psidSystem); + if (!(bResult && psidSystem)) { __leave; /*failed*/ } + + SID_IDENTIFIER_AUTHORITY sidAdministrators = SECURITY_NT_AUTHORITY; + bResult = AllocateAndInitializeSid(&sidAdministrators, 2, + SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, &psidAdmins); + + dwResult = GetLastError(); + assert(bResult && psidAdmins); + if (!(bResult && psidAdmins)) { __leave; /*failed*/ } + + const PSID psidArray[] = + { + psidEveryone, /* Deny most rights to everyone */ + psidCurUser, /* Allow what was not denied */ + psidSystem, /* Full control */ + psidAdmins, /* Full control */ + }; + + // Determine required size of the ACL + dwSize = sizeof(ACL); + + // First the DENY, then the ALLOW + dwSize += GetLengthSid(psidArray[0]); + dwSize += sizeof(ACCESS_DENIED_ACE) - sizeof(DWORD); + + for (UINT i = 1; i < _countof(psidArray); i++) + { + // DWORD is the SidStart field, which is not used for absolute format + dwSize += GetLengthSid(psidArray[i]); + dwSize += sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD); + } + + pDacl = (PACL)HeapAlloc(GetProcessHeap(), 0, dwSize); + dwResult = GetLastError(); + assert(NULL != pDacl); + if (NULL == pDacl) { __leave; /*failed*/ } + + bResult = InitializeAcl(pDacl, dwSize, ACL_REVISION); + dwResult = GetLastError(); + assert(TRUE == bResult); + if (FALSE == bResult) { __leave; /*failed*/ } + + // Mimic Protected Process + // http://www.microsoft.com/whdc/system/vista/process_vista.mspx + // Protected processes allow PROCESS_TERMINATE, which is + // probably not appropriate for high integrity software. + static const DWORD dwPoison = + /*READ_CONTROL |*/ WRITE_DAC | WRITE_OWNER | + PROCESS_CREATE_PROCESS | PROCESS_CREATE_THREAD | + PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | + PROCESS_SET_QUOTA | PROCESS_SET_INFORMATION | + PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | + // In addition to protected process + PROCESS_SUSPEND_RESUME | PROCESS_TERMINATE; + + bResult = AddAccessDeniedAce(pDacl, ACL_REVISION, dwPoison, psidArray[0]); + dwResult = GetLastError(); + assert(TRUE == bResult); + if (FALSE == bResult) { __leave; /*failed*/ } + + // Standard and specific rights not explicitly denied + static const DWORD dwAllowed = ~dwPoison & 0x1FFF; + bResult = AddAccessAllowedAce(pDacl, ACL_REVISION, dwAllowed, psidArray[1]); + dwResult = GetLastError(); + assert(TRUE == bResult); + if (FALSE == bResult) { __leave; /*failed*/ } + + // Because of ACE ordering, System will effectively have dwAllowed even + // though the ACE specifies PROCESS_ALL_ACCESS (unless software uses + // SeDebugPrivilege or SeTcbName and increases access). + // As an exercise, check behavior of tools such as Process Explorer under XP, + // Vista, and above. Vista and above should exhibit slightly different behavior + // due to Restricted tokens. + bResult = AddAccessAllowedAce(pDacl, ACL_REVISION, PROCESS_ALL_ACCESS, psidArray[2]); + dwResult = GetLastError(); + assert(TRUE == bResult); + if (FALSE == bResult) { __leave; /*failed*/ } + + // Because of ACE ordering, Administrators will effectively have dwAllowed + // even though the ACE specifies PROCESS_ALL_ACCESS (unless the Administrator + // invokes 'discretionary security' by taking ownership and increasing access). + // As an exercise, check behavior of tools such as Process Explorer under XP, + // Vista, and above. Vista and above should exhibit slightly different behavior + // due to Restricted tokens. + bResult = AddAccessAllowedAce(pDacl, ACL_REVISION, PROCESS_ALL_ACCESS, psidArray[3]); + dwResult = GetLastError(); + assert(TRUE == bResult); + if (FALSE == bResult) { __leave; /*failed*/ } + + pSecDesc = (PSECURITY_DESCRIPTOR)HeapAlloc(GetProcessHeap(), 0, SECURITY_DESCRIPTOR_MIN_LENGTH); + dwResult = GetLastError(); + assert(NULL != pSecDesc); + if (NULL == pSecDesc) { __leave; /*failed*/ } + + // InitializeSecurityDescriptor initializes a security descriptor in + // absolute format, rather than self-relative format. See + // http://msdn.microsoft.com/en-us/library/aa378863(VS.85).aspx + bResult = InitializeSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION); + dwResult = GetLastError(); + assert(TRUE == bResult); + if (FALSE == bResult) { __leave; /*failed*/ } + + bResult = SetSecurityDescriptorDacl(pSecDesc, TRUE, pDacl, FALSE); + dwResult = GetLastError(); + assert(TRUE == bResult); + if (FALSE == bResult) { __leave; /*failed*/ } + + dwResult = SetSecurityInfo( + GetCurrentProcess(), + SE_KERNEL_OBJECT, // process object + OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, + psidCurUser, // NULL, // Owner SID + NULL, // Group SID + pDacl, + NULL // SACL + ); + + dwResult = GetLastError(); + assert(ERROR_SUCCESS == dwResult); + if (ERROR_SUCCESS != dwResult) { __leave; /*failed*/ } + + dwResult = ERROR_SUCCESS; + } + __finally + { + if (NULL != pSecDesc) + { + HeapFree(GetProcessHeap(), 0, pSecDesc); + pSecDesc = NULL; + } + if (NULL != pDacl) + { + HeapFree(GetProcessHeap(), 0, pDacl); + pDacl = NULL; + } + if (psidAdmins) + { + FreeSid(psidAdmins); + psidAdmins = NULL; + } + if (psidSystem) + { + FreeSid(psidSystem); + psidSystem = NULL; + } + if (psidEveryone) + { + FreeSid(psidEveryone); + psidEveryone = NULL; + } + if (NULL != pTokenInfo) + { + HeapFree(GetProcessHeap(), 0, pTokenInfo); + pTokenInfo = NULL; + } + if (NULL != hToken) + { + CloseHandle(hToken); + hToken = NULL; + } + } + + return dwResult; + } + AntiCheat::AntiCheat() { AntiCheat::EmptyHash(); @@ -332,6 +580,9 @@ namespace Components // TODO: Probably move that :P AntiCheat::InitLoadLibHook(); + + // Prevent external processes from accessing our memory + AntiCheat::ProtectProcess(); #endif } diff --git a/src/Components/Modules/AntiCheat.hpp b/src/Components/Modules/AntiCheat.hpp index f9ed2e94..7c5796f3 100644 --- a/src/Components/Modules/AntiCheat.hpp +++ b/src/Components/Modules/AntiCheat.hpp @@ -1,6 +1,10 @@ // Uncomment that to see if we are preventing necessary libraries from being loaded //#define DEBUG_LOAD_LIBRARY +// Log detections +// Make sure to disable that before releasig! +//#define DEBUG_DETECTIONS + namespace Components { class AntiCheat : public Component @@ -25,6 +29,8 @@ namespace Components static void PerformCheck(); static void PatchWinAPI(); + static DWORD ProtectProcess(); + static void NullSub(); static void AssertCalleeModule(void* callee); diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index ce33141a..d05a997c 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -23,6 +23,7 @@ #include //#include #include +#include //#include //#include @@ -117,6 +118,7 @@ #pragma comment(lib, "Wininet.lib") #pragma comment(lib, "shlwapi.lib") #pragma comment(lib, "Urlmon.lib") +#pragma comment(lib, "Advapi32.lib") // Enable additional literals using namespace std::literals;