跳转至

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 返回的是平台判定的完整性结果,不是客户端自己算出的一个布尔值。接入流程要让服务器参与:

  1. 服务器生成一次性 nonce 或请求哈希。
  2. 客户端向 Play Integrity 请求 verdict。
  3. 客户端把完整 token 发给自己的服务器。
  4. 服务器调用 Google 接口验证,检查包名、证书摘要、请求哈希和时间。
  5. 服务器根据结果决定是否放行本次操作。

不要把 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 认可的应用实例。典型流程是:

  1. 客户端检查 DCAppAttestService.isSupported。
  2. 生成专用 key,向 Apple 请求 attestation。
  3. 服务器验证 attestation,并把它和账号、设备会话绑定。
  4. 后续敏感请求携带 assertion。
  5. 服务器验证签名、计数器、请求内容和会话状态。

关键点是:attestation 和 assertion 都必须在服务器验证。客户端自己判断“isSupported”或“attestation 成功”不能证明请求可信。

App Attest 不是越狱检测 API,也不能覆盖所有设备。旧设备、系统异常和网络失败都可能拿不到证明。关键接口可以把证明作为必要条件,普通玩法则应该允许降级,否则会误伤正常玩家。3

越狱和注入

常见检测点包括:

  • 沙箱外可写路径和越狱工具常见文件。
  • 可疑的 URL Scheme 或系统服务。
  • 动态加载的未知 dylib 或 framework。
  • 调试器附加、异常系统调用和可执行内存。

越狱工具会隐藏文件、重定向路径或 Hook 检测函数。命中这些信号不代表一定能阻止修改,只能说明环境需要复核。

客户端证书固定(pinning)可以增加中间人抓包成本,但它保护的是传输身份,不等于应用完整性。攻击者可以在 Hook 后绕过固定逻辑,所以普通 API 仍要在服务器做鉴权和参数校验。

测试矩阵

环境 目的 需要确认
正常设备 基线 没有误报、启动时间和帧率可接受
Root / 越狱设备 风险检测 命中后是限制还是复核,不会直接闪退
重签名包 签名校验 服务器是否拒绝,错误码是否可区分
Frida / 动态注入 运行时检测 模块和调试信号是否可记录,误报是否可控
虚拟多开 / 云手机 环境兼容 正常登录、支付和存档是否仍可用
无 Play 服务 / App Attest 不支持 降级路径 普通玩法可用,敏感操作有明确提示
弱网 / 断网 启动容错 完整性检查不会无限阻塞
低端机 性能 全量扫描不会造成明显掉帧和内存峰值

测试时不只看客户端有没有弹窗。要检查服务器最终拒绝了哪些请求、风险日志里有哪些证据,以及误判账号能否人工恢复。

加固顺序

  1. 先保证服务器能校验金币、伤害、掉落、购买和排行榜结果。
  2. 再做安装包签名和服务器版本白名单。
  3. 对交易、登录等敏感接口接入 Play Integrity 或 App Attest。
  4. 把 Root、越狱、调试器和模块信号作为风险输入,不单独封号。
  5. 为证明失败、网络失败和旧设备准备降级路径。
  6. 记录版本、证书指纹、平台、信号来源和采样时间,便于复盘。

移动端加固能提高改包和注入的成本,但不能把设备变成可信环境。能拒绝错误结果的还是服务器。

平台检查项可以对照 OWASP MASVS 的存储、网络、认证和韧性章节逐项确认。4