AzerothCore 反作弊与数据安全体系:从反外挂机制到完整性验证 原创
AzerothCore 反作弊与数据安全体系:从反外挂机制到完整性验证
面向有 AzerothCore 基础的开发者,深入剖析 WoW 模拟器的攻防博弈。
一、WoW 模拟器面临的常见作弊手段
在深入 AzerothCore 的防御机制之前,我们需要先理解”对手”是谁。WoW 私服生态中,作弊手段大致分为四类:
1.1 飞行与加速(Speed/Fly Hack)
客户端通过修改本地内存中的移动速度字段,或注入代码钩住 GetMoveSpeed() 函数,使角色移动速度超出服务端允许的上限。常见工具如 Cheat Engine 可以直接扫描并改写内存中的浮点速度值。
攻击表现:角色在短时间内跨越异常距离,或在不满足飞行条件时悬浮/飞行。
1.2 瞬移(Teleport Hack)
利用修改后的客户端或自定义工具,直接向服务端发送 MSG_MOVE_TELEPORT_ACK 或伪造 MOVEMENTFLAG 标志位,将角色坐标设为任意值。高级变种会”碎片化”瞬移——每帧移动一小段距离,避免触发简单的距离阈值检测。
1.3 发包伪造(Packet Forgery)
使用 WPE Pro 或自制代理拦截/篡改网络数据包。攻击者可以:
- 重放(Replay)合法的数据包以触发服务端逻辑
- 修改 opcode 参数(如将灰品质物品的 GUID 替换为紫装 GUID)
- 绕过客户端校验直接发送服务端不该收到的操作请求
1.4 内存修改(Memory Modification)
通过 DLL 注入修改客户端运行时数据——从简单的速度/坐标修改,到 hook FrameScript::Execute 执行任意 Lua,再到修改客户端校验逻辑让非法操作通过本地检查。
关键认知:服务端永远不能信任客户端。任何只依赖客户端校验的防线都是纸糊的。真正的防线必须在服务端。
二、AzerothCore 服务端反作弊机制
2.1 核心防线:WorldSession 包处理
AzerothCore 的所有客户端交互都经过 WorldSession 的 opcode 分发器。每个客户端发来的包,都会经过以下管线:
// 简化示意:WorldSession 包处理管线
bool WorldSession::Update(uint32 diff, PacketFilter& filter)
{
// 1. 从 recv 队列取出数据包
WorldPacket* packet = _recvQueue.Pop();
if (!packet)
return false;
// 2. 查找 opcode 处理器
auto handler = _opcodeTable.find(packet->GetOpcode());
if (handler == _opcodeTable.end()) {
sLog->outError("Received unknown opcode 0x%04X", packet->GetOpcode());
return false;
}
// 3. 检查会话状态(未登录时拒绝大多数操作)
if (handler->second.status >= STATUS_LOGGEDIN && !IsLoggedIn())
return false;
// 4. 调用具体 handler
(this->*(handler->second.handler))(*packet);
return true;
}
这个管线是第一道门——但仅仅是”门”,还谈不上”检测”。真正的反作弊逻辑发生在 handler 内部。
2.2 Anticheat Module 的工作原理
AzerothCore 社区维护着一个独立的 Anticheat Module(通常作为 server 模块加载)。其核心设计思路:
- Hook 移动处理链:在
Player::HandleMovement路径上插入校验逻辑 - 累积统计:不是一次异常就封号,而是累积”嫌疑分数”
- 多维度检测:速度、高度、距离、频率、地形,多角度交叉验证
- 延迟响应:检测到异常后,服务端可以强制回滚角色位置(rubberband),而不是直接踢线
模块的核心类结构:
// anticheat module 核心结构(简化)
class PlayerAnticheat
{
public:
PlayerAnticheat(Player* player) : _player(player) {}
// 每次收到移动包时调用
void HandleMovement(MovementInfo& moveInfo);
// 定期检查(受 World::Update 驱动)
void Update(uint32 diff);
// 速度检测
bool CheckSpeed(MovementInfo& moveInfo);
// 高度/飞行检测
bool CheckHeight(MovementInfo& moveInfo);
// 瞬移检测
bool CheckTeleport(MovementInfo& moveInfo);
// 地形碰撞检测
bool CheckCollision(MovementInfo& moveInfo);
private:
Player* _player;
float _lastSpeedRate = 1.0f;
uint32 _violationCount = 0;
uint32 _reportTimer = 0;
float _accumulatedDistance = 0.0f;
Position _lastValidPos;
time_t _lastViolationTime = 0;
};
关键设计:每个 Player 对象持有一个 PlayerAnticheat 实例,在移动包处理链中被调用。检测到异常后,调用 Player::TeleportToCacheClean 将角色回滚到上一个合法位置。
三、移动校验系统
3.1 MovementInfo 结构
WoW 客户端每个移动包都携带 MovementInfo,这是服务端判断移动合法性的核心数据:
// MovementInfo 核心字段(3.3.5a 协议)
struct MovementInfo
{
uint32 flags; // MOVEMENTFLAG_* 位掩码
uint16 flags2; // 额外标志(3.3.5a 新增)
uint32 time; // 客户端时间戳(ms)
Position pos; // 当前坐标 (x, y, z, orientation)
uint32 t_guid; // 传送目标 GUID(如果 flag 包含 TRANSPORT)
Position t_pos; // 传送上的坐标
uint8 t_seat; // 座位
uint32 t_time2; // 传送时间
uint32 t_time3; // 传送时间2
float pitch; // 俯仰角(飞行/游泳时)
float fallTime; // 下落时间
};
flags 是最关键的字段——它声明了角色的移动状态。常见的 flag 值包括:
| Flag | 值 | 含义 |
|---|---|---|
| MOVEFLAG_FORWARD | 0x00000001 | 向前移动 |
| MOVEFLAG_BACKWARD | 0x00000002 | 向后移动 |
| MOVEFLAG_STRAFE_LEFT | 0x00000004 | 左侧移动 |
| MOVEFLAG_STRAFE_RIGHT | 0x00000008 | 右侧移动 |
| MOVEFLAG_TURN_LEFT | 0x00000010 | 左转 |
| MOVEFLAG_TURN_RIGHT | 0x00000020 | 右转 |
| MOVEFLAG_FLYING | 0x00000200 | 飞行中 |
| MOVEFLAG_SWIMMING | 0x00200000 | 游泳中 |
| MOVEFLAG_ROOT | 0x00400000 | 定身 |
| MOVEFLAG_FALLING | 0x00000080 | 下落中 |
3.2 速度检测
速度检测的核心思路:客户端报告的位移速度,不能超过服务端计算的理论最大速度。
bool PlayerAnticheat::CheckSpeed(MovementInfo& moveInfo)
{
// 计算两次移动包之间的位移距离
float dx = moveInfo.pos.m_positionX - _lastValidPos.m_positionX;
float dy = moveInfo.pos.m_positionY - _lastValidPos.m_positionY;
float dz = moveInfo.pos.m_positionZ - _lastValidPos.m_positionZ;
float distance2D = std::sqrt(dx * dx + dy * dy);
// 客户端报告的时间差
uint32 timeDiff = moveInfo.time - _lastMoveTime;
if (timeDiff == 0)
timeDiff = 1;
// 实际速度(单位: 码/秒)
float actualSpeed = distance2D * 1000.0f / timeDiff;
// 服务端理论最大速度
// baseSpeed = 7.0 (跑步), 可被 aura/buff 提升
float maxSpeed = _player->GetSpeedRate(MOVE_RUN);
// 额外容忍 10% 误差(网络延迟/浮点精度)
maxSpeed *= 1.1f;
// 如果速度超标,记录违规
if (actualSpeed > maxSpeed && distance2D > 1.0f)
{
_violationCount++;
sLog->outChar("Anticheat: %s speed violation: %.2f > %.2f (dist=%.2f, tdiff=%u)",
_player->GetName().c_str(), actualSpeed, maxSpeed, distance2D, timeDiff);
return false; // 拒绝这次移动
}
_lastValidPos = moveInfo.pos;
_lastMoveTime = moveInfo.time;
return true;
}
为什么需要 10% 容忍? 因为网络延迟、客户端帧率和浮点精度误差会导致实际速度略高于理论值。容忍阈值太低会误杀正常玩家;太高则放行小幅加速。
3.3 高度与飞行检测
飞行检测的逻辑:如果 flags 不包含 MOVEFLAG_FLYING,但角色的 Z 坐标持续上升或保持在高空,则判定为非法飞行。
bool PlayerAnticheat::CheckHeight(MovementInfo& moveInfo)
{
// 如果客户端声明在飞行但角色没有飞行权限
if (moveInfo.flags & MOVEFLAG_FLYING)
{
if (!_player->HasAuraType(SPELL_AURA_FLY) &&
!_player->GetGMFly() &&
!_player->IsInFlight())
{
_violationCount++;
sLog->outChar("Anticheat: %s flying without aura - possible fly hack",
_player->GetName().c_str());
return false;
}
}
// 地面模式下的高度异常检测
float dz = moveInfo.pos.m_positionZ - _lastValidPos.m_positionZ;
float groundZ = _player->GetMap()->GetHeight(
_player->GetPhaseMask(),
moveInfo.pos.m_positionX,
moveInfo.pos.m_positionY,
moveInfo.pos.m_positionZ);
// 角色悬停在地面以上不合理的高度且未下落
if (!(moveInfo.flags & MOVEFLAG_FALLING) &&
!(moveInfo.flags & MOVEFLAG_FLYING) &&
!(moveInfo.flags & MOVEFLAG_SWIMMING))
{
float heightDiff = moveInfo.pos.m_positionZ - groundZ;
if (heightDiff > 3.0f && std::abs(dz) < 0.1f)
{
// 悬空检测:不在下落、不在飞行,但悬浮在空中
_violationCount++;
sLog->outChar("Anticheat: %s hovering at %.2f above ground",
_player->GetName().c_str(), heightDiff);
return false;
}
}
return true;
}
3.4 瞬移检测与位置回滚
瞬移检测是防范”碎片化瞬移”的关键。攻击者可能每次移动跳跃 10 码来规避简单的距离检查。
bool PlayerAnticheat::CheckTeleport(MovementInfo& moveInfo)
{
float distance2D = _player->GetExactDist2d(
moveInfo.pos.m_positionX,
moveInfo.pos.m_positionY);
// 正常移动的连续阈值
// 服务端每帧之间最大允许位移取决于速度和时间差
uint32 timeDiff = getMSTimeDiff(_lastMoveTime, moveInfo.time);
float maxAllowed = _player->GetSpeedRate(MOVE_RUN) * (timeDiff / 1000.0f) * 1.5f;
// 单次位移超过阈值
if (distance2D > maxAllowed && distance2D > 5.0f)
{
// 可能是正常的传送(法师传送门、飞行路径等)
if (_player->IsBeingTeleported() ||
_player->IsInFlight())
return true;
_violationCount++;
sLog->outChar("Anticheat: %s teleport detected: %.2f yards (max %.2f)",
_player->GetName().c_str(), distance2D, maxAllowed);
// 关键:回滚到上一个合法位置
_player->TeleportToCacheClean(
_player->GetMapId(),
_lastValidPos.m_positionX,
_lastValidPos.m_positionY,
_lastValidPos.m_positionZ,
_lastValidPos.m_orientation);
return false;
}
return true;
}
位置回滚(Rubberbanding) 是 AzerothCore 反作弊的核心策略——不让攻击者获得任何非法位移的收益。服务端在拒绝移动包后,主动发送 SMSG_MOVE_TELEPORT_ACK 将客户端角色拉回合法位置。
四、数据完整性验证
4.1 数据库一致性检查
作弊检测不只限于移动。数据库层面同样需要保护。以下是针对常见数据完整性问题的检查方案:
-- 检查1:物品来源追踪 - 查找可疑的高价值物品
-- 角色拥有的物品中,品质 >= 4(史诗/传说)但无合法来源记录的
SELECT
c.name AS character_name,
i.entry AS item_entry,
i.count AS stack_count,
c.account AS account_id
FROM character_inventory ci
JOIN item_instance i ON ci.item = i.guid
JOIN characters c ON ci.guid = c.guid
WHERE
-- 查询物品模板获取品质
i.entry IN (
SELECT entry FROM item_template WHERE Quality >= 4
)
AND ci.bag = 0 -- 仅检查装备栏和背包
AND c.level < 40 -- 低等级角色持有高价值物品
ORDER BY c.account, c.name;
-- 检查2:金币异常 - 查找金币来源不合理的角色
-- 计算每个角色从所有来源获得金币 vs 当前持有金币
SELECT
c.name,
c.money AS current_gold,
COALESCE(SUM(CASE WHEN cl.event_type = 3 THEN cl.amount ELSE 0 END), 0) AS loot_gold,
COALESCE(SUM(CASE WHEN cl.event_type = 6 THEN cl.amount ELSE 0 END), 0) AS vendor_gold,
COALESCE(SUM(CASE WHEN cl.event_type = 12 THEN cl.amount ELSE 0 END), 0) AS quest_gold,
ABS(c.money - COALESCE(SUM(cl.amount), 0)) AS unaccounted_gold
FROM characters c
LEFT JOIN character_log cl ON c.guid = cl.guid
WHERE c.money > 10000000 -- 超过1000金币(3.3.5a经济体系)
GROUP BY c.guid, c.name, c.money
HAVING unaccounted_gold > 100000 -- 100金币以上无法追溯
ORDER BY unaccounted_gold DESC;
-- 检查3:物品数量异常 - 单角色持有超过合理数量的同ID物品
SELECT
c.name AS character_name,
i.entry AS item_entry,
it.name AS item_name,
SUM(i.count) AS total_count,
it.stackable AS max_stack
FROM character_inventory ci
JOIN item_instance i ON ci.item = i.guid
JOIN item_template it ON i.entry = it.entry
JOIN characters c ON ci.guid = c.guid
GROUP BY c.name, i.entry, it.name, it.stackable
HAVING total_count > it.stackable * 20 -- 超过堆叠上限的20倍
ORDER BY total_count DESC;
4.2 物品来源追踪
AzerothCore 本身的日志系统有限。要实现完整的物品追踪链,我们需要扩展 Player::AddItem 的日志记录:
// 扩展物品日志:在 Player::AddItem 中注入追踪记录
// 对应 src/server/game/Entities/Player/Player.cpp
uint32 Player::AddItem(uint32 itemEntry, uint32 count, Item* pItem)
{
// ... 原有逻辑 ...
if (result != EQUIP_ERR_OK)
return result;
// 记录物品来源
// source: 0=loot, 1=vendor, 2=craft, 3=quest, 4=trade, 5=mail, 6=gm
CharacterDatabase.PExecute(
"INSERT INTO character_item_log "
"(guid, item_entry, count, source, timestamp, source_guid) "
"VALUES (%u, %u, %u, %u, %u, %u)",
GetGUIDLow(), itemEntry, count,
GetItemSource(), // 来源类型
uint32(time(nullptr)), // 时间戳
GetItemSourceGuid() // 来源 GUID(NPC/玩家)
);
return result;
}
-- 创建物品来源追踪表
CREATE TABLE IF NOT EXISTS character_item_log (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
guid INT UNSIGNED NOT NULL COMMENT '角色GUID',
item_entry INT UNSIGNED NOT NULL COMMENT '物品模板entry',
count INT UNSIGNED NOT NULL DEFAULT 1,
source TINYINT UNSIGNED NOT NULL COMMENT '0=loot 1=vendor 2=craft 3=quest 4=trade 5=mail 6=gm',
timestamp INT UNSIGNED NOT NULL,
source_guid INT UNSIGNED DEFAULT 0 COMMENT '来源GUID (NPC/玩家)',
INDEX idx_guid (guid),
INDEX idx_entry (item_entry),
INDEX idx_source (source),
INDEX idx_time (timestamp)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.3 金币流动审计
-- 金币流动审计:追踪玩家间金币转移
CREATE TABLE IF NOT EXISTS character_gold_audit (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
from_guid INT UNSIGNED NOT NULL COMMENT '转出角色',
to_guid INT UNSIGNED NOT NULL COMMENT '转入角色',
amount INT UNSIGNED NOT NULL COMMENT '金额(铜币)',
trade_type TINYINT UNSIGNED NOT NULL COMMENT '0=trade 1=mail 2=vendor_sell 3=gm_command',
timestamp INT UNSIGNED NOT NULL,
ip_from VARCHAR(64) DEFAULT NULL COMMENT '转出方IP',
ip_to VARCHAR(64) DEFAULT NULL COMMENT '转入方IP',
INDEX idx_from (from_guid),
INDEX idx_to (to_guid),
INDEX idx_amount (amount),
INDEX idx_time (timestamp)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 查找可疑的金币转移模式(RMT特征)
SELECT
from_guid, to_guid, amount, trade_type,
FROM_UNIXTIME(timestamp) AS transfer_time
FROM character_gold_audit
WHERE
amount > 1000000 -- 超过100金币
AND trade_type IN (0, 1) -- 交易或邮件
AND from_guid IN (
SELECT guid FROM characters
WHERE online = 0 AND deleteDate IS NOT NULL -- 已删除角色
)
ORDER BY timestamp DESC
LIMIT 100;
五、实战:配置和定制反作弊模块
5.1 模块安装与基础配置
社区版 Anticheat Module 通常放在 modules/ 目录下,通过 CMake 编译时自动链接:
# 拉取 anticheat 模块(社区维护版本)
cd /path/to/azerothcore/modules
git clone https://github.com/azerothcore/mod-anticheat.git
# 重新编译
cd /path/to/azerothcore
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
5.2 核心配置项
在 worldserver.conf 中添加模块配置:
########################################
# Anticheat Module Configuration
########################################
# 启用反作弊模块
Anticheat.Enable = 1
# 速度检测容忍系数(1.0 = 精确,1.1 = 允许10%误差)
Anticheat.Speed.Tolerance = 1.1
# 单次瞬移检测阈值(码)
Anticheat.Teleport.MaxDistance = 5.0
# 飞行检测启用
Anticheat.FlyCheck.Enable = 1
# 悬空检测高度阈值(码)
Anticheat.HoverCheck.MaxHeight = 3.0
# 违规次数阈值 - 达到此值后执行惩罚
Anticheat.Violation.Threshold = 5
# 惩罚动作:0=仅记录 1=回滚位置 2=踢线 3=封禁
Anticheat.Punish.Action = 1
# 封禁时长(小时,仅 Action=3 时生效)
Anticheat.Punish.BanDuration = 24
# GM 免疫(GM 等级 >= 此值不受检测)
Anticheat.GMImmunity = 3
# 实时告警到 GM 在线频道
Anticheat.NotifyGMs = 1
5.3 自定义检测规则
假设我们需要添加一个自定义检测:防止在战斗中使用炉石等传送物品(正常客户端会阻止,但发包伪造可以绕过)。
// 在 anticheat 模块中注册自定义 hook
// mod-anticheat/src/AnticheatHooks.cpp
#include "ScriptMgr.h"
#include "Player.h"
#include "SpellMgr.h"
#include "AnticheatMgr.h"
class AnticheatSpellCastingHook : public ModuleScript
{
public:
void OnBeforeSpellCast(Player* player, uint32 spellId,
Unit* /*target*/, Item* /*item*/) override
{
// 炉石类传送法术
if (spellId == 8690 || // Hearthstone
spellId == 556 || // Cold Weather Flying teleport
spellId == 39937) // Dimensional Ripper
{
if (player->IsInCombat())
{
// 战斗中使用传送法术 - 标记违规
if (auto* ac = player->GetAnticheat())
{
ac->AddViolation("combat_teleport",
"Attempted teleport spell in combat");
}
}
}
}
};
void AddSC_AnticheatHooks()
{
new AnticheatSpellCastingHook();
}
然后在模块的 ScriptLoader 中注册:
// mod-anticheat/src/Loader.cpp
void AddAnticheatScripts()
{
AddSC_AnticheatMgr();
AddSC_AnticheatHooks(); // 新增的自定义 hook
}
5.4 扩展违规处理链
默认的违规处理只是位置回滚。我们可以扩展为多级响应:
// 分级惩罚策略
void PlayerAnticheat::HandleViolation(const std::string& type, const std::string& detail)
{
_violationCount++;
_lastViolationTime = time(nullptr);
// 记录到数据库
CharacterDatabase.PExecute(
"INSERT INTO anticheat_violations "
"(guid, player_name, violation_type, detail, count, timestamp) "
"VALUES (%u, '%s', '%s', '%s', %u, %u) "
"ON DUPLICATE KEY UPDATE count = count + 1, timestamp = %u",
_player->GetGUIDLow(),
_player->GetName().c_str(),
type.c_str(),
detail.c_str(),
_violationCount,
uint32(time(nullptr)),
uint32(time(nullptr))
);
// 分级响应
switch (_violationCount)
{
case 1: // 第1次:静默回滚
RubberbandPlayer();
break;
case 3: // 第3次:警告
_player->GetSession()->SendNotification(
"检测到异常移动,位置已回滚。");
RubberbandPlayer();
break;
case 5: // 第5次:踢线
sLog->outChar("Anticheat: Kicking %s for repeated violations",
_player->GetName().c_str());
_player->GetSession()->KickPlayer();
break;
case 10: // 第10次:封禁
sLog->outChar("Anticheat: Banning %s for excessive cheating",
_player->GetName().c_str());
// 调用封禁 API
sWorld->BanCharacter(
_player->GetName(),
"24h",
"Anticheat: excessive movement violations"
);
break;
}
// 通知在线 GM
if (sWorld->getBoolConfig(CONFIG_ANTICHEAT_NOTIFY_GMS))
{
sWorld->SendGMText(
"Anticheat: %s 触发 %s (第%d次)", _player->GetName().c_str(),
type.c_str(), _violationCount);
}
}
六、服务端日志与告警体系搭建
6.1 违规日志表设计
-- anticheat 违规记录表
CREATE TABLE IF NOT EXISTS anticheat_violations (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
guid INT UNSIGNED NOT NULL,
player_name VARCHAR(32) NOT NULL,
violation_type VARCHAR(64) NOT NULL COMMENT 'speed/teleport/fly/packet/etc',
detail TEXT DEFAULT NULL,
count INT UNSIGNED DEFAULT 1,
timestamp INT UNSIGNED NOT NULL,
x FLOAT DEFAULT NULL COMMENT '违规时坐标',
y FLOAT DEFAULT NULL,
z FLOAT DEFAULT NULL,
map_id INT UNSIGNED DEFAULT NULL,
ip_address VARCHAR(64) DEFAULT NULL,
INDEX idx_guid (guid),
INDEX idx_type (violation_type),
INDEX idx_time (timestamp),
INDEX idx_ip (ip_address)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
6.2 日志查询视图
-- 视图:违规汇总看板
CREATE OR REPLACE VIEW v_anticheat_summary AS
SELECT
player_name,
ip_address,
COUNT(*) AS total_violations,
SUM(CASE WHEN violation_type = 'speed' THEN 1 ELSE 0 END) AS speed_violations,
SUM(CASE WHEN violation_type = 'teleport' THEN 1 ELSE 0 END) AS teleport_violations,
SUM(CASE WHEN violation_type = 'fly' THEN 1 ELSE 0 END) AS fly_violations,
MAX(timestamp) AS last_violation_time,
FROM_UNIXTIME(MAX(timestamp)) AS last_violation_readable
FROM anticheat_violations
GROUP BY guid, player_name, ip_address
ORDER BY total_violations DESC;
-- 查询:过去24小时内的活跃作弊者
SELECT * FROM v_anticheat_summary
WHERE last_violation_time > UNIX_TIMESTAMP() - 86400
ORDER BY total_violations DESC;
6.3 Discord/Telegram 告警集成
对于运营中的服务器,实时告警至关重要。通过 Lua 脚本或 C++ 模块将告警推送到即时通讯渠道:
// 告警推送:通过 HTTP 请求发送到 Discord Webhook
void AnticheatMgr::SendDiscordAlert(Player* player, const std::string& violationType,
const std::string& detail)
{
// 构建 JSON payload
std::ostringstream payload;
payload << "{"
<< " \"embeds\": [{"
<< " \"title\": \"Anticheat Alert\","
<< " \"color\": 16711680,"
<< " \"fields\": ["
<< " {\"name\": \"Player\", \"value\": \"" << player->GetName() << "\", \"inline\": true},"
<< " {\"name\": \"Type\", \"value\": \"" << violationType << "\", \"inline\": true},"
<< " {\"name\": \"Detail\", \"value\": \"" << detail << "\", \"inline\": false},"
<< " {\"name\": \"Map\", \"value\": \"" << player->GetMapId() << "\", \"inline\": true},"
<< " {\"name\": \"IP\", \"value\": \"" << player->GetSession()->GetRemoteAddress() << "\", \"inline\": true}"
<< " ],"
<< " \"timestamp\": \"" << GetISO8601Timestamp() << "\""
<< " }]"
<< "}";
// 异步发送 HTTP POST(使用 boost::asio 或 std::thread)
std::thread([payloadStr = payload.str()]() {
CurlPost(
"https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_TOKEN",
payloadStr,
"Content-Type: application/json"
);
}).detach();
}
6.4 日志轮转与归档
#!/bin/bash
# /etc/logrotate.d/azerothcore-anticheat
# anticheat 日志轮转配置
/var/log/azerothcore/anticheat.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 0644 azerothcore azerothcore
postrotate
# 发送 SIGHUP 让 worldserver 重新打开日志文件
kill -HUP $(cat /run/azerothcore-worldserver.pid 2>/dev/null) 2>/dev/null || true
endscript
}
对于数据库层面的数据归档,可以设置定期任务将超过 90 天的违规记录移到归档表:
-- 定时归档任务(配合 MySQL Event Scheduler)
DELIMITER //
CREATE EVENT IF NOT EXISTS archive_anticheat_violations
ON SCHEDULE EVERY 1 DAY
STARTS CURRENT_TIMESTAMP
DO
BEGIN
-- 移动90天前的记录到归档表
INSERT INTO anticheat_violations_archive
SELECT * FROM anticheat_violations
WHERE timestamp < UNIX_TIMESTAMP() - (90 * 86400);
-- 删除已归档的记录
DELETE FROM anticheat_violations
WHERE timestamp < UNIX_TIMESTAMP() - (90 * 86400);
-- 优化表空间
OPTIMIZE TABLE anticheat_violations;
END //
DELIMITER ;
七、最佳实践与安全加固清单
7.1 服务端加固要点
除了反作弊模块本身,整体安全加固同样重要:
// 1. 限制每秒包频率(防发包洪水)
// 在 WorldSession 中增加包频率检测
bool WorldSession::HandlePacketRateCheck(WorldPacket const* packet)
{
static constexpr uint32 MAX_PACKETS_PER_SEC = 100;
static constexpr uint32 WARN_PACKETS_PER_SEC = 50;
uint32 now = getMSTime();
uint32 windowStart = now - 1000; // 1秒窗口
// 清理过期记录
while (!_packetTimes.empty() && _packetTimes.front() < windowStart)
_packetTimes.pop();
_packetTimes.push(now);
uint32 count = _packetTimes.size();
if (count > MAX_PACKETS_PER_SEC)
{
// 超过频率上限,踢线
sLog->outChar("Packet flood from %s: %u packets/sec",
GetRemoteAddress().c_str(), count);
KickPlayer();
return false;
}
else if (count > WARN_PACKETS_PER_SEC)
{
// 警告但不踢线(可能是正常的高频操作)
sLog->outDebug("High packet rate from %s: %u/sec",
GetRemoteAddress().c_str(), count);
}
return true;
}
// 2. 限制同一账号的并发会话
bool AccountMgr::CheckConcurrentSessions(uint32 accountId, uint32 maxAllowed)
{
uint32 activeSessions = 0;
SessionMap::const_iterator itr;
for (itr = sWorld->GetAllSessions().begin();
itr != sWorld->GetAllSessions().end();
++itr)
{
if (itr->second->GetAccountId() == accountId)
activeSessions++;
}
return activeSessions < maxAllowed;
}
7.2 网络层加固
# 防火墙规则:限制游戏端口连接频率(防 DDoS)
iptables -A INPUT -p tcp --dport 8085 -m connlimit \
--connlimit-above 3 --connlimit-mask 32 -j DROP
# 限制每 IP 的连接数
iptables -A INPUT -p tcp --dport 8085 -m conntrack \
--ctstate NEW -m recent --set
iptables -A INPUT -p tcp --dport 8085 -m conntrack \
--ctstate NEW -m recent --update --seconds 60 --hitcount 10 -j DROP
# 白名单 GM IP
iptables -A INPUT -p tcp --dport 8085 -s 192.168.1.0/24 -j ACCEPT
7.3 定期安全审计
-- 检查管理员账号是否有异常登录
SELECT
a.id AS account_id,
a.username,
r.rl.abank AS gm_rank,
COUNT(*) AS login_count,
MAX(FROM_UNIXTIME(ll.loginTime)) AS last_login,
GROUP_CONCAT(DISTINCT ll.ip) AS login_ips
FROM account a
JOIN account_access r ON a.id = r.accountId
LEFT JOIN account_logintime ll ON a.id = ll.accountId
WHERE r.rl.abank >= 2 -- GM等级 >= 2
GROUP BY a.id, a.username, r.rl.abank
ORDER BY login_count DESC;
-- 检查非工作时间段的服务端操作日志
SELECT * FROM command_log
WHERE
command LIKE '%add item%'
OR command LIKE '%set money%'
OR command LIKE '%ban%'
AND HOUR(FROM_UNIXTIME(timestamp)) NOT BETWEEN 9 AND 22
ORDER BY timestamp DESC;
安全加固清单
| 分类 | 项目 | 风险等级 | 建议措施 |
|---|---|---|---|
| 移动检测 | 速度检测(Speed Hack) | 🔴 高 | 启用 Anticheat.SpeedCheck,容忍系数 ≤ 1.15,超阈值回滚位置 |
| 瞬移检测(Teleport Hack) | 🔴 高 | 设置 MaxDistance 阈值,实现碎片化瞬移检测(连续小跳跃累积) | |
| 飞行检测(Fly Hack) | 🔴 高 | 校验 MOVEFLAG_FLYING 与角色 aura 状态,检测无 aura 悬空 | |
| 包检测 | 包频率限制 | 🟡 中 | 单 IP 每秒包数上限 ≤ 100,超过踢线并记录 |
| Opcode 白名单 | 🟡 中 | 未登录会话拒绝除 AUTH 之外的所有 opcode | |
| 包重放检测 | 🟡 中 | 记录最近 N 个包的序列号,拒绝重复或倒序的包 | |
| 数据完整性 | 物品来源追踪 | 🔴 高 | 扩展 Player::AddItem 日志,记录物品来源类型和来源 GUID |
| 金币流动审计 | 🔴 高 | 记录玩家间金币转移,设置单笔转移上限告警阈值 | |
| 数据库一致性检查 | 🟡 中 | 每日 cron 执行异常物品/金币查询,结果推送给管理员 | |
| 定期数据快照 | 🟢 低 | 每周全量备份 character DB,保留 4 周滚动快照 | |
| 权限管理 | GM 权限最小化 | 🔴 高 | GM 等级分级,日常管理用 rank 2,仅运维用 rank 3 |
| GM 命令审计 | 🟡 中 | 记录所有 GM 命令到 command_log 表,每周审查 | |
| 管理后台 IP 白名单 | 🟡 中 | SOAP/RA 端口仅允许管理网段访问,防火墙 iptables 限制 | |
| 网络防护 | DDoS 防护 | 🔴 高 | connlimit 限制每 IP 并发连接数,考虑前置 CDN/反向代理 |
| 游戏端口隔离 | 🟡 中 | 世界服务器、SOAP、数据库端口分离,最小暴露面 | |
| SSL/TLS 加密 | 🟢 低 | SOAP 管理接口启用 HTTPS,客户端连接可选加密(AuthCrypt 已有) | |
| 告警体系 | 实时 Discord/Telegram 告警 | 🟡 中 | 高危违规(speed/teleport/fly)触发 Webhook 推送 |
| 每日安全报告 | 🟢 低 | Cron 任务汇总每日违规统计、异常物品/金币变动,邮件发送 |
结语
反作弊不是一劳永逸的工程,而是一场持续的攻防博弈。攻击者会不断研究服务端检测逻辑,寻找新的绕过方式。有效的防御策略是:
- 分层防御:不依赖单一检测点,从网络层 → 包处理 → 移动校验 → 数据审计,多层级交叉验证
- 持续迭代:关注社区安全公告,及时更新检测规则,对新型作弊手段快速响应
- 数据驱动:用日志和统计发现问题,而不是靠 GM 人工巡查
- 最小信任:服务端不信任任何客户端数据,所有关键操作服务端二次验证
AzerothCore 的模块化架构给了我们充分的扩展空间——从 anticheat 模块的移动检测,到自定义的物品追踪和金币审计,每一层都可以独立加固。记住:最好的防线不是一道墙,而是让攻击者的每一次尝试都付出高于收益的代价。
本文基于 AzerothCore 3.3.5a (WotLK) 分支的架构分析。不同版本的核心实现可能有所差异,请以实际代码为准。