From 35e91aa12002f089f8624b63421f370f17e3f4c7 Mon Sep 17 00:00:00 2001 From: pg9182 <96569817+pg9182@users.noreply.github.com> Date: Sun, 5 Mar 2023 18:43:07 -0500 Subject: Implement Atlas sigreq1 connectionless packet Consists of a JSON object including a type key and a HMAC-SHA256 signature using the gameserver-specific token from the masterserver as the key. --- NorthstarDLL/NorthstarDLL.vcxproj | 4 +- NorthstarDLL/masterserver/masterserver.cpp | 25 +++++ NorthstarDLL/masterserver/masterserver.h | 1 + NorthstarDLL/server/servernethooks.cpp | 153 +++++++++++++++++++++++++++++ 4 files changed, 181 insertions(+), 2 deletions(-) diff --git a/NorthstarDLL/NorthstarDLL.vcxproj b/NorthstarDLL/NorthstarDLL.vcxproj index f2607a82..707dcf29 100644 --- a/NorthstarDLL/NorthstarDLL.vcxproj +++ b/NorthstarDLL/NorthstarDLL.vcxproj @@ -69,7 +69,7 @@ Windows true false - $(SolutionDir)include\MinHook.x64.lib;$(SolutionDir)include\libcurl\lib\libcurl_a.lib;dbghelp.lib;Wldap32.lib;Normaliz.lib;version.lib;%(AdditionalDependencies) + $(SolutionDir)include\MinHook.x64.lib;$(SolutionDir)include\libcurl\lib\libcurl_a.lib;dbghelp.lib;Wldap32.lib;Normaliz.lib;Bcrypt.lib;version.lib;%(AdditionalDependencies) %(AdditionalLibraryDirectories) @@ -104,7 +104,7 @@ true true false - $(SolutionDir)include\MinHook.x64.lib;$(SolutionDir)include\libcurl\lib\libcurl_a.lib;dbghelp.lib;Wldap32.lib;Normaliz.lib;version.lib;%(AdditionalDependencies) + $(SolutionDir)include\MinHook.x64.lib;$(SolutionDir)include\libcurl\lib\libcurl_a.lib;dbghelp.lib;Wldap32.lib;Normaliz.lib;Bcrypt.lib;version.lib;%(AdditionalDependencies) %(AdditionalLibraryDirectories) diff --git a/NorthstarDLL/masterserver/masterserver.cpp b/NorthstarDLL/masterserver/masterserver.cpp index 8087237c..9b439027 100644 --- a/NorthstarDLL/masterserver/masterserver.cpp +++ b/NorthstarDLL/masterserver/masterserver.cpp @@ -763,6 +763,31 @@ void MasterServerManager::WritePlayerPersistentData(const char* playerId, const requestThread.detach(); } +void MasterServerManager::ProcessConnectionlessPacketSigreq1(std::string data) +{ + rapidjson_document obj; + obj.Parse(data); + + if (obj.HasParseError()) + { + // note: it's okay to print the data as-is since we've already checked that it actually came from Atlas + spdlog::error("invalid Atlas connectionless packet request ({}): {}", data, GetParseError_En(obj.GetParseError())); + return; + } + + if (!obj.HasMember("type") || !obj["type"].IsString()) + { + spdlog::error("invalid Atlas connectionless packet request ({}): missing type", data); + return; + } + + std::string type = obj["type"].GetString(); + + // TODO + + spdlog::error("invalid Atlas connectionless packet request: unknown type {}", type); +} + void ConCommand_ns_fetchservers(const CCommand& args) { g_pMasterServerManager->RequestServerList(); diff --git a/NorthstarDLL/masterserver/masterserver.h b/NorthstarDLL/masterserver/masterserver.h index 21082e2f..5cd6a695 100644 --- a/NorthstarDLL/masterserver/masterserver.h +++ b/NorthstarDLL/masterserver/masterserver.h @@ -126,6 +126,7 @@ class MasterServerManager void AuthenticateWithOwnServer(const char* uid, const char* playerToken); void AuthenticateWithServer(const char* uid, const char* playerToken, RemoteServerInfo server, const char* password); void WritePlayerPersistentData(const char* playerId, const char* pdata, size_t pdataSize); + void ProcessConnectionlessPacketSigreq1(std::string req); }; extern MasterServerManager* g_pMasterServerManager; diff --git a/NorthstarDLL/server/servernethooks.cpp b/NorthstarDLL/server/servernethooks.cpp index 1a760419..c367c1e1 100644 --- a/NorthstarDLL/server/servernethooks.cpp +++ b/NorthstarDLL/server/servernethooks.cpp @@ -1,12 +1,139 @@ #include "core/convar/convar.h" #include "engine/r2engine.h" #include "shared/exploit_fixes/ns_limits.h" +#include "masterserver/masterserver.h" #include +#include +#include AUTOHOOK_INIT() static ConVar* Cvar_net_debug_atlas_packet; +static ConVar* Cvar_net_debug_atlas_packet_insecure; + +#define HMACSHA256_LEN (256 / 8) +BCRYPT_ALG_HANDLE HMACSHA256; + +static bool InitHMACSHA256() +{ + NTSTATUS status; + DWORD hashLength = NULL; + ULONG hashLengthSz = NULL; + + if ((status = BCryptOpenAlgorithmProvider(&HMACSHA256, BCRYPT_SHA256_ALGORITHM, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG))) + { + spdlog::error("failed to initialize HMAC-SHA256: BCryptOpenAlgorithmProvider: error 0x{:08X}", (ULONG)status); + return false; + } + + if ((status = BCryptGetProperty(HMACSHA256, BCRYPT_HASH_LENGTH, (PUCHAR)&hashLength, sizeof(hashLength), &hashLengthSz, 0))) + { + spdlog::error("failed to initialize HMAC-SHA256: BCryptGetProperty(BCRYPT_HASH_LENGTH): error 0x{:08X}", (ULONG)status); + return false; + } + + if (hashLength != HMACSHA256_LEN) + { + spdlog::error("failed to initialize HMAC-SHA256: BCryptGetProperty(BCRYPT_HASH_LENGTH): unexpected value {}", hashLength); + return false; + } + + return true; +} + +// note: all Atlas connectionless packets should be idempotent so multiple attempts can be made to mitigate packet loss +// note: all long-running Atlas connectionless packet handlers should be started in a new thread (with copies of the data) to avoid blocking +// networking + +static bool VerifyHMACSHA256(std::string key, std::string sig, std::string data) +{ + bool result = false; + char hash[HMACSHA256_LEN]; + + NTSTATUS status; + BCRYPT_HASH_HANDLE h = NULL; + + if ((status = BCryptCreateHash(HMACSHA256, &h, NULL, 0, (PUCHAR)key.c_str(), (ULONG)key.length(), 0))) + { + spdlog::error("failed to verify HMAC-SHA256: BCryptCreateHash: error 0x{:08X}", (ULONG)status); + goto cleanup; + } + + if ((status = BCryptHashData(h, (PUCHAR)data.c_str(), (ULONG)data.length(), 0))) + { + spdlog::error("failed to verify HMAC-SHA256: BCryptHashData: error 0x{:08X}", (ULONG)status); + goto cleanup; + } + + if ((status = BCryptFinishHash(h, (PUCHAR)&hash, (ULONG)sizeof(hash), 0))) + { + spdlog::error("failed to verify HMAC-SHA256: BCryptFinishHash: error 0x{:08X}", (ULONG)status); + goto cleanup; + } + + if (std::string(hash, sizeof(hash)) == sig) + { + result = true; + goto cleanup; + } + +cleanup: + if (h) + BCryptDestroyHash(h); + return result; +} + +static void ProcessAtlasConnectionlessPacketSigreq1(R2::netpacket_t* packet, bool dbg, std::string pType, std::string pData) +{ + if (pData.length() < HMACSHA256_LEN) + { + if (dbg) + spdlog::warn("ignoring Atlas connectionless packet (size={} type={}): invalid: too short for signature", packet->size, pType); + return; + } + + std::string pSig; // is binary data, not actually an ASCII string + pSig = pData.substr(0, HMACSHA256_LEN); + pData = pData.substr(HMACSHA256_LEN); + + if (!g_pMasterServerManager || !g_pMasterServerManager->m_sOwnServerAuthToken[0]) + { + if (dbg) + spdlog::warn( + "ignoring Atlas connectionless packet (size={} type={}): invalid (data={}): no masterserver token yet", + packet->size, + pType, + pData); + return; + } + + if (!VerifyHMACSHA256(std::string(g_pMasterServerManager->m_sOwnServerAuthToken), pSig, pData)) + { + if (!Cvar_net_debug_atlas_packet_insecure->GetBool()) + { + if (dbg) + spdlog::warn( + "ignoring Atlas connectionless packet (size={} type={}): invalid: invalid signature (key={})", + packet->size, + pType, + std::string(g_pMasterServerManager->m_sOwnServerAuthToken)); + return; + } + spdlog::warn( + "processing Atlas connectionless packet (size={} type={}) with invalid signature due to net_debug_atlas_packet_insecure", + packet->size, + pType); + } + + if (dbg) + spdlog::info("got Atlas connectionless packet (size={} type={} data={})", packet->size, pType, pData); + + std::thread t([pData] { g_pMasterServerManager->ProcessConnectionlessPacketSigreq1(pData); }); + t.detach(); + + return; +} static void ProcessAtlasConnectionlessPacket(R2::netpacket_t* packet) { @@ -25,6 +152,13 @@ static void ProcessAtlasConnectionlessPacket(R2::netpacket_t* packet) } } + // v1 HMACSHA256-signed masterserver request + if (pType == "sigreq1") + { + ProcessAtlasConnectionlessPacketSigreq1(packet, dbg, pType, pData); + return; + } + if (dbg) spdlog::warn("ignoring Atlas connectionless packet (size={} type={}): unknown type", packet->size, pType); return; @@ -46,10 +180,29 @@ ON_DLL_LOAD_RELIESON("engine.dll", ServerNetHooks, ConVar, (CModule module)) { AUTOHOOK_DISPATCH_MODULE(engine.dll) + if (!InitHMACSHA256()) + throw std::runtime_error("failed to initialize bcrypt"); + + if (!VerifyHMACSHA256( + "test", + "\x88\xcd\x21\x08\xb5\x34\x7d\x97\x3c\xf3\x9c\xdf\x90\x53\xd7\xdd\x42\x70\x48\x76\xd8\xc9\xa9\xbd\x8e\x2d\x16\x82\x59\xd3\xdd" + "\xf7", + "test")) + throw std::runtime_error("bcrypt HMAC-SHA256 is broken"); + Cvar_net_debug_atlas_packet = new ConVar( "net_debug_atlas_packet", "0", FCVAR_NONE, "Whether to log detailed debugging information for Atlas connectionless packets (warning: this allows unlimited amounts of " "arbitrary data to be logged)"); + + Cvar_net_debug_atlas_packet_insecure = new ConVar( + "net_debug_atlas_packet_insecure", + "0", + FCVAR_NONE, + "Whether to disable signature verification for Atlas connectionless packets (DANGEROUS: this allows anyone to impersonate Atlas)"); + + if (Cvar_net_debug_atlas_packet_insecure->GetBool()) + spdlog::warn("DANGEROUS: Atlas connectionless packet signature verification disabled; anyone will be able to impersonate Atlas"); } -- cgit v1.2.3