iw4x-client/src/Components/Modules/AntiCheat.cpp

559 lines
16 KiB
C++
Raw Normal View History

2017-01-19 16:23:59 -05:00
#include "STDInclude.hpp"
namespace Components
{
Utils::Time::Interval AntiCheat::LastCheck;
std::string AntiCheat::Hash;
Utils::Hook AntiCheat::LoadLibHook[4];
unsigned long AntiCheat::Flags = NO_FLAG;
// 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
__declspec(naked) void AntiCheat::NullSub()
{
__asm
{
push ebp
push ecx
mov ebp, esp
xor eax, eax
mov eax, [ebp + 8h]
mov ecx, [ebp + 0Ch]
add eax, ecx
pop ecx
pop ebp
retn 4
}
}
void AntiCheat::CrashClient()
{
#ifdef DEBUG_DETECTIONS
Logger::Flush();
2017-01-20 08:36:52 -05:00
MessageBoxA(nullptr, "Check the log for more information!", "AntiCheat triggered", MB_ICONERROR);
2017-01-19 16:23:59 -05:00
ExitProcess(0xFFFFFFFF);
#else
static std::thread triggerThread;
if (!triggerThread.joinable())
{
triggerThread = std::thread([] ()
{
std::this_thread::sleep_for(43s);
Utils::Hook::Set<BYTE>(0x41BA2C, 0xEB);
});
}
#endif
}
void AntiCheat::AssertCalleeModule(void* callee)
{
HMODULE hModuleSelf = nullptr, hModuleTarget = nullptr, hModuleProcess = GetModuleHandleA(nullptr);
2017-01-19 16:23:59 -05:00
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))
{
#ifdef DEBUG_DETECTIONS
char buffer[MAX_PATH] = { 0 };
GetModuleFileNameA(hModuleTarget, buffer, sizeof buffer);
Logger::Print(Utils::String::VA("AntiCheat: Callee assertion failed: %X %s", reinterpret_cast<uint32_t>(callee), buffer));
#endif
AntiCheat::CrashClient();
}
}
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
HMODULE kernel32 = GetModuleHandleA(Utils::String::XOR(std::string(reinterpret_cast<char*>(kernel32Str), sizeof kernel32Str), -1).data());
if (kernel32)
{
FARPROC loadLibA = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast<char*>(loadLibAStr), sizeof loadLibAStr), -1).data());
FARPROC loadLibW = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast<char*>(loadLibWStr), sizeof loadLibWStr), -1).data());
2017-02-13 12:32:07 -05:00
std::string libExA = Utils::String::XOR(std::string(reinterpret_cast<char*>(loadLibAStr), sizeof loadLibAStr), -1);
std::string libExW = Utils::String::XOR(std::string(reinterpret_cast<char*>(loadLibWStr), sizeof loadLibWStr), -1);
libExA.insert(libExA.end() - 1, 'E');
libExA.insert(libExA.end() - 1, 'x');
libExW.insert(libExW.end() - 1, 'E');
libExW.insert(libExW.end() - 1, 'x');
FARPROC loadLibExA = GetProcAddress(kernel32, libExA.data());
FARPROC loadLibExW = GetProcAddress(kernel32, libExW.data());
if (loadLibA && loadLibW && loadLibExA && loadLibExW)
2017-01-19 16:23:59 -05:00
{
#ifdef DEBUG_LOAD_LIBRARY
AntiCheat::LoadLibHook[0].initialize(loadLibA, LoadLibaryAStub, HOOK_JUMP);
AntiCheat::LoadLibHook[1].initialize(loadLibW, LoadLibaryWStub, HOOK_JUMP);
2017-02-13 12:32:07 -05:00
AntiCheat::LoadLibHook[2].initialize(loadLibExA, LoadLibaryAStub, HOOK_JUMP);
AntiCheat::LoadLibHook[3].initialize(loadLibExW, LoadLibaryWStub, HOOK_JUMP);
2017-01-19 16:23:59 -05:00
#else
static uint8_t loadLibStub[] = { 0x33, 0xC0, 0xC2, 0x04, 0x00 }; // xor eax, eax; retn 04h
2017-02-13 12:32:07 -05:00
static uint8_t loadLibExStub[] = { 0x33, 0xC0, 0xC2, 0x0C, 0x00 }; // xor eax, eax; retn 0Ch
2017-01-19 16:23:59 -05:00
AntiCheat::LoadLibHook[0].initialize(loadLibA, loadLibStub, HOOK_JUMP);
AntiCheat::LoadLibHook[1].initialize(loadLibW, loadLibStub, HOOK_JUMP);
2017-02-13 12:32:07 -05:00
AntiCheat::LoadLibHook[2].initialize(loadLibExA, loadLibExStub, HOOK_JUMP);
AntiCheat::LoadLibHook[3].initialize(loadLibExW, loadLibExStub, HOOK_JUMP);
2017-01-19 16:23:59 -05:00
#endif
}
}
}
void AntiCheat::ReadIntegrityCheck()
{
#ifdef PROCTECT_PROCESS
2017-01-19 16:23:59 -05:00
static Utils::Time::Interval check;
if(check.elapsed(20s))
{
check.update();
2017-03-10 20:30:24 -05:00
if (HANDLE h = OpenProcess(PROCESS_VM_READ, FALSE, GetCurrentProcessId()))
2017-01-19 16:23:59 -05:00
{
#ifdef DEBUG_DETECTIONS
Logger::Print("AntiCheat: Process integrity check failed");
#endif
CloseHandle(h);
AntiCheat::CrashClient();
}
}
// Set the integrity flag
AntiCheat::Flags |= AntiCheat::IntergrityFlag::READ_INTEGRITY_CHECK;
#endif
2017-01-19 16:23:59 -05:00
}
void AntiCheat::FlagIntegrityCheck()
{
static Utils::Time::Interval check;
if (check.elapsed(30s))
{
check.update();
unsigned long flags = ((AntiCheat::IntergrityFlag::MAX_FLAG - 1) << 1) - 1;
if (AntiCheat::Flags != flags)
{
#ifdef DEBUG_DETECTIONS
Logger::Print(Utils::String::VA("AntiCheat: Flag integrity check failed: %X", AntiCheat::Flags));
#endif
AntiCheat::CrashClient();
}
}
}
void AntiCheat::ScanIntegrityCheck()
{
// If there was no check within the last 40 seconds, crash!
if (AntiCheat::LastCheck.elapsed(40s))
{
#ifdef DEBUG_DETECTIONS
Logger::Print("AntiCheat: Integrity check failed");
#endif
AntiCheat::CrashClient();
}
// Set the integrity flag
AntiCheat::Flags |= AntiCheat::IntergrityFlag::SCAN_INTEGRITY_CHECK;
}
void AntiCheat::PerformScan()
{
// Perform check only every 10 seconds
if (!AntiCheat::LastCheck.elapsed(10s)) return;
AntiCheat::LastCheck.update();
// Hash .text segment
// Add 1 to each value, so searching in memory doesn't reveal anything
size_t textSize = 0x2D6001;
uint8_t* textBase = reinterpret_cast<uint8_t*>(0x401001);
std::string hash = Utils::Cryptography::SHA512::Compute(textBase - 1, textSize - 1, false);
// Set the hash, if none is set
if (AntiCheat::Hash.empty())
{
AntiCheat::Hash = hash;
}
// Crash if the hashes don't match
else if (AntiCheat::Hash != hash)
{
#ifdef DEBUG_DETECTIONS
Logger::Print("AntiCheat: Memory scan failed");
#endif
AntiCheat::CrashClient();
}
// Set the memory scan flag
AntiCheat::Flags |= AntiCheat::IntergrityFlag::MEMORY_SCAN;
}
#ifdef DEBUG_LOAD_LIBRARY
2017-02-13 12:32:07 -05:00
HANDLE AntiCheat::LoadLibary(std::wstring library, HANDLE file, DWORD flags, void* callee)
2017-01-19 16:23:59 -05:00
{
HMODULE module;
char buffer[MAX_PATH] = { 0 };
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast<char*>(callee), &module);
GetModuleFileNameA(module, buffer, sizeof buffer);
MessageBoxA(nullptr, Utils::String::VA("Loading library %s via %s %X", std::string(library.begin(), library.end()).data(), buffer, reinterpret_cast<uint32_t>(callee)), nullptr, 0);
2017-01-19 16:23:59 -05:00
2017-02-13 12:32:07 -05:00
AntiCheat::LoadLibHook[3].uninstall();
HANDLE h = LoadLibraryExW(library.data(), file, flags);
AntiCheat::LoadLibHook[3].install();
return h;
2017-01-19 16:23:59 -05:00
}
HANDLE WINAPI AntiCheat::LoadLibaryAStub(const char* library)
{
std::string lib(library);
2017-02-13 12:32:07 -05:00
return AntiCheat::LoadLibary(std::wstring(lib.begin(), lib.end()), nullptr, 0, _ReturnAddress());
2017-01-19 16:23:59 -05:00
}
HANDLE WINAPI AntiCheat::LoadLibaryWStub(const wchar_t* library)
{
2017-02-13 12:32:07 -05:00
return AntiCheat::LoadLibary(library, nullptr, 0, _ReturnAddress());
}
HANDLE WINAPI AntiCheat::LoadLibaryExAStub(const char* library, HANDLE file, DWORD flags)
{
std::string lib(library);
return AntiCheat::LoadLibary(std::wstring(lib.begin(), lib.end()), file, flags, _ReturnAddress());
}
HANDLE WINAPI AntiCheat::LoadLibaryExWStub(const wchar_t* library, HANDLE file, DWORD flags)
{
return AntiCheat::LoadLibary(library, file, flags, _ReturnAddress());
2017-01-19 16:23:59 -05:00
}
#endif
void AntiCheat::UninstallLibHook()
{
for (int i = 0; i < ARRAYSIZE(AntiCheat::LoadLibHook); ++i)
{
AntiCheat::LoadLibHook[i].uninstall();
}
}
void AntiCheat::InstallLibHook()
{
AntiCheat::LoadLibHook[0].install();
AntiCheat::LoadLibHook[1].install();
2017-02-13 12:32:07 -05:00
AntiCheat::LoadLibHook[2].install();
AntiCheat::LoadLibHook[3].install();
2017-01-19 16:23:59 -05:00
}
void AntiCheat::PatchWinAPI()
{
AntiCheat::UninstallLibHook();
// Initialize directx
Utils::Hook::Call<void()>(0x5078C0)();
AntiCheat::InstallLibHook();
}
void AntiCheat::SoundInitStub(int a1, int a2, int a3)
{
AntiCheat::UninstallLibHook();
Game::SND_Init(a1, a2, a3);
AntiCheat::InstallLibHook();
}
void AntiCheat::SoundInitDriverStub()
{
AntiCheat::UninstallLibHook();
Game::SND_InitDriver();
AntiCheat::InstallLibHook();
}
void AntiCheat::LostD3DStub()
{
AntiCheat::UninstallLibHook();
// Reset directx
Utils::Hook::Call<void()>(0x508070)();
AntiCheat::InstallLibHook();
}
__declspec(naked) void AntiCheat::CinematicStub()
{
__asm
{
pushad
call AntiCheat::UninstallLibHook
popad
call Game::R_Cinematic_StartPlayback_Now
pushad
call AntiCheat::InstallLibHook
popad
retn
}
}
__declspec(naked) void AntiCheat::DObjGetWorldTagPosStub()
{
__asm
{
pushad
push [esp + 20h]
call AntiCheat::AssertCalleeModule
pop esi
popad
push ecx
mov ecx, [esp + 10h]
push 426585h
retn
}
}
__declspec(naked) void AntiCheat::AimTargetGetTagPosStub()
{
__asm
{
pushad
push [esp + 20h]
call AntiCheat::AssertCalleeModule
pop esi
popad
sub esp, 14h
cmp dword ptr[esi + 0E0h], 1
push 56AC6Ah
ret
}
}
unsigned long AntiCheat::ProtectProcess()
{
#ifdef PROCTECT_PROCESS
if (Dedicated::IsEnabled()) return 0;
2017-01-19 16:23:59 -05:00
Utils::Memory::Allocator allocator;
HANDLE hToken = nullptr;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_READ, &hToken))
2017-01-19 16:23:59 -05:00
{
if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_READ, TRUE, &hToken))
2017-01-19 16:23:59 -05:00
{
return GetLastError();
}
}
auto freeSid = [] (void* sid)
{
if (sid)
{
FreeSid(reinterpret_cast<PSID>(sid));
}
};
allocator.reference(hToken, [] (void* hToken)
{
if (hToken)
{
CloseHandle(hToken);
}
});
AntiCheat::AcquireDebugPriviledge(hToken);
2017-01-19 16:23:59 -05:00
DWORD dwSize = 0;
PVOID pTokenInfo = nullptr;
2017-01-20 08:36:52 -05:00
if (GetTokenInformation(hToken, TokenUser, nullptr, 0, &dwSize) || GetLastError() != ERROR_INSUFFICIENT_BUFFER) return GetLastError();
2017-01-19 16:23:59 -05:00
if (dwSize)
{
pTokenInfo = allocator.allocate(dwSize);
if (!pTokenInfo) return GetLastError();
}
if (!GetTokenInformation(hToken, TokenUser, pTokenInfo, dwSize, &dwSize) || !pTokenInfo) return GetLastError();
PSID psidCurUser = reinterpret_cast<TOKEN_USER*>(pTokenInfo)->User.Sid;
PSID psidEveryone = nullptr;
SID_IDENTIFIER_AUTHORITY sidEveryone = SECURITY_WORLD_SID_AUTHORITY;
if (!AllocateAndInitializeSid(&sidEveryone, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &psidEveryone) || !psidEveryone) return GetLastError();
allocator.reference(psidEveryone, freeSid);
PSID psidSystem = nullptr;
SID_IDENTIFIER_AUTHORITY sidSystem = SECURITY_NT_AUTHORITY;
if (!AllocateAndInitializeSid(&sidSystem, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &psidSystem) || !psidSystem) return GetLastError();
allocator.reference(psidSystem, freeSid);
PSID psidAdmins = nullptr;
SID_IDENTIFIER_AUTHORITY sidAdministrators = SECURITY_NT_AUTHORITY;
if (!AllocateAndInitializeSid(&sidAdministrators, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &psidAdmins) || !psidAdmins) return GetLastError();
allocator.reference(psidAdmins, freeSid);
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);
}
PACL pDacl = reinterpret_cast<PACL>(allocator.allocate(dwSize));
if (!pDacl || !InitializeAcl(pDacl, dwSize, ACL_REVISION)) return GetLastError();
// Just give access to what steam needs
static const DWORD dwPoison = 0UL | ~(SYNCHRONIZE | GENERIC_EXECUTE | GENERIC_ALL);
2017-01-19 16:23:59 -05:00
if (!AddAccessDeniedAce(pDacl, ACL_REVISION, dwPoison, psidArray[0])) return GetLastError();
// Standard and specific rights not explicitly denied
static const DWORD dwAllowed = 0UL | SYNCHRONIZE;
2017-01-19 16:23:59 -05:00
if (!AddAccessAllowedAce(pDacl, ACL_REVISION, dwAllowed, psidArray[1])) return GetLastError();
// 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.
if (!AddAccessAllowedAce(pDacl, ACL_REVISION, PROCESS_ALL_ACCESS, psidArray[2])) return GetLastError();
// 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.
if (!AddAccessAllowedAce(pDacl, ACL_REVISION, PROCESS_ALL_ACCESS, psidArray[3])) return GetLastError();
PSECURITY_DESCRIPTOR pSecDesc = allocator.allocate<SECURITY_DESCRIPTOR>();
if (!pSecDesc) return GetLastError();
// 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
if (!InitializeSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION)) return GetLastError();
if (!SetSecurityDescriptorDacl(pSecDesc, TRUE, pDacl, FALSE)) return GetLastError();
return SetSecurityInfo(
GetCurrentProcess(),
SE_KERNEL_OBJECT, // process object
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
psidCurUser, // NULL, // Owner SID
2017-01-20 08:36:52 -05:00
nullptr, // Group SID
2017-01-19 16:23:59 -05:00
pDacl,
2017-01-20 08:36:52 -05:00
nullptr // SACL
2017-01-19 16:23:59 -05:00
);
#else
return 0;
#endif
2017-01-19 16:23:59 -05:00
}
void AntiCheat::AcquireDebugPriviledge(HANDLE hToken)
{
LUID luid;
TOKEN_PRIVILEGES tp = { 0 };
DWORD cb = sizeof(TOKEN_PRIVILEGES);
if (!LookupPrivilegeValueW(nullptr, SE_DEBUG_NAME, &luid)) return;
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tp, cb, nullptr, nullptr);
//if (GetLastError() != ERROR_SUCCESS) return;
}
2017-01-19 16:23:59 -05:00
AntiCheat::AntiCheat()
{
AntiCheat::Flags = NO_FLAG;
AntiCheat::Hash.clear();
#ifdef DEBUG
Command::Add("penis", [] (Command::Params*)
{
AntiCheat::CrashClient();
});
#else
Utils::Hook(0x507BD5, AntiCheat::PatchWinAPI, HOOK_CALL).install()->quick();
Utils::Hook(0x5082FD, AntiCheat::LostD3DStub, HOOK_CALL).install()->quick();
Utils::Hook(0x51C76C, AntiCheat::CinematicStub, HOOK_CALL).install()->quick();
Utils::Hook(0x418209, AntiCheat::SoundInitStub, HOOK_CALL).install()->quick();
Utils::Hook(0x60BE9D, AntiCheat::SoundInitStub, HOOK_CALL).install()->quick();
Utils::Hook(0x60BE8E, AntiCheat::SoundInitDriverStub, HOOK_CALL).install()->quick();
Utils::Hook(0x418204, AntiCheat::SoundInitDriverStub, HOOK_CALL).install()->quick();
Renderer::OnFrame(AntiCheat::PerformScan);
// Detect aimbots
Utils::Hook(0x426580, AntiCheat::DObjGetWorldTagPosStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x56AC60, AntiCheat::AimTargetGetTagPosStub, HOOK_JUMP).install()->quick();
// TODO: Probably move that :P
if (!Dedicated::IsEnabled())
{
AntiCheat::InitLoadLibHook();
}
// Prevent external processes from accessing our memory
AntiCheat::ProtectProcess();
Renderer::OnDeviceRecoveryEnd([] ()
{
AntiCheat::ProtectProcess();
});
// Set the integrity flag
AntiCheat::Flags |= AntiCheat::IntergrityFlag::INITIALIZATION;
#endif
}
AntiCheat::~AntiCheat()
{
AntiCheat::Flags = NO_FLAG;
AntiCheat::Hash.clear();
for (int i = 0; i < ARRAYSIZE(AntiCheat::LoadLibHook); ++i)
{
AntiCheat::LoadLibHook[i].uninstall();
}
}
}