aboutsummaryrefslogtreecommitdiff
path: root/NorthstarDedicatedTest/ExploitFixes.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'NorthstarDedicatedTest/ExploitFixes.cpp')
-rw-r--r--NorthstarDedicatedTest/ExploitFixes.cpp196
1 files changed, 196 insertions, 0 deletions
diff --git a/NorthstarDedicatedTest/ExploitFixes.cpp b/NorthstarDedicatedTest/ExploitFixes.cpp
new file mode 100644
index 00000000..d36b4175
--- /dev/null
+++ b/NorthstarDedicatedTest/ExploitFixes.cpp
@@ -0,0 +1,196 @@
+#include "pch.h"
+
+#include "ExploitFixes.h"
+#include "ExploitFixes_UTF8Parser.h"
+#include "NSMem.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);
+}
+
+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() {
+ 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");
+
+// 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 {
+ 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;
+
+ if (msg->m_nBackupCommands < 0 || msg->m_nNewCommands < 1)
+ return false; // Nice try buster
+
+ constexpr int NUMCMD_SANITY_LIMIT = 16;
+ if ((msg->m_nNewCommands + msg->m_nBackupCommands) > NUMCMD_SANITY_LIMIT)
+ return false; // Im good
+
+ if (msg->m_nLength <= 0)
+ return false;
+
+ 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;
+
+ 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
+ !cmd->cameraPos.IsValid()) // IIRC this can crash spectating clients or anyone watching replays
+ 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
+
+ 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
+#include "gameutils.h"
+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)) {
+
+ static void* targetRetAddr = NSMem::PatternScan("engine.dll", "84 C0 75 2C 49 8B 16");
+
+ if (_ReturnAddress() == targetRetAddr) {
+ if (!ExploitFixes_UTF8Parser::CheckValid(a1, a2, strData)) {
+ spdlog::warn("ParseUTF8 Hook: Ignoring potentially-crashing utf8 string");
+ return false;
+ }
+ }
+
+ return oCrashFunc_ParseUTF8(a1, a2, strData);
+}
+
+//////////////////////////////////////////////////
+
+void DoBytePatches() {
+ uintptr_t engineBase = (uintptr_t)GetModuleHandleA("engine.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, {0xEB, 0x11});
+ NSMem::BytePatch(engineBase + 0x4FBAC, {0xEB, 0x16});
+
+ // disconnect concommand
+ {
+ uintptr_t addr = engineBase + 0x5ADA2D;
+ int val = *(int*)addr | FCVAR_SERVER_CAN_EXECUTE;
+ NSMem::BytePatch(addr, (BYTE*)&val, sizeof(int));
+ }
+}
+
+void ExploitFixes::LoadCallback(HMODULE unused) {
+ 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);
+ }
+} \ No newline at end of file