#include #include "loader/component_loader.hpp" #include "auth.hpp" #include "component/command.hpp" #include "network.hpp" #include #include #include #include #include #include "game/game.hpp" #include "steam/steam.hpp" namespace auth { namespace { std::string get_hdd_serial() { DWORD serial{}; if (!GetVolumeInformationA("C:\\", nullptr, 0, &serial, nullptr, nullptr, nullptr, 0)) { return {}; } return utils::string::va("%08X", serial); } std::string get_hw_profile_guid() { HW_PROFILE_INFO info; if (!GetCurrentHwProfileA(&info)) { return {}; } return std::string{info.szHwProfileGuid, sizeof(info.szHwProfileGuid)}; } std::string get_protected_data() { std::string input = "X-Labs-H1Mod-Auth"; DATA_BLOB data_in{}, data_out{}; data_in.pbData = reinterpret_cast(input.data()); data_in.cbData = static_cast(input.size()); if (CryptProtectData(&data_in, nullptr, nullptr, nullptr, nullptr, CRYPTPROTECT_LOCAL_MACHINE, &data_out) != TRUE) { return {}; } const auto size = std::min(data_out.cbData, 52ul); std::string result{reinterpret_cast(data_out.pbData), size}; LocalFree(data_out.pbData); return result; } std::string get_key_entropy() { std::string entropy{}; entropy.append(utils::smbios::get_uuid()); entropy.append(get_hw_profile_guid()); entropy.append(get_protected_data()); entropy.append(get_hdd_serial()); if (entropy.empty()) { entropy.resize(32); utils::cryptography::random::get_data(entropy.data(), entropy.size()); } return entropy; } utils::cryptography::ecc::key& get_key() { static auto key = utils::cryptography::ecc::generate_key(512, get_key_entropy()); return key; } // need to move this somewhere else probably std::string hash_string(const std::string& str) { const auto value = game::generateHashValue(str.data()); return utils::string::va("0x%lX", value); } bool send_connect_data(game::netsrc_t sock, game::netadr_s* adr, const char* format, const int len) { std::string connect_string(format, len); game::SV_Cmd_TokenizeString(connect_string.data()); const auto _ = gsl::finally([]() { game::SV_Cmd_EndTokenizedString(); }); const command::params_sv params; if (params.size() < 3) { return false; } const utils::info_string info_string{std::string{params[2]}}; const auto challenge = info_string.get(hash_string("challenge")); connect_string.clear(); connect_string.append(params[0]); connect_string.append(" "); connect_string.append(params[1]); connect_string.append(" "); connect_string.append("\"" + info_string.build() + "\""); proto::network::connect_info info; info.set_publickey(get_key().get_public_key()); info.set_signature(sign_message(get_key(), challenge)); info.set_infostring(connect_string); network::send(*adr, "connect", info.SerializeAsString()); return true; } void direct_connect(game::netadr_s* from, game::msg_t* msg) { const auto offset = sizeof("connect") + 4; proto::network::connect_info info; if (msg->cursize < offset || !info.ParseFromArray(msg->data + offset, msg->cursize - offset)) { network::send(*from, "error", "Invalid connect data!", '\n'); return; } game::SV_Cmd_EndTokenizedString(); game::SV_Cmd_TokenizeString(info.infostring().data()); const command::params_sv params; if (params.size() < 3) { network::send(*from, "error", "Invalid connect string!", '\n'); return; } const utils::info_string info_string{std::string{params[2]}}; const auto steam_id = info_string.get(hash_string("xuid")); const auto challenge = info_string.get(hash_string("challenge")); if (steam_id.empty() || challenge.empty()) { network::send(*from, "error", "Invalid connect data!", '\n'); return; } utils::cryptography::ecc::key key; key.set(info.publickey()); const auto xuid = strtoull(steam_id.data(), nullptr, 16); if (xuid != key.get_hash()) { //MessageBoxA(nullptr, steam_id.data(), std::to_string(key.get_hash()).data(), 0); network::send(*from, "error", utils::string::va("XUID doesn't match the certificate: %llX != %llX", xuid, key.get_hash()), '\n'); return; } if (!key.is_valid() || !verify_message(key, challenge, info.signature())) { network::send(*from, "error", "Challenge signature was invalid!", '\n'); return; } game::SV_DirectConnect(from); } void* get_direct_connect_stub() { return utils::hook::assemble([](utils::hook::assembler& a) { a.lea(rcx, qword_ptr(rsp, 0x20)); a.movaps(xmmword_ptr(rsp, 0x20), xmm0); a.pushad64(); a.mov(rdx, rsi); a.call_aligned(direct_connect); a.popad64(); a.jmp(0x1CAF64_b); }); } void* get_send_connect_data_stub() { return utils::hook::assemble([](utils::hook::assembler& a) { const auto false_ = a.newLabel(); const auto original = a.newLabel(); a.mov(ecx, eax); a.lea(r8, qword_ptr(rbp, 0x4C0)); a.mov(r9d, ebx); a.lea(rdx, qword_ptr(rsp, 0x30)); a.pushad64(); a.call_aligned(send_connect_data); a.test(al, al); a.popad64(); a.mov(rbx, qword_ptr(rsp, 0x9F0)); a.jmp(0x12D446_b); }); } } uint64_t get_guid() { if (game::environment::is_dedi()) { return 0x110000100000000 | (::utils::cryptography::random::get_integer() & ~0x80000000); } return get_key().get_hash(); } class component final : public component_interface { public: void post_unpack() override { // Patch steam id bit check if (game::environment::is_sp()) { //utils::hook::jump(0x140475C17, 0x140475C6A); // H1(1.4) //utils::hook::jump(0x140476AFF, 0x140476B40); // H1(1.4) //utils::hook::jump(0x140476FA4, 0x140476FF2); // H1(1.4) } else { // kill "disconnected from steam" error utils::hook::nop(0x1D61DF_b, 0x11); /*utils::hook::nop(0x1D6193_b, 103); // STEAM utils::hook::nop(0x60153_b, 0x60426 - 0x60153); // STEAM utils::hook::nop(0x603E1_b, 0x60426 - 0x603E1); // STEAM utils::hook::nop(0x1D7553_b, 0x1D7587 - 0x1D7553); // STEAM MAYBE `1401D7553` ON FIRST utils::hook::nop(0x1D7A82_b, 0x1D7AC8 - 0x1D7A82); // STEAM*/ utils::hook::jump(0x1CAE70_b, get_direct_connect_stub(), true); utils::hook::jump(0x12D426_b, get_send_connect_data_stub(), true); // Don't instantly timeout the connecting client ? not sure about this utils::hook::set(0x12D93C_b, 0xC3); } command::add("guid", []() { printf("Your guid: %llX\n", steam::SteamUser()->GetSteamID().bits); }); } }; } REGISTER_COMPONENT(auth::component)