Pentest Logo

Research

Android Root Detection Bypass using Frida (Part 3 – OWASP Uncrackable 3)

Researchers:

Richard Mason

This is the third part in a blog series I am creating about bypassing various Android application’s root detection mechanisms. Please see part 2 here and part 1 here.

In this part, we are going to be looking to bypass OWASP’s Uncrackable 3. I know, I know, we haven’t looked at Uncrackable 2, but that’s for good reason. To bypass Uncrackable 2, all we have to do is follow the exact same process we used to bypass Uncrackable 1 and Rootbeer, so not writing about it is a good way to put what we have learned to the test.

————

*Before I show how I have bypassed the root/emulator detection I should say that I’m not an expert at this and the way I have performed the bypass might not be the most efficient or “correct” way of doing so. At one point or another we have all been at the start of our learning journeys so please take this into consideration.

 

As before, you’re going to need some equipment and tools to perform the bypass:

  • Rooted Android device (I’m using an emulated device using Android Studio which has been rooted using rootAVD)
  • ADB
  • Frida
  • JADX
  • Ghidra
  • Anything you want to use to edit code

So let’s begin by starting how we always start. Let’s search for the string displayed when the application starts and detects the rooted device.

As we saw in the previous applications the string is called when a condition is met within a function, this time the functions are called checkRoot1, checkRoot2, checkRoot3 and isDebuggable.

And just like I’m repeating myself and copying from the previous posts, we are gonna search for these functions.

Then of course we will copy the function as a Frida snippet.

We all know what’s coming next. We place the snippet within a function and change the return value to false.

				
					Java.perform(function() {
  let RootDetection = Java.use("sg.vantagepoint.util.RootDetection");
  RootDetection["checkRoot1"].implementation = function () {
    console.log('checkRoot1 is called');
    let ret = this.checkRoot1();
    console.log('checkRoot1 ret value is ' + ret);
    return false;
  };
});
				
			

So let’s run the code and see what happens.

				
					.\frida.exe -U -l bypass3.js -f owasp.mstg.uncrackable3
     ____
    / _  |   Frida 16.0.2 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/
   . . . .
   . . . .   Connected to Android Emulator 5554 (id=emulator-5554)
Spawned `owasp.mstg.uncrackable3`. Resuming main thread!
[Android Emulator 5554::owasp.mstg.uncrackable3 ]-> checkRoot1 is called
checkRoot1 ret value is true
Process crashed: Trace/BPT trap

***
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'google/sdk_gphone_x86/generic_x86_arm:11/RSR1.201013.001/6903271:user/release-keys'
Revision: '0'
ABI: 'x86'
Timestamp: 2022-12-23 09:40:47+0000
pid: 10321, tid: 10354, name: tg.uncrackable3  >>> owasp.mstg.uncrackable3 <<<
uid: 10159
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
    eax 00000000  ebx 00002851  ecx 00002872  edx 00000006
    edi 00000006  esi 00002851
    ebp b8ef0f18  esp b8ef0ecc  eip e9dbcb99
backtrace:
      #00 pc 00000b99  [vdso] (__kernel_vsyscall+9)
      #01 pc 000ccc0c  /apex/com.android.runtime/lib/bionic/libc.so!libc.so (offset 0xcb000) (tgkill+28) (BuildId: 6e3a0180fa6637b68c0d181c343e6806)
      #02 pc 00084d24  /apex/com.android.runtime/lib/bionic/libc.so!libc.so (offset 0x7a000) (raise+68) (BuildId: 6e3a0180fa6637b68c0d181c343e6806)
      #03 pc 00003021  /data/app/~~bXW_fW3b1CCvmdCA29eaLQ==/owasp.mstg.uncrackable3-nonz_pThqMiBqPRPABFU5Q==/lib/x86/libfoo.so (goodbye()+33) (BuildId: 59b27d3ad212b5435a2b89003d9bf36b3bcf3aa5)
      #04 pc 00003179  /data/app/~~bXW_fW3b1CCvmdCA29eaLQ==/owasp.mstg.uncrackable3-nonz_pThqMiBqPRPABFU5Q==/lib/x86/libfoo.so (BuildId: 59b27d3ad212b5435a2b89003d9bf36b3bcf3aa5)
      #05 pc 000e6974  /apex/com.android.runtime/lib/bionic/libc.so!libc.so (offset 0xe2000) (__pthread_start(void*)+100) (BuildId: 6e3a0180fa6637b68c0d181c343e6806)
      #06 pc 00078567  /apex/com.android.runtime/lib/bionic/libc.so!libc.so (offset 0x77000) (__start_thread+71) (BuildId: 6e3a0180fa6637b68c0d181c343e6806)
