aboutsummaryrefslogtreecommitdiff
path: root/NorthstarDLL/ExploitFixes.cpp
diff options
context:
space:
mode:
authorBobTheBob9 <for.oliver.kirkham@gmail.com>2022-07-07 21:31:41 +0100
committerBobTheBob9 <for.oliver.kirkham@gmail.com>2022-07-07 21:31:41 +0100
commit2ae34b67e36b8ba05132d481876eb4ed7a826283 (patch)
tree63f44c8e2dcdc959d7a5317a3a7b36efedbd7d38 /NorthstarDLL/ExploitFixes.cpp
parent3406de7aaaf52cbef20b1549f2d7da0255d30f51 (diff)
downloadNorthstarLauncher-2ae34b67e36b8ba05132d481876eb4ed7a826283.tar.gz
NorthstarLauncher-2ae34b67e36b8ba05132d481876eb4ed7a826283.zip
almost fully replaced hooking lib
Diffstat (limited to 'NorthstarDLL/ExploitFixes.cpp')
-rw-r--r--NorthstarDLL/ExploitFixes.cpp828
1 files changed, 414 insertions, 414 deletions
diff --git a/NorthstarDLL/ExploitFixes.cpp b/NorthstarDLL/ExploitFixes.cpp
index 154091df..8f41ac32 100644
--- a/NorthstarDLL/ExploitFixes.cpp
+++ b/NorthstarDLL/ExploitFixes.cpp
@@ -1,415 +1,415 @@
-#include "pch.h"
-
-#include "ExploitFixes.h"
-#include "ExploitFixes_UTF8Parser.h"
-#include "NSMem.h"
-#include "cvar.h"
-#include "tier0.h"
-
-ConVar* ns_exploitfixes_log;
-#define SHOULD_LOG (ns_exploitfixes_log->m_Value.m_nValue > 0)
-#define BLOCKED_INFO(s) \
- ( \
- [=]() -> bool \
- { \
- if (SHOULD_LOG) \
- { \
- std::stringstream stream; \
- stream << "ExploitFixes.cpp: " << BLOCK_PREFIX << s; \
- spdlog::error(stream.str()); \
- } \
- return false; \
- }())
-
-// 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);
-}
-
-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);
- }
-};
-
-struct Angle
-{
- float pitch, yaw, roll;
-
- Angle(float pitch = 0, float yaw = 0, float roll = 0) : pitch(pitch), yaw(yaw), roll(roll) {}
-
- bool IsInvalid()
- {
- return !ValidateFloats(pitch, yaw, roll);
-
- if (!ValidateFloats(pitch, yaw, roll))
- return false;
-
- return (pitch > 90 || pitch < -90) || (yaw > 180 || yaw < -180) || (roll > 180 || roll < -180);
- }
-};
-
-#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
-BLOCK_NETMSG_FUNC(CLC_Screenshot_WriteToBuffer, "48 89 5C 24 ? 57 48 83 EC 20 8B 42 10");
-BLOCK_NETMSG_FUNC(CLC_Screenshot_ReadFromBuffer, "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC 20 48 8B DA 48 8B 52 38");
-
-// 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;
-
- bool areWeServer;
-
- {
- // Figure out of we are the client or the server
- // To do this, we utilize the msg's m_pMessageHandler pointer
- // m_pMessageHandler points to a virtual class that handles all net messages
- // The first virtual table function of our m_pMessageHandler will differ if it is IServerMessageHandler or IClientMessageHandler
- void* msgHandlerVTableFirstFunc = **(void****)(msg->m_pMessageHandler);
- static auto engineBaseAddress = (uintptr_t)GetModuleHandleA("engine.dll");
- auto offset = uintptr_t(msgHandlerVTableFirstFunc) - engineBaseAddress;
-
- constexpr uintptr_t CLIENTSTATE_FIRST_VFUNC_OFFSET = 0x8A15C;
- areWeServer = offset != CLIENTSTATE_FIRST_VFUNC_OFFSET;
- }
-
- std::string BLOCK_PREFIX = std::string {"NET_SetConVar ("} + (areWeServer ? "server" : "client") + "): Blocked dangerous/invalid msg: ";
-
- if (areWeServer)
- {
- constexpr int SETCONVAR_SANITY_AMOUNT_LIMIT = 69;
- if (msg->m_ConVars_count < 1 || msg->m_ConVars_count > SETCONVAR_SANITY_AMOUNT_LIMIT)
- {
- return BLOCKED_INFO("Invalid m_ConVars_count (" << msg->m_ConVars_count << ")");
- }
- }
-
- 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 BLOCKED_INFO("Missing null terminators");
-
- auto realVar = R2::g_pCVar->FindVar(entry->name);
-
- if (realVar)
- memcpy(
- entry->name,
- realVar->m_ConCommandBase.m_pszName,
- strlen(realVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case
-
- bool isValidFlags = true;
- if (areWeServer)
- {
- if (realVar)
- isValidFlags = realVar->IsFlagSet(FCVAR_USERINFO); // ConVar MUST be userinfo var
- }
- else
- {
- // TODO: Should probably have some sanity checks, but can't find any that are consistent
- }
-
- if (!isValidFlags)
- {
- if (!realVar)
- {
- return BLOCKED_INFO("Invalid flags on nonexistant cvar (how tho???)");
- }
- else
- {
- return BLOCKED_INFO(
- "Invalid flags (" << std::hex << "0x" << realVar->m_ConCommandBase.m_nFlags << "), var is " << entry->name);
- }
- }
- }
- else
- {
- return BLOCKED_INFO("Unreadable memory at " << (void*)entry); // 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 CLC_Move
- {
- BYTE gap0[24];
- void* m_pMessageHandler;
- int m_nBackupCommands;
- int m_nNewCommands;
- int m_nLength;
- // bf_read m_DataIn;
- // bf_write m_DataOut;
- };
-
- auto msg = (CLC_Move*)pMsg;
-
- const char* BLOCK_PREFIX = "ProcessUserCmds: ";
-
- if (msg->m_nBackupCommands < 0)
- {
- return BLOCKED_INFO("Invalid m_nBackupCommands (" << msg->m_nBackupCommands << ")");
- }
-
- if (msg->m_nNewCommands < 0)
- {
- return BLOCKED_INFO("Invalid m_nNewCommands (" << msg->m_nNewCommands << ")");
- }
-
- // removing, as vanilla already limits num usercmds per frame
- /*constexpr int NUMCMD_SANITY_LIMIT = 16;
- if ((msg->m_nNewCommands + msg->m_nBackupCommands) > NUMCMD_SANITY_LIMIT)
- {
- return BLOCKED_INFO("Command count is too high (new: " << msg->m_nNewCommands << ", backup: " << msg->m_nBackupCommands << ")");
-
- }*/
-
- if (msg->m_nLength <= 0)
- return BLOCKED_INFO("Invalid message length (" << msg->m_nLength << ")");
-
- 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))
-{
- // Let normal usercmd read happen first, it's safe
- oReadUsercmd(buf, pCmd_move, pCmd_from);
-
- // Now let's make sure the CMD we read isnt messed up to prevent numerous exploits (including server crashing)
- struct __declspec(align(4)) SV_CUserCmd
- {
- DWORD command_number;
- DWORD tick_count;
- float command_time;
- Angle worldViewAngles;
- BYTE gap18[4];
- Angle localViewAngles;
- Angle attackangles;
- Vector move;
- DWORD buttons;
- BYTE impulse;
- short weaponselect;
- DWORD meleetarget;
- BYTE gap4C[24];
- char headoffset;
- BYTE gap65[11];
- Vector cameraPos;
- Angle cameraAngles;
- BYTE gap88[4];
- int tickSomething;
- DWORD dword90;
- DWORD predictedServerEventAck;
- DWORD dword98;
- float frameTime;
- };
-
- auto cmd = (SV_CUserCmd*)pCmd_move;
- auto fromCmd = (SV_CUserCmd*)pCmd_from;
-
- std::string BLOCK_PREFIX =
- "ReadUsercmd (command_number delta: " + std::to_string(cmd->command_number - fromCmd->command_number) + "): ";
-
- if (cmd->worldViewAngles.IsInvalid())
- {
- BLOCKED_INFO("CMD has invalid worldViewAngles");
- goto INVALID_CMD;
- }
-
- if (cmd->attackangles.IsInvalid())
- {
- BLOCKED_INFO("CMD has invalid attackangles");
- goto INVALID_CMD;
- }
-
- if (cmd->localViewAngles.IsInvalid())
- {
- BLOCKED_INFO("CMD has invalid localViewAngles");
- goto INVALID_CMD;
- }
-
- if (cmd->cameraAngles.IsInvalid())
- {
- BLOCKED_INFO("CMD has invalid cameraAngles");
- goto INVALID_CMD;
- }
-
- if (cmd->frameTime <= 0 || cmd->tick_count == 0 || cmd->command_time <= 0)
- {
- BLOCKED_INFO(
- "Bogus cmd timing (tick_count: " << cmd->tick_count << ", frameTime: " << cmd->frameTime
- << ", commandTime : " << cmd->command_time << ")");
- goto INVALID_CMD; // No simulation of bogus-timed cmds
- }
-
- if (!cmd->move.IsValid())
- {
- BLOCKED_INFO("Invalid move vector");
- goto INVALID_CMD;
- }
-
- if (!cmd->cameraPos.IsValid())
- {
- BLOCKED_INFO("Invalid cameraPos"); // IIRC this can crash spectating clients or anyone watching replays
- goto INVALID_CMD;
- }
-
- return;
-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->tick_count = cmd->frameTime = 0;
- cmd->move = cmd->cameraPos = Vector(0, 0, 0);
- cmd->buttons = 0;
- cmd->meleetarget = 0;
-}
-
-// basically: by default r2 isn't set as a valve mod, meaning that m_bRestrictServerCommands is false
-// 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
-KHOOK(IsValveMod, ("engine.dll", "48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63"), bool, __fastcall, ())
-{
- return !Tier0::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))
-{
-
- static void* targetRetAddr = NSMem::PatternScan("engine.dll", "84 C0 75 2C 49 8B 16");
-
- if (_ReturnAddress() == targetRetAddr)
- {
- if (!ExploitFixes_UTF8Parser::CheckValid(a1, a2, strData))
- {
- const char* BLOCK_PREFIX = "ParseUTF8 Hook: ";
- BLOCKED_INFO("Ignoring potentially-crashing utf8 string");
- return false;
- }
- }
-
- return oCrashFunc_ParseUTF8(a1, a2, strData);
-}
-
-//////////////////////////////////////////////////
-
-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
- NSMem::BytePatch(engineBase + 0x4FB65, "EB 11");
- NSMem::BytePatch(engineBase + 0x4FBAC, "EB 16");
-
- { // 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(GetModuleHandleA("server.dll"), 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, "C3");
- spdlog::info("Patched AntiTamper function export \"{}\"", exportName);
- }
- }
- }
-}
-
-ON_DLL_LOAD_RELIESON("server.dll", ExploitFixes, ConVar, [](HMODULE baseAddress)
-{
- spdlog::info("ExploitFixes::LoadCallback ...");
-
- spdlog::info("\tByte patching...");
- DoBytePatches();
-
- if (KHook::InitAllHooks())
- {
- spdlog::info("\tInitialized " + std::to_string(KHook::_allHooks.size()) + " exploit-patch hooks.");
- }
- else
- {
- spdlog::critical("\tFAILED to initialize all exploit patches.");
-
- // Force exit?
- MessageBoxA(0, "FAILED to initialize all exploit patches.", "Northstar", MB_ICONERROR);
- exit(0);
- }
-
- ns_exploitfixes_log =
- new ConVar("ns_exploitfixes_log", "1", FCVAR_GAMEDLL, "Whether to log whenever ExploitFixes.cpp blocks/corrects something");
+#include "pch.h"
+
+#include "ExploitFixes.h"
+#include "ExploitFixes_UTF8Parser.h"
+#include "NSMem.h"
+#include "cvar.h"
+#include "tier0.h"
+
+ConVar* ns_exploitfixes_log;
+#define SHOULD_LOG (ns_exploitfixes_log->m_Value.m_nValue > 0)
+#define BLOCKED_INFO(s) \
+ ( \
+ [=]() -> bool \
+ { \
+ if (SHOULD_LOG) \
+ { \
+ std::stringstream stream; \
+ stream << "ExploitFixes.cpp: " << BLOCK_PREFIX << s; \
+ spdlog::error(stream.str()); \
+ } \
+ return false; \
+ }())
+
+// 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);
+}
+
+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);
+ }
+};
+
+struct Angle
+{
+ float pitch, yaw, roll;
+
+ Angle(float pitch = 0, float yaw = 0, float roll = 0) : pitch(pitch), yaw(yaw), roll(roll) {}
+
+ bool IsInvalid()
+ {
+ return !ValidateFloats(pitch, yaw, roll);
+
+ if (!ValidateFloats(pitch, yaw, roll))
+ return false;
+
+ return (pitch > 90 || pitch < -90) || (yaw > 180 || yaw < -180) || (roll > 180 || roll < -180);
+ }
+};
+
+#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
+BLOCK_NETMSG_FUNC(CLC_Screenshot_WriteToBuffer, "48 89 5C 24 ? 57 48 83 EC 20 8B 42 10");
+BLOCK_NETMSG_FUNC(CLC_Screenshot_ReadFromBuffer, "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC 20 48 8B DA 48 8B 52 38");
+
+// 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;
+
+ bool areWeServer;
+
+ {
+ // Figure out of we are the client or the server
+ // To do this, we utilize the msg's m_pMessageHandler pointer
+ // m_pMessageHandler points to a virtual class that handles all net messages
+ // The first virtual table function of our m_pMessageHandler will differ if it is IServerMessageHandler or IClientMessageHandler
+ void* msgHandlerVTableFirstFunc = **(void****)(msg->m_pMessageHandler);
+ static auto engineBaseAddress = (uintptr_t)GetModuleHandleA("engine.dll");
+ auto offset = uintptr_t(msgHandlerVTableFirstFunc) - engineBaseAddress;
+
+ constexpr uintptr_t CLIENTSTATE_FIRST_VFUNC_OFFSET = 0x8A15C;
+ areWeServer = offset != CLIENTSTATE_FIRST_VFUNC_OFFSET;
+ }
+
+ std::string BLOCK_PREFIX = std::string {"NET_SetConVar ("} + (areWeServer ? "server" : "client") + "): Blocked dangerous/invalid msg: ";
+
+ if (areWeServer)
+ {
+ constexpr int SETCONVAR_SANITY_AMOUNT_LIMIT = 69;
+ if (msg->m_ConVars_count < 1 || msg->m_ConVars_count > SETCONVAR_SANITY_AMOUNT_LIMIT)
+ {
+ return BLOCKED_INFO("Invalid m_ConVars_count (" << msg->m_ConVars_count << ")");
+ }
+ }
+
+ 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 BLOCKED_INFO("Missing null terminators");
+
+ auto realVar = R2::g_pCVar->FindVar(entry->name);
+
+ if (realVar)
+ memcpy(
+ entry->name,
+ realVar->m_ConCommandBase.m_pszName,
+ strlen(realVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case
+
+ bool isValidFlags = true;
+ if (areWeServer)
+ {
+ if (realVar)
+ isValidFlags = realVar->IsFlagSet(FCVAR_USERINFO); // ConVar MUST be userinfo var
+ }
+ else
+ {
+ // TODO: Should probably have some sanity checks, but can't find any that are consistent
+ }
+
+ if (!isValidFlags)
+ {
+ if (!realVar)
+ {
+ return BLOCKED_INFO("Invalid flags on nonexistant cvar (how tho???)");
+ }
+ else
+ {
+ return BLOCKED_INFO(
+ "Invalid flags (" << std::hex << "0x" << realVar->m_ConCommandBase.m_nFlags << "), var is " << entry->name);
+ }
+ }
+ }
+ else
+ {
+ return BLOCKED_INFO("Unreadable memory at " << (void*)entry); // 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 CLC_Move
+ {
+ BYTE gap0[24];
+ void* m_pMessageHandler;
+ int m_nBackupCommands;
+ int m_nNewCommands;
+ int m_nLength;
+ // bf_read m_DataIn;
+ // bf_write m_DataOut;
+ };
+
+ auto msg = (CLC_Move*)pMsg;
+
+ const char* BLOCK_PREFIX = "ProcessUserCmds: ";
+
+ if (msg->m_nBackupCommands < 0)
+ {
+ return BLOCKED_INFO("Invalid m_nBackupCommands (" << msg->m_nBackupCommands << ")");
+ }
+
+ if (msg->m_nNewCommands < 0)
+ {
+ return BLOCKED_INFO("Invalid m_nNewCommands (" << msg->m_nNewCommands << ")");
+ }
+
+ // removing, as vanilla already limits num usercmds per frame
+ /*constexpr int NUMCMD_SANITY_LIMIT = 16;
+ if ((msg->m_nNewCommands + msg->m_nBackupCommands) > NUMCMD_SANITY_LIMIT)
+ {
+ return BLOCKED_INFO("Command count is too high (new: " << msg->m_nNewCommands << ", backup: " << msg->m_nBackupCommands << ")");
+
+ }*/
+
+ if (msg->m_nLength <= 0)
+ return BLOCKED_INFO("Invalid message length (" << msg->m_nLength << ")");
+
+ 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))
+{
+ // Let normal usercmd read happen first, it's safe
+ oReadUsercmd(buf, pCmd_move, pCmd_from);
+
+ // Now let's make sure the CMD we read isnt messed up to prevent numerous exploits (including server crashing)
+ struct __declspec(align(4)) SV_CUserCmd
+ {
+ DWORD command_number;
+ DWORD tick_count;
+ float command_time;
+ Angle worldViewAngles;
+ BYTE gap18[4];
+ Angle localViewAngles;
+ Angle attackangles;
+ Vector move;
+ DWORD buttons;
+ BYTE impulse;
+ short weaponselect;
+ DWORD meleetarget;
+ BYTE gap4C[24];
+ char headoffset;
+ BYTE gap65[11];
+ Vector cameraPos;
+ Angle cameraAngles;
+ BYTE gap88[4];
+ int tickSomething;
+ DWORD dword90;
+ DWORD predictedServerEventAck;
+ DWORD dword98;
+ float frameTime;
+ };
+
+ auto cmd = (SV_CUserCmd*)pCmd_move;
+ auto fromCmd = (SV_CUserCmd*)pCmd_from;
+
+ std::string BLOCK_PREFIX =
+ "ReadUsercmd (command_number delta: " + std::to_string(cmd->command_number - fromCmd->command_number) + "): ";
+
+ if (cmd->worldViewAngles.IsInvalid())
+ {
+ BLOCKED_INFO("CMD has invalid worldViewAngles");
+ goto INVALID_CMD;
+ }
+
+ if (cmd->attackangles.IsInvalid())
+ {
+ BLOCKED_INFO("CMD has invalid attackangles");
+ goto INVALID_CMD;
+ }
+
+ if (cmd->localViewAngles.IsInvalid())
+ {
+ BLOCKED_INFO("CMD has invalid localViewAngles");
+ goto INVALID_CMD;
+ }
+
+ if (cmd->cameraAngles.IsInvalid())
+ {
+ BLOCKED_INFO("CMD has invalid cameraAngles");
+ goto INVALID_CMD;
+ }
+
+ if (cmd->frameTime <= 0 || cmd->tick_count == 0 || cmd->command_time <= 0)
+ {
+ BLOCKED_INFO(
+ "Bogus cmd timing (tick_count: " << cmd->tick_count << ", frameTime: " << cmd->frameTime
+ << ", commandTime : " << cmd->command_time << ")");
+ goto INVALID_CMD; // No simulation of bogus-timed cmds
+ }
+
+ if (!cmd->move.IsValid())
+ {
+ BLOCKED_INFO("Invalid move vector");
+ goto INVALID_CMD;
+ }
+
+ if (!cmd->cameraPos.IsValid())
+ {
+ BLOCKED_INFO("Invalid cameraPos"); // IIRC this can crash spectating clients or anyone watching replays
+ goto INVALID_CMD;
+ }
+
+ return;
+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->tick_count = cmd->frameTime = 0;
+ cmd->move = cmd->cameraPos = Vector(0, 0, 0);
+ cmd->buttons = 0;
+ cmd->meleetarget = 0;
+}
+
+// basically: by default r2 isn't set as a valve mod, meaning that m_bRestrictServerCommands is false
+// 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
+KHOOK(IsValveMod, ("engine.dll", "48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63"), bool, __fastcall, ())
+{
+ return !Tier0::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))
+{
+
+ static void* targetRetAddr = NSMem::PatternScan("engine.dll", "84 C0 75 2C 49 8B 16");
+
+ if (_ReturnAddress() == targetRetAddr)
+ {
+ if (!ExploitFixes_UTF8Parser::CheckValid(a1, a2, strData))
+ {
+ const char* BLOCK_PREFIX = "ParseUTF8 Hook: ";
+ BLOCKED_INFO("Ignoring potentially-crashing utf8 string");
+ return false;
+ }
+ }
+
+ return oCrashFunc_ParseUTF8(a1, a2, strData);
+}
+
+//////////////////////////////////////////////////
+
+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
+ NSMem::BytePatch(engineBase + 0x4FB65, "EB 11");
+ NSMem::BytePatch(engineBase + 0x4FBAC, "EB 16");
+
+ { // 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(GetModuleHandleA("server.dll"), 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, "C3");
+ spdlog::info("Patched AntiTamper function export \"{}\"", exportName);
+ }
+ }
+ }
+}
+
+ON_DLL_LOAD_RELIESON("server.dll", ExploitFixes, ConVar, [](HMODULE baseAddress)
+{
+ spdlog::info("ExploitFixes::LoadCallback ...");
+
+ spdlog::info("\tByte patching...");
+ DoBytePatches();
+
+ if (KHook::InitAllHooks())
+ {
+ spdlog::info("\tInitialized " + std::to_string(KHook::_allHooks.size()) + " exploit-patch hooks.");
+ }
+ else
+ {
+ spdlog::critical("\tFAILED to initialize all exploit patches.");
+
+ // Force exit?
+ MessageBoxA(0, "FAILED to initialize all exploit patches.", "Northstar", MB_ICONERROR);
+ exit(0);
+ }
+
+ ns_exploitfixes_log =
+ new ConVar("ns_exploitfixes_log", "1", FCVAR_GAMEDLL, "Whether to log whenever ExploitFixes.cpp blocks/corrects something");
}) \ No newline at end of file