Bypassing Root Detection and Emulator Detection in Android Apps using Frida

The Offensive Labs
9 min readMar 31, 2021

Introduction:

Root Detection is one of the most common client-side protection techniques used by Android Application developers. During a penetration test, it is often required to bypass root detection to be able to effectively pentest the application. In this article, we are going to cover root detection and emulator detection bypass techniques, which can be helpful for both pentesters as well as developers. First, we will begin by understanding what root detection and emulator detection are and then we will go through some bypass techniques for these.

What is root detection?

To understand root detection, let us first understand what rooting is. Rooting is the process of obtaining the highest privileges possible on the operating system. In android, rooting gives the ability to alter or replace system applications, files, and settings, run specialized applications (“apps”) that require administrator-level permissions.

Root detection is the process of detecting if a device is rooted. This is typically done when launching the application. Sometimes the root checks are implemented in the apps in such a way that the application will not respond or it will exit when it is run on a rooted device.

What is emulator detection?

When a security professional performs vulnerability assessments on mobile applications, some of them use emulators like Genymotion or the ones that come inbuilt with AndroidStudio. Some Application developers choose to implement the code to detect whether the application is being run on an emulator or android device. There are several ways emulator detection can be implemented such as by retrieving the hardware information.

A pentester needs to go through the source code by using reverse engineering with the help of tools like apktool or jadx. This will help him/her understand where and what type of detection is implemented.

Why should developers implement root/emulator detection as a security measure?

When an application is run on a rooted device, any malicious application running on the device will be able to access the data associated with our application, which is otherwise restricted in non rooted devices. Therefore developers need to implement complex code for detection of the root process in the device where the application is getting launched. Similarly, developers can use different techniques to detect if the app is being run on an emulator. Implementing emulator detection checks will force a user to use the app only on a physical device, which is reasonably safe from a security standpoint.

Introduction to Frida

Frida is an open-source dynamic instrumentation toolkit, which implements JavaScript code injection by writing code directly into process memory, along with a powerful API that provides a lot of useful functionality, including calling and hooking native functions and injecting structured data into memory.

By using frida we can inject the javascript code at runtime and bypass many checks which are implemented by developers such as root detection, SSL pinning, emulator detection etc. In this section of the article, we will discuss how to bypass root detection and emulator detection by using frida.

We will use androgoat, which is an intentionally vulnerable android app for this demonstration.

Bypassing Root detection using frida

In our lab setup, the Androgoat android application is installed to demonstrate the step by step process to bypass root detection.

Step 1: First, we will begin by using an emulator i.e virtual device from android studio for the demonstration. While exploring the application, there is a button named root detection which is intentionally designed to check if the device is rooted or not.

As shown in the following image, there is a toast message shown when the CHECK ROOT button is clicked. The message “Device is rooted” confirms that we are running the app on a rooted device.

Step 2: Pentester often needs to check the root detection code implemented by the developer by reverse engineering the target app, which is androgoat.apk in this case. This can be done using jadx or jd-gui.

Below is the code snippet in which the developer wants to check the existence of su binaries,superuser.apk, Xposed framework etc. These binaries and supersuer.apk are only found in rooted devices, and if these binaries and superuser.apk are present in the defined directory then the function isRooted() will return a boolean value true indicating that the device is rooted.

public final boolean isRooted()

{

String[] arrayOfString = new String[13];

int i = 0;

arrayOfString[0] = “/system/app/Superuser/Superuser.apk”;

arrayOfString[1] = “/system/app/Superuser.apk”;

arrayOfString[2] = “/sbin/su”;

arrayOfString[3] = “/system/bin/su”;

arrayOfString[4] = “/system/xbin/su”;

arrayOfString[5] = “/data/local/xbin/su”;

arrayOfString[6] = “/data/local/bin/su”;

arrayOfString[7] = “/system/sd/xbin/su”;

arrayOfString[8] = “/system/bin/failsafe/su”;

arrayOfString[9] = “/data/local/su”;

arrayOfString[10] = “/su/bin/su”;

arrayOfString[11] = “re.robv.android.xposed.installer-1.apk”;

arrayOfString[12] = “/data/app/eu.chainfire.supersu-1/base.apk”;

boolean bool = false;

int j = arrayOfString.length;

while (i < j)

{

bool = new File(arrayOfString[i]).exists();

if (bool) {

return bool;

}

i += 1;

}

return bool;

}

Step 3: Now, by using frida we can bypass this root detection functionality of the target android application. To do this, we need to write JavaScript code, which can be injected into the app’s runtime by using frida.

The code snippet shows that the implementation of isRooted function always returns true. As we can notice, the java function isRooted() is given a new implementation by specifying the package name and class name i.e owasp.sat.agoat and RootDetectionActivity respectively. The return value for this function isRooted() is set to be false. The androgoat’s original code returns true when root detection is successful. So we are returning the value as false during runtime to bypass this.

‘use strict’

if(Java.available){

Java.perform(function(){

try{

var Activity = Java.use(“owasp.sat.agoat.RootDetectionActivity”);

Activity.isRooted.implementation = function() {

return false;

}

}

catch(error){

console.log(“[-] Error Detected”);

console.log((error.stack));

}

});

}

else {

console.log(“ “)

console.log(“[-] Java is Not available”);

}

Step 4: Now we can save his code as rootdetect.js and inject the code using frida. Note that we should start the frida server in the emulator through adb shell before injecting the frida script into the app. The following figure shows that Frida server is started.