***
[Android Emulator 5554::owasp.mstg.uncrackable3 ]->

Thank you for using Frida!
				
			

This time we do not bypass the root detection and receive an error. So let’s investigate what is actually going on here.

So, we can see in the error message that it gives reference to a function called “goodbye()”. As it looks like native checks are now implemented in this application we are going to import the apk to Ghidra to figure out how to bypass them.

So, we can see that within the function “FUN_001030d0” that goodbye is called.

I think what we need to do here is dissect the function and figure out what it is actually doing here. So, the first thing this function is doing is opening “/proc/self/maps” and storing the contents within a variable. The application then checks the content of the variable and if it contains the string “frida” or “xposed” it will call the goodbye function and crash the application.

For anyone that doesn’t know, /proc/self/maps is used to continuously list virtual memory in a process or thread. So, when we use Frida to hook into the application, the string “frida” will show up and is the reason why the application crashes when we run our current root detection bypass script.

To get around this we are gonna get a bit more technical with Frida and use more of the API. As we are trying to bypass a native check we are going to be using the Interceptor API which will intercept calls to functions at a specified target. The documentation can be found here: JavaScript API | Frida • A world-class dynamic instrumentation toolkit

The below is what we have come up with.

				
					Interceptor.attach(Module.findExportByName("libc.so", "strstr"), {
  onEnter(args) {
    if (args[0].readUtf8String().indexOf("frida") != -1) {
      this.fridaDetection = 1;
    }
  },
  onLeave(retval) {
    if (this.fridaDetection = 1) {
      retval.replace(0);
    }
  }
});
				
			

As the application uses strstr to check for “frida” in the variable storing /proc/self/maps thats what we want to try and intercept. The onEnter function in our code checks whether the first argument passed to the strstr function contains “frida”. If it does, it sets a property on the current interception object to 1. The onLeave function then checks the value of the property and, if it is 1, replaces the return value of the intercepted function with 0.

So once it is all combined we have something like this.

				
					Java.perform(function() {
  let RootDetection = Java.use("sg.vantagepoint.util.RootDetection");
  RootDetection["checkRoot1"].implementation = function () {
    console.log('checkRoot1 is called');
    let ret = this.checkRoot1();
    console.log('checkRoot1 ret value is ' + ret);
    return false;
  };
  Interceptor.attach(Module.findExportByName("libc.so", "strstr"), {
    onEnter(args) {
      if (args[0].readUtf8String().indexOf("frida") != -1) {
        this.fridaDetection = 1;
      }
    },
    onLeave(retval) {
      if (this.fridaDetection = 1) {
        retval.replace(0);
      }
    }
  });
});
				
			

Let’s run it and see what happens.

				
					$.\frida.exe -U -l bypass3.js -f owasp.mstg.uncrackable3
     ____
    / _  |   Frida 16.0.2 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/
   . . . .
   . . . .   Connected to Android Emulator 5554 (id=emulator-5554)
Spawned `owasp.mstg.uncrackable3`. Resuming main thread!
[Android Emulator 5554::owasp.mstg.uncrackable3 ]-> checkRoot1 is called
checkRoot1 ret value is true
				
			

And voilà we have done it.

Hopefully you have found this run through somewhat helpful and has given you an understanding of using Ghidra and the Frida API in more detail. Like I said at the start of the write up, this might not be the most efficient or be the “correct” way to bypass the root/frida detection, but I am figuring this out as I go along. If there is any help or tips that you can give me to get better that would be much appreciated.

Looking for more than just a test provider?

Get in touch with our team and find out how our tailored services can provide you with the cybersecurity confidence you need.