过ROOT检测

root检测:

  1. 代码检测 ->hook重打包
  2. 检测系统属性看雪学苑|专业信息安全学习平台
  3. 查找字符串

对抗方法:

  1. hook
  2. 最新版magisk+shamiko(可有可无)+随机magisk
  3. 编译面具改su名称

魔改面具

为什么:
1.体验下如何编译面具源码
2.魔改面具-绕过更深的root检测

github:https://github.com/topjohnwu/Magisk

编译根据这篇文章
(2 封私信 / 7 条消息) 手把手教你从源码开始编译Magisk APP和依赖项 - 知乎

编译踩坑记录:
https://blog.csdn.net/u012932409/article/details/123001265

编译的时候可能出现问题
Unresolved reference:transferTo根据 https://github.com/topjohnwu/Magisk/issues/4712

Change transferTo to copyTo 这样修改就OK

附赠小知识

场景:当用魔改的面具时,名为mysu时。小黄鸟这一类需要root的应用就不能请求到。
hook了Runtime里面的exec。判断参数是否为mysu如果是改为su就可以

刷入模块

当然输入写好的模块确实是最省事的方式

hook过root检测

打开apk会发现提示当前处于root环境,探究该apk通过什么知道手机当前处于root环境

猜测打开内容是一个Toast,通过Toast去Hook打印相关堆栈

  • 获取堆栈的三种方式

    在 Android 中,获取调用栈有多种方法:

    方法一:使用 Exception 类

    1
    2
    3
    var Exception = Java.use("java.lang.Exception");
    var e = Exception.$new();
    var stackTrace = e.getStackTrace();

    方法二:使用 Log 类(更简洁)

    1
    2
    var Log = Java.use("android.util.Log");
    var stackTraceString = Log.getStackTraceString(exceptionInstance);

    方法三:使用 Thread 类

    1
    2
    var Thread = Java.use("java.lang.Thread");
    var stackTrace = Thread.currentThread().getStackTrace();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
setImmediate(function (){
function showStacks(){
console.log(
Java.use("android.util.Log").getStackTraceString(
Java.use("java.lang.Throwable").$new()
)
);
}

Java.perform(function (){
var toast = Java.use("android.widget.Toast");
toast.show.implementation = function (){
showStacks();
console.log("toast show: ");
return this.show();
}
})
})

查看包名 com.hoge.android.app.fujian

尝试注入frida -U -f com.hoge.android.app.fujian -l .\ToastShow.js

根据打印堆栈的结果猜测和包名相关的类中只有该类不是和Toast相关的

我们进一步对该类进行查看

第二个匿名类中的第一个匿名类run方法,观察发现该run方法在命名类中被嵌套而且传输了一个toast

我们发现 该Toast弹出shoeToast 和我们弹出堆栈一致 CustomToast.showToast(WelcomeActivity.**this**.mContext,WelcomeActivity.**this**.getString(R.string.root_toast), 100);

第一个是Context,第二个是字符,和一个显示长短

showToast不断弹出到

利用jeb反编译查看到值Resources→values→strings.xml

因为字符无法直接查询到位置,确定要hook的来定位函数的位置

分析逻辑,有两个关键函数

SystemUtils.checkSuFile()返回真且不返回NULL就可以

我们发现在通过命令行的方式执行which su,效果如下

如果process读取一下我们的显示信息

同样在这些路径下有没有su文件,用exists()去检测是否存在。

因此我们大致可以得到两种hook逻辑
checkSuFile()函数返回false,checkRootFile() 函数返回null即可。

1
2
3
4
5
6
7
8
9
10
Java.perform(function (){
var systemutils = Java.use("com.hoge.android.factory.util.system.SystemUtils");
systemutils.checkSuFile.implementation = function (){
return false;
}

systemutils.checkRootFile.implementation = function (){
return null;
}
})
  • 代码解释
    1. Java.perform(function (){ ... })
    • 用途: 这是Frida的标准入口点,确保代码在Java运行时环境中执行
    • 知识点:
      • Frida通过注入到目标进程来工作
      • Java.perform确保代码在Android应用的Java虚拟机上下文中执行
      • 所有Java相关的Hook操作都应该放在这个回调函数中
    1. var systemutils = Java.use("com.hoge.android.factory.util.system.SystemUtils");
    • 用途: 获取对目标Java类的引用
    • 知识点:
      • Java.use()是Frida的核心API,用于获取Java类的引用
      • 参数是完整的Java类名(包括包路径)
      • 这里获取了一个名为SystemUtils的工具类,通常用于系统级操作
    1. systemutils.checkSuFile.implementation = function (){ return false; }
    • 用途: Hook并修改checkSuFile方法的行为,使其总是返回false
    • 知识点:
      • .implementation属性用于替换Java方法的实现
      • 这个方法原本可能用于检测设备是否已root(通过检查su文件)
      • 返回false可以让应用认为设备未root,绕过root检测
    1. systemutils.checkRootFile.implementation = function (){ return null; }
    • 用途: Hook并修改checkRootFile方法的行为,使其总是返回null
    • 知识点:
      • 类似上面,这个方法可能用于检查root相关文件
      • 返回null同样是为了欺骗应用,使其认为设备未root

