如何设计防作弊系统
防作弊是游戏后端最有深度的话题之一,涉及服务器设计、统计检测、对抗博弈等多个维度。对 FC Mobile 这类有排名和经济系统的游戏来说,作弊问题直接影响玩家留存,面试很可能深入考察这块。
先把作弊类型分清楚
不同类型的作弊,防御思路完全不同:
客户端篡改:修改内存数值(金币、体力)、加速器(修改游戏时钟)、外挂注入(自动操作)、Root/越狱后绕过保护。
网络层攻击:抓包篡改请求、重放攻击(把一次购买请求发多次)、中间人修改响应数据。
逻辑层滥用:利用游戏规则漏洞刷资源、账号共享代打、两个账号互刷段位(Boosting)、故意输球操控排名(Tank)。
社会工程:盗号、黑产账号批量注册薅资源。
这几类的防御重心完全不同,先分清楚才能设计有针对性的系统。
第一道防线:服务器权威设计
这是最根本的,也是选择状态同步而非帧同步的重要原因之一。
原则:客户端永远不可信,所有关键逻辑在服务器执行。
// 错误做法:信任客户端发来的结果
void handle_match_result(Request& req) {
int score = req.get_int("score"); // 客户端说我进了 50 球
give_reward(player, score); // 直接奖励——可以被任意篡改
}
// 正确做法:服务器自己算
void handle_match_result(Request& req) {
auto& session = get_session(req.session_id);
int score = session.server_computed_score; // 服务器全程记录的分数
give_reward(player, score);
}
具体落地:关键数值(金币、积分、体力)只在服务器存储和计算,客户端只做展示。玩家进球这件事,服务器要自己根据球的物理轨迹和守门员位置来判定,而不是听客户端汇报。
第二道防线:网络包完整性
防重放攻击
每个请求带上时间戳和随机 nonce,服务器验证:
struct Request {
int64_t timestamp; // 请求时间
string nonce; // 随机串,服务器记录已用过的
string payload;
string hmac; // HMAC-SHA256(timestamp + nonce + payload, secret_key)
};
bool validate_request(const Request& req) {
// 1. 时间窗口校验:超过 30s 的请求直接拒绝
if (abs(now() - req.timestamp) > 30s) return false;
// 2. nonce 查重:防止同一个包发两次
if (nonce_cache.exists(req.nonce)) return false;
nonce_cache.insert(req.nonce, TTL_60s);
// 3. HMAC 校验:防止包体被篡改
return verify_hmac(req, server_secret_key);
}
SSL Pinning
移动端把服务器证书的公钥硬编码进客户端,防止抓包工具做中间人解密。被 Root 的设备可以绕过,但提高了攻击门槛。
第三道防线:输入合法性校验
这一层针对”操作速度超出人类极限”这类作弊:
void validate_player_action(Player& player, Action& action) {
auto now = current_tick();
// 射门频率校验:人类不可能每秒射门 10 次
if (now - player.last_shoot_tick < MIN_SHOOT_INTERVAL) {
flag_suspicious(player, "shoot_too_fast");
return;
}
// 移动距离校验:这帧的位移超出物理可能
float max_possible_dist = player.speed * FIXED_DT * 1.1f; // 10% 容差
if (distance(action.new_pos, player.pos) > max_possible_dist) {
flag_suspicious(player, "speed_hack");
return;
}
// 时序校验:动作发生在未来(客户端时钟被调快)
if (action.client_timestamp > now + MAX_CLOCK_DRIFT) {
flag_suspicious(player, "time_manipulation");
return;
}
}
注意这里用的是”flag 可疑”而不是”立刻封禁”——误判的代价太高,单次异常不足以定罪。
第四道防线:统计异常检测
这是对抗”规则合法但行为异常”的手段,是真正考验系统设计能力的地方。
实时风控打分引擎
把玩家的各种行为指标实时聚合,输出一个风险分:
胜率异常维度: 近 100 场胜率 > 95%?(正常玩家顶尖也难超 75%) 连胜场次超过 50 场无败绩? 效率异常维度: 场均进球数超过历史 3σ 之外? 操作 APM(每分钟动作数)持续超出人类上限? 时间维度: 连续游戏 20 小时无中断?(脚本特征) 每局耗时极度稳定(正负 5 秒以内)?(机器人特征) 社交图谱维度: 两账号频繁互相配对并且总是一赢一输?(Boosting) 新账号资源快速转移给某账号?(洗号)
异步分析 Pipeline
不能把所有检测都放在请求链路上,重型分析要异步:
玩家操作 → 写 Kafka → 实时检测(轻量规则,<5ms)
→ 流式聚合(Flink,计算滑动窗口统计)
→ 离线分析(每日跑 ML 模型,输出可疑账号列表)
→ 人工审核队列(高风险账号)
机器学习模型
大厂的防作弊最终都会引入 ML,把”哪些特征组合预示作弊”的问题交给模型去学。训练数据来自人工标注的确认作弊案例。特征工程是关键:行为序列的时间模式、玩家特征向量在高维空间中的离群程度等。
第五道防线:客户端环境检测
移动端特有,对抗 Root/越狱和模拟器:
// 伪代码:SDK 层做的检测
bool is_environment_trusted() {
// Root / 越狱检测
if (file_exists("/data/local/tmp/frida-server")) return false;
if (can_write("/system/app")) return false;
// 模拟器检测(PC 挂模拟器可能有外挂优势)
if (build_fingerprint.contains("generic")) return false;
if (sensor_data.is_perfect_zero()) return false; // 真机陀螺仪不会完全为零
// 内存完整性检测(检测关键函数是否被 hook)
if (function_checksum(critical_func) != EXPECTED) return false;
return true;
}
这层检测的结果不要在客户端直接告诉玩家(作弊者会针对性绕过),而是上报给服务器,作为风险评分的一个维度。
封禁策略:不要让作弊者知道他被检测到了
这是很多系统设计忽视的博弈思维。
延迟封禁:检测到作弊后不立刻封号,而是积累一到两周的证据,在某个批次统一处理。作弊者不知道哪个行为触发了检测,无法针对性调整。
影子封禁(Shadow Ban):账号表面上正常运行,但只会和其他作弊账号匹配,或者某些奖励被悄悄置为 0。作弊者以为自己没被发现,继续暴露更多作弊行为,同时对正常玩家没有影响。
分级处理:
风险分 60-80:静默观察,加大采样率 风险分 80-95:限制部分功能(禁止排位、禁止交易) 风险分 95+ :进人工审核队列 人工确认 :永久封禁,关联账号一并处理
申诉机制:误判永远存在,尤其是 ML 模型。必须有人工申诉通道,防止误封正常玩家造成公关危机。
经济系统的专项保护
FC Mobile 有抽卡、金币、交易等经济系统,这是黑产的重点攻击目标:
幂等性设计:每个购买/奖励请求带唯一 order_id,服务器保证同一 order_id 只发放一次奖励,从根本上防止重放攻击。
异步到账:高价值奖励不立刻到账,进入一个 pending 状态,几分钟内通过风控校验后再实际发放,留出撤销窗口。
资源流向追踪:记录每一笔金币的来源和去向,方便溯源。黑产洗资源的链路在日志里一定会留下痕迹。
面试里怎么用这些知识
EA 问这道题,考察的不是你背了多少技术词汇,而是你有没有系统性的防御思维:客户端不可信所以要服务器权威;单点规则容易被绕过所以要多层防御;实时封禁会暴露检测逻辑所以要延迟处理;误判代价大所以要有人工审核兜底。
如果你在之前项目里做过相关工作,哪怕只是某一个层面(比如设计过幂等订单系统,或者做过异常行为监控),都可以展开说,结合具体的技术决策会更有说服力。
