diff options
-rw-r--r-- | sql/updates/world/master/2022_02_03_24_world_2020_08_21_00_world.sql | 6 | ||||
-rw-r--r-- | src/server/game/Handlers/ChatHandler.cpp | 9 | ||||
-rw-r--r-- | src/server/game/Warden/Warden.cpp | 26 | ||||
-rw-r--r-- | src/server/game/Warden/Warden.h | 1 | ||||
-rw-r--r-- | src/server/game/Warden/WardenCheckMgr.cpp | 21 | ||||
-rw-r--r-- | src/server/game/Warden/WardenCheckMgr.h | 6 | ||||
-rw-r--r-- | src/server/game/Warden/WardenWin.cpp | 46 | ||||
-rw-r--r-- | src/server/game/Warden/enuminfo_WardenCheckMgr.cpp | 18 |
8 files changed, 100 insertions, 33 deletions
diff --git a/sql/updates/world/master/2022_02_03_24_world_2020_08_21_00_world.sql b/sql/updates/world/master/2022_02_03_24_world_2020_08_21_00_world.sql new file mode 100644 index 00000000000..fbe7598a97b --- /dev/null +++ b/sql/updates/world/master/2022_02_03_24_world_2020_08_21_00_world.sql @@ -0,0 +1,6 @@ +-- +DELETE FROM `warden_checks` WHERE `id` IN (788,789,790); +INSERT INTO `warden_checks` (`id`,`type`,`str`,`address`,`length`,`result`, `comment`) VALUES +(788, 139, 'forceinsecure() return issecure()', NULL, NULL, NULL, 'Detects naive Lua unlockers'), +(789, 139, 'return not not PQR_IsMoving', NULL, NULL, NULL, 'Detects PQR'), +(790, 139, 'local f=DEFAULT_CHAT_FRAME for i=1,f:GetNumMessages() do if (f:GetMessageInfo(i)):find("|cffffd200PQR|r") then return true end end', NULL, NULL, NULL, 'Detects PQR'); diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp index bae2ba82818..84f3ae5c9ce 100644 --- a/src/server/game/Handlers/ChatHandler.cpp +++ b/src/server/game/Handlers/ChatHandler.cpp @@ -39,6 +39,7 @@ #include "ScriptMgr.h" #include "SpellAuraEffects.h" #include "Util.h" +#include "Warden.h" #include "World.h" #include "WorldPacket.h" #include <algorithm> @@ -217,7 +218,6 @@ void WorldSession::HandleChatMessage(ChatMsg type, Language lang, std::string ms if (msg.size() > 255) return; - if (msg.empty()) return; @@ -466,6 +466,13 @@ void WorldSession::HandleChatAddonMessage(ChatMsg type, std::string prefix, std: if (prefix.empty() || prefix.length() > 16) return; + // Our Warden module also uses SendAddonMessage as a way to communicate Lua check results to the server, see if this is that + if (type == CHAT_MSG_GUILD) + { + if (_warden && _warden->ProcessLuaCheckResponse(text)) + return; + } + // Disabled addon channel? if (!sWorld->getBoolConfig(CONFIG_ADDON_CHANNEL)) return; diff --git a/src/server/game/Warden/Warden.cpp b/src/server/game/Warden/Warden.cpp index 105460ca44b..1f27bda664a 100644 --- a/src/server/game/Warden/Warden.cpp +++ b/src/server/game/Warden/Warden.cpp @@ -31,6 +31,8 @@ #include <openssl/sha.h> #include <openssl/md5.h> +#include <charconv> + Warden::Warden() : _session(nullptr), _checkTimer(10 * IN_MILLISECONDS), _clientResponseTimer(0), _dataSent(false), _initialized(false) { @@ -254,6 +256,30 @@ void Warden::HandleData(ByteBuffer& buff) } } +bool Warden::ProcessLuaCheckResponse(std::string const& msg) +{ + static constexpr char WARDEN_TOKEN[] = "_TW\t"; + if (!StringStartsWith(msg, WARDEN_TOKEN)) + return false; + + uint16 id = 0; + std::from_chars(msg.data() + sizeof(WARDEN_TOKEN) - 1, msg.data() + msg.size(), id, 10); + if (id < sWardenCheckMgr->GetMaxValidCheckId()) + { + WardenCheck const& check = sWardenCheckMgr->GetCheckData(id); + if (check.Type == LUA_EVAL_CHECK) + { + char const* penalty = ApplyPenalty(&check); + TC_LOG_WARN("warden", "%s failed Warden check %u (%s). Action: %s", _session->GetPlayerInfo().c_str(), id, EnumUtils::ToConstant(check.Type), penalty); + return true; + } + } + + char const* penalty = ApplyPenalty(nullptr); + TC_LOG_WARN("warden", "%s sent bogus Lua check response for Warden. Action: %s", _session->GetPlayerInfo().c_str(), penalty); + return true; +} + void WorldSession::HandleWardenData(WorldPackets::Warden::WardenData& packet) { if (!_warden || packet.Data.empty()) diff --git a/src/server/game/Warden/Warden.h b/src/server/game/Warden/Warden.h index d404622af17..1a81a6be422 100644 --- a/src/server/game/Warden/Warden.h +++ b/src/server/game/Warden/Warden.h @@ -91,6 +91,7 @@ class TC_GAME_API Warden virtual void Init(WorldSession* session, SessionKey const& K) = 0; void Update(uint32 diff); void HandleData(ByteBuffer& buff); + bool ProcessLuaCheckResponse(std::string const& msg); virtual size_t DEBUG_ForceSpecificChecks(std::vector<uint16> const& checks) = 0; diff --git a/src/server/game/Warden/WardenCheckMgr.cpp b/src/server/game/Warden/WardenCheckMgr.cpp index c4a4d7de49e..7fff6a69cce 100644 --- a/src/server/game/Warden/WardenCheckMgr.cpp +++ b/src/server/game/Warden/WardenCheckMgr.cpp @@ -73,6 +73,12 @@ void WardenCheckMgr::LoadWardenChecks() continue; } + if ((type == LUA_EVAL_CHECK) && (id > 9999)) + { + TC_LOG_ERROR("sql.sql", "Warden Lua check with id %u found in `warden_checks`. Lua checks may have four-digit IDs at most. Skipped.", id); + continue; + } + WardenCheck& wardenCheck = _checks[id]; wardenCheck.CheckId = id; wardenCheck.Type = type; @@ -90,13 +96,26 @@ void WardenCheckMgr::LoadWardenChecks() wardenCheck.Length = fields[5].GetUInt8(); // PROC_CHECK support missing - if (type == MEM_CHECK || type == MPQ_CHECK || type == LUA_STR_CHECK || type == DRIVER_CHECK || type == MODULE_CHECK) + if (type == MEM_CHECK || type == MPQ_CHECK || type == LUA_EVAL_CHECK || type == DRIVER_CHECK || type == MODULE_CHECK) wardenCheck.Str = fields[6].GetString(); wardenCheck.Comment = fields[7].GetString(); if (wardenCheck.Comment.empty()) wardenCheck.Comment = "Undocumented Check"; + if (type == LUA_EVAL_CHECK) + { + if (wardenCheck.Str.size() > WARDEN_MAX_LUA_CHECK_LENGTH) + { + TC_LOG_ERROR("sql.sql", "Found over-long Lua check for Warden check with id %u in `warden_checks`. Max length is %u. Skipped.", id, WARDEN_MAX_LUA_CHECK_LENGTH); + continue; + } + + std::string str = fmt::sprintf("%04u", id); + ASSERT(str.size() == 4); + std::copy(str.begin(), str.end(), wardenCheck.IdStr.begin()); + } + // initialize action with default action from config, this may be overridden later wardenCheck.Action = WardenActions(sWorld->getIntConfig(CONFIG_WARDEN_CLIENT_FAIL_ACTION)); diff --git a/src/server/game/Warden/WardenCheckMgr.h b/src/server/game/Warden/WardenCheckMgr.h index 9583684705c..7b0f81cf422 100644 --- a/src/server/game/Warden/WardenCheckMgr.h +++ b/src/server/game/Warden/WardenCheckMgr.h @@ -45,11 +45,11 @@ enum WardenCheckCategory : uint8 // EnumUtils: DESCRIBE THIS enum WardenCheckType : uint8 { - LUA_STR_CHECK = 0x8B, // 139: byte luaNameIndex (check to ensure LUA string isn't used) NONE_CHECK = 0, // SKIP TIMING_CHECK = 87, // nyi DRIVER_CHECK = 113, // uint Seed + byte[20] SHA1 + byte driverNameIndex (check to ensure driver isn't loaded) PROC_CHECK = 126, // nyi + LUA_EVAL_CHECK = 139, // evaluate arbitrary Lua check MPQ_CHECK = 152, // get hash of MPQ file (to check it is not modified) PAGE_CHECK_A = 178, // scans all pages for specified SHA1 hash PAGE_CHECK_B = 191, // scans only pages starts with MZ+PE headers for specified hash @@ -64,6 +64,7 @@ constexpr WardenCheckCategory GetWardenCheckCategory(WardenCheckType type) case TIMING_CHECK: return NUM_CHECK_CATEGORIES; case DRIVER_CHECK: return INJECT_CHECK_CATEGORY; case PROC_CHECK: return NUM_CHECK_CATEGORIES; + case LUA_EVAL_CHECK: return LUA_CHECK_CATEGORY; case MPQ_CHECK: return MODDED_CHECK_CATEGORY; case PAGE_CHECK_A: return INJECT_CHECK_CATEGORY; case PAGE_CHECK_B: return INJECT_CHECK_CATEGORY; @@ -93,9 +94,12 @@ struct WardenCheck uint8 Length; // PROC_CHECK, MEM_CHECK, PAGE_CHECK std::string Str; // LUA, MPQ, DRIVER std::string Comment; + std::array<char, 4> IdStr = {}; // LUA WardenActions Action; }; +constexpr uint8 WARDEN_MAX_LUA_CHECK_LENGTH = 170; + using WardenCheckResult = std::vector<uint8>; class TC_GAME_API WardenCheckMgr diff --git a/src/server/game/Warden/WardenWin.cpp b/src/server/game/Warden/WardenWin.cpp index 92e77378198..dcee3ba2a3a 100644 --- a/src/server/game/Warden/WardenWin.cpp +++ b/src/server/game/Warden/WardenWin.cpp @@ -37,6 +37,14 @@ #include "WorldSession.h" #include <sstream> + // GUILD is the shortest string that has no client validation (RAID only sends if in a raid group) +static constexpr char _luaEvalPrefix[] = "local S,T,R=SendAddonMessage,function()"; +static constexpr char _luaEvalMidfix[] = " end R=S and T()if R then S('_TW',"; +static constexpr char _luaEvalPostfix[] = ",'GUILD')end"; + +static_assert((sizeof(_luaEvalPrefix)-1 + sizeof(_luaEvalMidfix)-1 + sizeof(_luaEvalPostfix)-1 + WARDEN_MAX_LUA_CHECK_LENGTH) == 255); + + WardenWin::WardenWin() : Warden(), _serverTicks(0) { for (WardenCheckCategory category : EnumUtils::Iterate<WardenCheckCategory>()) @@ -104,7 +112,7 @@ void WardenWin::InitializeModule() Request.Unk3 = 4; Request.Unk4 = 0; Request.String_library2 = 0; - Request.Function2 = 0x00419D40; // 0x00400000 + 0x00419D40 FrameScript::GetText + Request.Function2 = 0x00419210; // 0x00400000 + 0x00419210 FrameScript::Execute Request.Function2_set = 1; Request.CheckSumm2 = BuildChecksum(&Request.Unk3, 8); @@ -213,7 +221,16 @@ void WardenWin::RequestChecks() uint16 const id = *(checksIt++); WardenCheck const& check = sWardenCheckMgr->GetCheckData(id); - if (!check.Str.empty()) + if (check.Type == LUA_EVAL_CHECK) + { + buff << uint8(sizeof(_luaEvalPrefix)-1 + check.Str.size() + sizeof(_luaEvalMidfix)-1 + check.IdStr.size() + sizeof(_luaEvalPostfix)-1); + buff.append(_luaEvalPrefix, sizeof(_luaEvalPrefix)-1); + buff.append(check.Str.data(), check.Str.size()); + buff.append(_luaEvalMidfix, sizeof(_luaEvalMidfix)-1); + buff.append(check.IdStr.data(), check.IdStr.size()); + buff.append(_luaEvalPostfix, sizeof(_luaEvalPostfix)-1); + } + else if (!check.Str.empty()) { buff << uint8(check.Str.size()); buff.append(check.Str.data(), check.Str.size()); @@ -255,7 +272,7 @@ void WardenWin::RequestChecks() break; } case MPQ_CHECK: - case LUA_STR_CHECK: + case LUA_EVAL_CHECK: { buff << uint8(index++); break; @@ -410,26 +427,13 @@ void WardenWin::HandleCheckResult(ByteBuffer &buff) TC_LOG_DEBUG("warden", "RESULT %s passed CheckId %u account Id %u", EnumUtils::ToConstant(check.Type), id, _session->GetAccountId()); break; } - case LUA_STR_CHECK: + case LUA_EVAL_CHECK: { - uint8 Lua_Result; - buff >> Lua_Result; + uint8 const result = buff.read<uint8>(); + if (result == 0) + buff.read_skip(buff.read<uint8>()); // discard attached string - if (Lua_Result == 0) - { - uint8 luaStrLen = buff.read<uint8>(); - if (luaStrLen != 0) - { - std::string str; - str.resize(luaStrLen); - buff.read(reinterpret_cast<uint8*>(str.data()), luaStrLen); - TC_LOG_DEBUG("warden", "Lua string: %s", str.c_str()); - TC_LOG_DEBUG("warden", "RESULT LUA_STR_CHECK fail, CheckId %u account Id %u", id, _session->GetAccountId()); - checkFailed = id; - continue; - } - } - TC_LOG_DEBUG("warden", "RESULT LUA_STR_CHECK passed, CheckId %u account Id %u", id, _session->GetAccountId()); + TC_LOG_DEBUG("warden", "LUA_EVAL_CHECK CheckId %u account Id %u got in-warden dummy response (%u)", id, _session->GetAccountId(), result); break; } case MPQ_CHECK: diff --git a/src/server/game/Warden/enuminfo_WardenCheckMgr.cpp b/src/server/game/Warden/enuminfo_WardenCheckMgr.cpp index 8496c4ca8aa..7a0eadda436 100644 --- a/src/server/game/Warden/enuminfo_WardenCheckMgr.cpp +++ b/src/server/game/Warden/enuminfo_WardenCheckMgr.cpp @@ -115,10 +115,10 @@ TC_API_EXPORT EnumText EnumUtils<WardenCheckType>::ToString(WardenCheckType valu { switch (value) { - case LUA_STR_CHECK: return { "LUA_STR_CHECK", "LUA_STR_CHECK", "139: byte luaNameIndex (check to ensure LUA string isn't used)" }; case TIMING_CHECK: return { "TIMING_CHECK", "TIMING_CHECK", "nyi" }; case DRIVER_CHECK: return { "DRIVER_CHECK", "DRIVER_CHECK", "uint Seed + byte[20] SHA1 + byte driverNameIndex (check to ensure driver isn't loaded)" }; case PROC_CHECK: return { "PROC_CHECK", "PROC_CHECK", "nyi" }; + case LUA_EVAL_CHECK: return { "LUA_EVAL_CHECK", "LUA_EVAL_CHECK", "evaluate arbitrary Lua check" }; case MPQ_CHECK: return { "MPQ_CHECK", "MPQ_CHECK", "get hash of MPQ file (to check it is not modified)" }; case PAGE_CHECK_A: return { "PAGE_CHECK_A", "PAGE_CHECK_A", "scans all pages for specified SHA1 hash" }; case PAGE_CHECK_B: return { "PAGE_CHECK_B", "PAGE_CHECK_B", "scans only pages starts with MZ+PE headers for specified hash" }; @@ -136,10 +136,10 @@ TC_API_EXPORT WardenCheckType EnumUtils<WardenCheckType>::FromIndex(size_t index { switch (index) { - case 0: return LUA_STR_CHECK; - case 1: return TIMING_CHECK; - case 2: return DRIVER_CHECK; - case 3: return PROC_CHECK; + case 0: return TIMING_CHECK; + case 1: return DRIVER_CHECK; + case 2: return PROC_CHECK; + case 3: return LUA_EVAL_CHECK; case 4: return MPQ_CHECK; case 5: return PAGE_CHECK_A; case 6: return PAGE_CHECK_B; @@ -154,10 +154,10 @@ TC_API_EXPORT size_t EnumUtils<WardenCheckType>::ToIndex(WardenCheckType value) { switch (value) { - case LUA_STR_CHECK: return 0; - case TIMING_CHECK: return 1; - case DRIVER_CHECK: return 2; - case PROC_CHECK: return 3; + case TIMING_CHECK: return 0; + case DRIVER_CHECK: return 1; + case PROC_CHECK: return 2; + case LUA_EVAL_CHECK: return 3; case MPQ_CHECK: return 4; case PAGE_CHECK_A: return 5; case PAGE_CHECK_B: return 6; |