#include "auth/auth_packets.hpp" #include "auth/crypto.hpp" #include "auth/integrity.hpp" #include "auth/srp.hpp" #include "network/tcp_socket.hpp" #include "network/packet.hpp" #include "core/logger.hpp" #include #include #include #include #include #include #include #include #include #include #include using namespace wowee; static void usage() { std::cerr << "Usage:\n" << " auth_login_probe \\\n" << " (--password | --hash ) [--proof legacy|v8|auto]\n" << "\n" << "Notes:\n" << " - --hash expects SHA1(UPPER(user):UPPER(pass)) in hex.\n" << " - This tool only probes auth; it does not connect to world.\n"; } static std::vector hexToBytes(const std::string& hex) { std::vector out; std::string h; h.reserve(hex.size()); for (char c : hex) { if (!std::isspace(static_cast(c))) h.push_back(c); } if (h.size() % 2 != 0) throw std::runtime_error("hex length must be even"); out.reserve(h.size() / 2); for (size_t i = 0; i < h.size(); i += 2) { auto byteStr = h.substr(i, 2); uint8_t b = static_cast(std::stoul(byteStr, nullptr, 16)); out.push_back(b); } return out; } static std::string upperAscii(std::string s) { std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return static_cast(std::toupper(c)); }); return s; } enum class ProofFormat { Auto, Legacy, V8 }; enum class CrcAFormat { Wire, BigEndian }; enum class WireAFormat { Little, Big }; int main(int argc, char** argv) { if (argc < 11) { usage(); return 2; } const std::string host = argv[1]; const int port = std::atoi(argv[2]); const std::string account = argv[3]; const int major = std::atoi(argv[4]); const int minor = std::atoi(argv[5]); const int patch = std::atoi(argv[6]); const int build = std::atoi(argv[7]); const int proto = std::atoi(argv[8]); const std::string locale = argv[9]; std::string password; std::vector authHash; bool havePassword = false; bool haveHash = false; ProofFormat proofFmt = ProofFormat::Auto; CrcAFormat crcA = CrcAFormat::Wire; WireAFormat wireA = WireAFormat::Little; std::string integrityExe = "WoW.exe"; bool serverValuesBigEndian = false; std::string miscDir = "Data/misc"; bool useHashedK = false; bool hashBigEndian = false; for (int i = 10; i < argc; ++i) { std::string a = argv[i]; if (a == "--password" && i + 1 < argc) { password = argv[++i]; havePassword = true; continue; } if (a == "--hash" && i + 1 < argc) { authHash = hexToBytes(argv[++i]); haveHash = true; continue; } if (a == "--proof" && i + 1 < argc) { std::string v = argv[++i]; if (v == "auto") proofFmt = ProofFormat::Auto; else if (v == "legacy") proofFmt = ProofFormat::Legacy; else if (v == "v8") proofFmt = ProofFormat::V8; else { std::cerr << "Unknown --proof value: " << v << "\n"; return 2; } continue; } if (a == "--crc-a" && i + 1 < argc) { std::string v = argv[++i]; if (v == "wire") crcA = CrcAFormat::Wire; else if (v == "be") crcA = CrcAFormat::BigEndian; else { std::cerr << "Unknown --crc-a value: " << v << " (expected wire|be)\n"; return 2; } continue; } if (a == "--integrity-exe" && i + 1 < argc) { integrityExe = argv[++i]; continue; } if (a == "--misc-dir" && i + 1 < argc) { miscDir = argv[++i]; continue; } if (a == "--server-values" && i + 1 < argc) { std::string v = argv[++i]; if (v == "le") serverValuesBigEndian = false; else if (v == "be") serverValuesBigEndian = true; else { std::cerr << "Unknown --server-values value: " << v << " (expected le|be)\n"; return 2; } continue; } if (a == "--wire-a" && i + 1 < argc) { std::string v = argv[++i]; if (v == "le") wireA = WireAFormat::Little; else if (v == "be") wireA = WireAFormat::Big; else { std::cerr << "Unknown --wire-a value: " << v << " (expected le|be)\n"; return 2; } continue; } if (a == "--k" && i + 1 < argc) { std::string v = argv[++i]; if (v == "3") useHashedK = false; else if (v == "hashed") useHashedK = true; else { std::cerr << "Unknown --k value: " << v << " (expected 3|hashed)\n"; return 2; } continue; } if (a == "--hash-endian" && i + 1 < argc) { std::string v = argv[++i]; if (v == "le") hashBigEndian = false; else if (v == "be") hashBigEndian = true; else { std::cerr << "Unknown --hash-endian value: " << v << " (expected le|be)\n"; return 2; } continue; } std::cerr << "Unknown arg: " << a << "\n"; return 2; } if (!havePassword && !haveHash) { std::cerr << "Must supply --password or --hash\n"; return 2; } auth::ClientInfo info; info.majorVersion = static_cast(major); info.minorVersion = static_cast(minor); info.patchVersion = static_cast(patch); info.build = static_cast(build); info.protocolVersion = static_cast(proto); info.locale = locale; info.platform = "x86"; info.os = "Win"; std::atomic done{false}; std::atomic sawDisconnect{false}; std::atomic challengeOk{false}; std::atomic proofStatus{-1}; std::atomic chalCode{-1}; network::TCPSocket sock; std::unique_ptr srp; uint8_t securityFlags = 0; uint32_t pinSeed = 0; std::array pinSalt{}; std::array checksumSalt{}; auto sendProof = [&]() { if (!srp) return; auto A = srp->getA(); if (wireA == WireAFormat::Big) { std::reverse(A.begin(), A.end()); } auto M1 = srp->getM1(); ProofFormat fmt = proofFmt; if (fmt == ProofFormat::Auto) { fmt = (info.protocolVersion < 8) ? ProofFormat::Legacy : ProofFormat::V8; } // Try to compute the classic client integrity hash using local Data/misc. std::array crcHash{}; const std::array* crcHashPtr = nullptr; { std::string err; std::vector crcABytes = A; if (crcA == CrcAFormat::BigEndian) { std::reverse(crcABytes.begin(), crcABytes.end()); } if (auth::computeIntegrityHashWin32WithExe(checksumSalt, crcABytes, miscDir, integrityExe, crcHash, err)) { crcHashPtr = &crcHash; std::cerr << "Computed integrity hash using " << miscDir << " (" << integrityExe << ")\n"; } else { std::cerr << "Integrity hash not computed: " << err << "\n"; } } if (fmt == ProofFormat::Legacy) { auto pkt = auth::LogonProofPacket::buildLegacy(A, M1, crcHashPtr); sock.send(pkt); std::cerr << "Sent LOGON_PROOF legacy (proto=" << (int)info.protocolVersion << ")\n"; } else { auto pkt = auth::LogonProofPacket::build(A, M1, securityFlags, crcHashPtr, nullptr, nullptr); sock.send(pkt); std::cerr << "Sent LOGON_PROOF v8 (secFlags=0x" << std::hex << (int)securityFlags << std::dec << ")\n"; } }; sock.setPacketCallback([&](const network::Packet& p) { network::Packet pkt = p; if (pkt.getSize() < 1) return; uint8_t opcode = pkt.readUInt8(); if (opcode == static_cast(auth::AuthOpcode::LOGON_CHALLENGE)) { auth::LogonChallengeResponse resp{}; if (!auth::LogonChallengeResponseParser::parse(pkt, resp)) { std::cerr << "Challenge parse failed\n"; done = true; return; } chalCode = static_cast(resp.result); if (!resp.isSuccess()) { std::cerr << "Challenge FAIL: " << auth::getAuthResultString(resp.result) << " (0x" << std::hex << (int)resp.result << std::dec << ")\n"; done = true; return; } challengeOk = true; securityFlags = resp.securityFlags; pinSeed = resp.pinGridSeed; pinSalt = resp.pinSalt; checksumSalt = resp.checksumSalt; srp = std::make_unique(); srp->setUseHashedK(useHashedK); srp->setHashBigEndian(hashBigEndian); if (haveHash) { srp->initializeWithHash(account, authHash); } else { srp->initialize(account, password); } if (serverValuesBigEndian) { auto rev = [](std::vector v) { std::reverse(v.begin(), v.end()); return v; }; srp->feed(rev(resp.B), rev(resp.g), rev(resp.N), rev(resp.salt)); } else { srp->feed(resp.B, resp.g, resp.N, resp.salt); } sendProof(); return; } if (opcode == static_cast(auth::AuthOpcode::LOGON_PROOF)) { auth::LogonProofResponse resp{}; if (!auth::LogonProofResponseParser::parse(pkt, resp)) { std::cerr << "Proof parse failed\n"; done = true; return; } proofStatus = resp.status; if (resp.isSuccess()) { std::cerr << "Proof SUCCESS\n"; } else { std::cerr << "Proof FAIL status=0x" << std::hex << (int)resp.status << std::dec << "\n"; } done = true; return; } }); if (!sock.connect(host, static_cast(port))) { std::cerr << "Connect failed\n"; return 3; } auto chal = auth::LogonChallengePacket::build(account, info); sock.send(chal); auto start = std::chrono::steady_clock::now(); while (!done) { sock.update(); std::this_thread::sleep_for(std::chrono::milliseconds(10)); if (!sock.isConnected() && !done) { sawDisconnect = true; done = true; break; } auto now = std::chrono::steady_clock::now(); if (std::chrono::duration_cast(now - start).count() > 6000) { break; } } sock.disconnect(); if (!done && sock.isConnected()) { std::cerr << "Timeout\n"; return 4; } if (sawDisconnect && challengeOk && proofStatus.load() < 0) { std::cerr << "Server disconnected after challenge (no proof response parsed)\n"; return 6; } if (chalCode.load() >= 0 && chalCode.load() != 0) return chalCode.load(); if (proofStatus.load() >= 0) return proofStatus.load(); return 0; }