aboutsummaryrefslogtreecommitdiff
path: root/NorthstarDedicatedTest/ExploitFixes.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'NorthstarDedicatedTest/ExploitFixes.cpp')
-rw-r--r--NorthstarDedicatedTest/ExploitFixes.cpp177
1 files changed, 143 insertions, 34 deletions
diff --git a/NorthstarDedicatedTest/ExploitFixes.cpp b/NorthstarDedicatedTest/ExploitFixes.cpp
index d36b4175..36bd36f4 100644
--- a/NorthstarDedicatedTest/ExploitFixes.cpp
+++ b/NorthstarDedicatedTest/ExploitFixes.cpp
@@ -3,39 +3,36 @@
#include "ExploitFixes.h"
#include "ExploitFixes_UTF8Parser.h"
#include "NSMem.h"
+#include "cvar.h"
// Make sure 3 or less floats are valid
-bool ValidateFloats(float a, float b = 0, float c = 0) {
- return !isnan(a) && !isnan(b) && !isnan(c);
-}
+bool ValidateFloats(float a, float b = 0, float c = 0) { return !isnan(a) && !isnan(b) && !isnan(c); }
-struct Vector {
+struct Vector
+{
float x, y, z;
Vector(float x = 0, float y = 0, float z = 0) : x(x), y(y), z(z) {}
- bool IsValid() {
- return ValidateFloats(x, y, z);
- }
+ bool IsValid() { return ValidateFloats(x, y, z); }
};
-struct Angle {
+struct Angle
+{
float pitch, yaw, roll;
Angle(float pitch = 0, float yaw = 0, float roll = 0) : pitch(pitch), yaw(yaw), roll(roll) {}
- bool IsInvalid() {
+ bool IsInvalid()
+ {
if (!ValidateFloats(pitch, yaw, roll))
return false;
- return
- (pitch > 90 || pitch < -90)
- || (yaw > 180 || yaw < -180)
- || (roll > 180 || roll < -180);
+ return (pitch > 90 || pitch < -90) || (yaw > 180 || yaw < -180) || (roll > 180 || roll < -180);
}
};
-#define BLOCK_NETMSG_FUNC(name, pattern) \
+#define BLOCK_NETMSG_FUNC(name, pattern) \
KHOOK(name, ("engine.dll", pattern), bool, __fastcall, (void* thisptr, void* buffer)) { return false; }
// Servers can literally request a screenshot from any client, yeah no
@@ -45,9 +42,83 @@ BLOCK_NETMSG_FUNC(CLC_Screenshot_ReadFromBuffer, "48 89 5C 24 ? 48 89 6C 24 ? 48
// This is unused ingame and a big exploit vector
BLOCK_NETMSG_FUNC(Base_CmdKeyValues_ReadFromBuffer, "40 55 48 81 EC ? ? ? ? 48 8D 6C 24 ? 48 89 5D 70");
+KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10"), bool, __fastcall, (void* pMsg))
+{
+
+ constexpr int ENTRY_STR_LEN = 260;
+ struct SetConVarEntry
+ {
+ char name[ENTRY_STR_LEN];
+ char val[ENTRY_STR_LEN];
+ };
+
+ struct NET_SetConVar
+ {
+ void* vtable;
+ void* unk1;
+ void* unk2;
+ void* m_pMessageHandler;
+ SetConVarEntry* m_ConVars; // convar entry array
+ void* unk5; // these 2 unks are just vector capacity or whatever
+ void* unk6;
+ int m_ConVars_count; // amount of cvar entries in array (this will not be out of bounds)
+ };
+
+ auto msg = (NET_SetConVar*)pMsg;
+
+ constexpr int SETCONVAR_SANITY_AMOUNT_LIMIT = 20;
+ if (msg->m_ConVars_count < 1 || msg->m_ConVars_count > SETCONVAR_SANITY_AMOUNT_LIMIT)
+ return false; // Nope
+
+ for (int i = 0; i < msg->m_ConVars_count; i++)
+ {
+ auto entry = msg->m_ConVars + i;
+
+ // Safety check for memory access
+ if (NSMem::IsMemoryReadable(entry, sizeof(*entry)))
+ {
+
+ // Find null terminators
+ bool nameValid = false, valValid = false;
+ for (int i = 0; i < ENTRY_STR_LEN; i++)
+ {
+ if (!entry->name[i])
+ nameValid = true;
+ if (!entry->val[i])
+ valValid = true;
+ }
+
+ if (!nameValid || !valValid)
+ return false; // Missing null terminators
+
+ auto realVar = g_pCVar->FindVar(entry->name);
+
+ if (!realVar)
+ // Not an actual cvar, no thanks
+ return false;
+
+ // Force name to match case
+ memcpy(entry->name, realVar->m_ConCommandBase.m_pszName, strlen(realVar->m_ConCommandBase.m_pszName) + 1);
+
+ if (!ConVar::IsFlagSet(realVar, FCVAR_USERINFO) || ConVar::IsFlagSet(realVar, FCVAR_CHEAT))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false; // Not risking that one, they all gotta be readable
+ }
+ }
+
+ return oCClient_ProcessSetConVar(msg);
+}
+
// Purpose: prevent invalid user CMDs
-KHOOK(CClient_ProcessUsercmds, ("engine.dll", "40 55 56 48 83 EC 58"), bool, __fastcall, (void* thisptr, void* pMsg)) {
- struct __declspec(align(8)) CLC_Move {
+KHOOK(CClient_ProcessUsercmds, ("engine.dll", "40 55 56 48 83 EC 58"), bool, __fastcall, (void* thisptr, void* pMsg))
+{
+ struct CLC_Move
+ {
BYTE gap0[24];
void* m_pMessageHandler;
int m_nBackupCommands;
@@ -72,7 +143,8 @@ KHOOK(CClient_ProcessUsercmds, ("engine.dll", "40 55 56 48 83 EC 58"), bool, __f
return oCClient_ProcessUsercmds(thisptr, pMsg);
}
-KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from)) {
+KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from))
+{
// Let normal usercmd read happen first, it's safe
oReadUsercmd(buf, pCmd_move, pCmd_from);
@@ -105,20 +177,18 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
};
auto cmd = (SV_CUserCmd*)pCmd_move;
- if (
- cmd->worldViewAngles.IsInvalid() ||
- cmd->localViewAngles.IsInvalid() ||
- cmd->attackangles.IsInvalid() ||
- cmd->cameraAngles.IsInvalid()) {
+ if (cmd->worldViewAngles.IsInvalid() || cmd->localViewAngles.IsInvalid() || cmd->attackangles.IsInvalid() ||
+ cmd->cameraAngles.IsInvalid())
+ {
goto INVALID_CMD;
}
if (cmd->frameTime <= 0 || cmd->tick_count == 0 || cmd->command_time <= 0)
goto INVALID_CMD; // No simulation of bogus-timed cmds
- if (!cmd->move.IsValid() || // Prevent player freeze (and even server crash) exploit
+ if (!cmd->move.IsValid() || // Prevent player freeze (and even server crash) exploit
!cmd->cameraPos.IsValid()) // IIRC this can crash spectating clients or anyone watching replays
- goto INVALID_CMD;
+ goto INVALID_CMD;
if (!ValidateFloats(cmd->cameraPos.x, cmd->cameraPos.y, cmd->cameraPos.z))
goto INVALID_CMD; // IIRC this can crash spectating clients or anyone watching replays
@@ -128,7 +198,7 @@ INVALID_CMD:
// Fix any gameplay-affecting cmd properties
// NOTE: Currently tickcount/frametime is set to 0, this ~shouldn't~ cause any problems
- cmd->worldViewAngles = cmd->localViewAngles = cmd->attackangles = cmd->cameraAngles = Angle(0,0,0);
+ cmd->worldViewAngles = cmd->localViewAngles = cmd->attackangles = cmd->cameraAngles = Angle(0, 0, 0);
cmd->tick_count = cmd->frameTime = 0;
cmd->move = cmd->cameraPos = Vector(0, 0, 0);
cmd->buttons = 0;
@@ -139,19 +209,24 @@ INVALID_CMD:
// this is HORRIBLE for security, because it means servers can run arbitrary concommands on clients
// especially since we have script commands this could theoretically be awful
#include "gameutils.h"
-KHOOK(IsValveMod, ("engine.dll", "48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63"), bool, __fastcall, ()) {
+KHOOK(IsValveMod, ("engine.dll", "48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63"), bool, __fastcall, ())
+{
return !CommandLine()->CheckParm("-norestrictservercommands");
}
// Fix respawn's crappy UTF8 parser so it doesn't crash -_-
// This also means you can launch multiplayer with "communities_enabled 1" and not crash, you're welcome
-KHOOK(CrashFunc_ParseUTF8, ("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 41 54 41 55 41 56 41 57 48 83 EC 20 8B 1A"),
- bool, __fastcall, (INT64* a1, DWORD* a2, char* strData)) {
+KHOOK(
+ CrashFunc_ParseUTF8, ("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 41 54 41 55 41 56 41 57 48 83 EC 20 8B 1A"), bool,
+ __fastcall, (INT64 * a1, DWORD* a2, char* strData))
+{
static void* targetRetAddr = NSMem::PatternScan("engine.dll", "84 C0 75 2C 49 8B 16");
- if (_ReturnAddress() == targetRetAddr) {
- if (!ExploitFixes_UTF8Parser::CheckValid(a1, a2, strData)) {
+ if (_ReturnAddress() == targetRetAddr)
+ {
+ if (!ExploitFixes_UTF8Parser::CheckValid(a1, a2, strData))
+ {
spdlog::warn("ParseUTF8 Hook: Ignoring potentially-crashing utf8 string");
return false;
}
@@ -162,8 +237,10 @@ KHOOK(CrashFunc_ParseUTF8, ("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74
//////////////////////////////////////////////////
-void DoBytePatches() {
+void DoBytePatches()
+{
uintptr_t engineBase = (uintptr_t)GetModuleHandleA("engine.dll");
+ uintptr_t serverBase = (uintptr_t)GetModuleHandleA("server.dll");
// patches to make commands run from client/ui script still work
// note: this is likely preventable in a nicer way? test prolly
@@ -176,17 +253,49 @@ void DoBytePatches() {
int val = *(int*)addr | FCVAR_SERVER_CAN_EXECUTE;
NSMem::BytePatch(addr, (BYTE*)&val, sizeof(int));
}
+
+ { // Dumb ANTITAMPER patches (they negatively impact performance and security)
+
+ constexpr const char* ANTITAMPER_EXPORTS[] = {
+ "ANTITAMPER_SPOTCHECK_CODEMARKER",
+ "ANTITAMPER_TESTVALUE_CODEMARKER",
+ "ANTITAMPER_TRIGGER_CODEMARKER",
+ };
+
+ // Prevent thesefrom actually doing anything
+ for (auto exportName : ANTITAMPER_EXPORTS)
+ {
+
+ auto address = (uintptr_t)GetProcAddress(NULL, exportName);
+ if (!address)
+ {
+ spdlog::warn("Failed to find AntiTamper function export \"{}\"", exportName);
+ }
+ else
+ {
+
+ // Just return, none of them have any args or are userpurge
+ NSMem::BytePatch(address, {0xC3});
+
+ spdlog::info("Patched AntiTamper function export \"{}\"", exportName);
+ }
+ }
+ }
}
-void ExploitFixes::LoadCallback(HMODULE unused) {
+void ExploitFixes::LoadCallback(HMODULE unused)
+{
spdlog::info("ExploitFixes::LoadCallback ...");
spdlog::info("\tByte patching...");
DoBytePatches();
- if (KHook::InitAllHooks()) {
+ if (KHook::InitAllHooks())
+ {
spdlog::info("\tInitialized " + std::to_string(KHook::_allHooks.size()) + " exploit-patch hooks.");
- } else {
+ }
+ else
+ {
spdlog::critical("\tFAILED to initialize all exploit patches.");
// Force exit?