From a389fd2ef46abd03ce2e6bd7808e0638b3ddb2c2 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 30 Mar 2026 15:12:27 -0700 Subject: [PATCH] refactor: name SRP/Warden crypto constants, add why-comments - srp: name kEphemeralBytes (19 = 152 bits, matches Blizzard client) and kMaxEphemeralAttempts (100) with why-comment explaining A != 0 mod N requirement and near-zero failure probability - warden_module: add why-comment on 0x400000 module base (default PE image base for 32-bit Windows executables) - warden_module: name kRsaSignatureSize (256 = RSA-2048) with why-comment explaining signature stripping (placeholder modulus can't verify Blizzard's signatures) --- src/auth/srp.cpp | 16 ++++++++++------ src/game/warden_module.cpp | 13 +++++++++---- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/auth/srp.cpp b/src/auth/srp.cpp index 154c4f55..99498164 100644 --- a/src/auth/srp.cpp +++ b/src/auth/srp.cpp @@ -129,11 +129,15 @@ std::vector SRP::computeAuthHash(const std::string& username, void SRP::computeClientEphemeral() { LOG_DEBUG("Computing client ephemeral"); - // Generate random private ephemeral a (19 bytes = 152 bits) - // Keep trying until we get a valid A + // Generate random private ephemeral a (19 bytes = 152 bits). + // WoW SRP-6a requires A != 0 mod N; in practice this almost never fails + // (probability ≈ 2^-152), but we retry to be safe. 100 attempts is far more + // than needed — if it fails, the RNG is broken. + static constexpr int kMaxEphemeralAttempts = 100; + static constexpr int kEphemeralBytes = 19; // 152 bits — matches Blizzard client int attempts = 0; - while (attempts < 100) { - a = BigNum::fromRandom(19); + while (attempts < kMaxEphemeralAttempts) { + a = BigNum::fromRandom(kEphemeralBytes); // A = g^a mod N A = g.modPow(a, N); @@ -146,8 +150,8 @@ void SRP::computeClientEphemeral() { attempts++; } - if (attempts >= 100) { - LOG_ERROR("Failed to generate valid client ephemeral after 100 attempts!"); + if (attempts >= kMaxEphemeralAttempts) { + LOG_ERROR("Failed to generate valid client ephemeral after ", kMaxEphemeralAttempts, " attempts!"); } } diff --git a/src/game/warden_module.cpp b/src/game/warden_module.cpp index bf44c26e..e49007e2 100644 --- a/src/game/warden_module.cpp +++ b/src/game/warden_module.cpp @@ -37,7 +37,9 @@ WardenModule::WardenModule() : loaded_(false) , moduleMemory_(nullptr) , moduleSize_(0) - , moduleBase_(0x400000) // Default module base address + // 0x400000 is the default PE image base for 32-bit Windows executables. + // Warden modules are loaded as if they were PE DLLs at this base address. + , moduleBase_(0x400000) { } @@ -77,10 +79,13 @@ bool WardenModule::load(const std::vector& moduleData, // Expected with placeholder modulus — verification is skipped gracefully } - // Step 4: Strip RSA signature (last 256 bytes) then zlib decompress + // Step 4: Strip RSA-2048 signature (last 256 bytes = 2048 bits) then zlib decompress. + // Blizzard signs each Warden module to prevent tampering; we strip it since we + // use a placeholder RSA modulus and can't verify the signature. + static constexpr size_t kRsaSignatureSize = 256; std::vector dataWithoutSig; - if (decryptedData_.size() > 256) { - dataWithoutSig.assign(decryptedData_.begin(), decryptedData_.end() - 256); + if (decryptedData_.size() > kRsaSignatureSize) { + dataWithoutSig.assign(decryptedData_.begin(), decryptedData_.end() - kRsaSignatureSize); } else { dataWithoutSig = decryptedData_; }