下面我将详细列举 Android 平台上的 Root 检测方法及其对抗方案。
Root 检测与对抗全景图
Root 检测的核心思路是寻找设备被 Root 后留下的 “痕迹”。对抗的核心思路则是 “隐藏痕迹” 或 “欺骗检测API”。
一、常规路径与文件检测
这是最基础、最常见的检测方法,检查通常只有 Root 后才会存在的特殊文件、路径和二进制工具。
检测方法检测目标示例对抗方法
检查 Superuser APK
/system/app/Superuser.apk/system/app/SuperSU.apk
重命名/隐藏APK:使用 Magisk Hide 或类似功能隐藏管理应用。
检查 SU 二进制文件
/system/bin/su/system/xbin/su/sbin/su/vendor/bin/su
移除默认SU路径:Magisk 将其 magisk 二进制文件放在随机化的路径下,并通过 su 符号链接调用。隐藏符号链接是关键。
检查其他Root工具
busybox, sqlite3 等常用工具
避免安装:不要安装不必要的、可能暴露环境的工具。
检查测试密钥
ro.build.tags=test-keys
修改Build属性:使用 MagiskHide Props Config 模块,将指纹 (fingerprint) 和标签 (tags) 模拟为官方发布密钥 (release-keys)。
对抗代码示例 (Hook java.io.File.exists()):
javascript
Java.perform(function() {
var suspiciousPaths = [
"/system/bin/su", "/system/xbin/su", "/sbin/su",
"/system/app/Superuser.apk", "/system/app/SuperSU"
];
var File = Java.use("java.io.File");
File.exists.implementation = function() {
var path = this.getAbsolutePath();
for (var i = 0; i < suspiciousPaths.length; i++) {
if (path.startsWith(suspiciousPaths[i])) {
console.log("[Bypass] Blocking access to: " + path);
return false; // 告诉调用者文件不存在
}
}
return this.exists(); // 正常调用原方法
};
});
二、环境属性检测
检查系统的各种属性,寻找与官方系统不一致的“非正常”值。
检测方法检测目标示例对抗方法
检查 ro.debuggable
getprop ro.debuggable != 0
动态修改属性值:使用 Magisk Hide 或通过 Hook 在应用读取时临时返回 0。
检查 ro.secure
getprop ro.secure != 1
同上,动态修改返回值。
检查 ro.build.type
getprop ro.build.type != user
修改为 user。
检查特定Magisk属性
ro.boot.vbmeta.device_statero.boot.veritymode
使用 Magisk 本身:Magisk 会主动隐藏这些痕迹。
对抗代码示例 (Hook android.os.SystemProperties.get()):
javascript
Java.perform(function() {
var SystemProperties = Java.use("android.os.SystemProperties");
SystemProperties.get.overload('java.lang.String').implementation = function(key) {
if (key === "ro.debuggable") {
return "0";
} else if (key === "ro.secure") {
return "1";
} else if (key === "ro.build.type") {
return "user";
} else if (key === "ro.build.tags") {
return "release-keys";
}
return this.get(key); // 其他属性正常返回
};
// 另一个重载方法也需要Hook
SystemProperties.get.overload('java.lang.String', 'java.lang.String').implementation = function(key, def) {
var origResult = this.get(key, def);
if (key === "ro.debuggable") {
return "0";
}
// ... 其他属性同上
return origResult;
};
});
三、运行命令检测
尝试执行 which su 或 pm list packages 等命令,查看输出结果。
检测方法检测目标示例对抗方法
which su
检查命令返回值或输出
隐藏 su 命令:Magisk 通过修改 PATH 环境变量或挂钩子 (hook) 系统调用来实现。
pm list packages
检查包列表中是否存在 magisk、supersu 等
隐藏管理包名:使用 Magisk Hide 功能隐藏特定的应用程序。
对抗代码示例 (Hook java.lang.Runtime.exec()):
javascript
Java.perform(function() {
var Runtime = Java.use("java.lang.Runtime");
var execOverloads = ['java.lang.String', '[Ljava.lang.String;'];
for (var i = 0; i < execOverloads.length; i++) {
Runtime.exec.overload(execOverloads[i]).implementation = function() {
var cmd = arguments[0];
if (typeof cmd === 'string') {
if (cmd.includes("which su") || cmd.includes("/system/bin/which su")) {
console.log("[Bypass] Blocking command: " + cmd);
// 可以返回一个空的Process对象或抛异常,但更安全的是返回一个“未找到”的模拟输出
// 这里需要更复杂的实现来模拟一个空的InputStream
return null; // 简单示例,实际处理更复杂
}
}
return this.exec.apply(this, arguments);
};
}
});
四、Native层检测
在 C/C++ 层进行更底层、更隐蔽的检测。
检测方法检测目标示例对抗方法
直接使用 access() 系统调用
access("/system/bin/su", F_OK)
Hook Native 函数:使用 Frida 或 Xposed (Whale) 来 Hook libc 中的 access、fopen、stat 等函数。
检查进程列表
读取 /proc/self/mounts 查找 magisk 路径
隐藏Magisk路径:Magisk 会使用 mount 命名空间来隔离其修改,使其对目标应用不可见。
检测加载的库
检查 maps 中是否加载了 libsu.so 等
隐藏库:避免使用会注入so库的Root管理App,或Hook读取 /proc/self/maps 的函数。
对抗代码示例 (Hook Native access()):
javascript
// Frida JS 代码
var libc = Module.findBaseName('libc.so');
var accessAddr = Module.findExportByName(libc, 'access');
Interceptor.attach(accessAddr, {
onEnter: function(args) {
this.path = args[0].readCString();
if (this.path && this.path.includes("su")) {
console.log("[Bypass] Blocking access to: " + this.path);
// 让 access 调用返回 -1 (表示文件不存在),并设置 errno 为 ENOENT
this.errno = 0x20000002; // 根据系统定义,通常为 2
}
},
onLeave: function(retval) {
if (this.errno) {
retval.replace(-1); // 返回 -1
// 在某些架构上,还需要通过 __errno location 来设置 errno
}
}
});
五、综合与高阶检测
检测方法检测目标示例对抗方法
SafetyNet API
Google 官方的完整性检验 API。
Magisk 的核心功能:Magisk 的 Universal SafetyNet Fix 模块通过模拟硬件 attestation 来通过验证。
执行结果差异性
对比 stat("/system/bin/su") 和 stat("/system/bin/ls") 的权限、用户组等。
完善的隐藏方案:需要完整的Root解决方案(如Magisk)在系统层面进行全局隐藏,而不是局部Hook。
内核特征检测
检测 syscall 表是否被修改(挂钩子)。
KernelSU:另一种Root方案,修改内核但力求隐藏得更好。对抗难度极大。
总结与最佳实践
对于普通用户/测试人员:
首选 Magisk:它是目前隐藏能力最强、更新最活跃的Root方案。
开启 Magisk Hide:在设置中启用,并勾选需要隐藏Root的目标应用(如银行App、游戏等)。
安装模块:安装 Universal SafetyNet Fix 和 MagiskHide Props Config 等模块来通过更严格的检测。
隐藏 Magisk 应用:在 Magisk 设置中,有一个选项可以将其自身应用包名随机化。
对于安全研究人员(需要对抗更强检测):
组合使用工具:Magisk + Frida。
定制化Hook:分析目标App的检测逻辑,使用上述 Frida 脚本示例,编写针对性的Hook代码。
环境隔离:使用基于内核的隔离方案(如 KernelSU),或者直接在定制ROM中实现隐藏。
动态分析:如果Hook失败,可能是因为检测在Native层或使用了反调试。需要逆向App的so库,找到检测函数并下断点分析,然后制定更底层的Hook方案。
记住:这是一场持续的攻防战。新的检测方法不断出现,而隐藏技术也在不断进化。没有一劳永逸的方案,关键在于理解原理,灵活运用各种工具。
过检工具
firda脚本绕过
https://github.com/AshenOneYe/FridaAntiRootDetection
Shamiko模块绕过
https://github.com/LSPosed/LSPosed.github.io/releases/tag/shamiko-344
狐狸面具绕过(推荐这种方法,一劳永逸)
对抗
const-string ([vp]\d+), “*.su”
替换
const-string $1, "/no_su"
最终效果
修改前:
smali
const-string v0, "/system/xbin/su" # 加载一个真实存在的Root路径
# file.exists() -> true (检测到Root)
修改后:
smali
const-string v0, "/no su" # 加载一个绝对不存在的假路径
# file.exists() -> false (未检测到Root)
通过将检测Root的关键字符串(su的路径)替换为一个无意义的、不存在的字符串,应用程序的Root检查逻辑就会被绕过,认为设备没有Root权限,从而允许应用继续正常运行。