diff options
author | KittenPopo <Pokeberry123@gmail.com> | 2022-03-23 13:03:56 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-03-23 20:03:56 +0000 |
commit | de7deafc7e6ba53deec8dd3c05d2ea5e7cf42264 (patch) | |
tree | 5bc5c82c9b720adbf02cdaeb505f46b48a02178f | |
parent | 5893c7700653e5f3d51c4f7eba7f6f25a2fe5be7 (diff) | |
download | NorthstarLauncher-de7deafc7e6ba53deec8dd3c05d2ea5e7cf42264.tar.gz NorthstarLauncher-de7deafc7e6ba53deec8dd3c05d2ea5e7cf42264.zip |
Implement KittenPopo exploit fixes (and sanity changes) (#112)
* Added main exploit fixes
* Fixed typo in sigscanning.cpp
* Fully implemented
* Added proper includes for new files
* Update README.md
* typo
* spare me my sanity (fixed ridiculous code)
* Added rest of KittenMemUtils
* Rename KittenMemUtils
* Removed all messy memory edits, implemented NSMem instead
* Update NorthstarDedicatedTest.vcxproj
* [1] Move everything from securitypatches to ExploitFixes
* [2] Move everything from securitypatches to ExploitFixes
* Fixed module offsets in stack trace
* Fixed UTF8 Parsing (Multiplayer Crash)
* Implemented UT8 fix
* Update NorthstarDedicatedTest.vcxproj
* Update hookutils.cpp
* Small fixes
* all my homies hate clang-format
* Temporarily restore README.md
26 files changed, 649 insertions, 415 deletions
diff --git a/.clang-format b/.clang-format deleted file mode 100644 index 5fafec43..00000000 --- a/.clang-format +++ /dev/null @@ -1,14 +0,0 @@ -Language: Cpp -Standard: Auto -IndentWidth: 4 -TabWidth: 4 -UseCRLF: false -SpacesInSquareBrackets: false -SpacesInParentheses: false -UseTab: Always -ColumnLimit: 140 -BreakBeforeBraces: Allman -AlignAfterOpenBracket: AlwaysBreak -IndentExternBlock: Indent -PointerAlignment: Left -SortIncludes: false
\ No newline at end of file diff --git a/LauncherInjector/main.cpp b/LauncherInjector/main.cpp index f721a54f..54c65059 100644 --- a/LauncherInjector/main.cpp +++ b/LauncherInjector/main.cpp @@ -293,8 +293,6 @@ int main(int argc, char* argv[]) if (dedicated && !nostubs) { - // clang-format keeps messing with the easy-to-read if statements, so - // clang-format off printf("[*] Loading stubs\n"); HMODULE gssao, gtxaa, d3d11; if (!(gssao = GetModuleHandleA("GFSDK_SSAO.win64.dll")) && @@ -332,7 +330,6 @@ int main(int argc, char* argv[]) "[*] WARNING: Failed to load stubs because conflicting modules are already loaded, so those will be used instead " "(did Northstar initialize too late?).\n"); } - // clang-format on } { diff --git a/NorthstarDedicatedTest/ExploitFixes.cpp b/NorthstarDedicatedTest/ExploitFixes.cpp new file mode 100644 index 00000000..d36b4175 --- /dev/null +++ b/NorthstarDedicatedTest/ExploitFixes.cpp @@ -0,0 +1,196 @@ +#include "pch.h" + +#include "ExploitFixes.h" +#include "ExploitFixes_UTF8Parser.h" +#include "NSMem.h" + +// Make sure 3 or less floats are valid +bool ValidateFloats(float a, float b = 0, float c = 0) { + return !isnan(a) && !isnan(b) && !isnan(c); +} + +struct Vector { + float x, y, z; + + Vector(float x = 0, float y = 0, float z = 0) : x(x), y(y), z(z) {} + + bool IsValid() { + return ValidateFloats(x, y, z); + } +}; + +struct Angle { + float pitch, yaw, roll; + + Angle(float pitch = 0, float yaw = 0, float roll = 0) : pitch(pitch), yaw(yaw), roll(roll) {} + + bool IsInvalid() { + if (!ValidateFloats(pitch, yaw, roll)) + return false; + + return + (pitch > 90 || pitch < -90) + || (yaw > 180 || yaw < -180) + || (roll > 180 || roll < -180); + } +}; + +#define BLOCK_NETMSG_FUNC(name, pattern) \ + KHOOK(name, ("engine.dll", pattern), bool, __fastcall, (void* thisptr, void* buffer)) { return false; } + +// Servers can literally request a screenshot from any client, yeah no +BLOCK_NETMSG_FUNC(CLC_Screenshot_WriteToBuffer, "48 89 5C 24 ? 57 48 83 EC 20 8B 42 10"); +BLOCK_NETMSG_FUNC(CLC_Screenshot_ReadFromBuffer, "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC 20 48 8B DA 48 8B 52 38"); + +// This is unused ingame and a big exploit vector +BLOCK_NETMSG_FUNC(Base_CmdKeyValues_ReadFromBuffer, "40 55 48 81 EC ? ? ? ? 48 8D 6C 24 ? 48 89 5D 70"); + +// Purpose: prevent invalid user CMDs +KHOOK(CClient_ProcessUsercmds, ("engine.dll", "40 55 56 48 83 EC 58"), bool, __fastcall, (void* thisptr, void* pMsg)) { + struct __declspec(align(8)) CLC_Move { + BYTE gap0[24]; + void* m_pMessageHandler; + int m_nBackupCommands; + int m_nNewCommands; + int m_nLength; + // bf_read m_DataIn; + // bf_write m_DataOut; + }; + + auto msg = (CLC_Move*)pMsg; + + if (msg->m_nBackupCommands < 0 || msg->m_nNewCommands < 1) + return false; // Nice try buster + + constexpr int NUMCMD_SANITY_LIMIT = 16; + if ((msg->m_nNewCommands + msg->m_nBackupCommands) > NUMCMD_SANITY_LIMIT) + return false; // Im good + + if (msg->m_nLength <= 0) + return false; + + return oCClient_ProcessUsercmds(thisptr, pMsg); +} + +KHOOK(ReadUsercmd, ("server.dll", "4C 89 44 24 ? 53 55 56 57"), void, __fastcall, (void* buf, void* pCmd_move, void* pCmd_from)) { + // Let normal usercmd read happen first, it's safe + oReadUsercmd(buf, pCmd_move, pCmd_from); + + // Now let's make sure the CMD we read isnt messed up to prevent numerous exploits (including server crashing) + struct __declspec(align(4)) SV_CUserCmd + { + DWORD command_number; + DWORD tick_count; + float command_time; + Angle worldViewAngles; + BYTE gap18[4]; + Angle localViewAngles; + Angle attackangles; + Vector move; + DWORD buttons; + BYTE impulse; + short weaponselect; + DWORD meleetarget; + BYTE gap4C[24]; + char headoffset; + BYTE gap65[11]; + Vector cameraPos; + Angle cameraAngles; + BYTE gap88[4]; + int tickSomething; + DWORD dword90; + DWORD predictedServerEventAck; + DWORD dword98; + float frameTime; + }; + auto cmd = (SV_CUserCmd*)pCmd_move; + + if ( + cmd->worldViewAngles.IsInvalid() || + cmd->localViewAngles.IsInvalid() || + cmd->attackangles.IsInvalid() || + cmd->cameraAngles.IsInvalid()) { + goto INVALID_CMD; + } + + if (cmd->frameTime <= 0 || cmd->tick_count == 0 || cmd->command_time <= 0) + goto INVALID_CMD; // No simulation of bogus-timed cmds + + if (!cmd->move.IsValid() || // Prevent player freeze (and even server crash) exploit + !cmd->cameraPos.IsValid()) // IIRC this can crash spectating clients or anyone watching replays + goto INVALID_CMD; + + if (!ValidateFloats(cmd->cameraPos.x, cmd->cameraPos.y, cmd->cameraPos.z)) + goto INVALID_CMD; // IIRC this can crash spectating clients or anyone watching replays + + return; +INVALID_CMD: + + // Fix any gameplay-affecting cmd properties + // NOTE: Currently tickcount/frametime is set to 0, this ~shouldn't~ cause any problems + cmd->worldViewAngles = cmd->localViewAngles = cmd->attackangles = cmd->cameraAngles = Angle(0,0,0); + cmd->tick_count = cmd->frameTime = 0; + cmd->move = cmd->cameraPos = Vector(0, 0, 0); + cmd->buttons = 0; + cmd->meleetarget = 0; +} + +// basically: by default r2 isn't set as a valve mod, meaning that m_bRestrictServerCommands is false +// this is HORRIBLE for security, because it means servers can run arbitrary concommands on clients +// especially since we have script commands this could theoretically be awful +#include "gameutils.h" +KHOOK(IsValveMod, ("engine.dll", "48 83 EC 28 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? E8 ? ? ? ? 85 C0 74 63"), bool, __fastcall, ()) { + return !CommandLine()->CheckParm("-norestrictservercommands"); +} + +// Fix respawn's crappy UTF8 parser so it doesn't crash -_- +// This also means you can launch multiplayer with "communities_enabled 1" and not crash, you're welcome +KHOOK(CrashFunc_ParseUTF8, ("engine.dll", "48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 41 54 41 55 41 56 41 57 48 83 EC 20 8B 1A"), + bool, __fastcall, (INT64* a1, DWORD* a2, char* strData)) { + + static void* targetRetAddr = NSMem::PatternScan("engine.dll", "84 C0 75 2C 49 8B 16"); + + if (_ReturnAddress() == targetRetAddr) { + if (!ExploitFixes_UTF8Parser::CheckValid(a1, a2, strData)) { + spdlog::warn("ParseUTF8 Hook: Ignoring potentially-crashing utf8 string"); + return false; + } + } + + return oCrashFunc_ParseUTF8(a1, a2, strData); +} + +////////////////////////////////////////////////// + +void DoBytePatches() { + uintptr_t engineBase = (uintptr_t)GetModuleHandleA("engine.dll"); + + // patches to make commands run from client/ui script still work + // note: this is likely preventable in a nicer way? test prolly + NSMem::BytePatch(engineBase + 0x4FB65, {0xEB, 0x11}); + NSMem::BytePatch(engineBase + 0x4FBAC, {0xEB, 0x16}); + + // disconnect concommand + { + uintptr_t addr = engineBase + 0x5ADA2D; + int val = *(int*)addr | FCVAR_SERVER_CAN_EXECUTE; + NSMem::BytePatch(addr, (BYTE*)&val, sizeof(int)); + } +} + +void ExploitFixes::LoadCallback(HMODULE unused) { + spdlog::info("ExploitFixes::LoadCallback ..."); + + spdlog::info("\tByte patching..."); + DoBytePatches(); + + if (KHook::InitAllHooks()) { + spdlog::info("\tInitialized " + std::to_string(KHook::_allHooks.size()) + " exploit-patch hooks."); + } else { + spdlog::critical("\tFAILED to initialize all exploit patches."); + + // Force exit? + MessageBoxA(0, "FAILED to initialize all exploit patches.", "Northstar", MB_ICONERROR); + exit(0); + } +}
\ No newline at end of file diff --git a/NorthstarDedicatedTest/ExploitFixes.h b/NorthstarDedicatedTest/ExploitFixes.h new file mode 100644 index 00000000..49928640 --- /dev/null +++ b/NorthstarDedicatedTest/ExploitFixes.h @@ -0,0 +1,8 @@ +// KittenPopo's exploit fix hooks, feel free to add more here + +#pragma once +#include "pch.h" + +namespace ExploitFixes { + void LoadCallback(HMODULE unused); +} diff --git a/NorthstarDedicatedTest/ExploitFixes_UTF8Parser.h b/NorthstarDedicatedTest/ExploitFixes_UTF8Parser.h new file mode 100644 index 00000000..6b767a0c --- /dev/null +++ b/NorthstarDedicatedTest/ExploitFixes_UTF8Parser.h @@ -0,0 +1,175 @@ +// Reimplementation of a exploitable UTF decoding function in titanfall + +#include "NSMem.h" + +#pragma once + +namespace ExploitFixes_UTF8Parser +{ + bool __fastcall CheckValid(INT64* a1, DWORD* a2, char* strData) + { + static auto sub_F1320 = (INT64(__fastcall*)(DWORD a1, char* a2)) NSMem::PatternScan("engine.dll", "83 F9 7F 77 08 88 0A"); + + 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 (!NSMem::IsMemoryReadable(v4, 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; + } +}
\ No newline at end of file diff --git a/NorthstarDedicatedTest/NSMem.h b/NorthstarDedicatedTest/NSMem.h new file mode 100644 index 00000000..a6ddf033 --- /dev/null +++ b/NorthstarDedicatedTest/NSMem.h @@ -0,0 +1,152 @@ +#pragma once +#include "pch.h" + +// KittenPopo's memory stuff, made for northstar (because I really can't handle working with northstar's original memory stuff tbh) + +#pragma region Pattern Scanning +namespace NSMem { + inline void* PatternScan(void* module, const int* pattern, int patternSize, int offset) { + if (!module) + return NULL; + + auto dosHeader = (PIMAGE_DOS_HEADER)module; + auto ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)module + dosHeader->e_lfanew); + + auto sizeOfImage = ntHeaders->OptionalHeader.SizeOfImage; + + auto scanBytes = (BYTE*)module; + + for (auto i = 0; i < sizeOfImage - patternSize; ++i) { + bool found = true; + for (auto j = 0; j < patternSize; ++j) { + if (scanBytes[i + j] != pattern[j] && pattern[j] != -1) { + found = false; + break; + } + } + + if (found) { + uintptr_t addressInt = (uintptr_t)(&scanBytes[i]) + offset; + return (uint8_t*)addressInt; + } + } + + return nullptr; + } + + inline void* PatternScan(const char* moduleName, const char* pattern, int offset = 0) { + std::vector<int> patternNums; + + bool lastChar = 0; + int size = strlen(pattern); + for (int i = 0; i < size; i++) { + char c = pattern[i]; + + // If this is a space character, ignore it + if (c == ' ' || c == '\t') + continue; + + if (c == '?') { + // Add a wildcard (-1) + patternNums.push_back(-1); + } else if (i < size - 1) { + BYTE result = 0; + for (int j = 0; j < 2; j++) { + int val = 0; + char c = (pattern + i + j)[0]; + if (c >= 'a') { + val = c - 'a' + 0xA; + } else if (c >= 'A') { + val = c - 'A' + 0xA; + } else { + val = c - '0'; + } + + result += (j == 0) ? val * 16 : val; + } + patternNums.push_back(result); + } + + i++; + } + return PatternScan(GetModuleHandleA(moduleName), &patternNums[0], patternNums.size(), offset); + } + + inline void BytePatch(uintptr_t address, const BYTE* vals, int size) { + WriteProcessMemory(GetCurrentProcess(), (LPVOID)address, vals, size, NULL); + } + + inline void BytePatch(uintptr_t address, std::initializer_list<BYTE> vals) { + std::vector<BYTE> bytes = vals; + if (!bytes.empty()) + BytePatch(address, &bytes[0], bytes.size()); + } + + inline void NOP(uintptr_t address, int size) { + BYTE* buf = (BYTE*)malloc(size); + memset(buf, 0x90, size); + BytePatch(address, buf, size); + free(buf); + } + + inline bool IsMemoryReadable(void* ptr, size_t size) { + BYTE* buffer = (BYTE*)malloc(size); + + size_t numWritten = 0; + ReadProcessMemory(GetCurrentProcess(), ptr, buffer, size, &numWritten); + free(buffer); + + return numWritten == size; + } +} + +#pragma region KHOOK +struct KHookPatternInfo { + const char* moduleName, *pattern; + int offset = 0; + + KHookPatternInfo(const char* moduleName, const char* pattern, int offset = 0) + : moduleName(moduleName), pattern(pattern), offset(offset) {} +}; + +struct KHook { + KHookPatternInfo targetFunc; + void* targetFuncAddr; + void* hookFunc; + void** original; + + static inline std::vector<KHook*> _allHooks; + + KHook(KHookPatternInfo targetFunc, void* hookFunc, void** original) : targetFunc(targetFunc) { + this->hookFunc = hookFunc; + this->original = original; + _allHooks.push_back(this); + } + + bool Setup() { + targetFuncAddr = NSMem::PatternScan(targetFunc.moduleName, targetFunc.pattern, targetFunc.offset); + if (!targetFuncAddr) + return false; + + return MH_CreateHook(targetFuncAddr, hookFunc, original) == MH_OK; + } + + // Returns true if succeeded + static bool InitAllHooks() { + for (KHook* hook : _allHooks) { + if (hook->Setup()) { + spdlog::info("KHook hooked at {}", hook->targetFuncAddr); + } else { + return false; + } + } + + return MH_EnableHook(MH_ALL_HOOKS) == MH_OK; + } +}; +#define KHOOK(name, funcPatternInfo, returnType, convention, args) \ + returnType convention hk##name args; \ + auto o##name = (returnType(convention*) args)0; \ + KHook k##name = KHook(KHookPatternInfo funcPatternInfo, &hk##name, (void**)&o##name); \ + returnType convention hk##name args +#pragma endregion
\ No newline at end of file diff --git a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj index e931fa51..4a383d37 100644 --- a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj +++ b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj @@ -549,7 +549,6 @@ <ClInclude Include="rpakfilesystem.h" /> <ClInclude Include="scriptbrowserhooks.h" /> <ClInclude Include="scriptmainmenupromos.h" /> - <ClInclude Include="securitypatches.h" /> <ClInclude Include="scriptmodmenu.h" /> <ClInclude Include="scriptserverbrowser.h" /> <ClInclude Include="scriptsrson.h" /> @@ -560,6 +559,9 @@ <ClInclude Include="sourceinterface.h" /> <ClInclude Include="squirrel.h" /> <ClInclude Include="state.h" /> + <ClInclude Include="ExploitFixes.h" /> + <ClInclude Include="ExploitFixes_UTF8Parser.h" /> + <ClInclude Include="NSMem.h" /> </ItemGroup> <ItemGroup> <ClCompile Include="audio.cpp" /> @@ -605,7 +607,6 @@ <ClCompile Include="rpakfilesystem.cpp" /> <ClCompile Include="scriptbrowserhooks.cpp" /> <ClCompile Include="scriptmainmenupromos.cpp" /> - <ClCompile Include="securitypatches.cpp" /> <ClCompile Include="scriptmodmenu.cpp" /> <ClCompile Include="scriptserverbrowser.cpp" /> <ClCompile Include="scriptsrson.cpp" /> @@ -617,6 +618,7 @@ <ClCompile Include="sourceconsole.cpp" /> <ClCompile Include="sourceinterface.cpp" /> <ClCompile Include="squirrel.cpp" /> + <ClCompile Include="ExploitFixes.cpp" /> </ItemGroup> <ItemGroup> <None Include="include\crypto\bn_conf.h.in" /> @@ -655,4 +657,4 @@ <ImportGroup Label="ExtensionTargets"> <Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" /> </ImportGroup> -</Project>
\ No newline at end of file +</Project> diff --git a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters index a5460132..c03d822f 100644 --- a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters +++ b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters @@ -127,6 +127,12 @@ <Filter Include="Header Files\Shared\Math"> <UniqueIdentifier>{44a83740-9d70-480d-9a7a-43b81f8eab9e}</UniqueIdentifier> </Filter> + <Filter Include="Source Files\Shared\Exploit Fixes"> + <UniqueIdentifier>{4a8a695a-a103-4b1f-b314-0ec19a253119}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files\Shared\Exploit Fixes\UTF8Parser"> + <UniqueIdentifier>{b30e08b1-b962-4264-8cbb-a0a31924b93e}</UniqueIdentifier> + </Filter> </ItemGroup> <ItemGroup> <ClInclude Include="pch.h"> @@ -582,9 +588,6 @@ <ClInclude Include="playlist.h"> <Filter>Header Files\Server</Filter> </ClInclude> - <ClInclude Include="securitypatches.h"> - <Filter>Header Files\Client</Filter> - </ClInclude> <ClInclude Include="dedicatedmaterialsystem.h"> <Filter>Header Files\Dedicated</Filter> </ClInclude> @@ -1488,6 +1491,15 @@ <ClInclude Include="clientvideooverrides.h"> <Filter>Header Files\Client</Filter> </ClInclude> + <ClInclude Include="ExploitFixes.h"> + <Filter>Source Files\Shared\Exploit Fixes</Filter> + </ClInclude> + <ClInclude Include="NSMem.h"> + <Filter>Source Files\Shared\Exploit Fixes</Filter> + </ClInclude> + <ClInclude Include="ExploitFixes_UTF8Parser.h"> + <Filter>Source Files\Shared\Exploit Fixes\UTF8Parser</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="dllmain.cpp"> @@ -1565,9 +1577,6 @@ <ClCompile Include="playlist.cpp"> <Filter>Source Files\Server</Filter> </ClCompile> - <ClCompile Include="securitypatches.cpp"> - <Filter>Source Files\Client</Filter> - </ClCompile> <ClCompile Include="dedicatedmaterialsystem.cpp"> <Filter>Source Files\Dedicated</Filter> </ClCompile> @@ -1646,6 +1655,9 @@ <ClCompile Include="clientvideooverrides.cpp"> <Filter>Source Files\Client</Filter> </ClCompile> + <ClCompile Include="ExploitFixes.cpp"> + <Filter>Source Files\Shared\Exploit Fixes</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <MASM Include="audio_asm.asm"> diff --git a/NorthstarDedicatedTest/buildainfile.cpp b/NorthstarDedicatedTest/buildainfile.cpp index 3adb8324..3f30eff3 100644 --- a/NorthstarDedicatedTest/buildainfile.cpp +++ b/NorthstarDedicatedTest/buildainfile.cpp @@ -4,6 +4,7 @@ #include "hookutils.h" #include <fstream> #include <filesystem> +#include "NSMem.h" namespace fs = std::filesystem; @@ -390,27 +391,10 @@ void InitialiseBuildAINFileHooks(HMODULE baseAddress) pUnkServerMapversionGlobal = (char**)((char*)baseAddress + 0xBFBE08); pMapName = (char*)baseAddress + 0x1053370; + uintptr_t base = (uintptr_t)baseAddress; + // remove a check that prevents a logging function in link generation from working // due to the sheer amount of logging this is a massive perf hit to generation, but spewlog_enable 0 exists so whatever - { - void* ptr = (char*)baseAddress + 0x3889B6; - TempReadWrite rw(ptr); - *((char*)ptr) = (char)0x90; - *((char*)ptr + 1) = (char)0x90; - *((char*)ptr + 2) = (char)0x90; - *((char*)ptr + 3) = (char)0x90; - *((char*)ptr + 4) = (char)0x90; - *((char*)ptr + 5) = (char)0x90; - } - - { - void* ptr = (char*)baseAddress + 0x3889BF; - TempReadWrite rw(ptr); - *((char*)ptr) = (char)0x90; - *((char*)ptr + 1) = (char)0x90; - *((char*)ptr + 2) = (char)0x90; - *((char*)ptr + 3) = (char)0x90; - *((char*)ptr + 4) = (char)0x90; - *((char*)ptr + 5) = (char)0x90; - } + NSMem::NOP(base + 0x3889B6, 6); + NSMem::NOP(base + 0x3889BF, 6); }
\ No newline at end of file diff --git a/NorthstarDedicatedTest/dedicated.cpp b/NorthstarDedicatedTest/dedicated.cpp index 9372b7a5..e0206d1f 100644 --- a/NorthstarDedicatedTest/dedicated.cpp +++ b/NorthstarDedicatedTest/dedicated.cpp @@ -117,278 +117,125 @@ DWORD WINAPI ConsoleInputThread(PVOID pThreadParameter) return 0; } +#include "NSMem.h" void InitialiseDedicated(HMODULE engineAddress) { spdlog::info("InitialiseDedicated"); + uintptr_t ea = (uintptr_t)engineAddress; + { // Host_Init // prevent a particle init that relies on client dll - - char* ptr = (char*)engineAddress + 0x156799; - TempReadWrite rw(ptr); - - *ptr = (char)0x90; - *(ptr + 1) = (char)0x90; - *(ptr + 2) = (char)0x90; - *(ptr + 3) = (char)0x90; - *(ptr + 4) = (char)0x90; + NSMem::NOP(ea + 0x156799, 5); } { // CModAppSystemGroup::Create // force the engine into dedicated mode by changing the first comparison to IsServerOnly to an assignment - char* ptr = (char*)engineAddress + 0x1C4EBD; - TempReadWrite rw(ptr); + auto ptr = ea + 0x1C4EBD; // cmp => mov - *(ptr + 1) = (char)0xC6; - *(ptr + 2) = (char)0x87; + NSMem::BytePatch(ptr + 1, {0xC6, 0x87}); // 00 => 01 - *((char*)ptr + 7) = (char)0x01; + NSMem::BytePatch(ptr + 7, {0x01}); } { // Some init that i'm not sure of that crashes - char* ptr = (char*)engineAddress + 0x156A63; - TempReadWrite rw(ptr); - // nop the call to it - *ptr = (char)0x90; - *(ptr + 1) = (char)0x90; - *(ptr + 2) = (char)0x90; - *(ptr + 3) = (char)0x90; - *(ptr + 4) = (char)0x90; + NSMem::NOP(ea + 0x156A63, 5); } { // runframeserver - char* ptr = (char*)engineAddress + 0x159819; - TempReadWrite rw(ptr); - // nop some access violations - *ptr = (char)0x90; - *(ptr + 1) = (char)0x90; - *(ptr + 2) = (char)0x90; - *(ptr + 3) = (char)0x90; - *(ptr + 4) = (char)0x90; - *(ptr + 5) = (char)0x90; - *(ptr + 6) = (char)0x90; - *(ptr + 7) = (char)0x90; - *(ptr + 8) = (char)0x90; - *(ptr + 9) = (char)0x90; - *(ptr + 10) = (char)0x90; - *(ptr + 11) = (char)0x90; - *(ptr + 12) = (char)0x90; - *(ptr + 13) = (char)0x90; - *(ptr + 14) = (char)0x90; - *(ptr + 15) = (char)0x90; - *(ptr + 16) = (char)0x90; + NSMem::NOP(ea + 0x159819, 17); } { - // HostState_State_NewGame - char* ptr = (char*)engineAddress + 0x156B4C; - TempReadWrite rw(ptr); - - // nop some access violations - *ptr = (char)0x90; - *(ptr + 1) = (char)0x90; - *(ptr + 2) = (char)0x90; - *(ptr + 3) = (char)0x90; - *(ptr + 4) = (char)0x90; - *(ptr + 5) = (char)0x90; - *(ptr + 6) = (char)0x90; + NSMem::NOP(ea + 0x156B4C, 7); // previously patched these, took me a couple weeks to figure out they were the issue // removing these will mess up register state when this function is over, so we'll write HS_RUN to the wrong address // so uhh, don't do that - //*(ptr + 7) = (char)0x90; - //*(ptr + 8) = (char)0x90; - //*(ptr + 9) = (char)0x90; - //*(ptr + 10) = (char)0x90; - //*(ptr + 11) = (char)0x90; - //*(ptr + 12) = (char)0x90; - //*(ptr + 13) = (char)0x90; - //*(ptr + 14) = (char)0x90; - - *(ptr + 15) = (char)0x90; - *(ptr + 16) = (char)0x90; - *(ptr + 17) = (char)0x90; - *(ptr + 18) = (char)0x90; - *(ptr + 19) = (char)0x90; - *(ptr + 20) = (char)0x90; - *(ptr + 21) = (char)0x90; - *(ptr + 22) = (char)0x90; - *(ptr + 23) = (char)0x90; + //NSMem::NOP(ea + 0x156B4C + 7, 8); + + NSMem::NOP(ea + 0x156B4C + 15, 9); } { // HostState_State_NewGame - char* ptr = (char*)engineAddress + 0xB934C; - TempReadWrite rw(ptr); - // nop an access violation - *ptr = (char)0x90; - *(ptr + 1) = (char)0x90; - *(ptr + 2) = (char)0x90; - *(ptr + 3) = (char)0x90; - *(ptr + 4) = (char)0x90; - *(ptr + 5) = (char)0x90; - *(ptr + 6) = (char)0x90; - *(ptr + 7) = (char)0x90; - *(ptr + 8) = (char)0x90; + NSMem::NOP(ea + 0xB934C, 9); } { // CEngineAPI::Connect - char* ptr = (char*)engineAddress + 0x1C4D7D; - TempReadWrite rw(ptr); - // remove call to Shader_Connect - *ptr = (char)0x90; - *(ptr + 1) = (char)0x90; - *(ptr + 2) = (char)0x90; - *(ptr + 3) = (char)0x90; - *(ptr + 4) = (char)0x90; + NSMem::NOP(ea + 0x1C4D7D, 5); } // currently does not work, crashes stuff, likely gotta keep this here //{ // // CEngineAPI::Connect - // char* ptr = (char*)engineAddress + 0x1C4E07; - // TempReadWrite rw(ptr); - // - // // remove calls to register ui rpak asset types - // *ptr = 0x90; - // *(ptr + 1) = (char)0x90; - // *(ptr + 2) = (char)0x90; - // *(ptr + 3) = (char)0x90; - // *(ptr + 4) = (char)0x90; + // // remove calls to register ui rpak asset types + // NSMem::NOP(ea + 0x1C4E07, 5); //} // not sure if this should be done, not loading ui at least is good, but should everything be gone? { // Host_Init - char* ptr = (char*)engineAddress + 0x15653B; - TempReadWrite rw(ptr); - // change the number of rpaks to load from 6 to 1, so we only load common.rpak - *(ptr + 1) = (char)0x01; + NSMem::BytePatch(ea + 0x15653B + 1, {0x01}); } { // Host_Init - char* ptr = (char*)engineAddress + 0x156595; - TempReadWrite rw(ptr); - // remove call to ui loading stuff - *ptr = (char)0x90; - *(ptr + 1) = (char)0x90; - *(ptr + 2) = (char)0x90; - *(ptr + 3) = (char)0x90; - *(ptr + 4) = (char)0x90; + NSMem::NOP(ea + 0x156595, 5); } { // some function that gets called from RunFrameServer - char* ptr = (char*)engineAddress + 0x15A0BB; - TempReadWrite rw(ptr); - // nop a function that makes requests to stryder, this will eventually access violation if left alone and isn't necessary anyway - *ptr = (char)0x90; - *(ptr + 1) = (char)0x90; - *(ptr + 2) = (char)0x90; - *(ptr + 3) = (char)0x90; - *(ptr + 4) = (char)0x90; + NSMem::NOP(ea + 0x15A0BB, 5); } { // RunFrameServer - char* ptr = (char*)engineAddress + 0x159BF3; - TempReadWrite rw(ptr); - // nop a function that access violations - *ptr = (char)0x90; - *(ptr + 1) = (char)0x90; - *(ptr + 2) = (char)0x90; - *(ptr + 3) = (char)0x90; - *(ptr + 4) = (char)0x90; + NSMem::NOP(ea + 0x159BF3, 5); } { // func that checks if origin is inited - char* ptr = (char*)engineAddress + 0x183B70; - TempReadWrite rw(ptr); - // always return 1 - *ptr = (char)0xB0; // mov al,01 - *(ptr + 1) = (char)0x01; - *(ptr + 2) = (char)0xC3; // ret + NSMem::BytePatch(ea + 0x183B70, { + 0xB0, 0x01, // mov al,01 + 0xC3 // ret + }); } { // HostState_State_ChangeLevel - char* ptr = (char*)engineAddress + 0x1552ED; - TempReadWrite rw(ptr); - // nop clientinterface call - *ptr = (char)0x90; - *(ptr + 1) = (char)0x90; - *(ptr + 2) = (char)0x90; - *(ptr + 3) = (char)0x90; - *(ptr + 4) = (char)0x90; - *(ptr + 5) = (char)0x90; - *(ptr + 6) = (char)0x90; - *(ptr + 7) = (char)0x90; - *(ptr + 8) = (char)0x90; - *(ptr + 9) = (char)0x90; - *(ptr + 10) = (char)0x90; - *(ptr + 11) = (char)0x90; - *(ptr + 12) = (char)0x90; - *(ptr + 13) = (char)0x90; - *(ptr + 14) = (char)0x90; - *(ptr + 15) = (char)0x90; + NSMem::NOP(ea + 0x1552ED, 16); } { // HostState_State_ChangeLevel - char* ptr = (char*)engineAddress + 0x155363; - TempReadWrite rw(ptr); - // nop clientinterface call - *ptr = (char)0x90; - *(ptr + 1) = (char)0x90; - *(ptr + 2) = (char)0x90; - *(ptr + 3) = (char)0x90; - *(ptr + 4) = (char)0x90; - *(ptr + 5) = (char)0x90; - *(ptr + 6) = (char)0x90; - *(ptr + 7) = (char)0x90; - *(ptr + 8) = (char)0x90; - *(ptr + 9) = (char)0x90; - *(ptr + 10) = (char)0x90; - *(ptr + 11) = (char)0x90; - *(ptr + 12) = (char)0x90; - *(ptr + 13) = (char)0x90; - *(ptr + 14) = (char)0x90; - *(ptr + 15) = (char)0x90; + NSMem::NOP(ea + 0x155363, 16); } // note: previously had DisableDedicatedWindowCreation patches here, but removing those rn since they're all shit and unstable and bad // and such check commit history if any are needed for reimplementation { // IVideoMode::CreateGameWindow - char* ptr = (char*)engineAddress + 0x1CD146; - TempReadWrite rw(ptr); - // nop call to ShowWindow - *ptr = (char)0x90; - *(ptr + 1) = (char)0x90; - *(ptr + 2) = (char)0x90; - *(ptr + 3) = (char)0x90; - *(ptr + 4) = (char)0x90; + NSMem::NOP(ea + 0x1CD146, 5); } CDedicatedExports* dedicatedExports = new CDedicatedExports; @@ -454,9 +301,9 @@ void InitialiseDedicatedOrigin(HMODULE baseAddress) // for any big ea lawyers, this can't be used to play the game without origin, game will throw a fit if you try to do anything without // an origin id as a client for dedi it's fine though, game doesn't care if origin is disabled as long as there's only a server - char* ptr = (char*)GetProcAddress(GetModuleHandleA("tier0.dll"), "Tier0_InitOrigin"); - TempReadWrite rw(ptr); - *ptr = (char)0xC3; // ret + NSMem::BytePatch((uintptr_t)GetProcAddress(GetModuleHandleA("tier0.dll"), "Tier0_InitOrigin"), { + 0xC3 // ret + }); } typedef void (*PrintFatalSquirrelErrorType)(void* sqvm); diff --git a/NorthstarDedicatedTest/dedicatedmaterialsystem.cpp b/NorthstarDedicatedTest/dedicatedmaterialsystem.cpp index 00652698..8fa2b58d 100644 --- a/NorthstarDedicatedTest/dedicatedmaterialsystem.cpp +++ b/NorthstarDedicatedTest/dedicatedmaterialsystem.cpp @@ -4,6 +4,7 @@ #include "dedicatedmaterialsystem.h" #include "hookutils.h" #include "gameutils.h" +#include "NSMem.h" typedef HRESULT (*__stdcall D3D11CreateDeviceType)( void* pAdapter, int DriverType, HMODULE Software, UINT Flags, int* pFeatureLevels, UINT FeatureLevels, UINT SDKVersion, void** ppDevice, @@ -59,14 +60,10 @@ void InitialiseDedicatedMaterialSystem(HMODULE baseAddress) { // CMaterialSystem::FindMaterial - char* ptr = (char*)baseAddress + 0x5F0F1; - TempReadWrite rw(ptr); - // make the game always use the error material - *ptr = (char)0xE9; - *(ptr + 1) = (char)0x34; - *(ptr + 2) = (char)0x03; - *(ptr + 3) = (char)0x00; + NSMem::BytePatch((uintptr_t)baseAddress + 0x5F0F1, { + 0xE9, 0x34, 0x03, 0x00 + }); } // previously had DisableDedicatedWindowCreation stuff here, removing for now since shit and unstable diff --git a/NorthstarDedicatedTest/dllmain.cpp b/NorthstarDedicatedTest/dllmain.cpp index f838d82a..55951b66 100644 --- a/NorthstarDedicatedTest/dllmain.cpp +++ b/NorthstarDedicatedTest/dllmain.cpp @@ -18,7 +18,6 @@ #include "chatcommand.h" #include "modlocalisation.h" #include "playlist.h" -#include "securitypatches.h" #include "miscserverscript.h" #include "clientauthhooks.h" #include "latencyflex.h" @@ -48,6 +47,7 @@ #include "rapidjson/stringbuffer.h" #include "rapidjson/writer.h" #include "rapidjson/error/en.h" +#include "ExploitFixes.h" typedef void (*initPluginFuncPtr)(void* getPluginObject); @@ -225,7 +225,6 @@ bool InitialiseNorthstar() // client-exclusive patches { AddDllLoadCallbackForClient("tier0.dll", InitialiseTier0LanguageHooks); - AddDllLoadCallbackForClient("engine.dll", InitialiseClientEngineSecurityPatches); AddDllLoadCallbackForClient("client.dll", InitialiseClientSquirrel); AddDllLoadCallbackForClient("client.dll", InitialiseSourceConsole); AddDllLoadCallbackForClient("engine.dll", InitialiseChatCommands); @@ -274,6 +273,9 @@ bool InitialiseNorthstar() // mod manager after everything else AddDllLoadCallback("engine.dll", InitialiseModManager); + // activate exploit fixes + AddDllLoadCallback("server.dll", ExploitFixes::LoadCallback); + // run callbacks for any libraries that are already loaded by now CallAllPendingDLLLoadCallbacks(); diff --git a/NorthstarDedicatedTest/hookutils.cpp b/NorthstarDedicatedTest/hookutils.cpp index b7d82724..f5df3996 100644 --- a/NorthstarDedicatedTest/hookutils.cpp +++ b/NorthstarDedicatedTest/hookutils.cpp @@ -3,22 +3,6 @@ #include <iostream> -TempReadWrite::TempReadWrite(void* ptr) -{ - m_ptr = ptr; - MEMORY_BASIC_INFORMATION mbi; - VirtualQuery(m_ptr, &mbi, sizeof(mbi)); - VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect); - m_origProtection = mbi.Protect; -} - -TempReadWrite::~TempReadWrite() -{ - MEMORY_BASIC_INFORMATION mbi; - VirtualQuery(m_ptr, &mbi, sizeof(mbi)); - VirtualProtect(mbi.BaseAddress, mbi.RegionSize, m_origProtection, &mbi.Protect); -} - void HookEnabler::CreateHook(LPVOID ppTarget, LPVOID ppDetour, LPVOID* ppOriginal, const char* targetName) { // the macro for this uses ppTarget's name as targetName, and this typically starts with & diff --git a/NorthstarDedicatedTest/hookutils.h b/NorthstarDedicatedTest/hookutils.h index 006bc16d..47ec12bf 100644 --- a/NorthstarDedicatedTest/hookutils.h +++ b/NorthstarDedicatedTest/hookutils.h @@ -1,18 +1,6 @@ #pragma once #include <vector> -// Sets an area of memory as writeable until the TempReadWrite object goes out of scope -class TempReadWrite -{ - private: - DWORD m_origProtection; - void* m_ptr; - - public: - TempReadWrite(void* ptr); - ~TempReadWrite(); -}; - // Enables all hooks created with the HookEnabler object when it goes out of scope and handles hook errors class HookEnabler { diff --git a/NorthstarDedicatedTest/include/spdlog/fmt/bundled/chrono.h b/NorthstarDedicatedTest/include/spdlog/fmt/bundled/chrono.h index 1a3b8d5e..bdfe2aa0 100644 --- a/NorthstarDedicatedTest/include/spdlog/fmt/bundled/chrono.h +++ b/NorthstarDedicatedTest/include/spdlog/fmt/bundled/chrono.h @@ -101,7 +101,6 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { return from; } // function -// clang-format off /** * converts From to To if possible, otherwise ec is set. * @@ -114,7 +113,6 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { * subnormal | best effort * -Inf | -Inf */ -// clang-format on template <typename To, typename From, FMT_ENABLE_IF(!std::is_same<From, To>::value)> FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { diff --git a/NorthstarDedicatedTest/logging.cpp b/NorthstarDedicatedTest/logging.cpp index 975bc879..9b2a0925 100644 --- a/NorthstarDedicatedTest/logging.cpp +++ b/NorthstarDedicatedTest/logging.cpp @@ -153,7 +153,10 @@ long __stdcall ExceptionFilter(EXCEPTION_POINTERS* exceptionInfo) GetModuleFileNameExA(GetCurrentProcess(), backtraceModuleHandle, backtraceModuleFullName, MAX_PATH); char* backtraceModuleName = strrchr(backtraceModuleFullName, '\\') + 1; - spdlog::error(" {} + {}", backtraceModuleName, (void*)framesToCapture[i]); + void* actualAddress = (void*)framesToCapture[i]; + void* relativeAddress = (void*)(uintptr_t(actualAddress) - uintptr_t(backtraceModuleHandle)); + + spdlog::error(" {} + {} ({})", backtraceModuleName, relativeAddress, actualAddress); } spdlog::error("RAX: 0x{0:x}", exceptionContext->Rax); diff --git a/NorthstarDedicatedTest/maxplayers.cpp b/NorthstarDedicatedTest/maxplayers.cpp index 674d3ef0..54f1a896 100644 --- a/NorthstarDedicatedTest/maxplayers.cpp +++ b/NorthstarDedicatedTest/maxplayers.cpp @@ -45,10 +45,9 @@ constexpr int Team_PlayerArray_AddedLength = NEW_MAX_PLAYERS - 32; constexpr int Team_PlayerArray_AddedSize = PAD_NUMBER(Team_PlayerArray_AddedLength * 8, 4); constexpr int Team_AddedSize = Team_PlayerArray_AddedSize; -template <class T> void ChangeOffset(void* addr, unsigned int offset) -{ - TempReadWrite rw(addr); - *((T*)addr) = offset; +#include "NSMem.h" +template <class T> void ChangeOffset(void* addr, unsigned int offset) { + NSMem::BytePatch((uintptr_t)addr, (BYTE*)&offset, sizeof(T)); } /* diff --git a/NorthstarDedicatedTest/miscserverfixes.cpp b/NorthstarDedicatedTest/miscserverfixes.cpp index fca9c169..0b9a12db 100644 --- a/NorthstarDedicatedTest/miscserverfixes.cpp +++ b/NorthstarDedicatedTest/miscserverfixes.cpp @@ -2,32 +2,26 @@ #include "miscserverfixes.h" #include "hookutils.h" +#include "NSMem.h" + void InitialiseMiscServerFixes(HMODULE baseAddress) { + uintptr_t ba = (uintptr_t)baseAddress; + // ret at the start of the concommand GenerateObjFile as it can crash servers { - char* ptr = reinterpret_cast<char*>(baseAddress) + 0x38D920; - TempReadWrite rw(ptr); - *ptr = 0xC3; + NSMem::BytePatch(ba + 0x38D920, {0xC3}); } // nop out call to VGUI shutdown since it crashes the game when quitting from the console { - char* ptr = reinterpret_cast<char*>(baseAddress) + 0x154A96; - TempReadWrite rw(ptr); - *(ptr++) = 0x90; // nop - *(ptr++) = 0x90; // nop - *(ptr++) = 0x90; // nop - *(ptr++) = 0x90; // nop - *ptr = 0x90; // nop + NSMem::NOP(ba + 0x154A96, 5); } // 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 { - char* ptr = reinterpret_cast<char*>(baseAddress) + 0x153920; - TempReadWrite rw(ptr); - *ptr = 0xC3; + NSMem::BytePatch(ba + 0x153920, {0xC3}); } } diff --git a/NorthstarDedicatedTest/playlist.cpp b/NorthstarDedicatedTest/playlist.cpp index 7f06844f..a0a2dc33 100644 --- a/NorthstarDedicatedTest/playlist.cpp +++ b/NorthstarDedicatedTest/playlist.cpp @@ -71,6 +71,7 @@ int GetCurrentGamemodeMaxPlayersHook() return maxPlayers; } +#include "NSMem.h" void InitialisePlaylistHooks(HMODULE baseAddress) { RegisterConCommand("setplaylist", SetPlaylistCommand, "Sets the current playlist", FCVAR_NONE); @@ -92,21 +93,16 @@ void InitialisePlaylistHooks(HMODULE baseAddress) ENABLER_CREATEHOOK( hook, (char*)baseAddress + 0x18C430, &GetCurrentGamemodeMaxPlayersHook, reinterpret_cast<LPVOID*>(&GetCurrentGamemodeMaxPlayers)); + uintptr_t ba = (uintptr_t)baseAddress; + // patch to prevent clc_SetPlaylistVarOverride from being able to crash servers if we reach max overrides due to a call to Error (why is // this possible respawn, wtf) todo: add a warning for this { - void* ptr = (char*)baseAddress + 0x18ED8D; - TempReadWrite rw(ptr); - *((char*)ptr) = (char)0xC3; // jmp => ret + NSMem::BytePatch(ba + 0x18ED8D, { + 0xC3 // jmp => ret + }); } // patch to allow setplaylistvaroverride to be called before map init on dedicated and private match launched through the game - void* ptr = (char*)baseAddress + 0x18ED17; - TempReadWrite rw(ptr); - *((char*)ptr) = (char)0x90; - *((char*)ptr + 1) = (char)0x90; - *((char*)ptr + 2) = (char)0x90; - *((char*)ptr + 3) = (char)0x90; - *((char*)ptr + 4) = (char)0x90; - *((char*)ptr + 5) = (char)0x90; + NSMem::NOP(ba + 0x18ED17, 6); } diff --git a/NorthstarDedicatedTest/plugin_abi.h b/NorthstarDedicatedTest/plugin_abi.h index edd44ea1..698c33ab 100644 --- a/NorthstarDedicatedTest/plugin_abi.h +++ b/NorthstarDedicatedTest/plugin_abi.h @@ -2,8 +2,6 @@ #include <string> #define ABI_VERSION 1 -//clang-format off -// I hate clang-format /// <summary> /// This enum is used for referencing the different types of objects we can pass to a plugin /// Anything exposed to a plugin must not a be C++ type, as they could break when compiling with a different compiler. @@ -67,6 +65,4 @@ struct PlayerInfo int (*getPlayerInfoChar)(char* out_buf, size_t out_buf_len, PlayerInfoType var); int (*getPlayerInfoInt)(int* out_ptr, PlayerInfoType var); int (*getPlayerInfoBool)(bool* out_ptr, PlayerInfoType var); -}; - -//clang-format on
\ No newline at end of file +};
\ No newline at end of file diff --git a/NorthstarDedicatedTest/securitypatches.cpp b/NorthstarDedicatedTest/securitypatches.cpp deleted file mode 100644 index 9352559a..00000000 --- a/NorthstarDedicatedTest/securitypatches.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "pch.h" -#include "securitypatches.h" -#include "hookutils.h" -#include "concommand.h" -#include "gameutils.h" -#include "convar.h" - -typedef bool (*IsValveModType)(); -IsValveModType IsValveMod; - -bool IsValveModHook() -{ - // 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 - return !CommandLine()->CheckParm("-norestrictservercommands"); -} - -typedef bool (*SVC_CmdKeyValues__ReadFromBufferType)(void* a1, void* a2); -SVC_CmdKeyValues__ReadFromBufferType SVC_CmdKeyValues__ReadFromBuffer; -// never parse server=>client keyvalues for clientcommandkeyvalues -bool SVC_CmdKeyValues__ReadFromBufferHook(void* a1, void* a2) { return false; } - -void InitialiseClientEngineSecurityPatches(HMODULE baseAddress) -{ - HookEnabler hook; - - // note: this could break some things - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1C6360, &IsValveModHook, reinterpret_cast<LPVOID*>(&IsValveMod)); - ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x222E70, &SVC_CmdKeyValues__ReadFromBufferHook, reinterpret_cast<LPVOID*>(&SVC_CmdKeyValues__ReadFromBuffer)); - - // patches to make commands run from client/ui script still work - // note: this is likely preventable in a nicer way? test prolly - { - void* ptr = (char*)baseAddress + 0x4FB65; - TempReadWrite rw(ptr); - - *((char*)ptr) = (char)0xEB; - *((char*)ptr + 1) = (char)0x11; - } - - { - void* ptr = (char*)baseAddress + 0x4FBAC; - TempReadWrite rw(ptr); - - *((char*)ptr) = (char)0xEB; - *((char*)ptr + 1) = (char)0x16; - } - - // byte patches to patch concommands that this messes up that we need - { - // disconnect concommand - void* ptr = (char*)baseAddress + 0x5ADA2D; - TempReadWrite rw(ptr); - - *((int*)ptr) |= FCVAR_SERVER_CAN_EXECUTE; - } -}
\ No newline at end of file diff --git a/NorthstarDedicatedTest/securitypatches.h b/NorthstarDedicatedTest/securitypatches.h deleted file mode 100644 index 063df46e..00000000 --- a/NorthstarDedicatedTest/securitypatches.h +++ /dev/null @@ -1,2 +0,0 @@ -#pragma once -void InitialiseClientEngineSecurityPatches(HMODULE baseAddress);
\ No newline at end of file diff --git a/NorthstarDedicatedTest/serverauthentication.cpp b/NorthstarDedicatedTest/serverauthentication.cpp index 26352675..4581e4b6 100644 --- a/NorthstarDedicatedTest/serverauthentication.cpp +++ b/NorthstarDedicatedTest/serverauthentication.cpp @@ -14,6 +14,7 @@ #include <filesystem> #include <thread> #include "configurables.h" +#include "NSMem.h" const char* AUTHSERVER_VERIFY_STRING = "I am a northstar server!"; @@ -623,47 +624,39 @@ void InitialiseServerAuthentication(HMODULE baseAddress) CCommand__Tokenize = (CCommand__TokenizeType)((char*)baseAddress + 0x418380); + uintptr_t ba = (uintptr_t)baseAddress; + // patch to disable kicking based on incorrect serverfilter in connectclient, since we repurpose it for use as an auth token { - void* ptr = (char*)baseAddress + 0x114655; - TempReadWrite rw(ptr); - *((char*)ptr) = (char)0xEB; // jz => jmp + NSMem::BytePatch(ba + 0x114655, { + 0xEB // jz => jmp + }); } // patch to disable fairfight marking players as cheaters and kicking them { - void* ptr = (char*)baseAddress + 0x101012; - TempReadWrite rw(ptr); - *((char*)ptr) = (char)0xE9; // jz => jmp - *((char*)ptr + 1) = (char)0x90; - *((char*)ptr + 2) = (char)0x0; + NSMem::BytePatch(ba + 0x101012, { + 0xE9, // jz => jmp + 0x90, + 0x0 + }); } // patch to allow same of multiple account { - void* ptr = (char*)baseAddress + 0x114510; - TempReadWrite rw(ptr); - *((char*)ptr) = (char)0xEB; // jz => jmp + NSMem::BytePatch(ba + 0x114510, { + 0xEB, // jz => jmp + }); } // patch to set bWasWritingStringTableSuccessful in CNetworkStringTableContainer::WriteBaselines if it fails { - bool* writeAddress = (bool*)(&bWasWritingStringTableSuccessful - ((bool*)baseAddress + 0x234EDC)); - - void* ptr = (char*)baseAddress + 0x234ED2; - TempReadWrite rw(ptr); - *((char*)ptr) = (char)0xC7; - *((char*)ptr + 1) = (char)0x05; - *(int*)((char*)ptr + 2) = (int)writeAddress; - *((char*)ptr + 6) = (char)0x00; - *((char*)ptr + 7) = (char)0x00; - *((char*)ptr + 8) = (char)0x00; - *((char*)ptr + 9) = (char)0x00; - - *((char*)ptr + 10) = (char)0x90; - *((char*)ptr + 11) = (char)0x90; - *((char*)ptr + 12) = (char)0x90; - *((char*)ptr + 13) = (char)0x90; - *((char*)ptr + 14) = (char)0x90; + uintptr_t writeAddress = (uintptr_t)(&bWasWritingStringTableSuccessful - (ba + 0x234EDC)); + + auto addr = ba + 0x234ED2; + NSMem::BytePatch(addr, { 0xC7, 0x05 }); + NSMem::BytePatch(addr + 2, (BYTE*)&writeAddress, sizeof(writeAddress)); + NSMem::BytePatch(addr + 6, {0, 0, 0, 0}); + NSMem::NOP(addr + 10, 5); } } diff --git a/NorthstarDedicatedTest/sigscanning.cpp b/NorthstarDedicatedTest/sigscanning.cpp index c75c80f0..761f9f08 100644 --- a/NorthstarDedicatedTest/sigscanning.cpp +++ b/NorthstarDedicatedTest/sigscanning.cpp @@ -5,7 +5,7 @@ // note: sigscanning is only really intended to be used for resolving stuff like shared function definitions // we mostly use raw function addresses for stuff -size_t GetDLLLength(HMODULE moduleHandle) +size_t GetModuleLength(HMODULE moduleHandle) { // based on sigscn code from ttf2sdk, which is in turn based on CSigScan from https://wiki.alliedmods.net/Signature_Scanning MEMORY_BASIC_INFORMATION mem; @@ -19,12 +19,14 @@ size_t GetDLLLength(HMODULE moduleHandle) void* FindSignature(std::string dllName, const char* sig, const char* mask) { - HMODULE dllAddress = GetModuleHandleA(dllName.c_str()); - char* dllEnd = (char*)(dllAddress + GetDLLLength(dllAddress)); + HMODULE module = GetModuleHandleA(dllName.c_str()); + + unsigned char* dllAddress = (unsigned char*)module; + unsigned char* dllEnd = dllAddress + GetModuleLength(module); size_t sigLength = strlen(mask); - for (char* i = (char*)dllAddress; i < dllEnd - sigLength; i++) + for (auto i = dllAddress; i < dllEnd - sigLength + 1; i++) { int j = 0; for (; j < sigLength; j++) @@ -32,7 +34,7 @@ void* FindSignature(std::string dllName, const char* sig, const char* mask) break; if (j == sigLength) // loop finished of its own accord - return (void*)i; + return i; } return nullptr; @@ -9,4 +9,4 @@ Check [BUILD.md](https://github.com/R2Northstar/NorthstarLauncher/blob/main/BUIL ## Format -This project uses [clang-format](https://clang.llvm.org/docs/ClangFormat.html), make sure you run `clang-format -i --style=file LauncherInjector/*.cpp LauncherInjector/*.h NorthstarDedicatedTest/*.cpp NorthstarDedicatedTest/*.h` when opening a Pull Request. Check the tool's website for instructions on how to integrate it with your IDE. +This project uses [clang-format](https://clang.llvm.org/docs/ClangFormat.html), make sure you run `clang-format -i --style=file LauncherInjector/*.cpp LauncherInjector/*.h NorthstarDedicatedTest/*.cpp NorthstarDedicatedTest/*.h` when opening a Pull Request. Check the tool's website for instructions on how to integrate it with your IDE.
\ No newline at end of file diff --git a/loader_wsock32_proxy/hookutils.cpp b/loader_wsock32_proxy/hookutils.cpp index 8603cb35..f933f993 100644 --- a/loader_wsock32_proxy/hookutils.cpp +++ b/loader_wsock32_proxy/hookutils.cpp @@ -3,23 +3,6 @@ #define ERROR(...) { char err[2048]; sprintf_s(err, __VA_ARGS__); MessageBoxA(GetForegroundWindow(), err, "Northstar Wsock32 Proxy Error", 0); } -TempReadWrite::TempReadWrite(void* ptr) -{ - m_ptr = ptr; - MEMORY_BASIC_INFORMATION mbi; - VirtualQuery(m_ptr, &mbi, sizeof(mbi)); - VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect); - m_origProtection = mbi.Protect; -} - -TempReadWrite::~TempReadWrite() -{ - MEMORY_BASIC_INFORMATION mbi; - VirtualQuery(m_ptr, &mbi, sizeof(mbi)); - VirtualProtect(mbi.BaseAddress, mbi.RegionSize, m_origProtection, &mbi.Protect); -} - - void HookEnabler::CreateHook(LPVOID ppTarget, LPVOID ppDetour, LPVOID* ppOriginal, const char* targetName) { // the macro for this uses ppTarget's name as targetName, and this typically starts with & |