Frida antiroot脚本场景下安卓App Root检测防绕过方案咨询
问题说明
在Magisk获取Root权限的设备上,攻击者使用下述Frida绕过脚本时,应用内置的、经普通代码混淆的Java层Root检测功能会失效,无法正确识别设备Root状态:
Java.perform(function() { var RootPackages = ["com.topjohnwu.magisk" ]; var RootBinaries = ["su", "busybox", "magisk"]; var RootProperties = { "ro.build.selinux": "1", "ro.debuggable": "0", "service.adb.root": "0", "ro.secure": "1" }; var RootPropertiesKeys = []; for (var k in RootProperties) RootPropertiesKeys.push(k); var NativeFile = Java.use('java.io.File'); var ProcessBuilder = Java.use('java.lang.ProcessBuilder'); var useProcessManager = false; NativeFile.exists.implementation = function() { var name = NativeFile.getName.call(this); var shouldFakeReturn = (RootBinaries.indexOf(name) > -1); if (shouldFakeReturn) { return false; } else { return this.exists.call(this); } }; ProcessBuilder.start.implementation = function() { var cmd = this.command.call(this); var shouldModifyCommand = false; for (var i = 0; i < cmd.size(); i = i + 1) { var tmp_cmd = cmd.get(i).toString(); if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd.indexOf("mount") != -1 || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd.indexOf("id") != -1) { shouldModifyCommand = true; } } if (shouldModifyCommand) { this.command.call(this, ["grep"]); return this.start.call(this); } if (cmd.indexOf("su") != -1) { this.command.call(this, ["SOMEFAKECMD"]); return this.start.call(this); } return this.start.call(this); }; if (useProcessManager) { var ProcManExec = ProcessManager.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;', 'java.io.File', 'boolean'); ProcManExec.implementation = function(cmd, env, workdir, redirectstderr) { var fake_cmd = cmd; for (var i = 0; i < cmd.length; i = i + 1) { var tmp_cmd = cmd[i]; if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id") { var fake_cmd = ["grep"]; } if (tmp_cmd == "su") { var fake_cmd = ["SOMEFAKECMD"]; } } return ProcManExec.call(this, fake_cmd, env, workdir, redirectstderr); }; } });
上述为裁剪后的Frida绕过脚本,移除了原公开脚本中的无关冗余逻辑。
可落地的加固技术手段
- 优先将核心Root检测逻辑下沉至Native层,避免依赖Java层被Hook的公开API
这个脚本所有拦截逻辑都作用在Java框架层,仅Hook了java.io.File.exists()、java.lang.ProcessBuilder.start()两个公开Java方法,预留的ProcessManager.exec Hook逻辑默认处于关闭状态,实际拦截覆盖范围非常有限,完全没有覆盖Native层的系统调用路径。你可以在C/C++层直接通过syscall调用open/access/stat接口检查su、Magisk相关二进制、Magisk专属挂载路径(如/dev/.magisk、/sbin/.magisk)的存在性,直接调用__system_property_get读取系统属性,直接通过fork+execve执行系统命令读取结果,全程不经过Java层的File、ProcessBuilder类,脚本的伪造返回值、篡改命令逻辑完全无法生效。为了进一步提升安全性,Native层的系统调用可以通过内联汇编直接触发,避免依赖libc导出函数被后续Native层Hook拦截。 - 在Root检测流程前增加Frida环境检测与API完整性校验
不要等Root检测逻辑跑起来才做校验,在应用启动的最早阶段就完成两项检查:- 反注入/反调试检查:扫描进程
/proc/self/maps内存映射表查找frida-agent、frida-server相关内存段特征,检测Frida默认监听的27042端口,检查进程是否被ptrace附着,扫描线程名是否包含frida特征字符串,发现异常直接终止检测流程、触发风险判定。 - 关键API完整性校验:检查Root检测用到的Java层系统方法(如File.exists、ProcessBuilder.start)的入口地址是否和系统原生方法一致,检查自身应用的核心检测方法是否被插桩篡改,如果发现方法被替换(即被Frida Hook),直接判定环境异常。
- 反注入/反调试检查:扫描进程
- 优化Java层检测逻辑,规避脚本的固定匹配规则
如果暂时无法完全把逻辑下沉到Native层,可以针对性绕过脚本的匹配逻辑:- 不要直接调用
File.exists()做文件存在性判断,可以通过FileDescriptor直接打开目标文件路径,根据打开结果判断文件是否存在,绕开被Hook的exists方法。 - 执行系统命令时不要直接传入完整的命令字符串,将命令关键词拆分、动态拼接后传入,规避脚本里对
getprop/mount/su等固定字符串的匹配;不要只依赖ProcessBuilder执行命令,可以通过反射调用Runtime.exec等其他命令执行路径,分散Hook点。 - 对检测逻辑做高强度混淆,包括字符串加密、控制流平坦化、类/方法名混淆,避免攻击者通过特征定位到你的Root检测代码段。
- 不要直接调用
- 扩展Root检测维度,做交叉校验
这个脚本仅覆盖了非常有限的Root检测绕过项:仅伪造了三个Root二进制文件的exists返回值、仅篡改了四个固定系统属性的读取路径、仅拦截了四个固定关键词的命令执行。你可以补充更多脚本未覆盖的检测维度:- 读取
/proc/self/mountinfo检查Magisk的挂载痕迹,检查Magisk修改的SELinux规则 - 校验boot镜像、系统分区的完整性,检查vbmeta锁定状态
- 扩展系统属性检测范围,补充
ro.boot.vbmeta.device_state、ro.build.type、ro.boot.verifiedbootstate等和Root、解锁状态相关的属性校验,不要只检查脚本里列的四个属性 - 实际执行su命令校验权限返回结果,而不是只判断su文件是否存在。
- 读取
参考防护方案的适用性判断
你提到的StackOverflow相关帖子里提出的核心防护思路——包括核心逻辑下沉Native层、Frida运行环境检测、多维度Root校验、代码完整性校验——完全适用于当前场景。但需要注意,帖子里给出的示例代码仅覆盖了基础防护场景,直接照搬会存在两个问题:一是公开的固定检测代码很容易被攻击者针对性写绕过规则,二是示例代码没有针对你遇到的这个脚本的Java层Hook点做专门的API完整性校验,落地时需要对代码做混淆改造、补充Hook检测逻辑,才能有效拦截这类绕过脚本。
内容的提问来源于stack exchange,提问作者peteryun
相关产品推荐
相关产品推荐

