Root Detection Bypass Using Frida-tools

Shadab Ahmed Ansari
8 min readNov 17, 2022

--

What is root detection bypass?

When an application is developed, developers implement a root detection mechanism to prevent the user from using that on a rooted android device. When a user tries to install some application, it throws an error message and doesn’t allow it to install on the conventional device. While performing the root bypass we make changes in the code and restrict the application from closing which further leads to the installation of the application on rooted android device. So to perform this, first of all, I will install all the dependencies.

Before we move ahead let's have a quick knowledge of what exactly ADB & Frida tools are

What is ADB & Frida-tools?

Android Debug Bridge (ADB) is a versatile command-line tool that lets you communicate with a device. The ADB command facilitates a variety of device actions, such as installing and debugging apps, and it provides access to a Unix shell that you can use to run a variety of commands on a device.

Frida is a dynamic code instrumentation toolkit. It allows you to inject your own code and to programmatically and interactively inspect and change running processes.

How to install ADB & Frida-tools

Step 1:- Download ADB and open the folder run cmd. Run ADB to check whether it's working as expected.

Step 2:- To install Frida run “pip3 install frida-tools” in cmd.

Step 3:- Check whether it's running as expected, “frida-ps”.

Steps 4:- After that to run our own script we will be installing the frida server in android.

Step 5:- Download the frida-server compatible with the android device, for example, 64,86,arm64,arm86 bit.

Step 6:- After the downloaded is completed, extract the file and change the file name to frida-server for easy execution.

Step 7:- Now in my case, I am using the genymotion for a virtual android device. To check whether the device is connected to our system run “adb devices”.

Step 8:- Now to push the frida-server file into the device we will copy the downloaded file and move the server file into ADB folder.

Step 9:- To push the server file we will run the below commands for pushing the file and execution of the server file.

“adb push “FRIDA_FILE_NAME” /data/local/tmp”

“adb shell”

“su” (run as a root for android terminal)

“cd /data/local/tmp/”

“chmod +x “FRIDA_FILE_NAME”” (To make the file executable will provide the required access to the file)

“./frida-server” (After successful setup, will run the frida server)

“frida-ps -U” (to check whether the frida server is running properly)

For root detection bypass attacks, we have successfully set up all the prerequisites to move further.

Let's begin with the steps on how to initiate the root detection bypass.

Step 1:- To do so first will create a fridantiroot.py file,

