[Server]: Complete sv_privatePassword implementation (#908)

This commit is contained in:
Edo 2023-04-06 19:57:40 +02:00 committed by GitHub
parent bd0acdb6cb
commit 44ca51e11b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 131 additions and 77 deletions

View File

@ -20,9 +20,11 @@ namespace Components
0x6f5597f103cc50e9 0x6f5597f103cc50e9
}; };
bool Auth::HasAccessToReservedSlot;
void Auth::Frame() void Auth::Frame()
{ {
if (Auth::TokenContainer.generating) if (TokenContainer.generating)
{ {
static double mseconds = 0; static double mseconds = 0;
static Utils::Time::Interval interval; static Utils::Time::Interval interval;
@ -31,38 +33,38 @@ namespace Components
{ {
interval.update(); interval.update();
int diff = Game::Sys_Milliseconds() - Auth::TokenContainer.startTime; int diff = Game::Sys_Milliseconds() - TokenContainer.startTime;
double hashPMS = (Auth::TokenContainer.hashes * 1.0) / diff; double hashPMS = (TokenContainer.hashes * 1.0) / diff;
double requiredHashes = std::pow(2, Auth::TokenContainer.targetLevel + 1) - Auth::TokenContainer.hashes; double requiredHashes = std::pow(2, TokenContainer.targetLevel + 1) - TokenContainer.hashes;
mseconds = requiredHashes / hashPMS; mseconds = requiredHashes / hashPMS;
if (mseconds < 0) mseconds = 0; if (mseconds < 0) mseconds = 0;
} }
Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", Utils::String::VA("Increasing security level from %d to %d (est. %s)", Auth::GetSecurityLevel(), Auth::TokenContainer.targetLevel, Utils::String::FormatTimeSpan(static_cast<int>(mseconds)).data())); Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", Utils::String::VA("Increasing security level from %d to %d (est. %s)",GetSecurityLevel(), TokenContainer.targetLevel, Utils::String::FormatTimeSpan(static_cast<int>(mseconds)).data()));
} }
else if (Auth::TokenContainer.thread.joinable()) else if (TokenContainer.thread.joinable())
{ {
Auth::TokenContainer.thread.join(); TokenContainer.thread.join();
Auth::TokenContainer.generating = false; TokenContainer.generating = false;
Auth::StoreKey(); StoreKey();
Logger::Debug("Security level is {}", Auth::GetSecurityLevel()); Logger::Debug("Security level is {}",GetSecurityLevel());
Command::Execute("closemenu security_increase_popmenu", false); Command::Execute("closemenu security_increase_popmenu", false);
if (!Auth::TokenContainer.cancel) if (!TokenContainer.cancel)
{ {
if (Auth::TokenContainer.command.empty()) if (TokenContainer.command.empty())
{ {
Game::ShowMessageBox(Utils::String::VA("Your new security level is %d", Auth::GetSecurityLevel()), "Success"); Game::ShowMessageBox(Utils::String::VA("Your new security level is %d", GetSecurityLevel()), "Success");
} }
else else
{ {
Toast::Show("cardicon_locked", "Success", Utils::String::VA("Your new security level is %d", Auth::GetSecurityLevel()), 5000); Toast::Show("cardicon_locked", "Success", Utils::String::VA("Your new security level is %d", GetSecurityLevel()), 5000);
Command::Execute(Auth::TokenContainer.command, false); Command::Execute(TokenContainer.command, false);
} }
} }
Auth::TokenContainer.cancel = false; TokenContainer.cancel = false;
} }
} }
@ -70,15 +72,15 @@ namespace Components
{ {
// Ensure our certificate is loaded // Ensure our certificate is loaded
Steam::SteamUser()->GetSteamID(); Steam::SteamUser()->GetSteamID();
if (!Auth::GuidKey.isValid()) if (!GuidKey.isValid())
{ {
Logger::Error(Game::ERR_SERVERDISCONNECT, "Connecting failed: Guid key is invalid!"); Logger::Error(Game::ERR_SERVERDISCONNECT, "Connecting failed: Guid key is invalid!");
return; return;
} }
if (std::find(Auth::BannedUids.begin(), Auth::BannedUids.end(), Steam::SteamUser()->GetSteamID().bits) != Auth::BannedUids.end()) if (std::find(BannedUids.begin(), BannedUids.end(), Steam::SteamUser()->GetSteamID().bits) != BannedUids.end())
{ {
Auth::GenerateKey(); GenerateKey();
Logger::Error(Game::ERR_SERVERDISCONNECT, "Your online profile is invalid. A new key has been generated."); Logger::Error(Game::ERR_SERVERDISCONNECT, "Your online profile is invalid. A new key has been generated.");
return; return;
} }
@ -121,9 +123,9 @@ namespace Components
Game::SV_Cmd_EndTokenizedString(); Game::SV_Cmd_EndTokenizedString();
Proto::Auth::Connect connectData; Proto::Auth::Connect connectData;
connectData.set_token(Auth::GuidToken.toString()); connectData.set_token(GuidToken.toString());
connectData.set_publickey(Auth::GuidKey.getPublicKey()); connectData.set_publickey(GuidKey.getPublicKey());
connectData.set_signature(Utils::Cryptography::ECC::SignMessage(Auth::GuidKey, challenge)); connectData.set_signature(Utils::Cryptography::ECC::SignMessage(GuidKey, challenge));
connectData.set_infostring(connectString); connectData.set_infostring(connectString);
Network::SendCommand(sock, adr, "connect", connectData.SerializeAsString()); Network::SendCommand(sock, adr, "connect", connectData.SerializeAsString());
@ -182,7 +184,7 @@ namespace Components
} }
// Parse the infostring // Parse the infostring
Utils::InfoString infostr(params[2]); Utils::InfoString infostr(params.get(2));
// Read the required data // Read the required data
const auto steamId = infostr.get("xuid"); const auto steamId = infostr.get("xuid");
@ -206,13 +208,13 @@ namespace Components
return; return;
} }
if (std::find(Auth::BannedUids.begin(), Auth::BannedUids.end(), xuid) != Auth::BannedUids.end()) if (std::find(BannedUids.begin(), BannedUids.end(), xuid) != BannedUids.end())
{ {
Network::Send(address, "error\nYour online profile is invalid. Delete your players folder and restart ^2IW4x^7."); Network::Send(address, "error\nYour online profile is invalid. Delete your players folder and restart ^2IW4x^7.");
return; return;
} }
if (xuid != Auth::GetKeyHash(connectData.publickey())) if (xuid != GetKeyHash(connectData.publickey()))
{ {
Network::Send(address, "error\nXUID doesn't match the certificate!"); Network::Send(address, "error\nXUID doesn't match the certificate!");
return; return;
@ -230,7 +232,7 @@ namespace Components
// Verify the security level // Verify the security level
auto ourLevel = Dvar::Var("sv_securityLevel").get<unsigned int>(); auto ourLevel = Dvar::Var("sv_securityLevel").get<unsigned int>();
auto userLevel = Auth::GetZeroBits(connectData.token(), connectData.publickey()); auto userLevel = GetZeroBits(connectData.token(), connectData.publickey());
if (userLevel < ourLevel) if (userLevel < ourLevel)
{ {
@ -252,7 +254,7 @@ namespace Components
lea eax, [esp + 20h] lea eax, [esp + 20h]
push eax push eax
push esi push esi
call Auth::ParseConnectData call ParseConnectData
pop esi pop esi
pop eax pop eax
popad popad
@ -262,6 +264,44 @@ namespace Components
} }
} }
char* Auth::Info_ValueForKeyStub(const char* s, const char* key)
{
auto* value = Game::Info_ValueForKey(s, key);
HasAccessToReservedSlot = std::strcmp((*Game::sv_privatePassword)->current.string, value) == 0;
return value;
}
__declspec(naked) void Auth::DirectConnectPrivateClientStub()
{
__asm
{
push eax
mov al, HasAccessToReservedSlot
test al, al
pop eax
je noAccess
// Set the number of private clients to 0 if the client has the right password
xor eax, eax
jmp safeContinue
noAccess:
mov eax, dword ptr [edx + 0x10]
safeContinue:
// Game code skipped by hook
add esp, 0xC
push 0x460FB3
ret
}
}
unsigned __int64 Auth::GetKeyHash(const std::string& key) unsigned __int64 Auth::GetKeyHash(const std::string& key)
{ {
std::string hash = Utils::Cryptography::SHA1::Compute(key); std::string hash = Utils::Cryptography::SHA1::Compute(key);
@ -276,18 +316,18 @@ namespace Components
unsigned __int64 Auth::GetKeyHash() unsigned __int64 Auth::GetKeyHash()
{ {
Auth::LoadKey(); LoadKey();
return Auth::GetKeyHash(Auth::GuidKey.getPublicKey()); return GetKeyHash(GuidKey.getPublicKey());
} }
void Auth::StoreKey() void Auth::StoreKey()
{ {
if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled() && Auth::GuidKey.isValid()) if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled() && GuidKey.isValid())
{ {
Proto::Auth::Certificate cert; Proto::Auth::Certificate cert;
cert.set_token(Auth::GuidToken.toString()); cert.set_token(GuidToken.toString());
cert.set_ctoken(Auth::ComputeToken.toString()); cert.set_ctoken(ComputeToken.toString());
cert.set_privatekey(Auth::GuidKey.serialize(PK_PRIVATE)); cert.set_privatekey(GuidKey.serialize(PK_PRIVATE));
Utils::IO::WriteFile("players/guid.dat", cert.SerializeAsString()); Utils::IO::WriteFile("players/guid.dat", cert.SerializeAsString());
} }
@ -295,30 +335,30 @@ namespace Components
void Auth::GenerateKey() void Auth::GenerateKey()
{ {
Auth::GuidToken.clear(); GuidToken.clear();
Auth::ComputeToken.clear(); ComputeToken.clear();
Auth::GuidKey = Utils::Cryptography::ECC::GenerateKey(512); GuidKey = Utils::Cryptography::ECC::GenerateKey(512);
Auth::StoreKey(); StoreKey();
} }
void Auth::LoadKey(bool force) void Auth::LoadKey(bool force)
{ {
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return; if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return;
if (!force && Auth::GuidKey.isValid()) return; if (!force && GuidKey.isValid()) return;
Proto::Auth::Certificate cert; Proto::Auth::Certificate cert;
if (cert.ParseFromString(::Utils::IO::ReadFile("players/guid.dat"))) if (cert.ParseFromString(::Utils::IO::ReadFile("players/guid.dat")))
{ {
Auth::GuidKey.deserialize(cert.privatekey()); GuidKey.deserialize(cert.privatekey());
Auth::GuidToken = cert.token(); GuidToken = cert.token();
Auth::ComputeToken = cert.ctoken(); ComputeToken = cert.ctoken();
} }
else else
{ {
Auth::GuidKey.free(); GuidKey.free();
} }
if (!Auth::GuidKey.isValid()) if (!GuidKey.isValid())
{ {
Auth::GenerateKey(); Auth::GenerateKey();
} }
@ -326,32 +366,32 @@ namespace Components
uint32_t Auth::GetSecurityLevel() uint32_t Auth::GetSecurityLevel()
{ {
return Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.getPublicKey()); return GetZeroBits(GuidToken, GuidKey.getPublicKey());
} }
void Auth::IncreaseSecurityLevel(uint32_t level, const std::string& command) void Auth::IncreaseSecurityLevel(uint32_t level, const std::string& command)
{ {
if (Auth::GetSecurityLevel() >= level) return; if (GetSecurityLevel() >= level) return;
if (!Auth::TokenContainer.generating) if (!TokenContainer.generating)
{ {
Auth::TokenContainer.cancel = false; TokenContainer.cancel = false;
Auth::TokenContainer.targetLevel = level; TokenContainer.targetLevel = level;
Auth::TokenContainer.command = command; TokenContainer.command = command;
// Open menu // Open menu
Command::Execute("openmenu security_increase_popmenu", true); Command::Execute("openmenu security_increase_popmenu", true);
// Start thread // Start thread
Auth::TokenContainer.thread = std::thread([&level]() TokenContainer.thread = std::thread([&level]()
{ {
Auth::TokenContainer.generating = true; TokenContainer.generating = true;
Auth::TokenContainer.hashes = 0; TokenContainer.hashes = 0;
Auth::TokenContainer.startTime = Game::Sys_Milliseconds(); TokenContainer.startTime = Game::Sys_Milliseconds();
Auth::IncrementToken(Auth::GuidToken, Auth::ComputeToken, Auth::GuidKey.getPublicKey(), Auth::TokenContainer.targetLevel, &Auth::TokenContainer.cancel, &Auth::TokenContainer.hashes); IncrementToken(GuidToken, ComputeToken, GuidKey.getPublicKey(), TokenContainer.targetLevel, &TokenContainer.cancel, &TokenContainer.hashes);
Auth::TokenContainer.generating = false; TokenContainer.generating = false;
if (Auth::TokenContainer.cancel) if (TokenContainer.cancel)
{ {
Logger::Print("Token incrementation thread terminated\n"); Logger::Print("Token incrementation thread terminated\n");
} }
@ -399,7 +439,7 @@ namespace Components
} }
// Check if we already have the desired security level // Check if we already have the desired security level
uint32_t lastLevel = Auth::GetZeroBits(token, publicKey); uint32_t lastLevel = GetZeroBits(token, publicKey);
uint32_t level = lastLevel; uint32_t level = lastLevel;
if (level >= zeroBits) return; if (level >= zeroBits) return;
@ -407,7 +447,7 @@ namespace Components
{ {
++computeToken; ++computeToken;
if (count) ++(*count); if (count) ++(*count);
level = Auth::GetZeroBits(computeToken, publicKey); level = GetZeroBits(computeToken, publicKey);
// Store level if higher than the last one // Store level if higher than the last one
if (level >= lastLevel) if (level >= lastLevel)
@ -425,30 +465,36 @@ namespace Components
Auth::Auth() Auth::Auth()
{ {
Auth::TokenContainer.cancel = false; TokenContainer.cancel = false;
Auth::TokenContainer.generating = false; TokenContainer.generating = false;
HasAccessToReservedSlot = false;
Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", ""); Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", "");
// Load the key // Load the key
Auth::LoadKey(true); LoadKey(true);
Steam::SteamUser()->GetSteamID(); Steam::SteamUser()->GetSteamID();
Scheduler::Loop(Auth::Frame, Scheduler::Pipeline::MAIN); Scheduler::Loop(Frame, Scheduler::Pipeline::MAIN);
// Register dvar // Register dvar
Dvar::Register<int>("sv_securityLevel", 23, 0, 512, Game::DVAR_SERVERINFO, "Security level for GUID certificates (POW)"); Dvar::Register<int>("sv_securityLevel", 23, 0, 512, Game::DVAR_SERVERINFO, "Security level for GUID certificates (POW)");
// Install registration hook // Install registration hook
Utils::Hook(0x6265F9, Auth::DirectConnectStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x6265F9, DirectConnectStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x41D3E3, Auth::SendConnectDataStub, HOOK_CALL).install()->quick(); Utils::Hook(0x460EF5, Info_ValueForKeyStub, HOOK_CALL).install()->quick();
Utils::Hook(0x460FAD, DirectConnectPrivateClientStub, HOOK_JUMP).install()->quick();
Utils::Hook::Nop(0x460FAD + 5, 1);
Utils::Hook(0x41D3E3, SendConnectDataStub, HOOK_CALL).install()->quick();
// SteamIDs can only contain 31 bits of actual 'id' data. // SteamIDs can only contain 31 bits of actual 'id' data.
// The other 33 bits are steam internal data like universe and so on. // The other 33 bits are steam internal data like universe and so on.
// Using only 31 bits for fingerprints is pretty insecure. // Using only 31 bits for fingerprints is pretty insecure.
// The function below verifies the integrity steam's part of the SteamID. // The function below verifies the integrity steam's part of the SteamID.
// Patching that check allows us to use 64 bit for fingerprints. // Patching that check allows us to use 64 bit for fingerprints.
Utils::Hook::Set<DWORD>(0x4D0D60, 0xC301B0); Utils::Hook::Set<std::uint32_t>(0x4D0D60, 0xC301B0);
// Guid command // Guid command
Command::Add("guid", [] Command::Add("guid", []
@ -462,42 +508,42 @@ namespace Components
{ {
if (params->size() < 2) if (params->size() < 2)
{ {
const auto level = Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.getPublicKey()); const auto level = GetZeroBits(GuidToken, GuidKey.getPublicKey());
Logger::Print("Your current security level is {}\n", level); Logger::Print("Your current security level is {}\n", level);
Logger::Print("Your security token is: {}\n", Utils::String::DumpHex(Auth::GuidToken.toString(), "")); Logger::Print("Your security token is: {}\n", Utils::String::DumpHex(GuidToken.toString(), ""));
Logger::Print("Your computation token is: {}\n", Utils::String::DumpHex(Auth::ComputeToken.toString(), "")); Logger::Print("Your computation token is: {}\n", Utils::String::DumpHex(ComputeToken.toString(), ""));
Toast::Show("cardicon_locked", "^5Security Level", Utils::String::VA("Your security level is %d", level), 3000); Toast::Show("cardicon_locked", "^5Security Level", Utils::String::VA("Your security level is %d", level), 3000);
} }
else else
{ {
const auto level = std::strtoul(params->get(1), nullptr, 10); const auto level = std::strtoul(params->get(1), nullptr, 10);
Auth::IncreaseSecurityLevel(level); IncreaseSecurityLevel(level);
} }
}); });
} }
UIScript::Add("security_increase_cancel", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) UIScript::Add("security_increase_cancel", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{ {
Auth::TokenContainer.cancel = true; TokenContainer.cancel = true;
Logger::Print("Token incrementation process canceled!\n"); Logger::Print("Token incrementation process canceled!\n");
}); });
} }
Auth::~Auth() Auth::~Auth()
{ {
Auth::StoreKey(); StoreKey();
} }
void Auth::preDestroy() void Auth::preDestroy()
{ {
Auth::TokenContainer.cancel = true; TokenContainer.cancel = true;
Auth::TokenContainer.generating = false; TokenContainer.generating = false;
// Terminate thread // Terminate thread
if (Auth::TokenContainer.thread.joinable()) if (TokenContainer.thread.joinable())
{ {
Auth::TokenContainer.thread.join(); TokenContainer.thread.join();
} }
} }

View File

@ -45,9 +45,13 @@ namespace Components
static Utils::Cryptography::ECC::Key GuidKey; static Utils::Cryptography::ECC::Key GuidKey;
static std::vector<std::uint64_t> BannedUids; static std::vector<std::uint64_t> BannedUids;
static bool HasAccessToReservedSlot;
static void SendConnectDataStub(Game::netsrc_t sock, Game::netadr_t adr, const char* format, int len); static void SendConnectDataStub(Game::netsrc_t sock, Game::netadr_t adr, const char* format, int len);
static void ParseConnectData(Game::msg_t* msg, Game::netadr_t* addr); static void ParseConnectData(Game::msg_t* msg, Game::netadr_t* addr);
static void DirectConnectStub(); static void DirectConnectStub();
static char* Info_ValueForKeyStub(const char* s, const char* key);
static void DirectConnectPrivateClientStub();
static void Frame(); static void Frame();
}; };

View File

@ -936,7 +936,7 @@ namespace Components
UIScript::Add("CreateListFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) UIScript::Add("CreateListFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{ {
auto* serverInfo = GetCurrentServer(); auto* serverInfo = GetCurrentServer();
if (info) if (info && serverInfo && serverInfo->addr.isValid())
{ {
StoreFavourite(serverInfo->addr.getString()); StoreFavourite(serverInfo->addr.getString());
} }
@ -944,7 +944,11 @@ namespace Components
UIScript::Add("CreateFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) UIScript::Add("CreateFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{ {
StoreFavourite(Dvar::Var("ui_favoriteAddress").get<std::string>()); const auto value = Dvar::Var("ui_favoriteAddress").get<std::string>();
if (!value.empty())
{
StoreFavourite(value);
}
}); });
UIScript::Add("CreateCurrentServerFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) UIScript::Add("CreateCurrentServerFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)