improve bb proxy robustness

This commit is contained in:
Martin Michelsen
2022-05-23 19:28:14 -07:00
parent 5a3a55b233
commit 1ff6a4c7e6
4 changed files with 102 additions and 33 deletions
+8 -6
View File
@@ -913,11 +913,12 @@ struct C_Login_BB_93 {
union ClientConfigFields {
ClientConfigBB cfg;
ptext<char, 0x28> version_string;
le_uint32_t as_u32[10];
};
ClientConfigFields old_clients_cfg;
struct NewFormat {
uint64_t hardware_info;
le_uint32_t hardware_info[2];
ClientConfigFields cfg;
} new_clients;
} var;
@@ -1544,13 +1545,14 @@ struct C_GuildCardDataRequest_BB_03DC {
le_uint32_t cont;
};
// DD (S->C): Unknown (BB)
// header.flag is used, but the command body is unused (no other arguments).
// DD (S->C): Send quest state to joining player (BB)
// header.flag is the client ID that the leader should send quest state to.
// No other arguments
// DE (S->C): Unknown (BB)
// DE (S->C): Rare monster configuration (BB)
struct S_Unknown_BB_DE {
le_uint32_t unknown_a1[8];
struct S_RareMonsterConfig_BB_DE {
le_uint16_t data[16];
};
// DF: Invalid command
+86 -25
View File
@@ -17,6 +17,7 @@
#include <iostream>
#include <phosg/Encoding.hh>
#include <phosg/Filesystem.hh>
#include <phosg/Hash.hh>
#include <phosg/Network.hh>
#include <phosg/Random.hh>
#include <phosg/Strings.hh>
@@ -226,34 +227,61 @@ static bool process_server_pc_gc_patch_02_17(shared_ptr<ServerState> s,
}
}
static bool process_server_bb_03(shared_ptr<ServerState>,
static bool process_server_bb_03(shared_ptr<ServerState> s,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
// Most servers don't include after_message or have a shorter
// after_message than newserv does, so don't require it
// Most servers don't include after_message or have a shorter after_message
// than newserv does, so don't require it
const auto& cmd = check_size_t<S_ServerInit_BB_03>(data,
offsetof(S_ServerInit_BB_03, after_message), 0xFFFF);
if (!session.detector_crypt.get()) {
throw runtime_error("BB linked session has no detector crypt");
// If the session has a detector crypt, then it was resumed from an unlinked
// session, during which we already sent an 03 command.
if (session.detector_crypt.get()) {
if (session.login_command_bb.empty()) {
throw logic_error("linked BB session does not have a saved login command");
}
// This isn't forwarded to the client, so only recreate the server's crypts.
// Use the same crypt type as the client... the server has the luxury of
// being able to try all the crypts it knows to detect what type the client
// uses, but the client can't do this since it sends the first encrypted
// data on the connection.
session.server_input_crypt.reset(new PSOBBMultiKeyImitatorEncryption(
session.detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), false));
session.server_output_crypt.reset(new PSOBBMultiKeyImitatorEncryption(
session.detector_crypt, cmd.client_key.data(), sizeof(cmd.client_key), false));
// Forward the login command we saved during the unlinked session.
if (session.enable_remote_ip_crc_patch && (session.login_command_bb.size() >= 0x98)) {
*reinterpret_cast<le_uint32_t*>(session.login_command_bb.data() + 0x94) =
session.remote_ip_crc ^ (1309539928 + 1248334810);
}
session.send_to_end(true, 0x93, 0x00, session.login_command_bb);
return false;
// If there's no detector crypt, then the session is new and was linked
// immediately at connect time, and an 03 was not yet sent to the client, so
// we should forward this one.
} else {
// Forward the command to the client before setting up the crypts, so the
// client receives the unencrypted data
session.send_to_end(false, 0x03, 0x00, data);
static const string expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8);
session.detector_crypt.reset(new PSOBBMultiKeyDetectorEncryption(
s->bb_private_keys, expected_first_data, cmd.client_key.data(), sizeof(cmd.client_key)));
session.client_input_crypt = session.detector_crypt;
session.client_output_crypt.reset(new PSOBBMultiKeyImitatorEncryption(
session.detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), true));
session.server_input_crypt.reset(new PSOBBMultiKeyImitatorEncryption(
session.detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), false));
session.server_output_crypt.reset(new PSOBBMultiKeyImitatorEncryption(
session.detector_crypt, cmd.client_key.data(), sizeof(cmd.client_key), false));
// We already forwarded the command, so don't do so again
return false;
}
if (session.login_command_bb.empty()) {
throw logic_error("linked BB session does not have a saved login command");
}
// This isn't forwarded to the client, so only recreate the server's crypts.
// Use the same crypt type as the client... the server has the luxury of being
// able to try all the crypts it knows to detect what type the client uses,
// but the client can't do this since it sends the first encrypted data on the
// connection.
session.server_input_crypt.reset(new PSOBBMultiKeyImitatorEncryption(
session.detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), false));
session.server_output_crypt.reset(new PSOBBMultiKeyImitatorEncryption(
session.detector_crypt, cmd.client_key.data(), sizeof(cmd.client_key), false));
// Forward the login command we saved during the unlinked session.
session.send_to_end(true, 0x93, 0x00, session.login_command_bb);
return false;
}
static bool process_server_dc_pc_gc_04(shared_ptr<ServerState>,
@@ -386,6 +414,16 @@ static bool process_server_B2(shared_ptr<ServerState>,
}
}
static bool process_server_E7(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
if (session.save_files) {
string output_filename = string_printf("player.bin.%" PRId64, now());
save_file(output_filename, data);
session.log(INFO, "Wrote player data to file %s", output_filename.c_str());
}
return true;
}
template <typename CmdT>
static bool process_server_C4(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t flag, string& data) {
@@ -414,6 +452,25 @@ static bool process_server_gc_E4(shared_ptr<ServerState>,
return true;
}
static bool process_server_bb_22(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
// We use this command (which is sent before the init encryption command) to
// detect a particular server behavior that we'll have to work around later.
// It looks like this command's existence is another anti-proxy measure, since
// this command is 0x34 bytes in total, and the logic that adds padding bytes
// when the command size isn't a multiple of 8 is only active when encryption
// is enabled. Presumably some simpler proxies would get this wrong.
// Editor's note: There's an unsavory message in this command's data field,
// hence the hash here instead of a direct string comparison. I'd love to hear
// the story behind why they put that string there.
if ((data.size() == 0x2C) &&
(fnv1a64(data.data(), data.size()) == 0x8AF8314316A27994)) {
session.log(INFO, "Enabling remote IP CRC patch");
session.enable_remote_ip_crc_patch = true;
}
return true;
}
static bool process_server_game_19_patch_14(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t command, uint32_t, string& data) {
// If the command is shorter than 6 bytes, use the previous server command to
@@ -432,6 +489,10 @@ static bool process_server_game_19_patch_14(shared_ptr<ServerState>,
data.resize(sizeof(S_Reconnect_19), '\0');
}
if (session.enable_remote_ip_crc_patch) {
session.remote_ip_crc = crc32(data.data(), 4);
}
// This weird maximum size is here to properly handle the version-split
// command that some servers (including newserv) use on port 9100
auto& cmd = check_size_t<S_Reconnect_19>(data, sizeof(S_Reconnect_19), 0xB0);
@@ -951,7 +1012,7 @@ static process_command_t gc_server_handlers[0x100] = {
static process_command_t bb_server_handlers[0x100] = {
/* 00 */ defh, defh, defh, process_server_bb_03, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, defh, defh, process_server_game_19_patch_14, defh, defh, defh, defh, defh, defh,
/* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 20 */ defh, defh, process_server_bb_22, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 40 */ defh, process_server_41<S_GuildCardSearchResult_BB_41>, defh, defh, process_server_44_A6<S_OpenFile_BB_44_A6>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 50 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
@@ -963,7 +1024,7 @@ static process_command_t bb_server_handlers[0x100] = {
/* B0 */ defh, defh, process_server_B2, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* C0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* D0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* E0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* E0 */ defh, defh, defh, defh, defh, defh, defh, process_server_E7, defh, defh, defh, defh, defh, defh, defh, defh,
/* F0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
};
static process_command_t patch_server_handlers[0x100] = {
+6 -2
View File
@@ -32,7 +32,8 @@ using namespace std;
static const uint32_t SESSION_TIMEOUT_USECS = 10 * 60 * 1000000; // 10 minutes
static const uint32_t LICENSED_SESSION_TIMEOUT_USECS = 5 * 60 * 1000000; // 5 minutes
static const uint32_t UNLICENSED_SESSION_TIMEOUT_USECS = 10 * 1000000; // 10 seconds
@@ -430,6 +431,8 @@ ProxyServer::LinkedSession::LinkedSession(
client_bev(nullptr, flush_and_free_bufferevent),
server_bev(nullptr, flush_and_free_bufferevent),
local_port(local_port),
remote_ip_crc(0),
enable_remote_ip_crc_patch(false),
version(version),
sub_version(0), // This is set during resume()
remote_guild_card_number(0),
@@ -653,7 +656,8 @@ void ProxyServer::LinkedSession::disconnect() {
// Set a timeout to delete the session entirely (in case the client doesn't
// reconnect)
struct timeval tv = usecs_to_timeval(SESSION_TIMEOUT_USECS);
struct timeval tv = usecs_to_timeval(this->license.get()
? LICENSED_SESSION_TIMEOUT_USECS : UNLICENSED_SESSION_TIMEOUT_USECS);
event_add(this->timeout_event.get(), &tv);
}
+2
View File
@@ -49,6 +49,8 @@ public:
struct sockaddr_storage next_destination;
uint8_t prev_server_command_bytes[6];
uint32_t remote_ip_crc;
bool enable_remote_ip_crc_patch;
GameVersion version;
uint32_t sub_version;