aboutsummaryrefslogtreecommitdiff
path: root/NorthstarDLL/exploitfixes.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'NorthstarDLL/exploitfixes.cpp')
-rw-r--r--NorthstarDLL/exploitfixes.cpp494
1 files changed, 195 insertions, 299 deletions
diff --git a/NorthstarDLL/exploitfixes.cpp b/NorthstarDLL/exploitfixes.cpp
index aa5adae3..de60a854 100644
--- a/NorthstarDLL/exploitfixes.cpp
+++ b/NorthstarDLL/exploitfixes.cpp
@@ -1,52 +1,63 @@
#include "pch.h"
-
-#include "exploitfixes.h"
-#include "exploitfixes_utf8parser.h"
-#include "nsmem.h"
#include "cvar.h"
-#include "gameutils.h"
+#include "limits.h"
+#include "dedicated.h"
+#include "tier0.h"
+#include "r2engine.h"
+#include "r2client.h"
+#include "vector.h"
+
+AUTOHOOK_INIT()
+
+ConVar* Cvar_ns_exploitfixes_log;
+ConVar* Cvar_ns_should_log_all_clientcommands;
+
+ConVar* Cvar_sv_cheats;
-ConVar* ns_exploitfixes_log;
-#define SHOULD_LOG (ns_exploitfixes_log->m_Value.m_nValue > 0)
#define BLOCKED_INFO(s) \
( \
[=]() -> bool \
{ \
- if (SHOULD_LOG) \
+ if (Cvar_ns_exploitfixes_log->GetBool()) \
{ \
std::stringstream stream; \
- stream << "exploitfixes.cpp: " << BLOCK_PREFIX << s; \
+ stream << "ExploitFixes.cpp: " << BLOCK_PREFIX << s; \
spdlog::error(stream.str()); \
} \
return false; \
}())
-struct Float3
+// 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
{
- float vals[3];
-
- void MakeValid()
- {
- for (auto& val : vals)
- if (isnan(val))
- val = 0;
- }
-};
-
-#define BLOCK_NETMSG_FUNC(name, pattern) \
- KHOOK(name, ("engine.dll", pattern), bool, __fastcall, (void* thisptr, void* buffer)) \
- { \
- return false; \
- }
+ 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");
+// 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
+{
+ return false;
+}
-// 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");
+// 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;
+}
-KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48 FF 60 10"), bool, __fastcall, (void* pMsg))
+// 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;
@@ -69,25 +80,12 @@ KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48
};
auto msg = (NET_SetConVar*)pMsg;
+ bool bIsServerFrame = Tier0::ThreadInServerFrameThread();
- 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: ";
+ std::string BLOCK_PREFIX =
+ std::string {"NET_SetConVar ("} + (bIsServerFrame ? "server" : "client") + "): Blocked dangerous/invalid msg: ";
- if (areWeServer)
+ if (bIsServerFrame)
{
constexpr int SETCONVAR_SANITY_AMOUNT_LIMIT = 69;
if (msg->m_ConVars_count < 1 || msg->m_ConVars_count > SETCONVAR_SANITY_AMOUNT_LIMIT)
@@ -101,9 +99,8 @@ KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48
auto entry = msg->m_ConVars + i;
// Safety check for memory access
- if (NSMem::IsMemoryReadable(entry, sizeof(*entry)))
+ if (MemoryAddress(entry).IsMemoryReadable(sizeof(*entry)))
{
-
// Find null terminators
bool nameValid = false, valValid = false;
for (int i = 0; i < ENTRY_STR_LEN; i++)
@@ -117,7 +114,7 @@ KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48
if (!nameValid || !valValid)
return BLOCKED_INFO("Missing null terminators");
- auto realVar = g_pCVar->FindVar(entry->name);
+ auto realVar = R2::g_pCVar->FindVar(entry->name);
if (realVar)
memcpy(
@@ -126,10 +123,10 @@ KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48
strlen(realVar->m_ConCommandBase.m_pszName) + 1); // Force name to match case
bool isValidFlags = true;
- if (areWeServer)
+ if (bIsServerFrame)
{
if (realVar)
- isValidFlags = ConVar::IsFlagSet(realVar, FCVAR_USERINFO); // ConVar MUST be userinfo var
+ isValidFlags = realVar->IsFlagSet(FCVAR_USERINFO); // ConVar MUST be userinfo var
}
else
{
@@ -155,11 +152,14 @@ KHOOK(CClient_ProcessSetConVar, ("engine.dll", "48 8B D1 48 8B 49 18 48 8B 01 48
}
}
- return oCClient_ProcessSetConVar(msg);
+ return CClient_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))
+// 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
{
@@ -186,22 +186,19 @@ KHOOK(CClient_ProcessUsercmds, ("engine.dll", "40 55 56 48 83 EC 58"), bool, __f
return BLOCKED_INFO("Invalid m_nNewCommands (" << msg->m_nNewCommands << ")");
}
- 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);
+ return CClient_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))
+// 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
- oReadUsercmd(buf, pCmd_move, pCmd_from);
+ 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
@@ -209,11 +206,11 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
DWORD command_number;
DWORD tick_count;
float command_time;
- Float3 worldViewAngles;
+ Vector3 worldViewAngles;
BYTE gap18[4];
- Float3 localViewAngles;
- Float3 attackangles;
- Float3 move;
+ Vector3 localViewAngles;
+ Vector3 attackangles;
+ Vector3 move;
DWORD buttons;
BYTE impulse;
short weaponselect;
@@ -221,8 +218,8 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
BYTE gap4C[24];
char headoffset;
BYTE gap65[11];
- Float3 cameraPos;
- Float3 cameraAngles;
+ Vector3 cameraPos;
+ Vector3 cameraAngles;
BYTE gap88[4];
int tickSomething;
DWORD dword90;
@@ -237,7 +234,7 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
std::string BLOCK_PREFIX =
"ReadUsercmd (command_number delta: " + std::to_string(cmd->command_number - fromCmd->command_number) + "): ";
- // Fix invalid player angles
+ // fix invalid player angles
cmd->worldViewAngles.MakeValid();
cmd->attackangles.MakeValid();
cmd->localViewAngles.MakeValid();
@@ -249,7 +246,7 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
// Fix invaid movement vector
cmd->move.MakeValid();
- if (cmd->tick_count == 0 || cmd->command_time <= 0)
+ 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
@@ -260,6 +257,7 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall
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};
@@ -269,285 +267,183 @@ INVALID_CMD:
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, ())
+// 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
{
- bool result = !CommandLine()->CheckParm("-norestrictservercommands");
- spdlog::info("ExploitFixes: Overriding IsValveMod to {}...", result);
- return result;
-}
-
-// 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");
+ // somewhat temp, store the modname here, since we don't have a proper ptr in engine to it rn
+ int iSize = strlen(pModName);
+ R2::g_pModName = new char[iSize + 1];
+ strcpy(R2::g_pModName, pModName);
-#ifdef _MSC_VER
- if (_ReturnAddress() == targetRetAddr)
-#else
- if (__builtin_return_address(0) == targetRetAddr)
-#endif
- {
- 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);
+ return (!strcmp("r2", pModName) || !strcmp("r1", pModName)) && !Tier0::CommandLine()->CheckParm("-norestrictservercommands");
}
-// GetEntByIndex (called by ScriptGetEntByIndex) doesn't check for the index being out of bounds when it's
-// above the max entity count. This allows it to be used to crash servers.
-typedef void*(__fastcall* GetEntByIndexType)(int idx);
-GetEntByIndexType GetEntByIndex;
+// ratelimit stringcmds, and prevent remote clients from calling commands that they shouldn't
+bool (*CCommand__Tokenize)(CCommand& self, const char* pCommandString, R2::cmd_source_t commandSource);
-static void* GetEntByIndexHook(int idx)
+// clang-format off
+AUTOHOOK(CGameClient__ExecuteStringCommand, engine.dll + 0x1022E0,
+bool, __fastcall, (R2::CBaseClient* self, uint32_t unknown, const char* pCommandString))
+// clang-format on
{
- if (idx >= 0x4000)
- {
- spdlog::info("GetEntByIndex {} is out of bounds", idx);
- return nullptr;
- }
- return GetEntByIndex(idx);
-}
-
-// RELOCATED FROM https://github.com/R2Northstar/NorthstarLauncher/commit/25dbf729cfc75107a0fcf0186924b58ecc05214b
-// 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.
-KHOOK(
- LZSS_SafeUncompress,
- ("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 33 ED 41 8B F9"),
- uint32_t,
- __fastcall,
- (void* self, const unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize))
-{
- static constexpr int LZSS_LOOKSHIFT = 4;
+ if (Cvar_ns_should_log_all_clientcommands->GetBool())
+ spdlog::info("player {} (UID: {}) sent command: \"{}\"", self->m_Name, self->m_UID, pCommandString);
- uint32_t totalBytes = 0;
- int getCmdByte = 0, cmdByte = 0;
-
- struct lzss_header_t
+ if (!g_pServerLimits->CheckStringCommandLimits(self))
{
- uint32_t id, actualSize;
- };
+ R2::CBaseClient__Disconnect(self, 1, "Sent too many stringcmd commands");
+ return false;
+ }
- lzss_header_t header = *(lzss_header_t*)pInput;
+ // 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 (pInput == NULL || header.id != 'SSZL' || header.actualSize == 0 || header.actualSize > unBufSize)
- return 0;
+ if (!CCommand__Tokenize(tempCommand, pCommandString, R2::cmd_source_t::kCommandSrcCode) || !tempCommand.ArgC())
+ return false;
- pInput += sizeof(lzss_header_t);
+ ConCommand* command = R2::g_pCVar->FindCommand(tempCommand.Arg(0));
- for (;;)
+ // 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))
{
- if (!getCmdByte)
- cmdByte = *pInput++;
+ // ensure FCVAR_GAMEDLL concommands without FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS can't be executed by remote clients
+ if (IsDedicatedServer())
+ return false;
- getCmdByte = (getCmdByte + 1) & 0x07;
+ if (strcmp(self->m_UID, R2::g_pLocalPlayerUserID))
+ return false;
+ }
- if (cmdByte & 0x01)
- {
- int position = *pInput++ << LZSS_LOOKSHIFT;
- position |= (*pInput >> LZSS_LOOKSHIFT);
- position += 1;
+ // 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)
- int count = (*pInput++ & 0x0F) + 1;
- if (count == 1)
- break;
+ // These both execute a command for every single entity for some reason, nice one valve
+ "pre_go_to_hub",
+ "pre_go_to_calibration",
- // Ensure reference chunk exists entirely within our buffer
- if (position > totalBytes)
- return 0;
+ "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()
+ };
- totalBytes += count;
- if (totalBytes > unBufSize)
- return 0;
+ int iCmdLength = strlen(tempCommand.Arg(0));
- unsigned char* pSource = pOutput - position;
- for (int i = 0; i < count; i++)
- *pOutput++ = *pSource++;
- }
- else
+ bool bIsBadCommand = false;
+ for (auto& blockedCommand : blockedCommands)
{
- totalBytes++;
- if (totalBytes > unBufSize)
- return 0;
+ if (iCmdLength != strlen(blockedCommand))
+ continue;
- *pOutput++ = *pInput++;
+ 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:;
}
- cmdByte = cmdByte >> 1;
}
- if (totalBytes == header.actualSize)
- {
- return totalBytes;
- }
- else
- {
- return 0;
- }
+ return CGameClient__ExecuteStringCommand(self, unknown, pCommandString);
}
-//////////////////////////////////////////////////
+// prevent clients from crashing servers through overflowing CNetworkStringTableContainer::WriteBaselines
+bool bWasWritingStringTableSuccessful;
-void DoBytePatches()
+// clang-format off
+AUTOHOOK(CBaseClient__SendServerInfo, engine.dll + 0x104FB0,
+void, __fastcall, (void* self))
+// clang-format on
{
- uintptr_t engineBase = (uintptr_t)GetModuleHandleA("engine.dll");
- uintptr_t serverBase = (uintptr_t)GetModuleHandleA("server.dll");
+ bWasWritingStringTableSuccessful = true;
+ CBaseClient__SendServerInfo(self);
+ if (!bWasWritingStringTableSuccessful)
+ R2::CBaseClient__Disconnect(
+ self, 1, "Overflowed CNetworkStringTableContainer::WriteBaselines, try restarting your client and reconnecting");
+}
- // 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");
+// 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;
- // disconnect concommand
+ if (i >= MAX_ENT_IDX)
{
- uintptr_t addr = engineBase + 0x5ADA2D;
- int val = *(int*)addr | FCVAR_SERVER_CAN_EXECUTE;
- NSMem::BytePatch(addr, (BYTE*)&val, sizeof(int));
+ spdlog::warn("GetEntByIndex {} is out of bounds (max {})", i, MAX_ENT_IDX);
+ return nullptr;
}
- { // 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);
- }
- }
- }
+ return GetEntByIndex(i);
}
-KHOOK(
- SpecialClientCommand,
- ("server.dll", "48 89 5C 24 ? 48 89 74 24 ? 55 57 41 56 48 8D 6C 24 ? 48 81 EC ? ? ? ? 83 3A 00"),
- bool,
- __fastcall,
- (void* player, CCommand* command))
+ON_DLL_LOAD("engine.dll", EngineExploitFixes, (CModule module))
{
+ AUTOHOOK_DISPATCH_MODULE(engine.dll)
- static ConVar* sv_cheats = g_pCVar->FindVar("sv_cheats");
-
- if (sv_cheats->GetBool())
- return oSpecialClientCommand(player, command); // Don't block anything if sv_cheats is on
+ CCommand__Tokenize = module.Offset(0x418380).As<bool (*)(CCommand&, const char*, R2::cmd_source_t)>();
- // These are mostly from Portal 2 (sigh)
- constexpr const char* blockedCommands[] = {
- "emit", // Sound-playing exploit (likely for Portal 2 coop devs testing splitscreen sound or something)
+ // allow client/ui to run clientcommands despite restricting servercommands
+ module.Offset(0x4FB65).Patch("EB 11");
+ module.Offset(0x4FBAC).Patch("EB 16");
- // 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()
- };
-
- if (command->ArgC() > 0)
+ // patch to set bWasWritingStringTableSuccessful in CNetworkStringTableContainer::WriteBaselines if it fails
{
- std::string cmdStr = command->Arg(0);
- for (char& c : cmdStr)
- c = tolower(c);
+ MemoryAddress writeAddress(&bWasWritingStringTableSuccessful - module.Offset(0x234EDC).m_nAddress);
- for (const char* blockedCommand : blockedCommands)
- {
- if (cmdStr.find(blockedCommand) != std::string::npos)
- {
- // Block this command
- spdlog::warn("Blocked exploititive client command \"{}\".", cmdStr);
- return true;
- }
- }
- }
+ MemoryAddress addr = module.Offset(0x234ED2);
+ addr.Patch("C7 05");
+ addr.Offset(2).Patch((BYTE*)&writeAddress, sizeof(writeAddress));
- return oSpecialClientCommand(player, command);
-}
+ addr.Offset(6).Patch("00 00 00 00");
-void SetupKHook(KHook* hook)
-{
- if (hook->Setup())
- {
- spdlog::debug("KHook::Setup(): Hooked at {}", hook->targetFuncAddr);
- }
- else
- {
- spdlog::critical("\tFAILED to initialize all exploit patches.");
-
- // Force exit
- MessageBoxA(0, "FAILED to initialize all exploit patches.", "Northstar", MB_ICONERROR);
- exit(0);
+ addr.Offset(10).NOP(5);
}
}
-void ExploitFixes::LoadCallback_MultiModule(HMODULE baseAddress)
+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");
- spdlog::info("ExploitFixes::LoadCallback_MultiModule({}) ...", (void*)baseAddress);
+ // Dumb ANTITAMPER patches (they negatively impact performance and security)
+ constexpr const char* ANTITAMPER_EXPORTS[] = {
+ "ANTITAMPER_SPOTCHECK_CODEMARKER",
+ "ANTITAMPER_TESTVALUE_CODEMARKER",
+ "ANTITAMPER_TRIGGER_CODEMARKER",
+ };
- int hooksEnabled = 0;
- for (auto itr = KHook::_allHooks.begin(); itr != KHook::_allHooks.end(); itr++)
+ // Prevent these from actually doing anything
+ for (auto exportName : ANTITAMPER_EXPORTS)
{
- auto curHook = *itr;
- if (GetModuleHandleA(curHook->targetFunc.moduleName) == baseAddress)
+ MemoryAddress exportAddr = module.GetExport(exportName);
+ if (exportAddr)
{
- SetupKHook(curHook);
- itr = KHook::_allHooks.erase(itr); // Prevent repeated initialization
-
- hooksEnabled++;
-
- if (itr == KHook::_allHooks.end())
- break;
+ // Just return, none of them have any args or are userpurge
+ exportAddr.Patch("C3");
+ spdlog::info("Patched AntiTamper function export \"{}\"", exportName);
}
}
- spdlog::info("\tEnabled {} hooks.", hooksEnabled);
-}
-
-void ExploitFixes::LoadCallback_Full(HMODULE baseAddress)
-{
- spdlog::info("ExploitFixes::LoadCallback_Full ...");
-
- spdlog::info("\tByte patching...");
- DoBytePatches();
-
- for (KHook* hook : KHook::_allHooks)
- SetupKHook(hook);
-
- spdlog::info("\tInitialized " + std::to_string(KHook::_allHooks.size()) + " late exploit-patch hooks.");
- KHook::_allHooks.clear();
-
- ns_exploitfixes_log =
- new ConVar("ns_exploitfixes_log", "1", FCVAR_GAMEDLL, "Whether to log whenever exploitfixes.cpp blocks/corrects something");
+ 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");
- HookEnabler hook;
- ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x2a8a50, &GetEntByIndexHook, reinterpret_cast<LPVOID*>(&GetEntByIndex));
+ Cvar_sv_cheats = R2::g_pCVar->FindVar("sv_cheats");
}