Protect OpenProcess and co.
This commit is contained in:
parent
b0df3e0e21
commit
1ca490d953
2
deps/mongoose
vendored
2
deps/mongoose
vendored
@ -1 +1 @@
|
||||
Subproject commit 7b48859f8d7bfa8d7370f4b25136eb866f04d294
|
||||
Subproject commit 7cace648c0adba4272fdf820f9ee8a471fb788fc
|
@ -97,8 +97,15 @@ namespace Components
|
||||
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast<char*>(callee), &hModuleTarget);
|
||||
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast<char*>(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<uint32_t>(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
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <Wininet.h>
|
||||
//#include <Urlmon.h>
|
||||
#include <d3d9.h>
|
||||
#include <Aclapi.h>
|
||||
|
||||
//#include <map>
|
||||
//#include <mutex>
|
||||
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user