OWASP UnCrackable 3 Android Application

Mohannad Handoumeh
T3CH
Published in
8 min readSep 12, 2024

Continuing the OWASPs Uncrackable challenges that I have started before by solving Uncrackable 1 and Uncrackable 2 challenges, I will continue solving Uncrackable 3.

What is special in this challenge?

  • It has anti-tampering protection, so we can’t bypass root detection by tampering the smali code as in the previous challenges.
  • It has Frida protection, the mobile application will be terminated if Frida is being used.

Ok, let’s start hacking through the mobile application.

I started by installing the mobile application into Genymotion emulator.

After running the application, a pop-up came out which say that “Rooting or tampering detected” which is because the Android device is rooted.

Let’s reverse-engineer the mobile application and see what is the issue.

By tracing the message that popped up, we found that there is an if statement that checks on 6 methods if they are not equal to 0, if they are not equal to 0 it will call the method showDialog with a string as shown in the image.

The methods that we will check on are the checkroot methods, lets first view the showDialog.

By viewing the showDialog method, it will create a pop-up message with the string that has been passed to it as a title when the method is called and set a message, and it has an “OK” button that will exit from the mobile application after clicking it.

Ok, let’s get back to the checkroot methods that are being called from the RootDetection class.

From what we got from the class, it has 3 root detection methods, the first one will check on the “su” binary in the mobile device, the second will check on the build-tags of the mobile device if it contain a string called “test-keys” and the third one will check on a specific binaries and files inside of the mobile device.

These methods can be bypassed easily by using Frida.

Java.perform(function(){
let RootDetection = Java.use("sg.vantagepoint.util.RootDetection");
RootDetection.checkRoot1.implementation = function(){
console.log("[+] Bypassing the first Root Detection check")
return false
}
RootDetection.checkRoot2.implementation = function(){
console.log("[+] Bypassing the second Root Detection check")
return false
}
RootDetection.checkRoot3.implementation = function(){
console.log("[+] Bypassing the third Root Detection check")
return false
}
})

This code will hook the 3 checkroot methods and it will make them return false. Let’s use this code using Frida on the mobile application.

After running Frida with the JS code to bypass the root protection, Frida and the mobile application crashed which is strange. Went through mobile device logs using logcat to see what did go wrong and found this.

From the logs, we notice at first that it is giving us the same message from Frida, and also in the red square we notice that from the libfoo.so shared library it is calling goodbye function.

Going through the APK again, we found that the application is loading a shared library “foo”, which is the same library that we saw in the logs of the mobile device.

After using apktool to decompile the APK, used ghidra to reverse-engineer the shared library and analyze it.

First I checked the function that I have found in the logs of the mobile device and found that goodbye function will only exit the mobile application, but where is this function is being called?

After analyzing the library found that there is _INIT_0 function which is an initialization function, Initialization functions are special functions in programming that are responsible for setting up the initial state of a system, these functions are being called at the beginning of the code.

As shown in the image, the _INIT0_ function is creating a new thread to execute a function “FUN_001037c0”.

By analyzing the function, we see that the goodbye function is being called from this function.

Also, we see that the function is opening “/proc/self/maps” file from the mobile device in read mode, then it does a loop and reads the content of the file, line by line and store it into “acStack_238” variable, after that it will call the function strstr with two values “acStack_238” variable and the word frida, strstr function returns a pointer to the first index of a specified substring in another string, so basically it will check if the word Frida is in the variable “acStack_238”, if the word Frida found it will break from the loop and call the goodbye function and exit from the mobile application.

So can this protection be bypassed?

The answer is yes, and of course by using Frida.

Using Frida we can hook the function strstr and replace the return value of this function to always be 0, by doing this it will not break out from the loop and it won’t call the goodbye function.

