在启动这个app时,我们会看到一个提示,表示设备处于root环境。如下图所示:
为了过掉到这个root检测,我们可以通过直接Hook Toast.show()方法,并打印调用堆栈信息来实现定位关键代码。以下是相关的Frida脚本代码:
Java.perform(function () {
function showStacks() {
console.log(
Java.use("android.util.Log")
.getStackTraceString(
Java.use("java.lang.Throwable").$new()
)
);
}
// Hook the android.widget.Toast class
var Toast = Java.use("android.widget.Toast");
Toast.show.implementation = function () {
showStacks();
console.log("Toast.show() called!");
console.log("Toast message: " + this.getView().getAccessibilityClassName());
this.show();
};
});
调用堆栈信息如下:
[Pixel 4::com.hoge.android.app.fujian]-> java.lang.Throwable
at android.widget.Toast.show(Native Method)
at com.hoge.android.util.CustomToast.showToast(CustomToast.java:172)
at com.hoge.android.factory.welcome.WelcomeActivity$2$1.run(WelcomeActivity.java:260)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
从堆栈信息中,我们可以看到关键函数为com.hoge.android.factory.welcome.WelcomeActivity$2$1.run()。我们可以使用JEB反编译APK,并查看代码,发现如下内容:
Util.getHandler(WelcomeActivity.this.mContext).postDelayed(new Runnable() {
@Override
public void run() {
if((SystemUtils.checkSuFile()) && SystemUtils.checkRootFile() != null) {
CustomToast.showToast(WelcomeActivity.this.mContext, WelcomeActivity.this.getString(0x7F11071B), 100); // string:root_toast "当前设备可能处于root环境,继续运行应用将有风险"
}
}
}, 1000L);
因此这里的关键函数为SystemUtils.checkSuFile()和SystemUtils.checkRootFile(),继续跟进这两个函数,看看究竟是怎么进行检测root环境的。checkSuFile()函数的代码如下:
public static boolean checkSuFile() {
Process process0 = null;
try {
// 尝试执行系统命令 "which su" 来检查是否存在 su 文件
process0 = Runtime.getRuntime().exec(new String[]{"which", "su"});
// 读取命令的输出,检查输出的第一行
String s = new BufferedReader(new InputStreamReader(process0.getInputStream())).readLine();
}
catch (Throwable unused_ex) {
if (process0 != null) {
process0.destroy();
}
return false;
}
catch (Throwable throwable0) {
if (process0 != null) {
process0.destroy();
}
throw throwable0;
}
// 如果读取到的结果不为null,说明设备上存在su文件
if (s != null) {
if (process0 != null) {
process0.destroy();
}
return true;
}
if (process0 != null) {
process0.destroy();
}
return false;
}
checkSuFile()函数核心就是通过which su命令检测su文件是否存在。接下来继续查看SystemUtils.checkRootFile()代码:
public static File checkRootFile() {
//包含可能存在的root文件路径
String[] arr_s = {
"/sbin/su",
"/system/bin/su",
"/system/xbin/su",
"/data/local/xbin/su",
"/data/local/bin/su",
"/system/sd/xbin/su",
"/system/bin/failsafe/su",
"/data/local/su"
};
File file0 = null;
int v = 0;
// 遍历所有路径
while (v < arr_s.length) {
File file1 = new File(arr_s[v]);
// 检查当前路径的文件是否存在
if (file1.exists()) {
return file1;
}
++v;
file0 = file1;
}
return file0;
}
checkRootFile()函数功能也相对简单,就是检测su文件可能存在路径。因此过掉这个这个root检测的方法很简单,只需要将checkSuFile()函数返回false,checkRootFile()函数返回null即可。Frida hook代码如下:
Java.perform(function() {
var SystemUtils = Java.use('com.hoge.android.factory.util.system.SystemUtils');
SystemUtils.checkSuFile.implementation = function() {
console.log('checkSuFile() is call');
return false; // 返回 false
};
SystemUtils.checkRootFile.implementation = function() {
console.log('checkRootFile() is call');
return null;
};
});