排查手游上报数据造假:从值域一路查到服务端重演

去年接了个放置项目的排查,离线收益对不上,玩家到手的比后台该发的多。上报日志翻下来签名、nonce 全过,值域也做了,还是漏。跟着一层层看他们校验漏在哪、又是怎么被绕的,最后落到得上服务端重演才按得住。把这趟的几个点记下来。

先说个前提。签名和 nonce 只证明包没在路上被改过、没被重发,跟里面那串数字是不是真打出来的是两码事。运营那边一开始也卡在这,想不通签名验得好好的怎么数据还是假的。根子在于服务端没在战斗现场,验不了真假,只能验合不合理。整趟排查就是逐层看:哪一层的「合不合理」没做、或者做了但被绕了。

值域:拦得住的比想象中少

他们最先做的是值域,拿服务端存的面板算理论上限,超了丢。上线当天就没拦住多少:对方不报 999999,报上限的 95% 贴着线走,阈值一次都不响。

问题在阈值从哪来。算上限用的伤害、暴击系数就在随包下发的数值表里,解个包就知道及格线在哪。等于把答案印在卷子背面。修法是把校验用的系数从下发表拆出来、服务端单独存。

还有个坑藏在接口返回里,翻日志才看出来。值域没过时接口老实返回了「数据异常」,其实等于给对方开了个测量工具——他拿不同的值试,看哪个返回异常哪个通过,二分几十次就把阈值试出来。修法很简单:没过照常返回成功、异步打标,别让返回值透出阈值信息。

自洽:数据全对得上,可就是假的

光查单字段不够,还得竖着看字段之间对不对得上:技能报了 8 次释放,可它 CD 8 秒、整局才打 30 秒,单看合法凑一起就露馅。

// 同一技能释放 n 次,至少要跨 (n-1) 个 CD
foreach (var g in req.SkillCasts.GroupBy(c => c.Id)) {
    long need = (g.Count() - 1) * SkillTable[g.Key].CooldownMs;
    if (need > req.DurationMs) Flag(uid, "cd_overflow");  // 只打标,照常返回成功
}

这层拦下一批,很快撞墙:有一批账号数据怎么查都自洽,产出就是不对。复现几次才明白,这类人根本没改包。内存改攻击力、变速齿轮拉帧,然后老老实实把这局打完、正常上报。数据当然全自洽,因为那场战斗在客户端确实发生过。假的不是数据,是那台客户端本身。查到这儿值域和自洽都到头了。

重演:他们上线第一天,误伤了一片

真正能验这类的只有重演,客户端别报结果、报输入。上传随机种子加操作序列,服务端拿同一份逻辑跑一遍比对结果。门槛不低,双端得算出一模一样的结果,为此他们把战斗核的浮点换成定点数、随机数换成自己带种子的实现。这套改动越晚做越伤,最好项目一开始就往这个方向留。

这次最值得说的坑不是安全问题,是他们自己挖的:重演一上线 replay_mismatch 直接刷屏,运营以为撞上外挂潮了。拉日志看绝大多数是两边逻辑没对齐——技能结算顺序差了一帧、某处随机调用次数对不上,真作弊的没几个。所以重演刚上线先只打标观察,别急着接封号或拦结算,等 mismatch 率降到个位数再谈处置,不然第一波误伤的全是正常玩家。

顺一句量级:重演在放置、卡牌这类回合制上跑得动,一局输入没几个字节,每局全量重演也不吃力;实时动作每帧都是输入,只能抽样。

收尾:别拿单条信号当封号开关

放置游戏尤其要提醒甲方留神误报。跨时区、断线补包、低端机 RTC 跳变都会让数据看着不合理,可人家是正常玩家。处置上给的建议是分级:先打标,再限制变现路径(交易、提现),信号凑够了才人工介入,没哪条单独拿来封号。

做到第几层不看技术,看这游戏的数据值多少钱:纯单机、数据只影响自己看的那个数,值域够了;能交易能提现的,重演那笔工程账再难看也得认。这个项目有离线收益变现,躲不掉重演这层。

(这几层的判断逻辑我在字节暗面单独整理过一版更完整的,可以搜「字节暗面」。)

posted @ 2026-07-27 16:14  字节暗面  阅读(8)  评论(0)    收藏  举报