过ROOT检测
root检测:
- 代码检测 ->hook重打包
- 检测系统属性看雪学苑|专业信息安全学习平台
- 查找字符串
对抗方法:
- hook
- 最新版magisk+shamiko(可有可无)+随机magisk
- 编译面具改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
3var Exception = Java.use("java.lang.Exception");
var e = Exception.$new();
var stackTrace = e.getStackTrace();方法二:使用 Log 类(更简洁)
1
2var Log = Java.use("android.util.Log");
var stackTraceString = Log.getStackTraceString(exceptionInstance);方法三:使用 Thread 类
1
2var Thread = Java.use("java.lang.Thread");
var stackTrace = Thread.currentThread().getStackTrace();
1 | |
查看包名 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 | |
- 代码解释
Java.perform(function (){ ... })
- 用途: 这是Frida的标准入口点,确保代码在Java运行时环境中执行
- 知识点:
- Frida通过注入到目标进程来工作
Java.perform确保代码在Android应用的Java虚拟机上下文中执行- 所有Java相关的Hook操作都应该放在这个回调函数中
var systemutils = Java.use("com.hoge.android.factory.util.system.SystemUtils");
- 用途: 获取对目标Java类的引用
- 知识点:
Java.use()是Frida的核心API,用于获取Java类的引用- 参数是完整的Java类名(包括包路径)
- 这里获取了一个名为SystemUtils的工具类,通常用于系统级操作
systemutils.checkSuFile.implementation = function (){ return false; }
- 用途: Hook并修改
checkSuFile方法的行为,使其总是返回false - 知识点:
.implementation属性用于替换Java方法的实现- 这个方法原本可能用于检测设备是否已root(通过检查su文件)
- 返回false可以让应用认为设备未root,绕过root检测
systemutils.checkRootFile.implementation = function (){ return null; }
- 用途: Hook并修改
checkRootFile方法的行为,使其总是返回null - 知识点:
- 类似上面,这个方法可能用于检查root相关文件
- 返回null同样是为了欺骗应用,使其认为设备未root

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

或者直接hook系统方法exists()含有su路径直接返回null
要想着知道exists()方法属于哪个类,我们可以直接通过编写代码查看路径
1 | |
代码解释
脚本概述
这个Frida脚本主要用于监控和检测Android应用中与root权限检测相关的操作,特别是对su文件的检查和使用su命令的行为。与直接绕过检测的脚本不同,这个脚本采用了被动监控的方式,只记录检测行为而不阻止它们。
逐行详细解析
1.
Java.perform(function () { ... });- 用途: 这是Frida的标准入口点,确保其中的代码在Java运行时环境中执行
- 知识点:
- Frida通过注入到目标进程来工作,
Java.perform确保代码在Android应用的Java虚拟机上下文中执行 - 所有Java相关的Hook操作都应该放在这个回调函数中
- 这是必要的,因为Frida需要确保在正确的线程和类加载器上下文中执行操作
- Frida通过注入到目标进程来工作,
3.
showStacks()函数定义1
2
3
4
5
6
7
8function 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
7Java.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
7Java.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
10Java.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
10Java.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来执行系统命令
关键知识点总结
- Frida基础: Frida是一个动态代码插桩工具,允许注入JavaScript到原生应用中
- Java Hook原理: 通过修改方法的implementation属性来改变方法行为
- 方法重载处理: 使用overload()指定要Hook的方法的具体签名
- 调用栈分析: 通过Throwable对象获取调用栈,帮助定位代码执行路径
- Root检测技术: 了解常见的root检测方法有助于编写更有效的Hook脚本
- 被动监控 vs 主动防御: 这个脚本采用被动监控方式,只记录不阻止,适合分析阶段使用
技术细节说明
- 类型签名: 在Java中,
[Ljava.lang.String;表示一个String数组,Ljava.lang.String;表示一个String对象 - 方法重载: Java允许方法名相同但参数不同的重载方法,Frida需要使用overload()指定具体要Hook的版本
- 实现替换: 通过给方法的implementation属性赋值来替换方法实现,原始方法可以通过this.methodName()调用
- 调用栈分析: 创建Throwable对象会捕获当前的调用栈,这是Java中获取调用信息的标准方法

