关于 bypassing-root-jailbreak-detection
面向获授权移动应用测试人员的技能,用于定位并分析 Android 和 iOS 应用的 Root 或越狱检测。内容区分应用检查、Frida 检测与硬件证明,并说明硬件证明的限制。
主要功能
- 01定位检测所在层级
- 02分析 Android Root 检查
- 03分析 iOS 越狱检查
- 04区分 Frida 检测与 Root 检测
- 05说明硬件证明限制
- 06记录检测效果与测试范围
适用场景
- 01排查应用启动时退出
- 02诊断测试设备上的兼容问题
- 03为动态检测建立测试环境
- 04记录硬件证明造成的覆盖限制
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ trilwu/ secskills --skill bypassing-root-jailbreak-detection安装后在 Claude Code 中输入/bypassing-root-jailbreak-detection确认已加载。
详细介绍
绕过 Root 和越狱检测
检测几乎从来都不是单项检查。它通常是在应用中散布的十几项低成本检查,此外,越来越多的应用还会进行一项硬件支持的证明,任何 Hook 都无法触及。逐项 Hook 检查只会陷入打地鼠游戏;关键在于找出应用实际依赖的层。
仅用于测试你获准测试的应用。
适用场景
- 应用启动时退出、显示“检测到已 Root 的设备”,或悄无声息地降低功能
- 应用在原厂设备上正常运行,但在你的测试设备或模拟器上无法运行
- Frida 成功附加后进程立即退出,或者
frida-ps没有显示任何内容 objection无法进行探索,或者 Hook 几秒后就停止触发- 你需要在进行其他测试前,先搭建稳定的检测环境
不适用场景
- TLS 拦截失败——使用
bypassing-mobile-pinning;代理错误是另一类问题,不过检测也可能伪装成此类问题 - 更广泛的评估——使用
testing-mobile-applications - 特定框架的逆向工程——使用相关的
reversing-*技能 - 破解 DRM 或许可机制以盗版应用——不在适用范围内
先确定检测层
检测分布在四个层级,每个层级都需要不同的应对方式。弄清楚触发的是哪一层,最能节省时间。
| 层级 | 信号 | 应对方式 |
|---|---|---|
| Java/托管代码检查 | RootBeer、File.exists("/system/xbin/su")、针对 Magisk 的软件包查询 | 在 Java 层 Hook(objection、Frida) |
.so 中的原生检查 | 对 su 执行 stat/access/fopen,扫描 /proc/self/maps | Hook libc,或修补 .so |
| 检测插桩工具 | 仅在附加 Frida 时退出;探测端口 27042;扫描线程名称 | 隐藏 Agent,而非 Root |
| 硬件证明 | Play Integrity、SafetyNet、DeviceCheck、App Attest | 无法通过 Hook 绕过——见下文 |
# What does the app reference? Decompile and look before hooking.
apktool d target.apk -o out
rg -n 'RootBeer|isRooted|su\b|magisk|superuser|test-keys|/system/xbin|busybox|xposed|frida' -i out/smali* out/res 2>/dev/null | head -30
rg -n 'SafetyNet|PlayIntegrity|IntegrityManager|attest|DeviceCheck' -i out/ | head
# Native side
unzip -j target.apk 'lib/arm64-v8a/*' -d libs
rg -a -o 'su|/system/bin/su|magisk|frida|gum-js-loop|gmain' libs/*.so | sort -u | head -20
在原生库中找到的字符串会告诉你应用在查找什么;这比盲目 Hook、等着应用退出要高效得多。
Android
先隐藏环境,而不是先 Hook。 隐藏良好的 Root 环境可以在完全不使用 Hook 的情况下绕过大多数检测,而且应用更新后也不会失效。
# Magisk: DenyList the target so the root is invisible to it
# Settings → Zygisk ON, Enforce DenyList ON, select the target package
# Shamiko (Zygisk module) hides more thoroughly than DenyList alone
# Play Integrity Fix (PIF) module for the attestation layer — see limits below
# Verify what the app can see
adb shell "pm list packages | grep -i magisk" # should return nothing to the app
然后再 Hook 剩下的检查:
objection -g com.target.app explore
# then: android root disable
frida -U -f com.target.app -l anti-root-bypass.js --no-pause
常见的 Java Hook 点,按通常出现的顺序排列:
// File existence checks — the single most common family
Java.use('java.io.File').exists.implementation = function () {
const p = this.getAbsolutePath();
if (/su$|magisk|superuser|busybox|xposed/i.test(p)) return false;
return this.exists();
};
// Runtime.exec("su") and ProcessBuilder
// Build.TAGS containing "test-keys"
// PackageManager.getPackageInfo for known root packages
// System properties: ro.debuggable, ro.secure, ro.build.selinux
原生检查需要原生 Hook。 如果 Java Hook 已就位,但应用仍然退出,那么检查就在某个 .so 中:
# See which syscall is finding the evidence
frida-trace -U -f com.target.app -i 'stat*' -i 'access' -i 'fopen' -i 'open'
# then edit the generated handlers to lie about the paths it probes
Frida 检测是另一类问题
如果应用在 Root 环境下运行正常,但你一附加它就退出,那么你面对的是插桩检测;隐藏 Root 并无帮助。
| 检查项 | 应对方法 |
|---|---|
| TCP 27042 端口开放,或 D-Bus 握手响应 | 在随机端口运行 frida-server,或改用 Gadget 而非 Server |
/proc/self/maps 中包含 frida-agent、gum | 重命名 Agent;使用已修补的构建版本 |
线程名称 gmain、gum-js-loop、pool-frida | 已修补的 Frida 构建版本会重命名这些名称 |
扫描 /proc/self/task/*/stat | 同上 |
命名管道和 re.frida.server 字符串 | 使用已修补的构建版本 |
| 启动后定期重新检查 | 在检查窗口结束后再附加,或 Hook 定时器 |
实用的做法是使用已修补的 Frida(有专门为此制作的社区构建版本),并注入 Gadget,而不是运行监听服务。如果检测仍然占上风,就退回到静态分析加定向修补 APK 的方式——虽然会失去交互能力,但仍能继续推进。
iOS
objection -g com.target.app explore
# then: ios jailbreak disable
# Tweak-based, system-wide: Liberty Lite, A-Bypass, Shadow (rootless jailbreaks)
iOS 应用会检查什么,以及要隐藏什么:
/Applications/Cydia.app、/bin/bash、/usr/sbin/sshd、/etc/apt、/private/var/lib/apt是否存在- 是否能在沙盒外写入(
fopen("/private/jailbreak.txt", "w")) fork()是否成功;在沙盒应用中它本不应成功dyld映像列表中是否包含 MobileSubstrate / substitutecydia://URL scheme 是否可以打开NSFileManager检查;与原始stat相比,这类检查更容易被 Hook
对于原生层,Hook stat、access、fopen、getenv("DYLD_INSERT_LIBRARIES") 和
_dyld_get_image_name;对于 Objective-C 层,Hook NSFileManager 方法。在未越狱设备上,使用 Frida
gadget 重新打包(objection patchipa)——并做好触发完整性验证的准备。
你无法 Hook 的内容
硬件支持的完整性验证是硬性边界。 Play Integrity 的
MEETS_STRONG_INTEGRITY、通过 TEE/StrongBox 进行的密钥验证,以及 Apple 的
App Attest,均由硬件密钥签名,并在供应商服务器上验证。
客户端 Hook 可以改变应用读取到的内容,但无法伪造服务器验证的签名。
这在实践中的含义:
- Magisk 加上 Play Integrity Fix 模块通常可以达到
BASIC和DEVICE完整性。STRONG则要求引导加载程序未被修改。 - 如果应用在强完整性验证失败时会拒绝运行,可靠的选择包括:使用未 Root 的设备运行重新打包的应用(但它本身会无法通过签名检查);使用搭载原厂 ROM 的设备,仅进行网络层测试;或者与客户商定在测试期间豁免完整性验证。
- 申请豁免。 要求提供关闭完整性验证的测试构建,是常见的测试项目请求,成本远低于花上数天时间却受制于 TEE。
应在报告中如实说明,而不是悄悄缩小测试范围:“由于硬件完整性验证,无法对生产构建进行动态插桩;测试覆盖了 X,但未覆盖 Y。”这是关于测试覆盖范围的发现,隐瞒它比承认限制更糟。
检测质量本身也是一项发现
在绕过检测时,记录检测机制的质量——客户会询问,这也应写入报告:
- 检测失败时是拒绝运行(退出),还是放行(记录日志后继续)?
- 检查结果会发送到服务器,还是仅在本地受信任?仅在本地执行的检查充其量只是参考。
- 检查是否集中在一个函数中(可以轻松 Hook),还是分散执行并反复检查?
- 是否使用硬件完整性验证,还是只依赖文件系统启发式检查?
如果应用的全部 Root 防护就是 File.exists("/system/xbin/su"),应列为低严重性说明;如果使用经过服务器验证的完整性证明,则应给予肯定。
应当摒弃的理由
- “objection 的绕过没成功,说明检测太强了。” 它能覆盖常见的 Java 检查。接下来看看原生层。
- “我隐藏了 Root,所以应该能用了。” 如果应用只在 Frida 环境下崩溃,你对抗的是插桩检测,而不是 Root 检测。
- “可以用 Frida 绕过 Play Integrity。” 硬件支持的验证结果无法这样绕过。Hook 只能改变应用读取到的内容,不能改变 TEE 签署的内容。
- “我会修补找到的每一项检查。” 分散的检查会反复执行。找到核心决策点,或者直接隐藏运行环境。
- “Root 检测是一项漏洞。” 它是一种防护措施,而且防护效果较弱。缺少这类检测只是低严重性说明,不应作为主要发现。
- “我无法对应用进行插桩,所以没什么可报告的。” 应明确报告测试覆盖范围的限制。
参考资料
testing-mobile-applications— 本文所解锁的评估工作bypassing-mobile-pinning— 移动应用“突然无法运行”的另一个原因analyzing-binaries— 修补原生检测例程reporting-security-findings— 如何如实说明测试覆盖范围的限制- Magisk + Zygisk、Shamiko、objection、Frida(及修补后的构建)、Liberty Lite、Shadow