From 6fbae4461ef8787e4c8d3cec109f21886c0f2a3b Mon Sep 17 00:00:00 2001 From: momo5502 <mauriceheumann@gmail.com> Date: Sun, 21 Feb 2016 19:57:56 +0100 Subject: [PATCH] Experimental guid authentication --- src/Components/Loader.cpp | 1 + src/Components/Loader.hpp | 1 + src/Components/Modules/Auth.cpp | 159 +++++++++++++++++++++++++++++ src/Components/Modules/Auth.hpp | 35 +++++++ src/Components/Modules/Party.cpp | 1 + src/Game/Functions.cpp | 31 ++++++ src/Game/Functions.hpp | 4 + src/Game/Structs.hpp | 2 +- src/Proto/auth.proto | 9 ++ src/STDInclude.hpp | 1 + src/Steam/Interfaces/SteamUser.cpp | 26 ++--- src/Steam/Interfaces/SteamUser.hpp | 2 + src/Utils/Cryptography.hpp | 47 ++++++--- 13 files changed, 294 insertions(+), 25 deletions(-) create mode 100644 src/Components/Modules/Auth.cpp create mode 100644 src/Components/Modules/Auth.hpp create mode 100644 src/Proto/auth.proto diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 012b31b1..82549e56 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -9,6 +9,7 @@ namespace Components Loader::Register(new Flags()); Loader::Register(new Singleton()); + Loader::Register(new Auth()); Loader::Register(new Dvar()); Loader::Register(new Maps()); Loader::Register(new News()); diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 33c8b94a..1ff881b3 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -23,6 +23,7 @@ namespace Components }; } +#include "Modules\Auth.hpp" #include "Modules\Dvar.hpp" #include "Modules\Maps.hpp" #include "Modules\News.hpp" diff --git a/src/Components/Modules/Auth.cpp b/src/Components/Modules/Auth.cpp new file mode 100644 index 00000000..4d041231 --- /dev/null +++ b/src/Components/Modules/Auth.cpp @@ -0,0 +1,159 @@ +#include "STDInclude.hpp" + +namespace Components +{ + Auth::AuthInfo Auth::ClientAuthInfo[18]; + + void Auth::Frame() + { + for (int i = 0; i < *Game::svs_numclients; i++) + { + Game::client_t* client = &Game::svs_clients[i]; + Auth::AuthInfo* info = &Auth::ClientAuthInfo[i]; + + // State must be 5 or greater here, as otherwise the client will crash when being kicked. + // That's due to the hunk being freed by that time, but it hasn't been reallocated, therefore all future allocations will cause a crash. + // Additionally, the game won't catch the errors and simply lose the connection, so we even have to add a delay to send the data. + + // Not sure if that's potentially unsafe, though. + // Players faking their GUID will be connected for 5 seconds, which allows them to fuck up everything. + // I think we have to perform the verification when clients are still in state 3, but for now it works. + + // I think we even have to lock the client into state 3 until the verification is done. + // Intercepting the entire connection process to perform the authentication within state 3 solely is necessary, due to having a timeout. + // Not sending a response might allow the player to connect for a few seconds (<= 5) until the timeout is reached. + if (client->state >= 5) + { + if (info->state == Auth::STATE_NEGOTIATING && (Game::Com_Milliseconds() - info->time) > 1000 * 5) + { + info->state = Auth::STATE_INVALID; + info->time = Game::Com_Milliseconds(); + Game::SV_KickClientError(client, "XUID verification timeout!"); + } + else if (info->state == Auth::STATE_UNKNOWN && info->time && (Game::Com_Milliseconds() - info->time) > 1000 * 5) // Wait 5 seconds (error delay) + { + Logger::Print("Sending XUID authentication request to %s\n", Network::Address(client->adr).GetString()); + + info->state = Auth::STATE_NEGOTIATING; + info->time = Game::Com_Milliseconds(); + info->challenge = Utils::VA("%X", Utils::Cryptography::Rand::GenerateInt()); + Network::SendCommand(client->adr, "xuidAuthReq", info->challenge); + } + else if (info->state == Auth::STATE_UNKNOWN && !info->time) + { + info->time = Game::Com_Milliseconds(); + } + } + } + } + + void Auth::RegisterClient(int clientNum) + { + if (clientNum >= 18) return; + + Network::Address address(Game::svs_clients[clientNum].adr); + + if (address.GetType() == Game::netadrtype_t::NA_BOT) + { + Auth::ClientAuthInfo[clientNum].state = Auth::STATE_VALID; + } + else + { + Logger::Print("Registering client %s\n", address.GetString()); + Auth::ClientAuthInfo[clientNum].time = 0; + Auth::ClientAuthInfo[clientNum].state = Auth::STATE_UNKNOWN; + } + } + + void __declspec(naked) Auth::RegisterClientStub() + { + __asm + { + push esi + call Auth::RegisterClient + pop esi + + imul esi, 366Ch + mov eax, 478A18h + jmp eax + } + } + + Auth::Auth() + { + // Only clients receive the auth request + if (!Dedicated::IsDedicated()) + { + Network::Handle("xuidAuthReq", [] (Network::Address address, std::string data) + { + Logger::Print("Received XUID authentication request from %s\n", address.GetString()); + + // Only accept requests from the server we're connected to + if (address != *Game::connectedHost) return; + if (!Steam::User::GuidKey.IsValid()) return; + + Proto::Auth::Response response; + response.set_publickey(Steam::User::GuidKey.GetPublicKey()); + response.set_signature(Utils::Cryptography::ECDSA::SignMessage(Steam::User::GuidKey, data+"1")); + + Network::SendCommand(address, "xuidAuthResp", response.SerializeAsString()); + }); + } + + Network::Handle("xuidAuthResp", [] (Network::Address address, std::string data) + { + Logger::Print("Received XUID authentication response from %s\n", address.GetString()); + + Proto::Auth::Response response; + response.ParseFromString(data); + + if (response.signature().empty()) return; + if (response.publickey().empty()) return; + + for (int i = 0; i < *Game::svs_numclients; i++) + { + Game::client_t* client = &Game::svs_clients[i]; + Auth::AuthInfo* info = &Auth::ClientAuthInfo[i]; + + if (client->state >= 3 && address == client->adr && info->state == Auth::STATE_NEGOTIATING) + { + unsigned int id = static_cast<unsigned int>(~0x110000100000000 & client->steamid); + + // Check if guid matches the certificate + if (id != (Utils::OneAtATime(response.publickey().data(), response.publickey().size()) & ~0x80000000)) + { + info->state = Auth::STATE_INVALID; + Game::SV_KickClientError(client, "XUID doesn't match the certificate!"); + } + else + { + info->publicKey.Set(response.publickey()); + + if (Utils::Cryptography::ECDSA::VerifyMessage(info->publicKey, info->challenge, response.signature())) + { + info->state = Auth::STATE_VALID; + Logger::Print("Verified XUID from %s\n", address.GetString()); + } + else + { + info->state = Auth::STATE_INVALID; + Game::SV_KickClientError(client, "Challenge signature was invalid!"); + } + } + } + } + }); + + // Install frame handlers + Dedicated::OnFrame(Auth::Frame); + Renderer::OnFrame(Auth::Frame); + + // Install registration hook + Utils::Hook(0x478A12, Auth::RegisterClientStub, HOOK_JUMP).Install()->Quick(); + } + + Auth::~Auth() + { + + } +} diff --git a/src/Components/Modules/Auth.hpp b/src/Components/Modules/Auth.hpp new file mode 100644 index 00000000..538b31be --- /dev/null +++ b/src/Components/Modules/Auth.hpp @@ -0,0 +1,35 @@ +namespace Components +{ + class Auth : public Component + { + public: + Auth(); + ~Auth(); + const char* GetName() { return "Auth"; }; + + private: + + enum AuthState + { + STATE_UNKNOWN, + STATE_NEGOTIATING, + STATE_VALID, + STATE_INVALID, + }; + + struct AuthInfo + { + Utils::Cryptography::ECDSA::Key publicKey; + std::string challenge; + AuthState state; + int time; + }; + + static AuthInfo ClientAuthInfo[18]; + + static void Frame(); + + static void RegisterClient(int clientNum); + static void RegisterClientStub(); + }; +} diff --git a/src/Components/Modules/Party.cpp b/src/Components/Modules/Party.cpp index dc9b8d90..908b7173 100644 --- a/src/Components/Modules/Party.cpp +++ b/src/Components/Modules/Party.cpp @@ -193,6 +193,7 @@ namespace Components // Disable host migration Utils::Hook::Set<BYTE>(0x5B58B2, 0xEB); + Utils::Hook::Set<BYTE>(0x4D6171, 0); // Patch playlist stuff for non-party behavior Utils::Hook::Set<Game::dvar_t**>(0x4A4093, &partyEnable); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 2c11eb38..c1d796c4 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -177,6 +177,8 @@ namespace Game gentity_t* g_entities = (gentity_t*)0x18835D8; + netadr_t* connectedHost = (netadr_t*)0xA1E888; + SOCKET* ip_socket = (SOCKET*)0x64A3008; void* ReallocateAssetPool(XAssetType type, unsigned int newSize) @@ -303,4 +305,33 @@ namespace Game return hash; } + + void SV_KickClient(client_t* client, const char* reason) + { + __asm + { + push edi + push esi + mov edi, 0 + mov esi, client + push reason + push 0 + push 0 + mov eax, 6249A0h + call eax + add esp, 0Ch + pop esi + pop edi + } + } + + void SV_KickClientError(client_t* client, const char* reason) + { + if (client->state < 5) + { + Components::Network::Send(client->adr, Utils::VA("error\n%s", reason)); + } + + SV_KickClient(client, reason); + } } diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index cf6f0a13..a415f3c7 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -357,6 +357,7 @@ namespace Game extern gentity_t* g_entities; + extern netadr_t* connectedHost; extern SOCKET* ip_socket; void* ReallocateAssetPool(XAssetType type, unsigned int newSize); @@ -372,4 +373,7 @@ namespace Game void MessageBox(std::string message, std::string title); unsigned int R_HashString(const char* string); + + void SV_KickClient(client_t* client, const char* reason); + void SV_KickClientError(client_t* client, const char* reason); } diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 6d831dec..d06b0924 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -905,7 +905,7 @@ namespace Game // 269044 char pad6[9228]; // 278272 - __int64 steamid; + unsigned __int64 steamid; // 278280 char pad7[403592]; } client_t; diff --git a/src/Proto/auth.proto b/src/Proto/auth.proto new file mode 100644 index 00000000..375c1a0f --- /dev/null +++ b/src/Proto/auth.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package Proto.Auth; + +message Response +{ + bytes signature = 1; + bytes publickey = 2; +} diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index 721a7f24..51adec2a 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -63,6 +63,7 @@ // Protobuf #include "proto/network.pb.h" +#include "proto/auth.pb.h" #include "proto/node.pb.h" #include "proto/rcon.pb.h" diff --git a/src/Steam/Interfaces/SteamUser.cpp b/src/Steam/Interfaces/SteamUser.cpp index 5df73753..a8341f18 100644 --- a/src/Steam/Interfaces/SteamUser.cpp +++ b/src/Steam/Interfaces/SteamUser.cpp @@ -2,6 +2,8 @@ namespace Steam { + ::Utils::Cryptography::ECDSA::Key User::GuidKey; + int User::GetHSteamUser() { return NULL; @@ -26,19 +28,19 @@ namespace Steam } else if (Components::Singleton::IsFirstInstance()) // Hardware guid { - DATA_BLOB Data[2]; - Data[0].pbData = (BYTE *)"AAAAAAAAAA"; - Data[0].cbData = 10; - - CryptProtectData(Data, NULL, NULL, NULL, NULL, CRYPTPROTECT_LOCAL_MACHINE, &Data[1]); - - subId = ::Utils::OneAtATime(reinterpret_cast<char*>(Data[1].pbData), 52); - - if (!subId) + if (!User::GuidKey.IsValid()) { - Components::Logger::Print("Hardware-based GUID generation failed!\n"); - subId = (Game::Com_Milliseconds() + timeGetTime()); + User::GuidKey.Import(::Utils::ReadFile("players/guid.dat"), PK_PRIVATE); + + if (!User::GuidKey.IsValid()) + { + User::GuidKey = ::Utils::Cryptography::ECDSA::GenerateKey(512); + ::Utils::WriteFile("players/guid.dat", User::GuidKey.Export(PK_PRIVATE)); + } } + + std::string publicKey = User::GuidKey.GetPublicKey(); + subId = ::Utils::OneAtATime(publicKey.data(), publicKey.size()); } else // Random guid { @@ -54,7 +56,7 @@ namespace Steam int User::InitiateGameConnection(void *pAuthBlob, int cbMaxAuthBlob, SteamID steamIDGameServer, unsigned int unIPServer, unsigned short usPortServer, bool bSecure) { - // TODO: Generate auth ticket! + Components::Logger::Print("%s\n", __FUNCTION__); return 0; } diff --git a/src/Steam/Interfaces/SteamUser.hpp b/src/Steam/Interfaces/SteamUser.hpp index 7d6140ce..187d3425 100644 --- a/src/Steam/Interfaces/SteamUser.hpp +++ b/src/Steam/Interfaces/SteamUser.hpp @@ -20,5 +20,7 @@ namespace Steam virtual void EndAuthSession(SteamID steamID); virtual void CancelAuthTicket(unsigned int hAuthTicket); virtual unsigned int UserHasLicenseForApp(SteamID steamID, unsigned int appID); + + static ::Utils::Cryptography::ECDSA::Key GuidKey; }; } diff --git a/src/Utils/Cryptography.hpp b/src/Utils/Cryptography.hpp index 4098a509..48a172f7 100644 --- a/src/Utils/Cryptography.hpp +++ b/src/Utils/Cryptography.hpp @@ -20,9 +20,9 @@ namespace Utils public: Key() : KeyStorage(new ecc_key) { - ZeroMemory(this->KeyStorage.get(), sizeof(*this->KeyStorage.get())); + ZeroMemory(this->KeyStorage.get(), sizeof(*this->GetKeyPtr())); }; - Key(ecc_key* key) : Key() { if(key) std::memmove(this->KeyStorage.get(), key, sizeof(*key)); }; + Key(ecc_key* key) : Key() { if(key) std::memmove(this->GetKeyPtr(), key, sizeof(*key)); }; Key(ecc_key key) : Key(&key) {}; ~Key() { @@ -34,7 +34,7 @@ namespace Utils bool IsValid() { - return (!Utils::MemIsSet(this->KeyStorage.get(), 0, sizeof(*this->KeyStorage.get()))); + return (!Utils::MemIsSet(this->GetKeyPtr(), 0, sizeof(*this->GetKeyPtr()))); } ecc_key* GetKeyPtr() @@ -59,20 +59,43 @@ namespace Utils { this->Free(); - if (ecc_ansi_x963_import(reinterpret_cast<const uint8_t*>(pubKeyBuffer.data()), pubKeyBuffer.size(), this->KeyStorage.get()) != CRYPT_OK) + if (ecc_ansi_x963_import(reinterpret_cast<const uint8_t*>(pubKeyBuffer.data()), pubKeyBuffer.size(), this->GetKeyPtr()) != CRYPT_OK) { - ZeroMemory(this->KeyStorage.get(), sizeof(*this->KeyStorage.get())); + ZeroMemory(this->KeyStorage.get(), sizeof(*this->GetKeyPtr())); } } + void Import(std::string key, int type = PK_PRIVATE) + { + this->Free(); + + if (ecc_import(reinterpret_cast<const uint8_t*>(key.data()), key.size(), this->GetKeyPtr()) != CRYPT_OK) + { + ZeroMemory(this->KeyStorage.get(), sizeof(*this->GetKeyPtr())); + } + } + + std::string Export(int type = PK_PRIVATE) + { + uint8_t buffer[4096] = { 0 }; + DWORD length = sizeof(buffer); + + if (ecc_export(buffer, &length, type, this->GetKeyPtr()) == CRYPT_OK) + { + return std::string(reinterpret_cast<char*>(buffer), length); + } + + return ""; + } + void Free() { if (this->IsValid()) { - ecc_free(this->KeyStorage.get()); + ecc_free(this->GetKeyPtr()); } - ZeroMemory(this->KeyStorage.get(), sizeof(*this->KeyStorage.get())); + ZeroMemory(this->GetKeyPtr(), sizeof(*this->GetKeyPtr())); } private: @@ -92,9 +115,9 @@ namespace Utils public: Key() : KeyStorage(new rsa_key) { - ZeroMemory(this->KeyStorage.get(), sizeof(*this->KeyStorage.get())); + ZeroMemory(this->KeyStorage.get(), sizeof(*this->GetKeyPtr())); }; - Key(rsa_key* key) : Key() { if (key) std::memmove(this->KeyStorage.get(), key, sizeof(*key)); }; + Key(rsa_key* key) : Key() { if (key) std::memmove(this->GetKeyPtr(), key, sizeof(*key)); }; Key(rsa_key key) : Key(&key) {}; ~Key() { @@ -111,17 +134,17 @@ namespace Utils bool IsValid() { - return (!Utils::MemIsSet(this->KeyStorage.get(), 0, sizeof(*this->KeyStorage.get()))); + return (!Utils::MemIsSet(this->GetKeyPtr(), 0, sizeof(*this->GetKeyPtr()))); } void Free() { if (this->IsValid()) { - rsa_free(this->KeyStorage.get()); + rsa_free(this->GetKeyPtr()); } - ZeroMemory(this->KeyStorage.get(), sizeof(*this->KeyStorage.get())); + ZeroMemory(this->GetKeyPtr(), sizeof(*this->GetKeyPtr())); } private: