aboutsummaryrefslogtreecommitdiff
path: root/NorthstarDLL/shared/exploit_fixes
diff options
context:
space:
mode:
authorJack <66967891+ASpoonPlaysGames@users.noreply.github.com>2023-12-27 00:32:01 +0000
committerGitHub <noreply@github.com>2023-12-27 01:32:01 +0100
commitf5ab6fb5e8be7b73e6003d4145081d5e0c0ce287 (patch)
tree90f2c6a4885dbd181799e2325cf33588697674e1 /NorthstarDLL/shared/exploit_fixes
parentbb8ed59f6891b1196c5f5bbe7346cd171c8215fa (diff)
downloadNorthstarLauncher-1.21.2.tar.gz
NorthstarLauncher-1.21.2.zip
Folder restructuring from primedev (#624)v1.21.2-rc3v1.21.2
Copies of over the primedev folder structure for easier cherry-picking of further changes Co-authored-by: F1F7Y <filip.bartos07@proton.me>
Diffstat (limited to 'NorthstarDLL/shared/exploit_fixes')
-rw-r--r--NorthstarDLL/shared/exploit_fixes/exploitfixes.cpp461
-rw-r--r--NorthstarDLL/shared/exploit_fixes/exploitfixes_lzss.cpp78
-rw-r--r--NorthstarDLL/shared/exploit_fixes/exploitfixes_utf8parser.cpp199
-rw-r--r--NorthstarDLL/shared/exploit_fixes/ns_limits.cpp298
-rw-r--r--NorthstarDLL/shared/exploit_fixes/ns_limits.h52
5 files changed, 0 insertions, 1088 deletions
diff --git a/NorthstarDLL/shared/exploit_fixes/exploitfixes.cpp b/NorthstarDLL/shared/exploit_fixes/exploitfixes.cpp
deleted file mode 100644
index 8064d5ac..00000000
--- a/NorthstarDLL/shared/exploit_fixes/exploitfixes.cpp
+++ /dev/null
@@ -1,461 +0,0 @@
-#include "core/convar/cvar.h"
-#include "ns_limits.h"
-#include "dedicated/dedicated.h"
-#include "core/tier0.h"
-#include "engine/r2engine.h"
-#include "client/r2client.h"
-#include "core/math/vector.h"
-#include "core/vanilla.h"
-
-AUTOHOOK_INIT()
-
-ConVar* Cvar_ns_exploitfixes_log;
-ConVar* Cvar_ns_should_log_all_clientcommands;
-
-ConVar* Cvar_sv_cheats;
-
-#define BLOCKED_INFO(s) \
- ( \
- [=]() -> bool \
- { \
- if (Cvar_ns_exploitfixes_log->GetBool()) \
- { \
- std::stringstream stream; \
- stream << "ExploitFixes.cpp: " << BLOCK_PREFIX << s; \
- spdlog::error(stream.str()); \
- } \
- return false; \
- }())
-
-// block bad netmessages
-// Servers can literally request a screenshot from any client, yeah no
-// clang-format off
-AUTOHOOK(CLC_Screenshot_WriteToBuffer, engine.dll + 0x22AF20,
-bool, __fastcall, (void* thisptr, void* buffer)) // 48 89 5C 24 ? 57 48 83 EC 20 8B 42 10
-// clang-format on
-{
- if (g_pVanillaCompatibility->GetVanillaCompatibility())
- return CLC_Screenshot_WriteToBuffer(thisptr, buffer);
- return false;
-}
-
-// clang-format off
-AUTOHOOK(CLC_Screenshot_ReadFromBuffer, engine.dll + 0x221F00,
-bool, __fastcall, (void* thisptr, void* buffer)) // 48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC 20 48 8B DA 48 8B 52 38
-// clang-format on
-{
- if (g_pVanillaCompatibility->GetVanillaCompatibility())
- return CLC_Screenshot_ReadFromBuffer(thisptr, buffer);
- return false;
-}
-
-// This is unused ingame and a big client=>server=>client exploit vector
-// clang-format off
-AUTOHOOK(Base_CmdKeyValues_ReadFromBuffer, engine.dll + 0x220040,
-bool, __fastcall, (void* thisptr, void* buffer)) // 40 55 48 81 EC ? ? ? ? 48 8D 6C 24 ? 48 89 5D 70
-// clang-format on
-{
- return false;
-}
-
-// clang-format off
-AUTOHOOK(CClient_ProcessSetConVar, engine.dll + 0x75CF0,
-bool, __fastcall, (void* pMsg)) // 48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10
-// clang-format on
-{
-
- 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 bIsServerFrame = ThreadInServerFrameThread();
-
- std::string BLOCK_PREFIX =
- std::string {"NET_SetConVar ("} + (bIsServerFrame ? "server" : "client") + "): Blocked dangerous/invalid msg: ";
-
- if (bIsServerFrame)
- {
- 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 (CMemoryAddress(entry).IsMemoryReadable(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");
-
- ConVar* pVar = g_pCVar->FindVar(entry->name);
-
- if (pVar)
- {
- memcpy(
- entry->name,
- pVar->m_ConCommandBase.m_pszName,
- strlen(pVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case
-
- int iFlags = bIsServerFrame ? FCVAR_USERINFO : FCVAR_REPLICATED;
- if (!pVar->IsFlagSet(iFlags))
- return BLOCKED_INFO(
- "Invalid flags (" << std::hex << "0x" << pVar->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 CClient_ProcessSetConVar(msg);
-}
-
-// prevent invalid user CMDs
-// clang-format off
-AUTOHOOK(CClient_ProcessUsercmds, engine.dll + 0x1040F0,
-bool, __fastcall, (void* thisptr, void* pMsg)) // 40 55 56 48 83 EC 58
-// clang-format on
-{
- 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 << ")");
- }
-
- if (msg->m_nLength <= 0)
- return BLOCKED_INFO("Invalid message length (" << msg->m_nLength << ")");
-
- return CClient_ProcessUsercmds(thisptr, pMsg);
-}
-
-// clang-format off
-AUTOHOOK(ReadUsercmd, server.dll + 0x2603F0,
-void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from)) // 4C 89 44 24 ? 53 55 56 57
-// clang-format on
-{
- // Let normal usercmd read happen first, it's safe
- ReadUsercmd(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 alignas(4) SV_CUserCmd
- {
- DWORD command_number;
- DWORD tick_count;
- float command_time;
- Vector3 worldViewAngles;
- BYTE gap18[4];
- Vector3 localViewAngles;
- Vector3 attackangles;
- Vector3 move;
- DWORD buttons;
- BYTE impulse;
- short weaponselect;
- DWORD meleetarget;
- BYTE gap4C[24];
- char headoffset;
- BYTE gap65[11];
- Vector3 cameraPos;
- Vector3 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) + "): ";
-
- // fix invalid player angles
- cmd->worldViewAngles.MakeValid();
- cmd->attackangles.MakeValid();
- cmd->localViewAngles.MakeValid();
-
- // Fix invalid camera angles
- cmd->cameraPos.MakeValid();
- cmd->cameraAngles.MakeValid();
-
- // Fix invaid movement vector
- cmd->move.MakeValid();
-
- 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
- }
-
- 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 = {0, 0, 0};
- cmd->tick_count = cmd->frameTime = 0;
- cmd->move = cmd->cameraPos = {0, 0, 0};
- cmd->buttons = 0;
- cmd->meleetarget = 0;
-}
-
-// ensure that GetLocalBaseClient().m_bRestrictServerCommands is set correctly, which the return value of this function controls
-// this is IsValveMod in source, but we're making it IsRespawnMod now since valve didn't make this one
-// clang-format off
-AUTOHOOK(IsRespawnMod, engine.dll + 0x1C6360,
-bool, __fastcall, (const char* pModName)) // 48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63
-// clang-format on
-{
- // somewhat temp, store the modname here, since we don't have a proper ptr in engine to it rn
- int iSize = strlen(pModName);
- g_pModName = new char[iSize + 1];
- strcpy(g_pModName, pModName);
-
- if (g_pVanillaCompatibility->GetVanillaCompatibility())
- return false;
-
- return (!strcmp("r2", pModName) || !strcmp("r1", pModName)) && !CommandLine()->CheckParm("-norestrictservercommands");
-}
-
-// ratelimit stringcmds, and prevent remote clients from calling commands that they shouldn't
-// clang-format off
-AUTOHOOK(CGameClient__ExecuteStringCommand, engine.dll + 0x1022E0,
-bool, __fastcall, (CBaseClient* self, uint32_t unknown, const char* pCommandString))
-// clang-format on
-{
- if (Cvar_ns_should_log_all_clientcommands->GetBool())
- spdlog::info("player {} (UID: {}) sent command: \"{}\"", self->m_Name, self->m_UID, pCommandString);
-
- if (!g_pServerLimits->CheckStringCommandLimits(self))
- {
- CBaseClient__Disconnect(self, 1, "Sent too many stringcmd commands");
- return false;
- }
-
- // verify the command we're trying to execute is FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS, if it's a concommand
- char* commandBuf[1040]; // assumedly this is the size of CCommand since we don't have an actual constructor
- memset(commandBuf, 0, sizeof(commandBuf));
- CCommand tempCommand = *(CCommand*)&commandBuf;
-
- if (!CCommand__Tokenize(tempCommand, pCommandString, cmd_source_t::kCommandSrcCode) || !tempCommand.ArgC())
- return false;
-
- ConCommand* command = g_pCVar->FindCommand(tempCommand.Arg(0));
-
- // if the command doesn't exist pass it on to ExecuteStringCommand for script clientcommands and stuff
- if (command && !command->IsFlagSet(FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS))
- {
- // ensure FCVAR_GAMEDLL concommands without FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS can't be executed by remote clients
- if (IsDedicatedServer())
- return false;
-
- if (strcmp(self->m_UID, g_pLocalPlayerUserID))
- return false;
- }
-
- // check for and block abusable legacy portal 2 commands
- // these aren't actually concommands weirdly enough, they seem to just be hardcoded
- if (!Cvar_sv_cheats->GetBool())
- {
- constexpr const char* blockedCommands[] = {
- "emit", // Sound-playing exploit (likely for Portal 2 coop devs testing splitscreen sound or something)
-
- // These both execute a command for every single entity for some reason, nice one valve
- "pre_go_to_hub",
- "pre_go_to_calibration",
-
- "end_movie", // Calls "__MovieFinished" script function, not sure exactly what this does but it certainly isn't needed
- "load_recent_checkpoint" // This is the instant-respawn exploit, literally just calls RespawnPlayer()
- };
-
- int iCmdLength = strlen(tempCommand.Arg(0));
-
- bool bIsBadCommand = false;
- for (auto& blockedCommand : blockedCommands)
- {
- if (iCmdLength != strlen(blockedCommand))
- continue;
-
- for (int i = 0; tempCommand.Arg(0)[i]; i++)
- if (tolower(tempCommand.Arg(0)[i]) != blockedCommand[i])
- goto NEXT_COMMAND; // break out of this loop, then go to next command
-
- // this is a command we need to block
- return false;
- NEXT_COMMAND:;
- }
- }
-
- return CGameClient__ExecuteStringCommand(self, unknown, pCommandString);
-}
-
-// prevent clients from crashing servers through overflowing CNetworkStringTableContainer::WriteBaselines
-bool bWasWritingStringTableSuccessful;
-
-// clang-format off
-AUTOHOOK(CBaseClient__SendServerInfo, engine.dll + 0x104FB0,
-void, __fastcall, (void* self))
-// clang-format on
-{
- bWasWritingStringTableSuccessful = true;
- CBaseClient__SendServerInfo(self);
- if (!bWasWritingStringTableSuccessful)
- CBaseClient__Disconnect(
- self, 1, "Overflowed CNetworkStringTableContainer::WriteBaselines, try restarting your client and reconnecting");
-}
-
-// return null when GetEntByIndex is passed an index >= 0x4000
-// this is called from exactly 1 script clientcommand that can be given an arbitrary index, and going above 0x4000 crashes
-// clang-format off
-AUTOHOOK(GetEntByIndex, server.dll + 0x2A8A50,
-void*, __fastcall, (int i))
-// clang-format on
-{
- const int MAX_ENT_IDX = 0x4000;
-
- if (i >= MAX_ENT_IDX)
- {
- spdlog::warn("GetEntByIndex {} is out of bounds (max {})", i, MAX_ENT_IDX);
- return nullptr;
- }
-
- return GetEntByIndex(i);
-}
-// clang-format off
-AUTOHOOK(CL_CopyExistingEntity, engine.dll + 0x6F940,
-bool, __fastcall, (void* a1))
-// clang-format on
-{
- struct CEntityReadInfo
- {
- BYTE gap[40];
- int nNewEntity;
- };
-
- CEntityReadInfo* pReadInfo = (CEntityReadInfo*)a1;
- if (pReadInfo->nNewEntity >= 0x1000 || pReadInfo->nNewEntity < 0)
- {
- // Value isn't sanitized in release builds for
- // every game powered by the Source Engine 1
- // causing read/write outside of array bounds.
- // This defect has let to the achievement of a
- // full-chain RCE exploit. We hook and perform
- // sanity checks for the value of m_nNewEntity
- // here to prevent this behavior from happening.
- return false;
- }
-
- return CL_CopyExistingEntity(a1);
-}
-
-ON_DLL_LOAD("engine.dll", EngineExploitFixes, (CModule module))
-{
- AUTOHOOK_DISPATCH_MODULE(engine.dll)
-
- // allow client/ui to run clientcommands despite restricting servercommands
- module.Offset(0x4FB65).Patch("EB 11");
- module.Offset(0x4FBAC).Patch("EB 16");
-
- // patch to set bWasWritingStringTableSuccessful in CNetworkStringTableContainer::WriteBaselines if it fails
- {
- CMemoryAddress writeAddress(&bWasWritingStringTableSuccessful - module.Offset(0x234EDC).m_nAddress);
-
- CMemoryAddress addr = module.Offset(0x234ED2);
- addr.Patch("C7 05");
- addr.Offset(2).Patch((BYTE*)&writeAddress, sizeof(writeAddress));
-
- addr.Offset(6).Patch("00 00 00 00");
-
- addr.Offset(10).NOP(5);
- }
-}
-
-ON_DLL_LOAD_RELIESON("server.dll", ServerExploitFixes, ConVar, (CModule module))
-{
- AUTOHOOK_DISPATCH_MODULE(server.dll)
-
- // ret at the start of CServerGameClients::ClientCommandKeyValues as it has no benefit and is forwarded to client (i.e. security issue)
- // this prevents the attack vector of client=>server=>client, however server=>client also has clientside patches
- module.Offset(0x153920).Patch("C3");
-
- // Dumb ANTITAMPER patches (they negatively impact performance and security)
- constexpr const char* ANTITAMPER_EXPORTS[] = {
- "ANTITAMPER_SPOTCHECK_CODEMARKER",
- "ANTITAMPER_TESTVALUE_CODEMARKER",
- "ANTITAMPER_TRIGGER_CODEMARKER",
- };
-
- // Prevent these from actually doing anything
- for (auto exportName : ANTITAMPER_EXPORTS)
- {
- CMemoryAddress exportAddr = module.GetExport(exportName);
- if (exportAddr)
- {
- // Just return, none of them have any args or are userpurge
- exportAddr.Patch("C3");
- spdlog::info("Patched AntiTamper function export \"{}\"", exportName);
- }
- }
-
- Cvar_ns_exploitfixes_log =
- new ConVar("ns_exploitfixes_log", "1", FCVAR_GAMEDLL, "Whether to log whenever ExploitFixes.cpp blocks/corrects something");
- Cvar_ns_should_log_all_clientcommands =
- new ConVar("ns_should_log_all_clientcommands", "0", FCVAR_NONE, "Whether to log all clientcommands");
-
- Cvar_sv_cheats = g_pCVar->FindVar("sv_cheats");
-}
diff --git a/NorthstarDLL/shared/exploit_fixes/exploitfixes_lzss.cpp b/NorthstarDLL/shared/exploit_fixes/exploitfixes_lzss.cpp
deleted file mode 100644
index ccb6ac18..00000000
--- a/NorthstarDLL/shared/exploit_fixes/exploitfixes_lzss.cpp
+++ /dev/null
@@ -1,78 +0,0 @@
-
-AUTOHOOK_INIT()
-
-static constexpr int LZSS_LOOKSHIFT = 4;
-
-struct lzss_header_t
-{
- unsigned int id;
- unsigned int actualSize;
-};
-
-// Rewrite of CLZSS::SafeUncompress to fix a vulnerability where malicious compressed payloads could cause the decompressor to try to read
-// out of the bounds of the output buffer.
-// clang-format off
-AUTOHOOK(CLZSS__SafeDecompress, engine.dll + 0x432A10,
-unsigned int, __fastcall, (void* self, const unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize))
-// clang-format on
-{
- unsigned int totalBytes = 0;
- int getCmdByte = 0;
- int cmdByte = 0;
-
- lzss_header_t header = *(lzss_header_t*)pInput;
-
- if (!pInput || !header.actualSize || header.id != 0x53535A4C || header.actualSize > unBufSize)
- return 0;
-
- pInput += sizeof(lzss_header_t);
-
- for (;;)
- {
- if (!getCmdByte)
- cmdByte = *pInput++;
-
- getCmdByte = (getCmdByte + 1) & 0x07;
-
- if (cmdByte & 0x01)
- {
- int position = *pInput++ << LZSS_LOOKSHIFT;
- position |= (*pInput >> LZSS_LOOKSHIFT);
- position += 1;
- int count = (*pInput++ & 0x0F) + 1;
- if (count == 1)
- break;
-
- // Ensure reference chunk exists entirely within our buffer
- if (position > totalBytes)
- return 0;
-
- totalBytes += count;
- if (totalBytes > unBufSize)
- return 0;
-
- unsigned char* pSource = pOutput - position;
- for (int i = 0; i < count; i++)
- *pOutput++ = *pSource++;
- }
- else
- {
- totalBytes++;
- if (totalBytes > unBufSize)
- return 0;
-
- *pOutput++ = *pInput++;
- }
- cmdByte = cmdByte >> 1;
- }
-
- if (totalBytes != header.actualSize)
- return 0;
-
- return totalBytes;
-}
-
-ON_DLL_LOAD("engine.dll", ExploitFixes_LZSS, (CModule module))
-{
- AUTOHOOK_DISPATCH()
-}
diff --git a/NorthstarDLL/shared/exploit_fixes/exploitfixes_utf8parser.cpp b/NorthstarDLL/shared/exploit_fixes/exploitfixes_utf8parser.cpp
deleted file mode 100644
index 3d97f750..00000000
--- a/NorthstarDLL/shared/exploit_fixes/exploitfixes_utf8parser.cpp
+++ /dev/null
@@ -1,199 +0,0 @@
-
-AUTOHOOK_INIT()
-
-INT64(__fastcall* sub_F1320)(DWORD a1, char* a2);
-
-// Reimplementation of an exploitable UTF decoding function in titanfall
-bool __fastcall CheckUTF8Valid(INT64* a1, DWORD* a2, char* strData)
-{
- DWORD v3; // eax
- char* v4; // rbx
- char v5; // si
- char* _strData; // rdi
- char* v7; // rbp
- char v11; // al
- DWORD v12; // er9
- DWORD v13; // ecx
- DWORD v14; // edx
- DWORD v15; // er8
- int v16; // eax
- DWORD v17; // er9
- int v18; // eax
- DWORD v19; // er9
- DWORD v20; // ecx
- int v21; // eax
- int v22; // er9
- DWORD v23; // edx
- int v24; // eax
- int v25; // er9
- DWORD v26; // er9
- DWORD v27; // er10
- DWORD v28; // ecx
- DWORD v29; // edx
- DWORD v30; // er8
- int v31; // eax
- DWORD v32; // er10
- int v33; // eax
- DWORD v34; // er10
- DWORD v35; // ecx
- int v36; // eax
- int v37; // er10
- DWORD v38; // edx
- int v39; // eax
- int v40; // er10
- DWORD v41; // er10
- INT64 v43; // r8
- INT64 v44; // rdx
- INT64 v45; // rcx
- INT64 v46; // rax
- INT64 v47; // rax
- char v48; // al
- INT64 v49; // r8
- INT64 v50; // rdx
- INT64 v51; // rcx
- INT64 v52; // rax
- INT64 v53; // rax
-
- v3 = a2[2];
- v4 = (char*)(a1[1] + *a2);
- v5 = 0;
- _strData = strData;
- v7 = &v4[*((UINT16*)a2 + 2)];
- if (v3 >= 2)
- {
- ++v4;
- --v7;
- if (v3 != 2)
- {
- while (1)
- {
- if (!CMemoryAddress(v4).IsMemoryReadable(1))
- return false; // INVALID
-
- v11 = *v4++; // crash potential
- if (v11 != 92)
- goto LABEL_6;
- v11 = *v4++;
- if (v11 == 110)
- break;
- switch (v11)
- {
- case 't':
- v11 = 9;
- goto LABEL_6;
- case 'r':
- v11 = 13;
- goto LABEL_6;
- case 'b':
- v11 = 8;
- goto LABEL_6;
- case 'f':
- v11 = 12;
- goto LABEL_6;
- }
- if (v11 != 117)
- goto LABEL_6;
- v12 = *v4 | 0x20;
- v13 = v4[1] | 0x20;
- v14 = v4[2] | 0x20;
- v15 = v4[3] | 0x20;
- v16 = 87;
- if (v12 <= 0x39)
- v16 = 48;
- v17 = v12 - v16;
- v18 = 87;
- v19 = v17 << 12;
- if (v13 <= 0x39)
- v18 = 48;
- v20 = v13 - v18;
- v21 = 87;
- v22 = (v20 << 8) | v19;
- if (v14 <= 0x39)
- v21 = 48;
- v23 = v14 - v21;
- v24 = 87;
- v25 = (16 * v23) | v22;
- if (v15 <= 0x39)
- v24 = 48;
- v4 += 4;
- v26 = (v15 - v24) | v25;
- if (v26 - 55296 <= 0x7FF)
- {
- if (v26 >= 0xDC00)
- return true;
- if (*v4 != 92 || v4[1] != 117)
- return true;
-
- v27 = v4[2] | 0x20;
- v28 = v4[3] | 0x20;
- v29 = v4[4] | 0x20;
- v30 = v4[5] | 0x20;
- v31 = 87;
- if (v27 <= 0x39)
- v31 = 48;
- v32 = v27 - v31;
- v33 = 87;
- v34 = v32 << 12;
- if (v28 <= 0x39)
- v33 = 48;
- v35 = v28 - v33;
- v36 = 87;
- v37 = (v35 << 8) | v34;
- if (v29 <= 0x39)
- v36 = 48;
- v38 = v29 - v36;
- v39 = 87;
- v40 = (16 * v38) | v37;
- if (v30 <= 0x39)
- v39 = 48;
- v4 += 6;
- v41 = ((v30 - v39) | v40) - 56320;
- if (v41 > 0x3FF)
- return true;
- v26 = v41 | ((v26 - 55296) << 10);
- }
- _strData += (DWORD)sub_F1320(v26, _strData);
- LABEL_7:
- if (v4 == v7)
- goto LABEL_48;
- }
- v11 = 10;
- LABEL_6:
- v5 |= v11;
- *_strData++ = v11;
- goto LABEL_7;
- }
- }
-LABEL_48:
- return true;
-}
-
-// prevent utf8 parser from crashing when provided bad data, which can be sent through user-controlled openinvites
-// clang-format off
-AUTOHOOK(Rson_ParseUTF8, engine.dll + 0xEF670,
-bool, __fastcall, (INT64* a1, DWORD* a2, char* strData)) // 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
-// clang-format on
-{
- static void* targetRetAddr = CModule("engine.dll").FindPattern("84 C0 75 2C 49 8B 16");
-
- // only call if we're parsing utf8 data from the network (i.e. communities), otherwise we get perf issues
- void* pReturnAddress =
-#ifdef _MSC_VER
- _ReturnAddress()
-#else
- __builtin_return_address(0)
-#endif
- ;
-
- if (pReturnAddress == targetRetAddr && !CheckUTF8Valid(a1, a2, strData))
- return false;
-
- return Rson_ParseUTF8(a1, a2, strData);
-}
-
-ON_DLL_LOAD("engine.dll", EngineExploitFixes_UTF8Parser, (CModule module))
-{
- AUTOHOOK_DISPATCH()
-
- sub_F1320 = module.FindPattern("83 F9 7F 77 08 88 0A").RCast<INT64(__fastcall*)(DWORD, char*)>();
-}
diff --git a/NorthstarDLL/shared/exploit_fixes/ns_limits.cpp b/NorthstarDLL/shared/exploit_fixes/ns_limits.cpp
deleted file mode 100644
index bd855ee4..00000000
--- a/NorthstarDLL/shared/exploit_fixes/ns_limits.cpp
+++ /dev/null
@@ -1,298 +0,0 @@
-#include "ns_limits.h"
-#include "engine/hoststate.h"
-#include "client/r2client.h"
-#include "engine/r2engine.h"
-#include "server/r2server.h"
-#include "core/tier0.h"
-#include "core/math/vector.h"
-#include "server/auth/serverauthentication.h"
-
-AUTOHOOK_INIT()
-
-ServerLimitsManager* g_pServerLimits;
-
-float (*CEngineServer__GetTimescale)();
-
-// todo: make this work on higher timescales, also possibly disable when sv_cheats is set
-void ServerLimitsManager::RunFrame(double flCurrentTime, float flFrameTime)
-{
- if (Cvar_sv_antispeedhack_enable->GetBool())
- {
- // for each player, set their usercmd processing budget for the frame to the last frametime for the server
- for (int i = 0; i < g_pGlobals->m_nMaxClients; i++)
- {
- CBaseClient* player = &g_pClientArray[i];
-
- if (m_PlayerLimitData.find(player) != m_PlayerLimitData.end())
- {
- PlayerLimitData* pLimitData = &g_pServerLimits->m_PlayerLimitData[player];
- if (pLimitData->flFrameUserCmdBudget < g_pGlobals->m_flTickInterval * Cvar_sv_antispeedhack_maxtickbudget->GetFloat())
- {
- pLimitData->flFrameUserCmdBudget += g_pServerLimits->Cvar_sv_antispeedhack_budgetincreasemultiplier->GetFloat() *
- fmax(flFrameTime, g_pGlobals->m_flFrameTime * CEngineServer__GetTimescale());
- }
- }
- }
- }
-}
-
-void ServerLimitsManager::AddPlayer(CBaseClient* player)
-{
- PlayerLimitData limitData;
- limitData.flFrameUserCmdBudget =
- g_pGlobals->m_flTickInterval * CEngineServer__GetTimescale() * Cvar_sv_antispeedhack_maxtickbudget->GetFloat();
-
- m_PlayerLimitData.insert(std::make_pair(player, limitData));
-}
-
-void ServerLimitsManager::RemovePlayer(CBaseClient* player)
-{
- if (m_PlayerLimitData.find(player) != m_PlayerLimitData.end())
- m_PlayerLimitData.erase(player);
-}
-
-bool ServerLimitsManager::CheckStringCommandLimits(CBaseClient* player)
-{
- if (CVar_sv_quota_stringcmdspersecond->GetInt() != -1)
- {
- // note: this isn't super perfect, legit clients can trigger it in lobby if they try, mostly good enough tho imo
- if (Plat_FloatTime() - m_PlayerLimitData[player].lastClientCommandQuotaStart >= 1.0)
- {
- // reset quota
- m_PlayerLimitData[player].lastClientCommandQuotaStart = Plat_FloatTime();
- m_PlayerLimitData[player].numClientCommandsInQuota = 0;
- }
-
- m_PlayerLimitData[player].numClientCommandsInQuota++;
- if (m_PlayerLimitData[player].numClientCommandsInQuota > CVar_sv_quota_stringcmdspersecond->GetInt())
- {
- // too many stringcmds, dc player
- return false;
- }
- }
-
- return true;
-}
-
-bool ServerLimitsManager::CheckChatLimits(CBaseClient* player)
-{
- if (Plat_FloatTime() - m_PlayerLimitData[player].lastSayTextLimitStart >= 1.0)
- {
- m_PlayerLimitData[player].lastSayTextLimitStart = Plat_FloatTime();
- m_PlayerLimitData[player].sayTextLimitCount = 0;
- }
-
- if (m_PlayerLimitData[player].sayTextLimitCount >= Cvar_sv_max_chat_messages_per_sec->GetInt())
- return false;
-
- m_PlayerLimitData[player].sayTextLimitCount++;
- return true;
-}
-
-// clang-format off
-AUTOHOOK(CNetChan__ProcessMessages, engine.dll + 0x2140A0,
-char, __fastcall, (void* self, void* buf))
-// clang-format on
-{
- enum eNetChanLimitMode
- {
- NETCHANLIMIT_WARN,
- NETCHANLIMIT_KICK
- };
-
- double startTime = Plat_FloatTime();
- char ret = CNetChan__ProcessMessages(self, buf);
-
- // check processing limits, unless we're in a level transition
- if (g_pHostState->m_iCurrentState == HostState_t::HS_RUN && ThreadInServerFrameThread())
- {
- // player that sent the message
- CBaseClient* sender = *(CBaseClient**)((char*)self + 368);
-
- // if no sender, return
- // relatively certain this is fine?
- if (!sender || !g_pServerLimits->m_PlayerLimitData.count(sender))
- return ret;
-
- // reset every second
- if (startTime - g_pServerLimits->m_PlayerLimitData[sender].lastNetChanProcessingLimitStart >= 1.0 ||
- g_pServerLimits->m_PlayerLimitData[sender].lastNetChanProcessingLimitStart == -1.0)
- {
- g_pServerLimits->m_PlayerLimitData[sender].lastNetChanProcessingLimitStart = startTime;
- g_pServerLimits->m_PlayerLimitData[sender].netChanProcessingLimitTime = 0.0;
- }
- g_pServerLimits->m_PlayerLimitData[sender].netChanProcessingLimitTime += (Plat_FloatTime() * 1000) - (startTime * 1000);
-
- if (g_pServerLimits->m_PlayerLimitData[sender].netChanProcessingLimitTime >=
- g_pServerLimits->Cvar_net_chan_limit_msec_per_sec->GetInt())
- {
- spdlog::warn(
- "Client {} hit netchan processing limit with {}ms of processing time this second (max is {})",
- (char*)sender + 0x16,
- g_pServerLimits->m_PlayerLimitData[sender].netChanProcessingLimitTime,
- g_pServerLimits->Cvar_net_chan_limit_msec_per_sec->GetInt());
-
- // never kick local player
- if (g_pServerLimits->Cvar_net_chan_limit_mode->GetInt() != NETCHANLIMIT_WARN && strcmp(g_pLocalPlayerUserID, sender->m_UID))
- {
- CBaseClient__Disconnect(sender, 1, "Exceeded net channel processing limit");
- return false;
- }
- }
- }
-
- return ret;
-}
-
-bool ServerLimitsManager::CheckConnectionlessPacketLimits(netpacket_t* packet)
-{
- static const ConVar* Cvar_net_data_block_enabled = g_pCVar->FindVar("net_data_block_enabled");
-
- // don't ratelimit datablock packets as long as datablock is enabled
- if (packet->adr.type == NA_IP &&
- (!(packet->data[4] == 'N' && Cvar_net_data_block_enabled->GetBool()) || !Cvar_net_data_block_enabled->GetBool()))
- {
- // bad lookup: optimise later tm
- UnconnectedPlayerLimitData* sendData = nullptr;
- for (UnconnectedPlayerLimitData& foundSendData : g_pServerLimits->m_UnconnectedPlayerLimitData)
- {
- if (!memcmp(packet->adr.ip, foundSendData.ip, 16))
- {
- sendData = &foundSendData;
- break;
- }
- }
-
- if (!sendData)
- {
- sendData = &g_pServerLimits->m_UnconnectedPlayerLimitData.emplace_back();
- memcpy(sendData->ip, packet->adr.ip, 16);
- }
-
- if (Plat_FloatTime() < sendData->timeoutEnd)
- return false;
-
- if (Plat_FloatTime() - sendData->lastQuotaStart >= 1.0)
- {
- sendData->lastQuotaStart = Plat_FloatTime();
- sendData->packetCount = 0;
- }
-
- sendData->packetCount++;
-
- if (sendData->packetCount >= g_pServerLimits->Cvar_sv_querylimit_per_sec->GetInt())
- {
- spdlog::warn(
- "Client went over connectionless ratelimit of {} per sec with packet of type {}",
- g_pServerLimits->Cvar_sv_querylimit_per_sec->GetInt(),
- packet->data[4]);
-
- // timeout for a minute
- sendData->timeoutEnd = Plat_FloatTime() + 60.0;
- return false;
- }
- }
-
- return true;
-}
-
-// this is weird and i'm not sure if it's correct, so not using for now
-/*AUTOHOOK(CBasePlayer__PhysicsSimulate, server.dll + 0x5A6E50, bool, __fastcall, (void* self, int a2, char a3))
-{
- spdlog::info("CBasePlayer::PhysicsSimulate");
- return CBasePlayer__PhysicsSimulate(self, a2, a3);
-}*/
-
-struct alignas(4) SV_CUserCmd
-{
- DWORD command_number;
- DWORD tick_count;
- float command_time;
- Vector3 worldViewAngles;
- BYTE gap18[4];
- Vector3 localViewAngles;
- Vector3 attackangles;
- Vector3 move;
- DWORD buttons;
- BYTE impulse;
- short weaponselect;
- DWORD meleetarget;
- BYTE gap4C[24];
- char headoffset;
- BYTE gap65[11];
- Vector3 cameraPos;
- Vector3 cameraAngles;
- BYTE gap88[4];
- int tickSomething;
- DWORD dword90;
- DWORD predictedServerEventAck;
- DWORD dword98;
- float frameTime;
-};
-
-// clang-format off
-AUTOHOOK(CPlayerMove__RunCommand, server.dll + 0x5B8100,
-void, __fastcall, (void* self, CBasePlayer* player, SV_CUserCmd* pUserCmd, uint64_t a4))
-// clang-format on
-{
- if (g_pServerLimits->Cvar_sv_antispeedhack_enable->GetBool())
- {
- CBaseClient* pClient = &g_pClientArray[player->m_nPlayerIndex - 1];
-
- if (g_pServerLimits->m_PlayerLimitData.find(pClient) != g_pServerLimits->m_PlayerLimitData.end())
- {
- PlayerLimitData* pLimitData = &g_pServerLimits->m_PlayerLimitData[pClient];
-
- pLimitData->flFrameUserCmdBudget = fmax(0.0, pLimitData->flFrameUserCmdBudget - pUserCmd->frameTime);
-
- if (pLimitData->flFrameUserCmdBudget <= 0.0)
- {
- spdlog::warn("player {} went over usercmd budget ({})", pClient->m_Name, pLimitData->flFrameUserCmdBudget);
- return;
- }
- }
- }
-
- CPlayerMove__RunCommand(self, player, pUserCmd, a4);
-}
-
-ON_DLL_LOAD_RELIESON("engine.dll", ServerLimits, ConVar, (CModule module))
-{
- AUTOHOOK_DISPATCH_MODULE(engine.dll)
-
- g_pServerLimits = new ServerLimitsManager;
-
- g_pServerLimits->CVar_sv_quota_stringcmdspersecond = new ConVar(
- "sv_quota_stringcmdspersecond",
- "60",
- FCVAR_GAMEDLL,
- "How many string commands per second clients are allowed to submit, 0 to disallow all string commands, -1 to disable");
- g_pServerLimits->Cvar_net_chan_limit_mode =
- new ConVar("net_chan_limit_mode", "0", FCVAR_GAMEDLL, "The mode for netchan processing limits: 0 = warn, 1 = kick");
- g_pServerLimits->Cvar_net_chan_limit_msec_per_sec = new ConVar(
- "net_chan_limit_msec_per_sec",
- "100",
- FCVAR_GAMEDLL,
- "Netchannel processing is limited to so many milliseconds, abort connection if exceeding budget");
- g_pServerLimits->Cvar_sv_querylimit_per_sec = new ConVar("sv_querylimit_per_sec", "15", FCVAR_GAMEDLL, "");
- g_pServerLimits->Cvar_sv_max_chat_messages_per_sec = new ConVar("sv_max_chat_messages_per_sec", "5", FCVAR_GAMEDLL, "");
- g_pServerLimits->Cvar_sv_antispeedhack_enable =
- new ConVar("sv_antispeedhack_enable", "0", FCVAR_NONE, "whether to enable antispeedhack protections");
- g_pServerLimits->Cvar_sv_antispeedhack_maxtickbudget = new ConVar(
- "sv_antispeedhack_maxtickbudget",
- "64",
- FCVAR_GAMEDLL,
- "Maximum number of client-issued usercmd ticks that can be replayed in packet loss conditions");
- g_pServerLimits->Cvar_sv_antispeedhack_budgetincreasemultiplier = new ConVar(
- "sv_antispeedhack_budgetincreasemultiplier",
- "1",
- FCVAR_GAMEDLL,
- "Increase usercmd processing budget by tickinterval * value per tick");
-
- CEngineServer__GetTimescale = module.Offset(0x240840).RCast<float (*)()>();
-}
-
-ON_DLL_LOAD("server.dll", ServerLimitsServer, (CModule module))
-{
- AUTOHOOK_DISPATCH_MODULE(server.dll)
-}
diff --git a/NorthstarDLL/shared/exploit_fixes/ns_limits.h b/NorthstarDLL/shared/exploit_fixes/ns_limits.h
deleted file mode 100644
index 546fec6f..00000000
--- a/NorthstarDLL/shared/exploit_fixes/ns_limits.h
+++ /dev/null
@@ -1,52 +0,0 @@
-#pragma once
-#include "engine/r2engine.h"
-#include "core/convar/convar.h"
-#include <unordered_map>
-
-struct PlayerLimitData
-{
- double lastClientCommandQuotaStart = -1.0;
- int numClientCommandsInQuota = 0;
-
- double lastNetChanProcessingLimitStart = -1.0;
- double netChanProcessingLimitTime = 0.0;
-
- double lastSayTextLimitStart = -1.0;
- int sayTextLimitCount = 0;
-
- float flFrameUserCmdBudget = 0.0;
-};
-
-struct UnconnectedPlayerLimitData
-{
- char ip[16];
- double lastQuotaStart = 0.0;
- int packetCount = 0;
- double timeoutEnd = -1.0;
-};
-
-class ServerLimitsManager
-{
-public:
- ConVar* CVar_sv_quota_stringcmdspersecond;
- ConVar* Cvar_net_chan_limit_mode;
- ConVar* Cvar_net_chan_limit_msec_per_sec;
- ConVar* Cvar_sv_querylimit_per_sec;
- ConVar* Cvar_sv_max_chat_messages_per_sec;
- ConVar* Cvar_sv_antispeedhack_enable;
- ConVar* Cvar_sv_antispeedhack_maxtickbudget;
- ConVar* Cvar_sv_antispeedhack_budgetincreasemultiplier;
-
- std::unordered_map<CBaseClient*, PlayerLimitData> m_PlayerLimitData;
- std::vector<UnconnectedPlayerLimitData> m_UnconnectedPlayerLimitData;
-
-public:
- void RunFrame(double flCurrentTime, float flFrameTime);
- void AddPlayer(CBaseClient* player);
- void RemovePlayer(CBaseClient* player);
- bool CheckStringCommandLimits(CBaseClient* player);
- bool CheckChatLimits(CBaseClient* player);
- bool CheckConnectionlessPacketLimits(netpacket_t* packet);
-};
-
-extern ServerLimitsManager* g_pServerLimits;