diff --git a/conf/Anticheat.conf.dist b/conf/Anticheat.conf.dist index 745ee72..fe35e07 100644 --- a/conf/Anticheat.conf.dist +++ b/conf/Anticheat.conf.dist @@ -85,6 +85,7 @@ Anticheat.DetectGravityHack = 1 Anticheat.AntiKnockBack = 1 Anticheat.NoFallDamage = 1 Anticheat.DetectBGStartHack = 1 +Anticheat.OpAckOrderHack = 1 # Anticheat.StricterFlyHackCheck # Description: Checks moveflag ascending (may give false positives) diff --git a/src/AnticheatMgr.cpp b/src/AnticheatMgr.cpp index 1dc50fd..6ffa299 100644 --- a/src/AnticheatMgr.cpp +++ b/src/AnticheatMgr.cpp @@ -35,6 +35,9 @@ constexpr auto LANG_ANTICHEAT_IGNORECONTROL = 30089; constexpr auto LANG_ANTICHEAT_DUEL = 30090; constexpr auto LANG_ANTICHEAT_BG_EXPLOIT = 30091; +// Time between server sends acknowledgement, and client is actually acknowledged +constexpr auto ALLOWED_ACK_LAG = 2000; + enum Spells { SHACKLES = 38505, @@ -46,6 +49,29 @@ enum Spells AnticheatMgr::AnticheatMgr() { + _opackorders = + { + { SMSG_FORCE_WALK_SPEED_CHANGE, CMSG_FORCE_WALK_SPEED_CHANGE_ACK }, + { SMSG_FORCE_RUN_SPEED_CHANGE, CMSG_FORCE_RUN_SPEED_CHANGE_ACK }, + { SMSG_FORCE_RUN_BACK_SPEED_CHANGE, CMSG_FORCE_RUN_BACK_SPEED_CHANGE_ACK }, + { SMSG_FORCE_SWIM_SPEED_CHANGE, CMSG_FORCE_SWIM_SPEED_CHANGE_ACK }, + { SMSG_FORCE_SWIM_BACK_SPEED_CHANGE, CMSG_FORCE_SWIM_BACK_SPEED_CHANGE_ACK }, + { SMSG_FORCE_TURN_RATE_CHANGE, CMSG_FORCE_TURN_RATE_CHANGE_ACK }, + { SMSG_FORCE_PITCH_RATE_CHANGE, CMSG_FORCE_PITCH_RATE_CHANGE_ACK }, + { SMSG_FORCE_FLIGHT_SPEED_CHANGE, CMSG_FORCE_FLIGHT_SPEED_CHANGE_ACK }, + { SMSG_FORCE_FLIGHT_BACK_SPEED_CHANGE, CMSG_FORCE_FLIGHT_BACK_SPEED_CHANGE_ACK }, + { SMSG_FORCE_MOVE_ROOT, CMSG_FORCE_MOVE_ROOT_ACK }, + { SMSG_FORCE_MOVE_UNROOT, CMSG_FORCE_MOVE_UNROOT_ACK }, + { SMSG_MOVE_KNOCK_BACK, CMSG_MOVE_KNOCK_BACK_ACK }, + { SMSG_MOVE_FEATHER_FALL, SMSG_MOVE_NORMAL_FALL, CMSG_MOVE_FEATHER_FALL_ACK }, + { SMSG_MOVE_SET_HOVER, SMSG_MOVE_UNSET_HOVER, CMSG_MOVE_HOVER_ACK }, + { SMSG_MOVE_SET_CAN_FLY, SMSG_MOVE_UNSET_CAN_FLY, CMSG_MOVE_SET_CAN_FLY_ACK }, + { SMSG_MOVE_WATER_WALK, SMSG_MOVE_LAND_WALK, CMSG_MOVE_WATER_WALK_ACK }, + { SMSG_MOVE_SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY, SMSG_MOVE_UNSET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY, CMSG_MOVE_SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY_ACK }, + { SMSG_MOVE_GRAVITY_ENABLE, CMSG_MOVE_GRAVITY_ENABLE_ACK }, + { SMSG_MOVE_GRAVITY_DISABLE, CMSG_MOVE_GRAVITY_DISABLE_ACK }, + { SMSG_MOVE_SET_COLLISION_HGT, CMSG_MOVE_SET_COLLISION_HGT_ACK } + }; } AnticheatMgr::~AnticheatMgr() @@ -384,7 +410,7 @@ void AnticheatMgr::TeleportHackDetection(Player* player, MovementInfo movementIn if (player->IsFalling() || (player->IsFalling() && player->IsMounted())) return; - + if (player->duel) { if ((xDiff >= 50.0f || yDiff >= 50.0f || (zDiff >= 10.0f && !player->IsFlying())) && !player->CanTeleport()) @@ -1226,6 +1252,60 @@ void AnticheatMgr::HandlePlayerLogout(Player* player) m_Players.erase(player->GetGUID()); } +void AnticheatMgr::AckUpdate(Player* player, uint32 diff) +{ + if (_updateCheckTimer <= diff) + { + DoActions(player); + _updateCheckTimer = 4000; + } + else + { + _updateCheckTimer -= diff; + } +} + +void AnticheatMgr::DoActions(Player* player) +{ + auto const now = getMSTime(); + + for (auto& order : _opackorders) + { + if (order.counter > 0 && order.lastRcvd < order.lastSent && (now - order.lastSent) > ALLOWED_ACK_LAG) + { + uint32 latency = 0; + latency = player->GetSession()->GetLatency(); + LOG_INFO("anticheat.module", "Opcode Manipulation Hack detected player {} ({}) - Latency: {} ms", player->GetName(), player->GetGUID().ToString(), latency); + order.counter = 0; + } + } +} + +void AnticheatMgr::OrderSent(WorldPacket const* data) +{ + for (auto& order : _opackorders) + { + if (order.serverOpcode1 == data->GetOpcode() || order.serverOpcode2 == data->GetOpcode()) + { + order.lastSent = getMSTime(); + ++order.counter; + break; + } + } +} + +void AnticheatMgr::CheckForOrderAck(uint32 opcode) +{ + for (auto& order : _opackorders) + { + if (order.clientResp == opcode) + { + --order.counter; + break; + } + } +} + void AnticheatMgr::SavePlayerData(Player* player) { AnticheatData playerData = m_Players[player->GetGUID()]; diff --git a/src/AnticheatMgr.h b/src/AnticheatMgr.h index 910b469..4e1b28c 100644 --- a/src/AnticheatMgr.h +++ b/src/AnticheatMgr.h @@ -57,6 +57,21 @@ enum ReportTypes // GUID is the key. typedef std::map AnticheatPlayersDataMap; +class ServerOrderData +{ +public: + ServerOrderData(uint32 serv, uint32 resp) : serverOpcode1(serv), serverOpcode2(0), clientResp(resp), lastSent(0), lastRcvd(0), counter(0) {} + ServerOrderData(uint32 serv1, uint32 serv2, uint32 resp) : serverOpcode1(serv1), serverOpcode2(serv2), clientResp(resp), lastSent(0), lastRcvd(0), counter(0) {} + + uint32 serverOpcode1; + uint32 serverOpcode2; + uint32 clientResp; + + uint32 lastSent; + uint32 lastRcvd; + int32 counter; +}; + class AnticheatMgr { AnticheatMgr(); @@ -76,6 +91,13 @@ class AnticheatMgr void SavePlayerDataDaily(Player* player); void HandlePlayerLogin(Player* player); void HandlePlayerLogout(Player* player); + void AckUpdate(Player* player, uint32 diff); + void DoActions(Player* player); + + // orders + void OrderSent(WorldPacket const* data); + void CheckForOrderAck(uint32 opcode); + std::vector _opackorders; // Packets sent by server, triggering *_ACK from client uint32 GetTotalReports(ObjectGuid guid); float GetAverage(ObjectGuid guid); @@ -105,6 +127,7 @@ class AnticheatMgr bool MustCheckTempReports(uint8 type); uint32 _counter = 0; uint32 _alertFrequency = 0; + uint32 _updateCheckTimer = 4000; AnticheatPlayersDataMap m_Players; ///< Player data }; diff --git a/src/AnticheatScripts.cpp b/src/AnticheatScripts.cpp index f4b21d1..5a19803 100644 --- a/src/AnticheatScripts.cpp +++ b/src/AnticheatScripts.cpp @@ -51,6 +51,12 @@ public: if (sConfigMgr->GetOption("Anticheat.LoginMessage", true)) ChatHandler(player->GetSession()).PSendSysMessage("This server is running an Anticheat Module."); } + + void OnUpdate(Player* player, uint32 diff) override + { + if (sConfigMgr->GetOption("Anticheat.OpAckOrderHack", true)) + sAnticheatMgr->AckUpdate(player, diff); + } }; class AnticheatWorldScript : public WorldScript