Android 与 iOS 客户端加固¶
先分清签名、证明和检测¶
移动端有沙箱、代码签名和应用商店审核。普通玩家不能像 Windows 上那样直接打开游戏进程,但 Root、越狱、重签名、Frida、改包和虚拟多开仍然存在。
三类手段解决的问题不同:
- 安装包签名:判断当前 APK 或 IPA 是否由预期证书签发。
- 平台证明:把设备、应用和请求交给 Play Integrity 或 App Attest 做服务端验证。
- 环境检测:观察 Root、越狱、调试器和注入痕迹,作为风险信号。
签名通过不代表进程没有被 Hook,App Attest 通过也不能证明游戏内存没有被读。经济、战斗、排行榜和交易结果仍然要由服务器校验。
Android¶
校验签名证书¶
包名可以重复,applicationId 不能作为安装包身份。Android 9 及以上使用 GET_SIGNING_CERTIFICATES,同时处理多签名和密钥轮换。1
val info = packageManager.getPackageInfo(
packageName,
PackageManager.GET_SIGNING_CERTIFICATES
)
val signingInfo = info.signingInfo ?: return false
val certificates = if (signingInfo.hasMultipleSigners()) {
signingInfo.apkContentsSigners
} else {
signingInfo.signingCertificateHistory
}
val expected = setOf(
"AA:BB:CC:..." // 服务端维护的 SHA-256 指纹
)
return certificates.any { certificate ->
val digest = MessageDigest
.getInstance("SHA-256")
.digest(certificate.toByteArray())
val fingerprint = digest.joinToString(":") {
"%02X".format(it)
}
fingerprint in expected
}
这里有几个容易漏的点:
- Android 9 以下要走
GET_SIGNATURES兼容路径。 - 多签名者要检查
apkContentsSigners,不能只取第一个。 - 使用 Play App Signing 时,本地上传证书和商店分发证书不是同一份。
- 启用密钥轮换后,新旧证书可能同时出现在历史里。
- 指纹写死在客户端只能挡住直接改包,判断分支仍然可以被改。
更稳的做法是让服务器维护允许的证书指纹,客户端上报 APK 的实际签名和安装来源,由服务器决定是否接受。离线场景可以内置指纹,但要有明确的更新和过期策略。
Play Integrity¶
Play Integrity 返回的是平台判定的完整性结果,不是客户端自己算出的一个布尔值。接入流程要让服务器参与:
- 服务器生成一次性
nonce或请求哈希。 - 客户端向 Play Integrity 请求 verdict。
- 客户端把完整 token 发给自己的服务器。
- 服务器调用 Google 接口验证,检查包名、证书摘要、请求哈希和时间。
- 服务器根据结果决定是否放行本次操作。
不要把 token 解析后由客户端告诉服务器“通过了”。也不要把 Play Integrity 当成唯一登录条件:没有 Google Play 服务的设备、模拟器、云手机、网络超时和审核中的版本都会影响结果。
更合理的用法是分级处理:
- 正常:允许登录和普通玩法。
- 不可判定:允许进入,但限制交易或排行榜。
- 明确不一致:要求重新登录或进入人工复核。
具体字段和验证方式按当前 Play Integrity 文档接入,不同地区和设备环境会有差异。2
原生库和资源¶
Android 的 native 代码可能在 lib/<abi>/ 下,也可能直接打进 APK。android:extractNativeLibs="false" 时,库从 APK 映射执行,不能假设磁盘上一定有一个可单独计算哈希的 libil2cpp.so。
AAB 拆分后还要注意:
- 基础 APK、配置 APK、ABI 分包和资源包不是同一个文件。
- Play Asset Delivery 的资源位置和安装方式与普通 assets 不同。
sourceDir和nativeLibraryDir能说明加载位置,但不能证明文件内容。- 分别修改单个 split 可能破坏签名,但攻击者可以重新签名并重新打包。
因此,关键原生库优先依赖 APK 签名和服务器结果。只有在安装流程能控制文件来源时,才适合额外做逐文件哈希。
不要把支付密钥、后台口令或签名私钥放进 BuildConfig、strings.xml、assets 或原生库字符串里。这些位置都能被提取。
Root 和注入检测¶
常见信号包括:
su、Magisk、KernelSU 等路径或挂载。/proc/self/maps里出现来源不明的可执行映射。- 进程被调试或
ptrace附加。 - Frida 常见线程名、默认端口或异常调试端口。
- 系统分区可写、
ro.debuggable=1等属性异常。 - 应用运行在虚拟多开、容器或克隆环境里。
这些信号都有误报和绕过。定制 ROM、开发设备、企业设备和某些测试工具会命中路径;Magisk、Zygisk 和改包工具也能隐藏一部分痕迹。
检测代码不应在每个 UI 帧执行。可以放在启动、登录、进入比赛和结算阶段,并限制采样范围。命中后先记录模块路径、来源、签名和系统属性,不要只上报一个 root=true。
数据存储¶
Keychain 或 Android Keystore 适合保存令牌和设备私钥,不适合保存决定游戏结果的固定密钥。客户端里的密钥最终可以被提取,区别只是成本。
登录 token 也要考虑:
- 是否绑定设备和账号。
- 是否支持撤销和轮换。
- 是否设置了合理的有效期。
- 是否能在服务端主动失效。
把 token 加密保存只能防止普通文件读取,不能阻止已经被 Root 或 Hook 的设备拿到解密后的值。
iOS¶
代码签名和描述文件¶
App Store 安装由系统校验代码签名。发布包至少确认:
- 使用正式的 Distribution 签名。
- 不包含
get-task-allow调试权限。 - 关键逻辑不依赖客户端里的固定密钥。
- Provisioning Profile 和 Bundle ID 与发布配置一致。
运行时的签名检查可以被越狱环境和 Hook 绕过。它适合在启动和更新后做一次完整性判断,不适合作为唯一的防作弊依据。
App Attest¶
App Attest 用来向服务器证明请求来自 Apple 认可的应用实例。典型流程是:
- 客户端检查
DCAppAttestService.isSupported。 - 生成专用 key,向 Apple 请求 attestation。
- 服务器验证 attestation,并把它和账号、设备会话绑定。
- 后续敏感请求携带 assertion。
- 服务器验证签名、计数器、请求内容和会话状态。
关键点是:attestation 和 assertion 都必须在服务器验证。客户端自己判断“isSupported”或“attestation 成功”不能证明请求可信。
App Attest 不是越狱检测 API,也不能覆盖所有设备。旧设备、系统异常和网络失败都可能拿不到证明。关键接口可以把证明作为必要条件,普通玩法则应该允许降级,否则会误伤正常玩家。3
越狱和注入¶
常见检测点包括:
- 沙箱外可写路径和越狱工具常见文件。
- 可疑的 URL Scheme 或系统服务。
- 动态加载的未知 dylib 或 framework。
- 调试器附加、异常系统调用和可执行内存。
越狱工具会隐藏文件、重定向路径或 Hook 检测函数。命中这些信号不代表一定能阻止修改,只能说明环境需要复核。
客户端证书固定(pinning)可以增加中间人抓包成本,但它保护的是传输身份,不等于应用完整性。攻击者可以在 Hook 后绕过固定逻辑,所以普通 API 仍要在服务器做鉴权和参数校验。
测试矩阵¶
| 环境 | 目的 | 需要确认 |
|---|---|---|
| 正常设备 | 基线 | 没有误报、启动时间和帧率可接受 |
| Root / 越狱设备 | 风险检测 | 命中后是限制还是复核,不会直接闪退 |
| 重签名包 | 签名校验 | 服务器是否拒绝,错误码是否可区分 |
| Frida / 动态注入 | 运行时检测 | 模块和调试信号是否可记录,误报是否可控 |
| 虚拟多开 / 云手机 | 环境兼容 | 正常登录、支付和存档是否仍可用 |
| 无 Play 服务 / App Attest 不支持 | 降级路径 | 普通玩法可用,敏感操作有明确提示 |
| 弱网 / 断网 | 启动容错 | 完整性检查不会无限阻塞 |
| 低端机 | 性能 | 全量扫描不会造成明显掉帧和内存峰值 |
测试时不只看客户端有没有弹窗。要检查服务器最终拒绝了哪些请求、风险日志里有哪些证据,以及误判账号能否人工恢复。
加固顺序¶
- 先保证服务器能校验金币、伤害、掉落、购买和排行榜结果。
- 再做安装包签名和服务器版本白名单。
- 对交易、登录等敏感接口接入 Play Integrity 或 App Attest。
- 把 Root、越狱、调试器和模块信号作为风险输入,不单独封号。
- 为证明失败、网络失败和旧设备准备降级路径。
- 记录版本、证书指纹、平台、信号来源和采样时间,便于复盘。
移动端加固能提高改包和注入的成本,但不能把设备变成可信环境。能拒绝错误结果的还是服务器。
平台检查项可以对照 OWASP MASVS 的存储、网络、认证和韧性章节逐项确认。4