最近帮一个项目查"领奖接口被刷",顺着把协议抓包、改包、重放捋了一遍,记录一下。
协议这块跟客户端代码不太一样。客户端代码再怎么藏,攻击者还得花力气逆;协议是明摆着要在网络上跑的,client 和 server 之间每个包都要从玩家手机上过一遍,而这台手机不归你管…
很多人第一反应是上 HTTPS 就完事了。HTTPS 当然要上,甚至明文 HTTP 在手游里还真不少,老项目、活动接口尤其爱偷懒,一个 Charles 挂上去接口参数返回全在眼前,连逆向都省了。
但 HTTPS 只能防止攻击者在传输链路上偷看,管不了玩家自己。玩家想看自己设备上的流量,太容易了。比如:挂个系统代理指到 Charles 或者 mitmproxy,把抓包工具的根证书塞进系统证书库,root 机直接放,没 root 的上 Magisk 模块——TLS 就被透明解密了,明文照看不误。

顺带提一个容易被忽略的版本坑:Android 7.0(API 24) 之后 App 默认不信任用户装的 CA,你得自己在 network_security_config.xml 里放行。所以要是 targetSdk 还压在 24 以下,等于自己把门开着,这种包我见过不止一个。
想防也简单,用证书校验(pinning):握手时只认写死的指纹,系统证书库里塞多少假证书都没用。Unity 给 UnityWebRequest 挂个 handler 就行:
protected override bool ValidateCertificate(byte[] certData) {
var cert = new X509Certificate2(certData);
var pin = Convert.ToBase64String(
SHA256.Create().ComputeHash(cert.GetPublicKey()));
return pin == PUB_KEY_PIN; // 对不上直接断握手
}
OkHttp 那边 CertificatePinner 一行就够,装完随手抓包基本废了。当然并不绝对,因为函数跑在 C# / Java 层,对会 Hook 的人来说不值一提。objection 一句 android sslpinning disable,或者甩个 frida-multiple-unpinning,专挑 checkServerTrusted、native 的 SSL_CTX_set_custom_verify 这些点,让它无脑返回通过。
套路和签名校验一模一样——攻击的从来不是校验逻辑,是那个能被劫持的校验函数。所以能下沉 native 就下沉,比对在 C++ 里做完,别把个 bool 端回托管层送人头;再叠个 Frida / 调试器检测,逮到先埋点上报,闪不闪退另算(误报成本不低)。

到这儿才算正题。pinning 绕了、明文也看着了,有经验的攻击者下一步往往懒得改包——直接重放。
这地方很多人转不过弯:TLS 防的是中间人,可玩家自己的客户端就是个合法的 TLS 端点。他在自己机器上把流量解出来,挑一个成功的请求原样再请求一百遍,TLS 帮不上任何忙,因为在协议眼里这就是合法 client 发的合法请求。领奖接口重放刷奖励、结算包重放多结算、支付回调重放多到账,都是这么来的。我查的那个项目,领奖接口在三天内被重放了 1.2 万次,损失惨重…

防重放就一句话:每个请求只能用一次。客户端带上时间戳和随机 nonce,对 path + body + ts + nonce 签名:
long ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
string nonce = Guid.NewGuid().ToString("N");
string sign = HmacSha256(secretKey, $"{path}{body}{ts}{nonce}");
服务端三道关:
- 时间戳超出 ±60s 窗口丢掉;
- nonce 在 Redis 里出现过丢掉(
SETNX,过期时间设成窗口大小,存储不会爆); - 签名对不上丢掉,nonce 一次性,原样重放第二次必被拦。
听着挺周全,但有个前提——secretKey 不能泄露,可它就躺在客户端里。攻击者把 SO 拖进 IDA,算法和密钥扒出来,自己就能造合法包:ts 取当前、nonce 每次换、签名照算,你这套校验全给放行,因为人家发的确实"合法"。绕一圈又回到加固——密钥和算法能撑多久,看 SO 被逆向的成本。
到这层,真正能加固的东西基本都挪到服务端了。密钥和算法这边能做的,无非是往native挪、再叠加固把逆向成本抬上去,但这只是拖时间。最实在的还是那句老话:客户端报上来的一概别信。领奖看后端记的次数,不看 client 说领没领;结算认后端算的战斗,不认客户端报的结果;充值以支付平台的服务端回调为准。再挂个风控盯高频调用和越界数值——客户端被绕的量一上来,异常总会在服务端数据里冒出来。
协议安全跟加固是一码事,都是拿成本换的,一层层垒上去,不是为了挡死,是让动手的人觉得不划算。客户端在玩家手里、不可信,这条改不了,所以最后拍板那道判断只能在服务端。