From 49c1cb3affb42b058c284a7baf2d1808cb987488 Mon Sep 17 00:00:00 2001 From: Jett <55758076+Jettford@users.noreply.github.com> Date: Sun, 12 Dec 2021 03:41:11 +0000 Subject: [PATCH 1/8] Implement FDB Checksum - Added config option `check_fdb` - Added check in WorldServer.cpp - Added raw MD5 function in MD5.cpp and MD5.h --- dCommon/MD5.cpp | 8 ++++++++ dCommon/MD5.h | 1 + dWorldServer/WorldServer.cpp | 24 ++++++++++++++++++++++++ resources/worldconfig.ini | 3 +++ 4 files changed, 36 insertions(+) diff --git a/dCommon/MD5.cpp b/dCommon/MD5.cpp index 36c0d2cf..d20dbd6b 100644 --- a/dCommon/MD5.cpp +++ b/dCommon/MD5.cpp @@ -115,6 +115,14 @@ MD5::MD5(const std::string &text) finalize(); } +// raw md5 construstor +MD5::MD5(const char * input, size_type length) +{ + init(); + update(input, length); + finalize(); +} + ////////////////////////////// void MD5::init() diff --git a/dCommon/MD5.h b/dCommon/MD5.h index 1ada98a5..3b84d6f8 100644 --- a/dCommon/MD5.h +++ b/dCommon/MD5.h @@ -54,6 +54,7 @@ public: MD5(); MD5(const std::string& text); + MD5(const char * input, size_type length); void update(const unsigned char *buf, size_type length); void update(const char *buf, size_type length); MD5& finalize(); diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 5d155f29..1449ed86 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -838,7 +838,31 @@ void HandlePacket(Packet* packet) { case MSG_WORLD_CLIENT_VALIDATION: { std::string username = PacketUtils::ReadString(0x08, packet, true); std::string sessionKey = PacketUtils::ReadString(74, packet, true); + std::string fdbChecksum = PacketUtils::ReadString(packet->length - 33, packet, false); + if (bool(std::stoi(Game::config->GetValue("check_fdb")))) { + std::ifstream fileStream; + fileStream.open ("res/CDServer.fdb", std::ios::binary | std::ios::in); + fileStream.seekg (0, std::ios::end); + uint64_t fileStreamLength = fileStream.tellg(); + fileStream.seekg (0, std::ios::beg); + char * fileStreamData = new char[fileStreamLength + 1]; + fileStream.read(fileStreamData, fileStreamLength); + + *(fileStreamData + (fileStreamLength + 1)) = 0x00; // null terminate the string + + MD5 md5 = MD5(fileStreamData, fileStreamLength + 1); + std::string ourFdbChecksum = md5.hexdigest(); + + Game::logger->Log("WorldServer", "Got client checksum %s and we have server checksum %s. \n", fdbChecksum.c_str(), ourFdbChecksum.c_str()); + + if (fdbChecksum != ourFdbChecksum) { + Game::logger->Log("WorldServer", "Client checksum does not match server checksum.\n"); + Game::server->Disconnect(packet->systemAddress, SERVER_DISCON_KICK); + return; + } + } + //Request the session info from Master: CBITSTREAM; PacketUtils::WriteHeader(bitStream, MASTER, MSG_MASTER_REQUEST_SESSION_KEY); diff --git a/resources/worldconfig.ini b/resources/worldconfig.ini index e5932ec7..1c9b3d84 100644 --- a/resources/worldconfig.ini +++ b/resources/worldconfig.ini @@ -47,3 +47,6 @@ solo_racing=0 # Disables the anti-speedhack system. If you get kicked randomly you might want to disable this, as it might just be lag disable_anti_speedhack=0 + +# 0 or 1, check server fdb (res/CDServer.fdb) against clients +check_fdb=0 \ No newline at end of file From 830cf22beba6c4382a153479cc3869f8e9218245 Mon Sep 17 00:00:00 2001 From: Jett <55758076+Jettford@users.noreply.github.com> Date: Sun, 12 Dec 2021 15:14:04 +0000 Subject: [PATCH 2/8] Calculate FDB checksum in chunks --- dWorldServer/WorldServer.cpp | 47 ++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 1449ed86..8cbb7276 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -89,6 +89,7 @@ struct tempSessionInfo { std::map m_PendingUsers; int instanceID = 0; int g_CloneID = 0; +std::string fdbChecksum = ""; int main(int argc, char** argv) { Diagnostics::SetProcessName("World"); @@ -234,6 +235,30 @@ int main(int argc, char** argv) { Game::physicsWorld = &dpWorld::Instance(); //just in case some old code references it dZoneManager::Instance()->Initialize(LWOZONEID(zoneID, instanceID, cloneID)); g_CloneID = cloneID; + + // pre calculate the FDB checksum + if (bool(std::stoi(Game::config->GetValue("check_fdb")))) { + std::ifstream fileStream; + fileStream.open ("res/CDServer.fdb", std::ios::binary | std::ios::in); + const int bufferSize = 1024; + MD5* md5 = new MD5(); + + while (!fileStream.eof()) { + char * fileStreamBuffer = new char[bufferSize]; + fileStream.read(fileStreamBuffer, bufferSize); + std::streamsize size = ((fileStream) ? bufferSize : fileStream.gcount()); + md5->update(fileStreamBuffer, size); + } + + const char* nullTerminateBuffer = "\0"; + md5->update(nullTerminateBuffer, 1); // null terminate the data + md5->finalize(); + fdbChecksum = md5->hexdigest(); + + delete md5; + + Game::logger->Log("WorldServer", "FDB Checksum calculated as: %s\n", fdbChecksum.c_str()); + } } while (true) { @@ -838,25 +863,11 @@ void HandlePacket(Packet* packet) { case MSG_WORLD_CLIENT_VALIDATION: { std::string username = PacketUtils::ReadString(0x08, packet, true); std::string sessionKey = PacketUtils::ReadString(74, packet, true); - std::string fdbChecksum = PacketUtils::ReadString(packet->length - 33, packet, false); + std::string theirFdbChecksum = PacketUtils::ReadString(packet->length - 33, packet, false); - if (bool(std::stoi(Game::config->GetValue("check_fdb")))) { - std::ifstream fileStream; - fileStream.open ("res/CDServer.fdb", std::ios::binary | std::ios::in); - fileStream.seekg (0, std::ios::end); - uint64_t fileStreamLength = fileStream.tellg(); - fileStream.seekg (0, std::ios::beg); - char * fileStreamData = new char[fileStreamLength + 1]; - fileStream.read(fileStreamData, fileStreamLength); - - *(fileStreamData + (fileStreamLength + 1)) = 0x00; // null terminate the string - - MD5 md5 = MD5(fileStreamData, fileStreamLength + 1); - std::string ourFdbChecksum = md5.hexdigest(); - - Game::logger->Log("WorldServer", "Got client checksum %s and we have server checksum %s. \n", fdbChecksum.c_str(), ourFdbChecksum.c_str()); - - if (fdbChecksum != ourFdbChecksum) { + if (bool(std::stoi(Game::config->GetValue("check_fdb"))) && fdbChecksum != "") { // if fdbChecksum is empty, likely means we are a character server. + Game::logger->Log("WorldServer", "Got client checksum %s and we have server checksum %s. \n", theirFdbChecksum.c_str(), fdbChecksum.c_str()); + if (theirFdbChecksum != fdbChecksum) { Game::logger->Log("WorldServer", "Client checksum does not match server checksum.\n"); Game::server->Disconnect(packet->systemAddress, SERVER_DISCON_KICK); return; From 9e032223eba7292bb61de6d3afd16c7ab7134184 Mon Sep 17 00:00:00 2001 From: Jett <55758076+Jettford@users.noreply.github.com> Date: Sun, 12 Dec 2021 15:53:19 +0000 Subject: [PATCH 3/8] Remove unused MD5 functions and fixed memory leak --- dCommon/MD5.cpp | 8 -------- dCommon/MD5.h | 1 - dWorldServer/WorldServer.cpp | 13 ++++++++----- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/dCommon/MD5.cpp b/dCommon/MD5.cpp index d20dbd6b..36c0d2cf 100644 --- a/dCommon/MD5.cpp +++ b/dCommon/MD5.cpp @@ -115,14 +115,6 @@ MD5::MD5(const std::string &text) finalize(); } -// raw md5 construstor -MD5::MD5(const char * input, size_type length) -{ - init(); - update(input, length); - finalize(); -} - ////////////////////////////// void MD5::init() diff --git a/dCommon/MD5.h b/dCommon/MD5.h index 3b84d6f8..1ada98a5 100644 --- a/dCommon/MD5.h +++ b/dCommon/MD5.h @@ -54,7 +54,6 @@ public: MD5(); MD5(const std::string& text); - MD5(const char * input, size_type length); void update(const unsigned char *buf, size_type length); void update(const char *buf, size_type length); MD5& finalize(); diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 8cbb7276..9c3aa6b3 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -239,17 +239,20 @@ int main(int argc, char** argv) { // pre calculate the FDB checksum if (bool(std::stoi(Game::config->GetValue("check_fdb")))) { std::ifstream fileStream; - fileStream.open ("res/CDServer.fdb", std::ios::binary | std::ios::in); + fileStream.open("res/CDServer.fdb", std::ios::binary | std::ios::in); const int bufferSize = 1024; MD5* md5 = new MD5(); + std::vector fileStreamBuffer = std::vector(bufferSize, 0); + while (!fileStream.eof()) { - char * fileStreamBuffer = new char[bufferSize]; - fileStream.read(fileStreamBuffer, bufferSize); - std::streamsize size = ((fileStream) ? bufferSize : fileStream.gcount()); - md5->update(fileStreamBuffer, size); + fileStreamBuffer.clear(); + fileStream.read(fileStreamBuffer.data(), bufferSize); + md5->update(fileStreamBuffer.data(), fileStream.gcount()); } + fileStreamBuffer.clear(); + const char* nullTerminateBuffer = "\0"; md5->update(nullTerminateBuffer, 1); // null terminate the data md5->finalize(); From 543b231a80db0f02c0709684d4cf5973f5fd4ca0 Mon Sep 17 00:00:00 2001 From: Jett <55758076+Jettford@users.noreply.github.com> Date: Tue, 14 Dec 2021 18:12:44 +0000 Subject: [PATCH 4/8] Update WorldServer.cpp --- dWorldServer/WorldServer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 9c3aa6b3..82fb2520 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -237,7 +237,7 @@ int main(int argc, char** argv) { g_CloneID = cloneID; // pre calculate the FDB checksum - if (bool(std::stoi(Game::config->GetValue("check_fdb")))) { + if (Game::config->GetValue("check_fdb") == "1") { std::ifstream fileStream; fileStream.open("res/CDServer.fdb", std::ios::binary | std::ios::in); const int bufferSize = 1024; @@ -868,7 +868,7 @@ void HandlePacket(Packet* packet) { std::string sessionKey = PacketUtils::ReadString(74, packet, true); std::string theirFdbChecksum = PacketUtils::ReadString(packet->length - 33, packet, false); - if (bool(std::stoi(Game::config->GetValue("check_fdb"))) && fdbChecksum != "") { // if fdbChecksum is empty, likely means we are a character server. + if (Game::config->GetValue("check_fdb") == "1" && fdbChecksum != "") { // if fdbChecksum is empty, likely means we are a character server. Game::logger->Log("WorldServer", "Got client checksum %s and we have server checksum %s. \n", theirFdbChecksum.c_str(), fdbChecksum.c_str()); if (theirFdbChecksum != fdbChecksum) { Game::logger->Log("WorldServer", "Client checksum does not match server checksum.\n"); From ff2f5cb2cecab1107f8afa93dfe248069745d10c Mon Sep 17 00:00:00 2001 From: Jett <55758076+Jettford@users.noreply.github.com> Date: Tue, 14 Dec 2021 19:25:30 +0000 Subject: [PATCH 5/8] (untested) --- dWorldServer/WorldServer.cpp | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 82fb2520..b735cb89 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -869,11 +869,25 @@ void HandlePacket(Packet* packet) { std::string theirFdbChecksum = PacketUtils::ReadString(packet->length - 33, packet, false); if (Game::config->GetValue("check_fdb") == "1" && fdbChecksum != "") { // if fdbChecksum is empty, likely means we are a character server. - Game::logger->Log("WorldServer", "Got client checksum %s and we have server checksum %s. \n", theirFdbChecksum.c_str(), fdbChecksum.c_str()); - if (theirFdbChecksum != fdbChecksum) { - Game::logger->Log("WorldServer", "Client checksum does not match server checksum.\n"); - Game::server->Disconnect(packet->systemAddress, SERVER_DISCON_KICK); - return; + uint32_t gmLevel = 0; + sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT gm_level FROM accounts WHERE name=? LIMIT 1;"); + stmt->setString(1, username.c_str()); + + sql::ResultSet* res = stmt->executeQuery(); + while (res->next()) { + gmLevel = res->getInt(1); + } + + delete stmt; + delete res; + + if (gmLevel != 9) { + Game::logger->Log("WorldServer", "Got client checksum %s and we have server checksum %s. \n", theirFdbChecksum.c_str(), fdbChecksum.c_str()); + if (theirFdbChecksum != fdbChecksum) { + Game::logger->Log("WorldServer", "Client checksum does not match server checksum.\n"); + Game::server->Disconnect(packet->systemAddress, SERVER_DISCON_KICK); + return; + } } } From a5d527d0cf6350ff7e67f41f74e0a4bce72c5990 Mon Sep 17 00:00:00 2001 From: Jett <55758076+Jettford@users.noreply.github.com> Date: Wed, 15 Dec 2021 18:45:42 +0000 Subject: [PATCH 6/8] client fdb checksum bug --- dWorldServer/WorldServer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index b735cb89..eec3bcb6 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -867,6 +867,7 @@ void HandlePacket(Packet* packet) { std::string username = PacketUtils::ReadString(0x08, packet, true); std::string sessionKey = PacketUtils::ReadString(74, packet, true); std::string theirFdbChecksum = PacketUtils::ReadString(packet->length - 33, packet, false); + theirFdbChecksum = theirFdbChecksum.substr(0, 32); // sometimes client puts a null terminator at the end of the checksum and sometimes doesn't; weird if (Game::config->GetValue("check_fdb") == "1" && fdbChecksum != "") { // if fdbChecksum is empty, likely means we are a character server. uint32_t gmLevel = 0; From 49aba62dbbc7b037f83eaa5783f993ac5ed9243d Mon Sep 17 00:00:00 2001 From: wincent Date: Thu, 16 Dec 2021 23:11:51 +0100 Subject: [PATCH 7/8] Updated database check When applied this commit updates the style of the database check, changes some logging statements, and makes developers able to skip the check. --- dWorldServer/WorldServer.cpp | 57 +++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index eec3bcb6..55e3efb2 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -89,7 +89,7 @@ struct tempSessionInfo { std::map m_PendingUsers; int instanceID = 0; int g_CloneID = 0; -std::string fdbChecksum = ""; +std::string databaseChecksum = ""; int main(int argc, char** argv) { Diagnostics::SetProcessName("World"); @@ -239,28 +239,42 @@ int main(int argc, char** argv) { // pre calculate the FDB checksum if (Game::config->GetValue("check_fdb") == "1") { std::ifstream fileStream; - fileStream.open("res/CDServer.fdb", std::ios::binary | std::ios::in); + + static const std::vector alieses = { + "res/CDServers.fdb", + "res/cdserver.fdb", + "res/CDClient.fdb", + "res/cdclient.fdb", + }; + + for (const auto& file : alieses) { + fileStream.open(file); + if (fileStream.is_open()) { + break; + } + } + const int bufferSize = 1024; MD5* md5 = new MD5(); - std::vector fileStreamBuffer = std::vector(bufferSize, 0); + char fileStreamBuffer[1024] = {}; while (!fileStream.eof()) { - fileStreamBuffer.clear(); - fileStream.read(fileStreamBuffer.data(), bufferSize); - md5->update(fileStreamBuffer.data(), fileStream.gcount()); + memset(fileStreamBuffer, 0, bufferSize); + fileStream.read(fileStreamBuffer, bufferSize); + md5->update(fileStreamBuffer, fileStream.gcount()); } - fileStreamBuffer.clear(); + fileStream.close(); const char* nullTerminateBuffer = "\0"; md5->update(nullTerminateBuffer, 1); // null terminate the data md5->finalize(); - fdbChecksum = md5->hexdigest(); + databaseChecksum = md5->hexdigest(); delete md5; - Game::logger->Log("WorldServer", "FDB Checksum calculated as: %s\n", fdbChecksum.c_str()); + Game::logger->Log("WorldServer", "FDB Checksum calculated as: %s\n", databaseChecksum.c_str()); } } @@ -866,15 +880,18 @@ void HandlePacket(Packet* packet) { case MSG_WORLD_CLIENT_VALIDATION: { std::string username = PacketUtils::ReadString(0x08, packet, true); std::string sessionKey = PacketUtils::ReadString(74, packet, true); - std::string theirFdbChecksum = PacketUtils::ReadString(packet->length - 33, packet, false); - theirFdbChecksum = theirFdbChecksum.substr(0, 32); // sometimes client puts a null terminator at the end of the checksum and sometimes doesn't; weird + std::string clientDatabaseChecksum = PacketUtils::ReadString(packet->length - 33, packet, false); - if (Game::config->GetValue("check_fdb") == "1" && fdbChecksum != "") { // if fdbChecksum is empty, likely means we are a character server. + // sometimes client puts a null terminator at the end of the checksum and sometimes doesn't, weird + clientDatabaseChecksum = clientDatabaseChecksum.substr(0, 32); + + // If the check is turned on, validate the client's database checksum. + if (Game::config->GetValue("check_fdb") == "1" && !databaseChecksum.empty()) { uint32_t gmLevel = 0; - sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT gm_level FROM accounts WHERE name=? LIMIT 1;"); + auto* stmt = Database::CreatePreppedStmt("SELECT gm_level FROM accounts WHERE name=? LIMIT 1;"); stmt->setString(1, username.c_str()); - sql::ResultSet* res = stmt->executeQuery(); + auto* res = stmt->executeQuery(); while (res->next()) { gmLevel = res->getInt(1); } @@ -882,13 +899,11 @@ void HandlePacket(Packet* packet) { delete stmt; delete res; - if (gmLevel != 9) { - Game::logger->Log("WorldServer", "Got client checksum %s and we have server checksum %s. \n", theirFdbChecksum.c_str(), fdbChecksum.c_str()); - if (theirFdbChecksum != fdbChecksum) { - Game::logger->Log("WorldServer", "Client checksum does not match server checksum.\n"); - Game::server->Disconnect(packet->systemAddress, SERVER_DISCON_KICK); - return; - } + // Developers may skip this check + if (gmLevel < 8 && clientDatabaseChecksum != databaseChecksum) { + Game::logger->Log("WorldServer", "Client's database checksum does not match the server's, aborting connection.\n"); + Game::server->Disconnect(packet->systemAddress, SERVER_DISCON_KICK); + return; } } From 6f932fe2cb41d31037c2ec6cff1010ecf581c666 Mon Sep 17 00:00:00 2001 From: Jett <55758076+Jettford@users.noreply.github.com> Date: Thu, 16 Dec 2021 22:51:37 +0000 Subject: [PATCH 8/8] Spelling and set filestream mode --- dWorldServer/WorldServer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 55e3efb2..92bc1889 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -240,15 +240,15 @@ int main(int argc, char** argv) { if (Game::config->GetValue("check_fdb") == "1") { std::ifstream fileStream; - static const std::vector alieses = { + static const std::vector aliases = { "res/CDServers.fdb", "res/cdserver.fdb", "res/CDClient.fdb", "res/cdclient.fdb", }; - for (const auto& file : alieses) { - fileStream.open(file); + for (const auto& file : aliases) { + fileStream.open(file, std::ios::binary | std::ios::in); if (fileStream.is_open()) { break; }