我们就可以发现重启后没有提示检测,说明成功绕过。

或者直接hook系统方法exists()含有su路径直接返回null

要想着知道exists()方法属于哪个类,我们可以直接通过编写代码查看路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Java.perform(function () {
// var SystemUtils = Java.use("com.hoge.android.factory.util.system.SystemUtils");
// SystemUtils.checkSuFile.implementation = function () {
// return false;
// }
// SystemUtils.checkRootFile.implementation = function () {
// return null;
// }

function showStacks() {
console.log(
Java.use("android.util.Log")
.getStackTraceString(
Java.use("java.lang.Throwable").$new()
)
);
}

Java.use("java.io.File").$init.overload("java.lang.String").implementation = function (str) {
if (str.toLowerCase().endsWith("/su") || str.toLowerCase() == "su") {
console.log("发现检测su文件");
showStacks();
}
return this.$init(str);
}
Java.use("java.lang.Runtime").exec.overload("java.lang.String").implementation = function (str) {
if (str.endsWith("/su") || str == "su") {
console.log("发现尝试执行su命令的行为");
showStacks();
}
return this.exec(str);
}
Java.use("java.lang.Runtime").exec.overload("[Ljava.lang.String;").implementation = function (stringArray) {
for (var i = 0; i < stringArray.length; i++){
if (stringArray[i].includes("su") || stringArray[i].includes("/su") || stringArray[i] == "su"){
console.log("发现尝试执行su命令的行为");
showStacks();
break;
}
}
return this.exec(stringArray);
}
Java.use("java.lang.ProcessBuilder").$init.overload("[Ljava.lang.String;").implementation = function (stringArray){
for (var i = 0;i < stringArray.length; i++) {
if (stringArray[i].includes("su") || stringArray[i].includes("/su") || stringArray[i] == "su") {
console.log("发现尝试执行su命令的行为");
showStacks();
break;
}
}
return this.$init(stringArray);
}
});
  • 代码解释

    脚本概述

    这个Frida脚本主要用于监控和检测Android应用中与root权限检测相关的操作,特别是对su文件的检查和使用su命令的行为。与直接绕过检测的脚本不同,这个脚本采用了被动监控的方式,只记录检测行为而不阻止它们。

    逐行详细解析

    1. Java.perform(function () { ... });

    • 用途: 这是Frida的标准入口点,确保其中的代码在Java运行时环境中执行
    • 知识点:
      • Frida通过注入到目标进程来工作,Java.perform确保代码在Android应用的Java虚拟机上下文中执行
      • 所有Java相关的Hook操作都应该放在这个回调函数中
      • 这是必要的,因为Frida需要确保在正确的线程和类加载器上下文中执行操作

    3. showStacks()函数定义

    1
    2
    3
    4
    5
    6
    7
    8
    function showStacks() {
    console.log(
    Java.use("android.util.Log")
    .getStackTraceString(
    Java.use("java.lang.Throwable").$new()
    )
    );
    }
    • 用途: 定义一个工具函数,用于获取并打印当前Java调用栈
    • 详细解析:
      • Java.use("java.lang.Throwable").$new(): 创建一个新的Throwable对象,它会捕获当前的调用栈
      • Java.use("android.util.Log").getStackTraceString(): 使用Android的Log类将调用栈转换为字符串格式
      • console.log(): 将调用栈信息输出到Frida控制台
    • 知识点:
      • 调用栈分析是逆向工程和安全研究中的重要技术
      • 通过查看调用栈,可以了解检测代码的执行路径和来源
      • 这种方法可以帮助定位应用中进行root检测的具体位置

    4. Hook File类构造函数

    1
    2
    3
    4
    5
    6
    7
    Java.use("java.io.File").$init.overload("java.lang.String").implementation = function (str) {
    if (str.toLowerCase().endsWith("/su") || str.toLowerCase() == "su") {
    console.log("发现检测su文件");
    showStacks();
    }
    return this.$init(str);
    }
    • 用途: 监控File对象的创建,检测是否有对su文件的访问
    • 详细解析:
      • Java.use("java.io.File"): 获取Java的File类的引用
      • .$init.overload("java.lang.String"): 指定要Hook的构造函数重载(接收一个字符串参数的构造函数)
      • implementation: 替换该方法的实现
      • 在实现中检查文件路径是否指向su文件(不区分大小写)
      • 检测到su文件访问时,输出日志并显示调用栈
      • 最后调用原始实现(this.$init(str))保持正常功能
    • 知识点:
      • 许多root检测方法会检查系统中是否存在su文件
      • File类用于表示文件和目录路径名,是文件系统访问的基础
      • 使用overload()指定要Hook的方法的具体签名很重要,因为Java支持方法重载

    5. Hook Runtime.exec方法(字符串参数版本)

    1
    2
    3
    4
    5
    6
    7
    Java.use("java.lang.Runtime").exec.overload("java.lang.String").implementation = function (str) {
    if (str.endsWith("/su") || str == "su") {
    console.log("发现尝试执行su命令的行为");
    showStacks();
    }
    return this.exec(str);
    }
    • 用途: 监控通过Runtime.exec执行命令的行为,检测是否有执行su命令的尝试
    • 详细解析:
      • Java.use("java.lang.Runtime"): 获取Runtime类的引用
      • .exec.overload("java.lang.String"): 指定要Hook的exec方法的重载版本(接收一个字符串参数)
      • 检查执行的命令是否包含su(通常是su命令的路径或名称)
      • 检测到su命令执行时,输出日志并显示调用栈
      • 保持原始功能不变
    • 知识点:
      • Runtime.exec是Java中执行系统命令的主要方式
      • 许多root检测会尝试执行su命令来验证是否具有root权限
      • 即使命令执行失败,尝试执行su命令本身也是检测root的一种方式

    6. Hook Runtime.exec方法(字符串数组参数版本)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Java.use("java.lang.Runtime").exec.overload("[Ljava.lang.String;").implementation = function (stringArray) {
    for (var i = 0; i < stringArray.length; i++){
    if (stringArray[i].includes("su") || stringArray[i].includes("/su") || stringArray[i] == "su"){
    console.log("发现尝试执行su命令的行为");
    showStacks();
    break;
    }
    }
    return this.exec(stringArray);
    }
    • 用途: 监控通过Runtime.exec执行命令数组的行为,检测是否有执行su命令的尝试
    • 详细解析:
      • .exec.overload("[Ljava.lang.String;"): 指定要Hook的exec方法的重载版本(接收一个字符串数组参数)
      • [Ljava.lang.String;是Java中字符串数组的类型签名
      • 遍历命令参数数组,检查是否包含su相关参数
      • 检测到可疑参数时输出日志和调用栈
      • 保持原始功能
    • 知识点:
      • 使用字符串数组执行命令可以更精确地控制命令参数
      • 这种形式的exec调用更常见于复杂的命令执行场景
      • 类型签名在Frida Hook中非常重要,必须准确匹配

    7. Hook ProcessBuilder构造函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Java.use("java.lang.ProcessBuilder").$init.overload("[Ljava.lang.String;").implementation = function (stringArray){
    for (var i = 0;i < stringArray.length; i++) {
    if (stringArray[i].includes("su") || stringArray[i].includes("/su") || stringArray[i] == "su") {
    console.log("发现尝试执行su命令的行为");
    showStacks();
    break;
    }
    }
    return this.$init(stringArray);
    }
    • 用途: 监控ProcessBuilder的创建,检测是否有使用su命令创建进程的尝试
    • 详细解析:
      • Java.use("java.lang.ProcessBuilder"): 获取ProcessBuilder类的引用
      • .$init.overload("[Ljava.lang.String;"): 指定要Hook的构造函数重载(接收一个字符串数组参数)
      • 检查命令参数中是否包含su
      • 检测到可疑操作时输出日志和调用栈
      • 保持原始功能
    • 知识点:
      • ProcessBuilder是比Runtime.exec更现代的命令执行方式
      • 它提供了更多的进程控制选项,如重定向输入输出
      • 许多应用使用ProcessBuilder来执行系统命令

    关键知识点总结

    1. Frida基础: Frida是一个动态代码插桩工具,允许注入JavaScript到原生应用中
    2. Java Hook原理: 通过修改方法的implementation属性来改变方法行为
    3. 方法重载处理: 使用overload()指定要Hook的方法的具体签名
    4. 调用栈分析: 通过Throwable对象获取调用栈,帮助定位代码执行路径
    5. Root检测技术: 了解常见的root检测方法有助于编写更有效的Hook脚本
    6. 被动监控 vs 主动防御: 这个脚本采用被动监控方式,只记录不阻止,适合分析阶段使用

    技术细节说明

    1. 类型签名: 在Java中,[Ljava.lang.String;表示一个String数组,Ljava.lang.String;表示一个String对象
    2. 方法重载: Java允许方法名相同但参数不同的重载方法,Frida需要使用overload()指定具体要Hook的版本
    3. 实现替换: 通过给方法的implementation属性赋值来替换方法实现,原始方法可以通过this.methodName()调用
    4. 调用栈分析: 创建Throwable对象会捕获当前的调用栈,这是Java中获取调用信息的标准方法


过ROOT检测
https://cc-nx.github.io/2026/01/15/过ROOT检测/
作者
CC
发布于
2026年1月15日
许可协议