Step 5: Now, it’s time to inject the javascript code using frida. The script will provide a new definition to the function and when it is invoked, it will always return the value as false.

Let us run frida and load the script as shown in the following command. The launched application will be in paused state and running the command %resume will resume the application’s execution.

After injecting the frida script shown earlier into the app, we can observe that the root detection is bypassed. Access the application installed on the emulator and click the button “CHECK ROOT”.

The preceding image shows the toast message Device is not Rooted indicating that we have successfully bypassed root detection.

Bypassing Emulator detection using frida

Now that we have discussed how to bypass Root Detection in Androgoat Android application, let us discuss how we can use the same technique to bypass emulator detection in Androgoat.

As shown earlier, the Androgoat android application is installed to demonstrate the step by step process to bypass emulator detection in our lab setup,

Step 1: First, we will begin by using an emulator i.e virtual device from android studio for the demonstration. While exploring the application, there is a button named emulator detection, Inside which there is a button which is for checking emulator i.e “Check emulator”. It is obvious that this button is checking whether the application is running on an emulator or not.

As shown in the following image, there is a toast message shown when the CHECK EMULATION button is clicked. The message “This is Emulator” confirms that we are running the app on an emulator.

Step 2: Like we did earlier, we will need to check the emulator detection code implemented by the developer by reverse engineering the target app using tools like jadx or jd-gui.

Below is the code snippet in which the developer is checking if the app is running on an emulator.

public final boolean isEmulator()

{

Object localObject = new StringBuilder();

((StringBuilder)localObject).append(Build.FINGERPRINT);

((StringBuilder)localObject).append(Build.DEVICE);

((StringBuilder)localObject).append(Build.MODEL);

((StringBuilder)localObject).append(Build.BRAND);

((StringBuilder)localObject).append(Build.PRODUCT);

((StringBuilder)localObject).append(Build.MANUFACTURER);

((StringBuilder)localObject).append(Build.HARDWARE);

localObject = ((StringBuilder)localObject).toString();

if (localObject != null)

{

localObject = ((String)localObject).toLowerCase();

Intrinsics.checkExpressionValueIsNotNull(localObject, “(this as java.lang.String).toLowerCase()”);

CharSequence localCharSequence1 = (CharSequence)localObject;

CharSequence localCharSequence2 = (CharSequence)”generic”;

boolean bool = false;

if ((StringsKt.contains$default(localCharSequence1, localCharSequence2, false, 2, null)) || (StringsKt.contains$default((CharSequence)localObject, (CharSequence)”unknown”, false, 2, null)) || (StringsKt.contains$default((CharSequence)localObject, (CharSequence)”emulator”, false, 2, null)) || (StringsKt.contains$default((CharSequence)localObject, (CharSequence)”sdk”, false, 2, null)) || (StringsKt.contains$default((CharSequence)localObject, (CharSequence)”vbox”, false, 2, null)) || (StringsKt.contains$default((CharSequence)localObject, (CharSequence)”genymotion”, false, 2, null)) || (StringsKt.contains$default((CharSequence)localObject, (CharSequence)”x86", false, 2, null)) || (StringsKt.contains$default((CharSequence)localObject, (CharSequence)”goldfish”, false, 2, null)) || (StringsKt.contains$default((CharSequence)localObject, (CharSequence)”test-keys”, false, 2, null))) {

bool = true;

}

return bool;

In the preceding code snippet, isEmulator() is checking for some values from the device configuration. If the extracted configuration contains strings like emulator, unknown, sdk, vbox, genymotion the device will be considered an emulator and the function return a boolean value true indicating that the app is running on an emulator.

Step 3: Once again, let us see the steps to bypass this check using frida. Let us first ensure that the frida server is running on the emulator as shown in the figure below.

Step 4: Following is the frida script written to bypass the emulator detection check in Androgiat. The script has a custom implementation for the method isEmulator within the class owasp.sat.agoat.EmulatorDetectionActivity. Every time the isEmulator method is triggered in the app, this custom implementation will be executed. This implementation always returns false fooling the emulator detection check.

‘use strict’

if(Java.available){

Java.perform(function(){

try{

var Activity = Java.use(“owasp.sat.agoat.EmulatorDetectionActivity”);

Activity.isEmulator.implementation = function() {

return false;

}

}

catch(error){

console.log(“[-] Error Detected”);

console.log((error.stack));

}

});

}

else {

console.log(“ “)

console.log(“[-] Java is Not available”);

}

Step 5: Let us save this code as “emulator detection.js” and inject this js file into the application’s runtime using the following command.

frida -l “emulator detection.js” -U -f owasp.sat.agoat

Running this command will launch the application and the execution will be paused. We can type the command %resume to resume the execution of the app.

After running the preceding commands, we can observe that the emulator detection is bypassed. Access the application installed on the emulator and click the button “CHECK EMULATOR”. If everything went as expected, we should observe the toast message “This is not an Emulator” as shown in the following figure.

The preceding figure shows that we have successfully bypassed emulator detection in Androgoat application.

Conclusion:

It is apparent that client-side protections such as root detection and emulator detection can be great to have but they may be easily bypassed using tools like Frida. Complex root detection implementations can slow down the attackers a bit, and such implementations should be considered by developers even though no client-side protections are foolproof. We have also seen the power of Frida in this article. It can help us write powerful scripts to play with the app’s runtime.

If you would like to learn more about Android Security check out our course on “Hacking and Pentesting Android Applications”

This article was written by Sushma Ahuja for The Offensive Labs.

--

--