diff options
-rw-r--r-- | NorthstarDedicatedTest/ExploitFixes.cpp | 237 | ||||
-rw-r--r-- | NorthstarDedicatedTest/ExploitFixes.h | 3 | ||||
-rw-r--r-- | NorthstarDedicatedTest/bansystem.cpp | 6 | ||||
-rw-r--r-- | NorthstarDedicatedTest/dllmain.cpp | 2 | ||||
-rw-r--r-- | NorthstarDedicatedTest/miscserverfixes.cpp | 108 | ||||
-rw-r--r-- | NorthstarDedicatedTest/miscserverfixes.h | 3 | ||||
-rw-r--r-- | NorthstarDedicatedTest/rpakfilesystem.cpp | 42 | ||||
-rw-r--r-- | NorthstarDedicatedTest/rpakfilesystem.h | 8 | ||||
-rw-r--r-- | NorthstarDedicatedTest/serverauthentication.cpp | 14 | ||||
-rw-r--r-- | NorthstarDedicatedTest/squirrel.cpp | 44 | ||||
-rw-r--r-- | NorthstarDedicatedTest/squirrel.h | 28 |
11 files changed, 278 insertions, 217 deletions
diff --git a/NorthstarDedicatedTest/ExploitFixes.cpp b/NorthstarDedicatedTest/ExploitFixes.cpp index af7d48ac..44b62d55 100644 --- a/NorthstarDedicatedTest/ExploitFixes.cpp +++ b/NorthstarDedicatedTest/ExploitFixes.cpp @@ -5,9 +5,6 @@ #include "NSMem.h" #include "cvar.h" -typedef char(__fastcall* function_containing_emit_t)(uint64_t a1, uint64_t a2); -function_containing_emit_t function_containing_emit; -ConVar* sv_cheats; ConVar* ns_exploitfixes_log; #define SHOULD_LOG (ns_exploitfixes_log->m_Value.m_nValue > 0) #define BLOCKED_INFO(s) \ @@ -23,36 +20,15 @@ ConVar* ns_exploitfixes_log; return false; \ }()) -// Make sure 3 or less floats are valid -bool ValidateFloats(float a, float b = 0, float c = 0) +struct Float3 { - return !isnan(a) && !isnan(b) && !isnan(c); -} - -struct Vector -{ - float x, y, z; + float vals[3]; - Vector(float x = 0, float y = 0, float z = 0) : x(x), y(y), z(z) {} - - bool IsValid() + void MakeValid() { - 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); + for (auto& val : vals) + if (isnan(val)) + val = 0; } }; @@ -232,11 +208,11 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall DWORD command_number; DWORD tick_count; float command_time; - Angle worldViewAngles; + Float3 worldViewAngles; BYTE gap18[4]; - Angle localViewAngles; - Angle attackangles; - Vector move; + Float3 localViewAngles; + Float3 attackangles; + Float3 move; DWORD buttons; BYTE impulse; short weaponselect; @@ -244,8 +220,8 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall BYTE gap4C[24]; char headoffset; BYTE gap65[11]; - Vector cameraPos; - Angle cameraAngles; + Float3 cameraPos; + Float3 cameraAngles; BYTE gap88[4]; int tickSomething; DWORD dword90; @@ -260,31 +236,19 @@ 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) + "): "; - if (cmd->worldViewAngles.IsInvalid()) - { - BLOCKED_INFO("CMD has invalid worldViewAngles"); - goto INVALID_CMD; - } - - if (cmd->attackangles.IsInvalid()) - { - BLOCKED_INFO("CMD has invalid attackangles"); - goto INVALID_CMD; - } + // Fix invalid player angles + cmd->worldViewAngles.MakeValid(); + cmd->attackangles.MakeValid(); + cmd->localViewAngles.MakeValid(); - if (cmd->localViewAngles.IsInvalid()) - { - BLOCKED_INFO("CMD has invalid localViewAngles"); - goto INVALID_CMD; - } + // Fix invalid camera angles + cmd->cameraPos.MakeValid(); + cmd->cameraAngles.MakeValid(); - if (cmd->cameraAngles.IsInvalid()) - { - BLOCKED_INFO("CMD has invalid cameraAngles"); - goto INVALID_CMD; - } + // Fix invaid movement vector + cmd->move.MakeValid(); - if (cmd->frameTime <= 0 || cmd->tick_count == 0 || cmd->command_time <= 0) + if (cmd->tick_count == 0 || cmd->command_time <= 0) { BLOCKED_INFO( "Bogus cmd timing (tick_count: " << cmd->tick_count << ", frameTime: " << cmd->frameTime @@ -292,26 +256,14 @@ KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall goto INVALID_CMD; // No simulation of bogus-timed cmds } - if (!cmd->move.IsValid()) - { - BLOCKED_INFO("Invalid move vector"); - goto INVALID_CMD; - } - - if (!cmd->cameraPos.IsValid()) - { - BLOCKED_INFO("Invalid cameraPos"); // IIRC this can crash spectating clients or anyone watching replays - goto INVALID_CMD; - } - return; -INVALID_CMD: +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->worldViewAngles = cmd->localViewAngles = cmd->attackangles = cmd->cameraAngles = {0, 0, 0}; cmd->tick_count = cmd->frameTime = 0; - cmd->move = cmd->cameraPos = Vector(0, 0, 0); + cmd->move = cmd->cameraPos = {0, 0, 0}; cmd->buttons = 0; cmd->meleetarget = 0; } @@ -320,7 +272,11 @@ INVALID_CMD: // 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, ()) + +typedef void (*IsValveModType)(); +IsValveModType IsValveMod; + +bool IsValveModHook() { return !CommandLine()->CheckParm("-norestrictservercommands"); } @@ -365,6 +321,83 @@ static void* GetEntByIndexHook(int idx) 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; + + uint32_t totalBytes = 0; + int getCmdByte = 0, cmdByte = 0; + + struct lzss_header_t + { + uint32_t id, actualSize; + }; + + lzss_header_t header = *(lzss_header_t*)pInput; + + if (pInput == NULL || header.id != 'SSZL' || header.actualSize == 0 || 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 totalBytes; + } + else + { + return 0; + } +} + ////////////////////////////////////////////////// void DoBytePatches() @@ -412,16 +445,49 @@ void DoBytePatches() } } -char function_containing_emit_hook(uint64_t unknown_value, uint64_t command_ptr) +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)) { - char* command_string = *(char**)(command_ptr + 1040); // From decompile - if (!sv_cheats->m_Value.m_nValue && !_strnicmp(command_string, "emit", 5)) + 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 + + // 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) + + // 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) { - spdlog::info("Blocking command \"emit\" because sv_cheats was 0"); - return 1; + std::string cmdStr = command->Arg(0); + for (char& c : cmdStr) + c = tolower(c); + + for (const char* blockedCommand : blockedCommands) + { + if (cmdStr.find(blockedCommand) != std::string::npos) + { + // Block this command + spdlog::warn("Blocked exploititive client command \"{}\".", cmdStr); + return true; + } + } } - return function_containing_emit(unknown_value, command_ptr); + + return oSpecialClientCommand(player, command); } void ExploitFixes::LoadCallback(HMODULE baseAddress) @@ -448,9 +514,12 @@ void ExploitFixes::LoadCallback(HMODULE baseAddress) new ConVar("ns_exploitfixes_log", "1", FCVAR_GAMEDLL, "Whether to log whenever ExploitFixes.cpp blocks/corrects something"); HookEnabler hook; - - sv_cheats = g_pCVar->FindVar("sv_cheats"); - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x5889A0, &function_containing_emit_hook, reinterpret_cast<LPVOID*>(&function_containing_emit)); ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x2a8a50, &GetEntByIndexHook, reinterpret_cast<LPVOID*>(&GetEntByIndex)); } + +void ExploitFixes::LoadCallbackEngine(HMODULE baseAddress) +{ + spdlog::info("ExploitFixes::LoadCallbackEngine ..."); + HookEnabler hook; + ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1C6360, &IsValveModHook, reinterpret_cast<LPVOID*>(&IsValveMod)); +}
\ No newline at end of file diff --git a/NorthstarDedicatedTest/ExploitFixes.h b/NorthstarDedicatedTest/ExploitFixes.h index 7a407a3d..1cafe758 100644 --- a/NorthstarDedicatedTest/ExploitFixes.h +++ b/NorthstarDedicatedTest/ExploitFixes.h @@ -6,4 +6,5 @@ namespace ExploitFixes { void LoadCallback(HMODULE unused); -}
\ No newline at end of file + void LoadCallbackEngine(HMODULE baseAddress); +} // namespace ExploitFixes
\ No newline at end of file diff --git a/NorthstarDedicatedTest/bansystem.cpp b/NorthstarDedicatedTest/bansystem.cpp index 93cf9c65..9c2507fd 100644 --- a/NorthstarDedicatedTest/bansystem.cpp +++ b/NorthstarDedicatedTest/bansystem.cpp @@ -20,7 +20,7 @@ void ServerBanSystem::OpenBanlist() { std::string line; while (std::getline(enabledModsStream, line)) - m_vBannedUids.push_back(strtoll(line.c_str(), nullptr, 10)); + m_vBannedUids.push_back(strtoull(line.c_str(), nullptr, 10)); enabledModsStream.close(); } @@ -74,7 +74,7 @@ void BanPlayerCommand(const CCommand& args) if (!strcmp((char*)player + 0x16, args.Arg(1)) || !strcmp((char*)player + 0xF500, args.Arg(1))) { - g_ServerBanSystem->BanUID(strtoll((char*)player + 0xF500, nullptr, 10)); + g_ServerBanSystem->BanUID(strtoull((char*)player + 0xF500, nullptr, 10)); CBaseClient__Disconnect(player, 1, "Banned from server"); break; } @@ -87,7 +87,7 @@ void UnbanPlayerCommand(const CCommand& args) return; // assumedly the player being unbanned here wasn't already connected, so don't need to iterate over players or anything - g_ServerBanSystem->UnbanUID(strtoll(args.Arg(1), nullptr, 10)); + g_ServerBanSystem->UnbanUID(strtoull(args.Arg(1), nullptr, 10)); } void ClearBanlistCommand(const CCommand& args) diff --git a/NorthstarDedicatedTest/dllmain.cpp b/NorthstarDedicatedTest/dllmain.cpp index 266bf10f..67b350de 100644 --- a/NorthstarDedicatedTest/dllmain.cpp +++ b/NorthstarDedicatedTest/dllmain.cpp @@ -264,7 +264,6 @@ bool InitialiseNorthstar() AddDllLoadCallback("engine.dll", InitialiseSharedMasterServer); AddDllLoadCallback("server.dll", InitialiseMiscServerScriptCommand); AddDllLoadCallback("server.dll", InitialiseMiscServerFixes); - AddDllLoadCallback("engine.dll", InitialiseMiscEngineServerFixes); AddDllLoadCallback("server.dll", InitialiseBuildAINFileHooks); AddDllLoadCallback("engine.dll", InitializeCHostStateHooks); @@ -289,6 +288,7 @@ bool InitialiseNorthstar() // activate exploit fixes AddDllLoadCallback("server.dll", ExploitFixes::LoadCallback); + AddDllLoadCallback("engine.dll", ExploitFixes::LoadCallbackEngine); // run callbacks for any libraries that are already loaded by now CallAllPendingDLLLoadCallbacks(); diff --git a/NorthstarDedicatedTest/miscserverfixes.cpp b/NorthstarDedicatedTest/miscserverfixes.cpp index ddbfcfbb..e85950c4 100644 --- a/NorthstarDedicatedTest/miscserverfixes.cpp +++ b/NorthstarDedicatedTest/miscserverfixes.cpp @@ -23,110 +23,4 @@ void InitialiseMiscServerFixes(HMODULE baseAddress) { NSMem::BytePatch(ba + 0x153920, "C3"); } -} - -typedef unsigned int(__fastcall* CLZSS__SafeUncompressType)( - void* self, const unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize); -CLZSS__SafeUncompressType CLZSS__SafeUncompress; - -struct lzss_header_t -{ - unsigned int id; - unsigned int actualSize; -}; - -static constexpr int LZSS_LOOKSHIFT = 4; - -// 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. -static unsigned int CLZSS__SafeUncompressHook(void* self, const unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize) -{ - unsigned int totalBytes = 0; - int getCmdByte = 0; - int cmdByte = 0; - - lzss_header_t header = *(lzss_header_t*)pInput; - - if (pInput == NULL) - { - return 0; - } - if (header.id != 0x53535a4c) - { - return 0; - } - if (header.actualSize == 0) - { - return 0; - } - if (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; - - return 0; -} - -void InitialiseMiscEngineServerFixes(HMODULE baseAddress) -{ - HookEnabler hook; - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x432a10, &CLZSS__SafeUncompressHook, reinterpret_cast<LPVOID*>(&CLZSS__SafeUncompress)); -} +}
\ No newline at end of file diff --git a/NorthstarDedicatedTest/miscserverfixes.h b/NorthstarDedicatedTest/miscserverfixes.h index 3fdf56a8..d1c05a6b 100644 --- a/NorthstarDedicatedTest/miscserverfixes.h +++ b/NorthstarDedicatedTest/miscserverfixes.h @@ -1,2 +1 @@ -void InitialiseMiscServerFixes(HMODULE baseAddress); -void InitialiseMiscEngineServerFixes(HMODULE baseAddress);
\ No newline at end of file +void InitialiseMiscServerFixes(HMODULE baseAddress);
\ No newline at end of file diff --git a/NorthstarDedicatedTest/rpakfilesystem.cpp b/NorthstarDedicatedTest/rpakfilesystem.cpp index d52f9ef1..b3105996 100644 --- a/NorthstarDedicatedTest/rpakfilesystem.cpp +++ b/NorthstarDedicatedTest/rpakfilesystem.cpp @@ -43,11 +43,37 @@ void PakLoadManager::LoadPakAsync(const char* path, bool bMarkForUnload) void PakLoadManager::UnloadPaks() { for (int pakHandle : m_pakHandlesToUnload) + { g_pakLoadApi->UnloadPak(pakHandle, nullptr); + // remove pak from loadedPaks and loadedPaksInv + RemoveLoadedPak(pakHandle); + } m_pakHandlesToUnload.clear(); } +bool PakLoadManager::IsPakLoaded(int32_t pakHandle) +{ + return loadedPaks.find(pakHandle) != loadedPaks.end(); +} + +bool PakLoadManager::IsPakLoaded(size_t hash) +{ + return loadedPaksInv.find(hash) != loadedPaksInv.end(); +} + +void PakLoadManager::AddLoadedPak(int32_t pakHandle, size_t hash) +{ + loadedPaks[pakHandle] = hash; + loadedPaksInv[hash] = pakHandle; +} + +void PakLoadManager::RemoveLoadedPak(int32_t pakHandle) +{ + loadedPaksInv.erase(loadedPaks[pakHandle]); + loadedPaks.erase(pakHandle); +} + void HandlePakAliases(char** map) { // convert the pak being loaded to it's aliased one, e.g. aliasing mp_hub_timeshift => sp_hub_timeshift @@ -152,6 +178,13 @@ void* LoadPakSyncHook(char* path, void* unknownSingleton, int flags) LoadPakAsyncType LoadPakAsyncOriginal; int LoadPakAsyncHook(char* path, void* unknownSingleton, int flags, void* callback0, void* callback1) { + size_t hash = STR_HASH(path); + // if the hash is already in the map, dont load the pak, it has already been loaded + if (g_PakLoadManager->IsPakLoaded(hash)) + { + return -1; + } + HandlePakAliases(&path); bool bNeedToFreePakName = false; @@ -176,6 +209,9 @@ int LoadPakAsyncHook(char* path, void* unknownSingleton, int flags, void* callba int ret = LoadPakAsyncOriginal(path, unknownSingleton, flags, callback0, callback1); spdlog::info("LoadPakAsync {} {}", path, ret); + // add the hash to the map + g_PakLoadManager->AddLoadedPak(ret, hash); + if (bNeedToFreePakName) delete[] path; @@ -185,6 +221,12 @@ int LoadPakAsyncHook(char* path, void* unknownSingleton, int flags, void* callba UnloadPakType UnloadPakOriginal; void* UnloadPakHook(int pakHandle, void* callback) { + if (g_PakLoadManager->IsPakLoaded(pakHandle)) + { + // remove the entry + g_PakLoadManager->RemoveLoadedPak(pakHandle); + } + static bool bShouldUnloadPaks = true; if (bShouldUnloadPaks) { diff --git a/NorthstarDedicatedTest/rpakfilesystem.h b/NorthstarDedicatedTest/rpakfilesystem.h index 3c104822..12ef983e 100644 --- a/NorthstarDedicatedTest/rpakfilesystem.h +++ b/NorthstarDedicatedTest/rpakfilesystem.h @@ -9,8 +9,16 @@ class PakLoadManager void LoadPakAsync(const char* path, bool bMarkForUnload); void UnloadPaks(); + bool IsPakLoaded(int32_t pakHandle); + bool IsPakLoaded(size_t hash); + void AddLoadedPak(int32_t pakHandle, size_t hash); + void RemoveLoadedPak(int32_t pakHandle); + private: std::vector<int> m_pakHandlesToUnload; + // these size_t s are the asset path hashed with STR_HASH + std::unordered_map<int32_t, size_t> loadedPaks {}; + std::unordered_map<size_t, int32_t> loadedPaksInv {}; }; extern PakLoadManager* g_PakLoadManager;
\ No newline at end of file diff --git a/NorthstarDedicatedTest/serverauthentication.cpp b/NorthstarDedicatedTest/serverauthentication.cpp index 8d7c063b..0672f3c6 100644 --- a/NorthstarDedicatedTest/serverauthentication.cpp +++ b/NorthstarDedicatedTest/serverauthentication.cpp @@ -16,6 +16,9 @@ const char* AUTHSERVER_VERIFY_STRING = "I am a northstar server!"; +// This convar defines whether to log all client commands +ConVar* Cvar_ns_should_log_all_clientcommands; + // hook types typedef void* (*CBaseServer__ConnectClientType)( @@ -243,7 +246,8 @@ bool ServerAuthenticationManager::AuthenticatePlayer(void* player, int64_t uid, // set persistent data as ready, we use 0x3 internally to mark the client as using local persistence *((char*)player + 0x4a0) = (char)0x3; - if (!CVar_ns_auth_allow_insecure->GetBool()) // no auth data and insecure connections aren't allowed, so dc the client + // no auth data and insecure connections aren't allowed, so dc the client + if (!CVar_ns_auth_allow_insecure->GetBool() && strncmp(GetCurrentPlaylistName(), "solo", 5) != 0) return false; // insecure connections are allowed, try reading from disk @@ -477,6 +481,12 @@ CCommand__TokenizeType CCommand__Tokenize; char CGameClient__ExecuteStringCommandHook(void* self, uint32_t unknown, const char* pCommandString) { + // Only log clientcommands if the convar `ns_should_log_all_clientcommands` equals 1 + if (Cvar_ns_should_log_all_clientcommands->GetBool()) + { + spdlog::info("{} (UID: {}) executed command: \"{}\"", (char*)self + 0x16, (char*)self + 0xF500, pCommandString); + } + if (CVar_sv_quota_stringcmdspersecond->GetInt() != -1) { // note: this isn't super perfect, legit clients can trigger it in lobby, mostly good enough tho imo @@ -669,6 +679,8 @@ void InitialiseServerAuthentication(HMODULE baseAddress) "100", FCVAR_GAMEDLL, "Netchannel processing is limited to so many milliseconds, abort connection if exceeding budget"); + Cvar_ns_should_log_all_clientcommands = + new ConVar("ns_should_log_all_clientcommands", "0", FCVAR_NONE, "Whether to log all clientcommands"); Cvar_ns_player_auth_port = new ConVar("ns_player_auth_port", "8081", FCVAR_GAMEDLL, ""); Cvar_sv_querylimit_per_sec = new ConVar("sv_querylimit_per_sec", "15", FCVAR_GAMEDLL, ""); Cvar_sv_max_chat_messages_per_sec = new ConVar("sv_max_chat_messages_per_sec", "5", FCVAR_GAMEDLL, ""); diff --git a/NorthstarDedicatedTest/squirrel.cpp b/NorthstarDedicatedTest/squirrel.cpp index 573fb29e..2fd558cd 100644 --- a/NorthstarDedicatedTest/squirrel.cpp +++ b/NorthstarDedicatedTest/squirrel.cpp @@ -8,6 +8,7 @@ #include "dedicated.h" #include "rcon_shared.h" #include "sv_rcon.h" +#include "gameutils.h" // hook forward declarations typedef SQInteger (*SQPrintType)(void* sqvm, char* fmt, ...); @@ -36,6 +37,10 @@ CallScriptInitCallbackType ClientCallScriptInitCallback; CallScriptInitCallbackType ServerCallScriptInitCallback; template <ScriptContext context> char CallScriptInitCallbackHook(void* sqvm, const char* callback); +RegisterSquirrelFuncType ClientRegisterSquirrelFunc; +RegisterSquirrelFuncType ServerRegisterSquirrelFunc; +template <ScriptContext context> int64_t RegisterSquirrelFuncHook(void* sqvm, SQFuncRegistration* funcReg, char unknown); + // core sqvm funcs sq_compilebufferType ClientSq_compilebuffer; sq_compilebufferType ServerSq_compilebuffer; @@ -46,9 +51,6 @@ sq_pushroottableType ServerSq_pushroottable; sq_callType ClientSq_call; sq_callType ServerSq_call; -RegisterSquirrelFuncType ClientRegisterSquirrelFunc; -RegisterSquirrelFuncType ServerRegisterSquirrelFunc; - // sq stack array funcs sq_newarrayType ClientSq_newarray; sq_newarrayType ServerSq_newarray; @@ -164,6 +166,11 @@ void InitialiseClientSquirrel(HMODULE baseAddress) (char*)baseAddress + 0x10190, &CallScriptInitCallbackHook<ScriptContext::CLIENT>, reinterpret_cast<LPVOID*>(&ClientCallScriptInitCallback)); // client callscriptinitcallback function + ENABLER_CREATEHOOK( + hook, + (char*)baseAddress + 0x108E0, + &RegisterSquirrelFuncHook<ScriptContext::CLIENT>, + reinterpret_cast<LPVOID*>(&ClientRegisterSquirrelFunc)); // client registersquirrelfunc function } void InitialiseServerSquirrel(HMODULE baseAddress) @@ -219,6 +226,12 @@ void InitialiseServerSquirrel(HMODULE baseAddress) &CallScriptInitCallbackHook<ScriptContext::SERVER>, reinterpret_cast<LPVOID*>(&ServerCallScriptInitCallback)); // server callscriptinitcallback function + ENABLER_CREATEHOOK( + hook, + (char*)baseAddress + 0x1DD10, + &RegisterSquirrelFuncHook<ScriptContext::SERVER>, + reinterpret_cast<LPVOID*>(&ServerRegisterSquirrelFunc)); // server registersquirrelfunc function + // cheat and clientcmd_can_execute allows clients to execute this, but since it's unsafe we only allow it when cheats are enabled // for script_client and script_ui, we don't use cheats, so clients can execute them on themselves all they want RegisterConCommand( @@ -477,4 +490,29 @@ template <ScriptContext context> void ExecuteCodeCommand(const CCommand& args) g_UISquirrelManager->ExecuteCode(args.ArgS()); else if (context == ScriptContext::SERVER) g_ServerSquirrelManager->ExecuteCode(args.ArgS()); +} + +SQRESULT SQ_DevFuncStub(void* sqvm) +{ + spdlog::warn("Blocked execution of squirrel developer function for security reasons. To re-enable them use start parameter " + "-allowSquirrelDevFunctions."); + return SQRESULT_NULL; +} + +template <ScriptContext context> int64_t RegisterSquirrelFuncHook(void* sqvm, SQFuncRegistration* funcReg, char unknown) +{ + static std::set<std::string> allowedDevFunctions = { + "Dev_CommandLineHasParm", + "Dev_CommandLineParmValue", + "Dev_CommandLineRemoveParm", + }; + + if ((funcReg->devLevel == 1) && (!CommandLine()->CheckParm("-allowSquirrelDevFunctions")) && + (!allowedDevFunctions.count(funcReg->squirrelFuncName))) + funcReg->funcPtr = SQ_DevFuncStub; + + if (context == ScriptContext::SERVER) + return ServerRegisterSquirrelFunc(sqvm, funcReg, unknown); + else + return ClientRegisterSquirrelFunc(sqvm, funcReg, unknown); }
\ No newline at end of file diff --git a/NorthstarDedicatedTest/squirrel.h b/NorthstarDedicatedTest/squirrel.h index cc39cc2a..d6dfcc57 100644 --- a/NorthstarDedicatedTest/squirrel.h +++ b/NorthstarDedicatedTest/squirrel.h @@ -37,25 +37,23 @@ struct SQFuncRegistration const char* squirrelFuncName; const char* cppFuncName; const char* helpText; - const char* returnValueType; + const char* returnTypeString; const char* argTypes; - int16_t somethingThatsZero; - int16_t padding1; - int32_t unknown1; - int64_t unknown2; - int32_t unknown3; - int32_t padding2; - int64_t unknown4; - int64_t unknown5; - int64_t unknown6; - int32_t unknown7; - int32_t padding3; + __int32 unknown1; + __int32 devLevel; + const char* shortNameMaybe; + __int32 unknown2; + __int32 returnTypeEnum; + __int32* externalBufferPointer; + __int64 externalBufferSize; + __int64 unknown3; + __int64 unknown4; void* funcPtr; SQFuncRegistration() { memset(this, 0, sizeof(SQFuncRegistration)); - this->padding2 = 32; + this->returnTypeEnum = 32; } }; @@ -277,8 +275,8 @@ template <ScriptContext context> class SquirrelManager reg->helpText = new char[helpText.size() + 1]; strcpy((char*)reg->helpText, helpText.c_str()); - reg->returnValueType = new char[returnType.size() + 1]; - strcpy((char*)reg->returnValueType, returnType.c_str()); + reg->returnTypeString = new char[returnType.size() + 1]; + strcpy((char*)reg->returnTypeString, returnType.c_str()); reg->argTypes = new char[argTypes.size() + 1]; strcpy((char*)reg->argTypes, argTypes.c_str()); |