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