Skip to content

Root 检测绕过 agent/src/android/root.ts ​

在目标 Android 进程内拦截各类 root 检测实现,按调用方期望的方向返回“已 root”或“未 root”的结果。模块导出 disable() 与 enable() 两个 RPC:前者让检测返回“未 root”,后者让检测返回“已 root”,用于反向验证检测点是否被覆盖。两条 RPC 各注册一个 Job(root-detection-disable / root-detection-enable)。

📋 模块概览 ​

项目值
源码路径agent/src/android/root.ts
平台Android(Java 层)
导出的 RPCdisable(agent/src/rpc/android.ts:90 → androidRootDetectionDisable)、enable(agent/src/rpc/android.ts:91 → androidRootDetectionEnable)
依赖../lib/jobs.js、../lib/color.js、./lib/libjava.js、./lib/types.js

🎯 解决的问题 ​

  • 应用通过 Build.TAGS.contains("test-keys") 判断是否运行在测试签名固件。
  • 应用调用 Runtime.exec("su") 或检查 java.io.File.exists() 判断 /system/xbin/su 等 su 路径是否存在。
  • 集成了 RootBeer 库的应用通过 isRooted()、checkForBinary()、RootBeerNative.checkForRoot() 等多个方法交叉验证。
  • 集成了 JailMonkey 的 RN 应用通过 JailMonkeyModule.getConstants() 一次性返回 isJailBroken 等多个布尔标志。

🏗️ 导出的 RPC 方法 ​

RPC 名说明
disable()让全部 13 条检测返回“未 root”,注册为 root-detection-disable Job
enable()让 4 条基础检测 + RootBeer 大部分检测返回“已 root”,用于验证覆盖完整性

rpc.disable — 让检测返回“未 root” ​

源码:agent/src/android/root.ts:430

disable() 对每个 helper 传入 success=false,表示“把检测结果标记为未 root”。所有 helper 在 Java.perform 内替换方法实现;RootBeer/JailMonkey 相关类不存在时返回 null,由 Job.addImplementation 跳过。

ts
export const disable = async (): Promise<void> => {
  const job: jobs.Job = new jobs.Job(jobs.identifier(), 'root-detection-disable');
  job.addImplementation(await testKeysCheck(false, job.identifier));
  job.addImplementation(await execSuCheck(false, job.identifier));
  job.addImplementation(await fileExistsCheck(false, job.identifier));
  job.addImplementation(await jailMonkeyBypass(false, job.identifier));
  job.addImplementation(await rootBeerIsRooted(false, job.identifier));
  // ... 其余 RootBeer helper 同样传 false
  jobs.add(job);
};

rpc.enable — 让检测返回“已 root” ​

源码:agent/src/android/root.ts:450

enable() 用于反向验证:把检测结果强制为“已 root”,观察 App 是否因此触发额外行为,从而确认对应检测点已被 Hook 覆盖。注意 rootBeerCheckSeLinux 在 enable 分支里仍传 false(源码 agent/src/root.ts 第 466 行),与 disable 一致。

关键 helper:testKeysCheck ​

源码:agent/src/android/root.ts:30

不直接 Hook Build.TAGS,而是 Hook java.lang.String.contains,仅在参数为 "test-keys" 时返回伪造结果,其余字符串走原实现,避免误伤业务逻辑。

ts
JavaString.contains.implementation = function (name) {
  if (name !== "test-keys") { return this.contains.call(this, name); }
  if (success) { return true; }
  return false;
};

关键 helper:execSuCheck ​

源码:agent/src/android/root.ts:52

Hook Runtime.exec(String),当命令以 "su" 结尾时:success=true 放行原调用,success=false 抛 java.io.IOException("objection anti-root")。

关键 helper:fileExistsCheck ​

源码:agent/src/android/root.ts:77

Hook java.io.File.exists(),对 commonPaths 列表中的 13 条 su 路径返回伪造值,其余路径走原实现。commonPaths 定义在 agent/src/android/root.ts:14。

关键 helper:jailMonkeyBypass ​

源码:agent/src/android/root.ts:378

Hook com.gantix.JailMonkey.JailMonkeyModule.getConstants(),返回一个 java.util.HashMap,对 isJailBroken、hookDetected、canMockLocation、isOnExternalStorage、AdbEnabled 五个键统一填 Boolean.TRUE 或 Boolean.FALSE。

ts
const JavaFalseObject = JavaBoolean.FALSE.value;
JavaJailMonkeyModule.getConstants.implementation = function () {
  const hm = JavaHashMap.$new();
  hm.put("isJailBroken", JavaFalseObject);
  hm.put("hookDetected", JavaFalseObject);
  // ...
  return hm;
};

RootBeer 系列 helper ​

helper目标方法位置
rootBeerIsRootedRootBeer.isRooted()agent/src/android/root.ts:108
rootBeerCheckForBinaryRootBeer.checkForBinary(String)agent/src/android/root.ts:143
rootBeerCheckForDangerousPropsRootBeer.checkForDangerousProps()agent/src/android/root.ts:176
rootBeerDetectRootCloakingAppsRootBeer.detectRootCloakingApps()agent/src/android/root.ts:209
rootBeerCheckSuExistsRootBeer.checkSuExists()agent/src/android/root.ts:244
rootBeerDetectTestKeysRootBeer.detectTestKeys()agent/src/android/root.ts:277
rootBeerCheckSeLinuxcom.scottyab.rootbeer.util.isSelinuxFlagInEnabled()agent/src/android/root.ts:310
rootBeerNativeRootBeerNative.checkForRoot([Ljava.lang.Object;)agent/src/android/root.ts:343

⚙️ 实现要点 ​

  • success 双向标志:同一套 helper 通过布尔参数切换“伪造为真/伪造为假”,enable 分支可用来探测 App 对“检测到 root”后的反应,定位隐藏的检测点。
  • 类不存在即跳过:RootBeer/JailMonkey 的 helper 用 try/catch 捕获 ClassNotFoundException,返回 null 后由 Job.addImplementation 跳过。
  • 窄范围 Hook:testKeysCheck 只在参数为 "test-keys" 时介入,fileExistsCheck 只对 commonPaths 列表内的路径介入,最大限度减少对业务逻辑的副作用。
  • Native 层覆盖:rootBeerNative 处理 RootBeer 调用 native 库的 RootBeerNative.checkForRoot,返回 1/0 表示伪造结果。
  • 异步消息:每次 Hook 命中走 send() 带 [ident] 前缀的提示,便于在控制台追踪哪个检测被触发。

🔍 源码索引 ​

符号位置
commonPathsagent/src/android/root.ts:14
testKeysCheckagent/src/android/root.ts:30
execSuCheckagent/src/android/root.ts:52
fileExistsCheckagent/src/android/root.ts:77
rootBeerIsRootedagent/src/android/root.ts:108
rootBeerCheckForBinaryagent/src/android/root.ts:143
rootBeerCheckForDangerousPropsagent/src/android/root.ts:176
rootBeerDetectRootCloakingAppsagent/src/android/root.ts:209
rootBeerCheckSuExistsagent/src/android/root.ts:244
rootBeerDetectTestKeysagent/src/android/root.ts:277
rootBeerCheckSeLinuxagent/src/android/root.ts:310
rootBeerNativeagent/src/android/root.ts:343
jailMonkeyBypassagent/src/android/root.ts:378
export const disableagent/src/android/root.ts:430
export const enableagent/src/android/root.ts:450

🔗 相关文档 ​

基于 GPL-3.0-or-later 许可发布