diff options
author | Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> | 2023-12-27 00:32:01 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-27 01:32:01 +0100 |
commit | f5ab6fb5e8be7b73e6003d4145081d5e0c0ce287 (patch) | |
tree | 90f2c6a4885dbd181799e2325cf33588697674e1 /NorthstarDLL/shared/exploit_fixes | |
parent | bb8ed59f6891b1196c5f5bbe7346cd171c8215fa (diff) | |
download | NorthstarLauncher-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.cpp | 461 | ||||
-rw-r--r-- | NorthstarDLL/shared/exploit_fixes/exploitfixes_lzss.cpp | 78 | ||||
-rw-r--r-- | NorthstarDLL/shared/exploit_fixes/exploitfixes_utf8parser.cpp | 199 | ||||
-rw-r--r-- | NorthstarDLL/shared/exploit_fixes/ns_limits.cpp | 298 | ||||
-rw-r--r-- | NorthstarDLL/shared/exploit_fixes/ns_limits.h | 52 |
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; |