Java.perform(function() {
var RootPackages = [“com.noshufou.android.su”, “com.noshufou.android.su.elite”, “eu.chainfire.supersu”,
“com.koushikdutta.superuser”, “com.thirdparty.superuser”, “com.yellowes.su”, “com.koushikdutta.rommanager”,
“com.koushikdutta.rommanager.license”, “com.dimonvideo.luckypatcher”, “com.chelpus.lackypatch”,
“com.ramdroid.appquarantine”, “com.ramdroid.appquarantinepro”, “com.devadvance.rootcloak”, “com.devadvance.rootcloakplus”,
“de.robv.android.xposed.installer”, “com.saurik.substrate”, “com.zachspong.temprootremovejb”, “com.amphoras.hidemyroot”,
“com.amphoras.hidemyrootadfree”, “com.formyhm.hiderootPremium”, “com.formyhm.hideroot”, “me.phh.superuser”,
“eu.chainfire.supersu.pro”, “com.kingouser.com”, “com.topjohnwu.magisk”
];

var RootBinaries = [“su”, “busybox”, “supersu”, “Superuser.apk”, “KingoUser.apk”, “SuperSu.apk”, “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 PackageManager = Java.use(“android.app.ApplicationPackageManager”);

var Runtime = Java.use(‘java.lang.Runtime’);

var NativeFile = Java.use(‘java.io.File’);

var String = Java.use(‘java.lang.String’);

var SystemProperties = Java.use(‘android.os.SystemProperties’);

var BufferedReader = Java.use(‘java.io.BufferedReader’);

var ProcessBuilder = Java.use(‘java.lang.ProcessBuilder’);

var StringBuffer = Java.use(‘java.lang.StringBuffer’);

var loaded_classes = Java.enumerateLoadedClassesSync();

send(“Loaded “ + loaded_classes.length + “ classes!”);

var useKeyInfo = false;

var useProcessManager = false;

send(“loaded: “ + loaded_classes.indexOf(‘java.lang.ProcessManager’));

if (loaded_classes.indexOf(‘java.lang.ProcessManager’) != -1) {
try {
//useProcessManager = true;
//var ProcessManager = Java.use(‘java.lang.ProcessManager’);
} catch (err) {
send(“ProcessManager Hook failed: “ + err);
}
} else {
send(“ProcessManager hook not loaded”);
}

var KeyInfo = null;

if (loaded_classes.indexOf(‘android.security.keystore.KeyInfo’) != -1) {
try {
//useKeyInfo = true;
//var KeyInfo = Java.use(‘android.security.keystore.KeyInfo’);
} catch (err) {
send(“KeyInfo Hook failed: “ + err);
}
} else {
send(“KeyInfo hook not loaded”);
}

PackageManager.getPackageInfo.overload(‘java.lang.String’, ‘int’).implementation = function(pname, flags) {
var shouldFakePackage = (RootPackages.indexOf(pname) > -1);
if (shouldFakePackage) {
send(“Bypass root check for package: “ + pname);
pname = “set.package.name.to.a.fake.one.so.we.can.bypass.it”;
}
return this.getPackageInfo.overload(‘java.lang.String’, ‘int’).call(this, pname, flags);
};

NativeFile.exists.implementation = function() {
var name = NativeFile.getName.call(this);
var shouldFakeReturn = (RootBinaries.indexOf(name) > -1);
if (shouldFakeReturn) {
send(“Bypass return value for binary: “ + name);
return false;
} else {
return this.exists.call(this);
}
};

var exec = Runtime.exec.overload(‘[Ljava.lang.String;’);
var exec1 = Runtime.exec.overload(‘java.lang.String’);
var exec2 = Runtime.exec.overload(‘java.lang.String’, ‘[Ljava.lang.String;’);
var exec3 = Runtime.exec.overload(‘[Ljava.lang.String;’, ‘[Ljava.lang.String;’);
var exec4 = Runtime.exec.overload(‘[Ljava.lang.String;’, ‘[Ljava.lang.String;’, ‘java.io.File’);
var exec5 = Runtime.exec.overload(‘java.lang.String’, ‘[Ljava.lang.String;’, ‘java.io.File’);

exec5.implementation = function(cmd, env, dir) {
if (cmd.indexOf(“getprop”) != -1 || cmd == “mount” || cmd.indexOf(“build.prop”) != -1 || cmd == “id” || cmd == “sh”) {
var fakeCmd = “grep”;
send(“Bypass “ + cmd + “ command”);
return exec1.call(this, fakeCmd);
}
if (cmd == “su”) {
var fakeCmd = “justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled”;
send(“Bypass “ + cmd + “ command”);
return exec1.call(this, fakeCmd);
}
return exec5.call(this, cmd, env, dir);
};

exec4.implementation = function(cmdarr, env, file) {
for (var i = 0; i < cmdarr.length; i = i + 1) {
var tmp_cmd = cmdarr[i];
if (tmp_cmd.indexOf(“getprop”) != -1 || tmp_cmd == “mount” || tmp_cmd.indexOf(“build.prop”) != -1 || tmp_cmd == “id” || tmp_cmd == “sh”) {
var fakeCmd = “grep”;
send(“Bypass “ + cmdarr + “ command”);
return exec1.call(this, fakeCmd);
}

if (tmp_cmd == “su”) {
var fakeCmd = “justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled”;
send(“Bypass “ + cmdarr + “ command”);
return exec1.call(this, fakeCmd);
}
}
return exec4.call(this, cmdarr, env, file);
};

exec3.implementation = function(cmdarr, envp) {
for (var i = 0; i < cmdarr.length; i = i + 1) {
var tmp_cmd = cmdarr[i];
if (tmp_cmd.indexOf(“getprop”) != -1 || tmp_cmd == “mount” || tmp_cmd.indexOf(“build.prop”) != -1 || tmp_cmd == “id” || tmp_cmd == “sh”) {
var fakeCmd = “grep”;
send(“Bypass “ + cmdarr + “ command”);
return exec1.call(this, fakeCmd);
}

if (tmp_cmd == “su”) {
var fakeCmd = “justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled”;
send(“Bypass “ + cmdarr + “ command”);
return exec1.call(this, fakeCmd);
}
}
return exec3.call(this, cmdarr, envp);
};

exec2.implementation = function(cmd, env) {
if (cmd.indexOf(“getprop”) != -1 || cmd == “mount” || cmd.indexOf(“build.prop”) != -1 || cmd == “id” || cmd == “sh”) {
var fakeCmd = “grep”;
send(“Bypass “ + cmd + “ command”);
return exec1.call(this, fakeCmd);
}
if (cmd == “su”) {
var fakeCmd = “justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled”;
send(“Bypass “ + cmd + “ command”);
return exec1.call(this, fakeCmd);
}
return exec2.call(this, cmd, env);
};

exec.implementation = function(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” || tmp_cmd == “sh”) {
var fakeCmd = “grep”;
send(“Bypass “ + cmd + “ command”);
return exec1.call(this, fakeCmd);
}

if (tmp_cmd == “su”) {
var fakeCmd = “justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled”;
send(“Bypass “ + cmd + “ command”);
return exec1.call(this, fakeCmd);
}
}

return exec.call(this, cmd);
};

exec1.implementation = function(cmd) {
if (cmd.indexOf(“getprop”) != -1 || cmd == “mount” || cmd.indexOf(“build.prop”) != -1 || cmd == “id” || cmd == “sh”) {
var fakeCmd = “grep”;
send(“Bypass “ + cmd + “ command”);
return exec1.call(this, fakeCmd);
}
if (cmd == “su”) {
var fakeCmd = “justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled”;
send(“Bypass “ + cmd + “ command”);
return exec1.call(this, fakeCmd);
}
return exec1.call(this, cmd);
};

String.contains.implementation = function(name) {
if (name == “test-keys”) {
send(“Bypass test-keys check”);
return false;
}
return this.contains.call(this, name);
};

var get = SystemProperties.get.overload(‘java.lang.String’);

get.implementation = function(name) {
if (RootPropertiesKeys.indexOf(name) != -1) {
send(“Bypass “ + name);
return RootProperties[name];
}
return this.get.call(this, name);
};

Interceptor.attach(Module.findExportByName(“libc.so”, “fopen”), {
onEnter: function(args) {
var path = Memory.readCString(args[0]);
path = path.split(“/”);
var executable = path[path.length — 1];
var shouldFakeReturn = (RootBinaries.indexOf(executable) > -1)
if (shouldFakeReturn) {
Memory.writeUtf8String(args[0], “/notexists”);
send(“Bypass native fopen”);
}
},
onLeave: function(retval) {

}
});

Interceptor.attach(Module.findExportByName(“libc.so”, “system”), {
onEnter: function(args) {
var cmd = Memory.readCString(args[0]);
send(“SYSTEM CMD: “ + cmd);
if (cmd.indexOf(“getprop”) != -1 || cmd == “mount” || cmd.indexOf(“build.prop”) != -1 || cmd == “id”) {
send(“Bypass native system: “ + cmd);
Memory.writeUtf8String(args[0], “grep”);
}
if (cmd == “su”) {
send(“Bypass native system: “ + cmd);
Memory.writeUtf8String(args[0], “justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled”);
}
},
onLeave: function(retval) {

}
});

/*

TO IMPLEMENT:

Exec Family

int execl(const char *path, const char *arg0, …, const char *argn, (char *)0);
int execle(const char *path, const char *arg0, …, const char *argn, (char *)0, char *const envp[]);
int execlp(const char *file, const char *arg0, …, const char *argn, (char *)0);
int execlpe(const char *file, const char *arg0, …, const char *argn, (char *)0, char *const envp[]);
int execv(const char *path, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

*/

BufferedReader.readLine.overload(‘boolean’).implementation = function() {
var text = this.readLine.overload(‘boolean’).call(this);
if (text === null) {
// just pass , i know it’s ugly as hell but test != null won’t work :(
} else {
var shouldFakeRead = (text.indexOf(“ro.build.tags=test-keys”) > -1);
if (shouldFakeRead) {
send(“Bypass build.prop file read”);
text = text.replace(“ro.build.tags=test-keys”, “ro.build.tags=release-keys”);
}
}
return text;
};

var executeCommand = ProcessBuilder.command.overload(‘java.util.List’);

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) {
send(“Bypass ProcessBuilder “ + cmd);
this.command.call(this, [“grep”]);
return this.start.call(this);
}
if (cmd.indexOf(“su”) != -1) {
send(“Bypass ProcessBuilder “ + cmd);
this.command.call(this, [“justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled”]);
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’);
var ProcManExecVariant = ProcessManager.exec.overload(‘[Ljava.lang.String;’, ‘[Ljava.lang.String;’, ‘java.lang.String’, ‘java.io.FileDescriptor’, ‘java.io.FileDescriptor’, ‘java.io.FileDescriptor’, ‘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”];
send(“Bypass “ + cmdarr + “ command”);
}

if (tmp_cmd == “su”) {
var fake_cmd = [“justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled”];
send(“Bypass “ + cmdarr + “ command”);
}
}
return ProcManExec.call(this, fake_cmd, env, workdir, redirectstderr);
};

ProcManExecVariant.implementation = function(cmd, env, directory, stdin, stdout, stderr, redirect) {
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”];
send(“Bypass “ + cmdarr + “ command”);
}

if (tmp_cmd == “su”) {
var fake_cmd = [“justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled”];
send(“Bypass “ + cmdarr + “ command”);
}
}
return ProcManExecVariant.call(this, fake_cmd, env, directory, stdin, stdout, stderr, redirect);
};
}

if (useKeyInfo) {
KeyInfo.isInsideSecureHardware.implementation = function() {
send(“Bypass isInsideSecureHardware”);
return true;
}
}

});

Step 2:- Save the file as fridantiroot.py and move the file into ADB folder.

Step 3:- Now, push the script in the android device using adb, “adb push “SCRIPT_NAME.py /data/local/tmp/””

Step 4:- will run the frida script to bypass root detection

“frida -U — codeshare -l “SCRIPT_NAME” -f “APPLICATION_PACKAGE_NAME””

NOTE: I HAVE USED PYTHON, WHERE IT'S NOT REQUIRED TO MENTION THE EXTENSION IN THIS CASE

Figure 1: Running frida scripts
Figure 2: Successfully bypass

Thank you and please don't forget to like the blog :).

--

--

Shadab Ahmed Ansari
Shadab Ahmed Ansari

Written by Shadab Ahmed Ansari

Senior Security Analyst | Penetration Tester

No responses yet