Java.perform(function(){
var strstr_fun = Module.findExportByName(null,"strstr")
Interceptor.attach(strstr_fun,{
onEnter : function(args){
this.frida = ptr(args[0]).readCString()
if (this.frida.indexOf("frida")>=0){
console.log("[+] frida have been detected")
}
},
onLeave: function(retval){
retval.replace(0)
}
})
let RootDetection = Java.use("sg.vantagepoint.util.RootDetection");
RootDetection.checkRoot1.implementation = function(){
console.log("[+] Bypassing the first Root Detection check")
return false
}
RootDetection.checkRoot2.implementation = function(){
console.log("[+] Bypassing the second Root Detection check")
return false
}
RootDetection.checkRoot3.implementation = function(){
console.log("[+] Bypassing the third Root Detection check")
return false
}
})

In this code, we’re chaining two methods: the first one bypasses Frida detection, and the second one bypasses root detection.

We bypassed Frida protection by hooking the strstr function to inspect its arguments and manipulate its return value, let’s explain more.

var strstr_fun = Module.findExportByName(null, "strstr")

This line uses Frida’s Module.findExportByName to locate the strstr function in the target process.

Interceptor.attach(strstr_fun,{
onEnter : function(args){
this.frida = ptr(args[0]).readCString()
if (this.frida.indexOf("frida")>=0){
console.log("[+] frida has been detected")
}
}

Then it will attach an interceptor on the location of strstr function.

onEnter is executed when the strstr function is called, it will read the string from memory at the pointer args[0] and converts it to a readable C string, then stores it intothis.frida” variable.

If the word Frida is in the this.frida” variable it will print that “Frida has been detected”.

onLeave: function(retval){
retval.replace(0)
}

onLeave is executed after strstr finishes, after that it will replace the retval (return value) to always be 0, if the value of the strstr always returns 0 it will bypass the Frida protection and won’t break out from the loop nor call the goodbye function.

Let’s try executing the code using Frida.

Great, we managed to bypass Frida protection and root detection.

Now after we managed to bypass Frida detection and root detection, we need to figure out the secret string.

Let’s get back to the code of the APK and analyze it.

At the beginning of the class, we noticed that it is creating a global variable xorkey, from the name of the variable it will be used to make an xor operation.

As we can see in the onCreate function, it will call the init function and pass the xorkey variable to it.

The init is being called from the libfoo.so shared library.

The init function will store the xorkey value into the _src variable, and then it will call the strncpy function, it will copy the value of _src into a destination buffer “DAT_00107040”.

So basically, it will store that value of the xorkey into a buffer to use it later.

Continuing analyzing the APK code, we found the method that will check on our input, it will pass our input into check_code method.

The check_code method will take our input and pass it to bar function which is a function that is being called from the shared library libfoo.so.

The bar function will call FUN_001012c0the function and pass the local_48 variable, then it will use two variables lVar2 and iVar1, lVar2 will be our input and iVar1 the length of our input, it will check the length of our input if the length is 24 it will proceed to the while do loop.

In the loop, it will make an if statement, in the if statement it will make an xor operation between the buffer “DAT_00107040” which has the value of the xorkey and local_48 variable, and then it will compare our input with the results of the xor operation.

Now we need to analyze FUN_001012c0 to see what the value that will be stored in local_48.

The function has a lot of operations in it, but we only need this part of the code, it will store these hex values into param_1 variable.

So, if we took these hex values and xor it with the xorkey we should get the secret string.

By using CyberChef, we used “Swap endianness” to reverse the order of the bytes, then we used “From Hex” and finally we used the “XOR” operation with xorkeythe value we found in the APK and managed to get the secret string (making owasp great again).

And solved!!

Conclusion

  • Implementing weak Frida protection can be bypassed easily with Frida.
  • Don’t use weak encryption method such as xor operation.
  • Always check the initialization function in the shared library.

Happy reading :).

--

--

Mohannad Handoumeh
T3CH
Writer for

Offensive Security Consultant | OSCP | CRTP | eMAPT