Android Penetration Testing: Frida
Introduction
Frida is a dynamic instrumentation toolkit that is used by researchers to perform android hooking (intercepting IPC and modifying it to make a function perform the desired function). Frida uses javascript to perform hooking since Android’s native code and javascript both run on JIT compilation techniques, it can intercept its inter-process communication, add the code specified in a script and completely change the function’s implementation. Some of its use cases in real life are:
- Spy on Crypto APIs
- Modify function’s output
- Bypass AES encryption
- Bypass SSLPinning and Root detection
- Trace private application code
- Bypass various software sided locks (like applock)
In this article, we’ll explain the basics of Frida, how to create your own Frida script, hook it into processes and perform various functions. Needless to say, there is no end to what a program can do, therefore, there is no limit on frida’s applications, hence, this article is only restricted to basics. If you want an advanced look into Frida and reverse engineering, I’ll tag the resources at the bottom of the article.
Table of Content
- Root detection bypass
- Hooking different kinds of methods used in Java
- Native methods – onCreate, onStart etc.
- exit()
- user defined methods
- variable
- SSLPinning bypass
- Implementing hooking in python
- Playing a game!
Let’s go then.
Root Detection Bypass
Application developers sometimes hard code a detection logic per which an application successfully detects the presence of various SU binaries and stops execution of the application. One such example is demonstrated below. As you can see the app gives a popup of restriction and exits as soon user hits ok.
Now, we’ll try and remove this restriction using Frida. First, it is recommended you install a Frida server in the device (Follow steps here). Next, we’ll launch the server onto the device.
adb connect 192.168.27.105 adb shell "/tmp/frida-server &"
Now, we’ll first install frida with the command:
pip install frida pip install frida-tools
After a successful install, we can see all the running process in the device on which frida server is running by the command:
frida-ps -U
As you can see that our app is running here. We have to bypass root detection here. We can either try and reverse engineer the jar files, create our own javascript code and bypass root detection or we can rely on code already created by a large community of developers on codeshare frida repo.
Weblink to the site is: https://codeshare.frida.re/browse
Here, we can see an antiroot script by dzonerzy. We’ll run it with the following command:
frida -U --codeshare dzonerzy/fridantiroot -f in.<package company>.<package name>
Now, press y to trust the project.
Now, all that’s left to do is press “%resume” to resume the execution with our hooked code!
And just like that, we can see that root detection has been successfully bypassed!
Hooking different methods in java
Now, a class might have multiple methods and each of these methods have a specific purpose. For example, the onCreate() method defines the implementation of activity as soon as the activity is created (or launched). So, what is, we can hook this function and change the behaviour of the activity when it is created. For the demonstration purpose, I’ll just print some custom text in my console as soon as the activity is called but the possibilities are limitless. Typically you won’t have access to the source code, hence, what we’ll do is extract the apk first and then decompile it to view source code. To pull the apk we’ll first know it’s the path and then pull it.
adb shell pm path jakhar.aseem.diva adb pull /data/app/jakhar.aseem.diva-dxAm4hRxYY4VgIq2X5zU6w==/base.apk
Now, as explained in part 1 of this series (refer para 3 of the article here), we’ll decompile it using apktool and then use dex2jar to convert it in jar format, and finally use jd-gui to view the decompiled source code like below. Here is the MainActivity class decompiled.
Here we see the following things:
- We can see that onCreate has a Bundle parameter
- It’s creating a view of the main page
Now, below is an example of how to hook onCreate() method.
console.log("Script loaded!"); Java.perform(function(){ var mainapp = Java.use("jakhar.aseem.diva.MainActivity"); mainapp.onCreate.implementation = function(){ console.log("My script called!"); var ret = this.onCreate.overload("android.os.Bundle").call(this); }; send("Hooks installed"); });
Explanation:
- Any implementation of the hook is put inside perform(function(){ //<code>
- The activity we want to hook (main activity) is put inside use(“jakhar.aseem.diva.MainActivity”), and assign a variable to it. Here, mainapp
- Now, onCreate.implementation sets a definition of the function.
- Here, we can insert any code we cant to run in the onCreate method. I just inserted log function to output “My script called!” every time onCreate is called.
- New variable ret calls this newly formed implementation function. overload method is used to add this code to the existing piece of code. Here, “os.Bundle” is input as a parameter since in the original function a bundle object is used.
- Finally, the call method is used to call the current method using “this” pointer.
- send() function outputs the text in double-quotes on the current frida command line.
To launch this script we type in the following command:
frida -U -l mainactivityhook.js -f jakhar.aseem.diva
As you can see now, the hook is successfully installed, activity launches and our custom output is now displayed and the hook is successfully installed
Hooking a defined method: Unlike the onCreate method that is present in the native libraries, some methods are custom created. For example, if you inspect the code of diva, you’ll see a function startChallenge() that is launching challenges in the application. I’m not putting the code in here but you can refer to the decompiled code in the above step. Now, we’ll observe that startChallenge is launching activities present in the project. And since it is launching an activity, it has an “android.view.VIEW” argument passed in its code. Now in the code below, every time a user hits a button to start any challenge, we’ll just force him to call our hook and our defined output would be displayed (that is MainActivity.startChallenge() is now started). Needless to say, we can change this by any implementation we want.
console.log("Hooked startChallenge() function"); Java.perform(function(){ var newstart = Java.use("jakhar.aseem.diva.MainActivity"); newstart.startChallenge.overload("android.view.View").implementation = function(v){ //enter any implementation of startChallenge you want //for demo I'm just sending an alert on frida console send("MainActivity.startChallenge() is now started"); var ret = this.startChallenge.overload("android.view.View").call(this); }; });
To call this script, without having to input %resume this time, we can type in the command with –no-pause filter:
frida -U -l main_startchallenge.js -f jakhar.aseem.diva
And sure enough, every time a button is pressed, our custom input is displayed.
Hooking exit() method: We can also tamper the exit method in android just like we tampered onCreate method. Here, I’m using a demonstration application that I custom coded (link here). It has a button that is performing an exit function. You can see a sample screenshot below:
Now, here we see the exit button. As the name states, on pressing it, application exits.
We create a hook down below that will stop the exit. Here, “java.lang.System” is the package that has exit function and so we’ll overload it using “sysexit.exit.overload().implementation.” Now, whenever a user clicks on exit, our send method will be called and exit will be stopped.
console.log("Hooking on exit function"); Java.perform(function(){ var sysexit = Java.use("java.lang.System"); sysexit.exit.overload("int").implementation = function(var_0) { send("I've stopped java.lang.System.exit() now!"); }; });
Let’s fire this script up and sure enough, we can see that the process is not terminated when the exit button is clicked. If it had been terminated frida must have thrown a process terminated error and closed the console.
frida -U -l avoidexit.js -f com.example.harshitrajpal --no-pause
Hooking return value: We have hooked methods till now, but a return variable can also be hooked and its output be tampered with. In article 3 of this series, I had already demonstrated this using Objection tool but today we’ll do this using Frida and our manual code. In the application that I custom coded which is mentioned above, there is a simple program to display output of 10 and 50. We’ll hook this return value and output 100. The code to do this is pretty straightforward:
console.log(“Hook for implementation of method”); Java.perform(function myFunc() { var myClass = Java.use(“com.example.harshitrajpal.MainActivity”); myClass.returnValue.implementation = function(){ //we will manipulate the return value here var ret = 100; return ret; } });
Let’s first run the program without loading our hook. We can see that the program outputs 60 which is the correct answer.
Now, we’ll fire up our script and see what changes happen in the application now.
frida -U -l retValueHook.js -f com.example.harshitrajpal --no-pause
And sure enough, the output gets tampered and 100 is returned now!
SSLPinning Bypass
Frida is most commonly used to bypass SSLPinning in android so that researchers and pen testers can intercept its network calls and conduct a traffic analysis. For the demo of this attack, I downloaded an application named “Certificate Pinning Demo”. For the demonstration of this attack, you must have your burp suite configured with your device (follow point 3 of the article here). Now, when I pin the client and send an HTTPS request, it throws an SSL error.
And sure enough, no communication is intercepted in burp suite as well.
Now, on the codeshare repository here, akabe1 has put a great script to perform SSLPinning bypass. We’ll use this script to perform the attack. Note that applications might have different code of pinning, so these codes need to be modified as and when required.
frida -U --codeshare akabe1/frida-multiple-unpinning -f com.osfg.certificatepinning
Type %resume once the script gets loaded.
And finally, when we now send a request to sslabs.com in pinned mode, we are able to get an HTTP 200 response code!
Surely, we are now able to intercept communication in burp suite as well.
Hooking in Python
Python coders can customize a whole fridascript to run in python environment using the python’s frida package and API. This would make performing multiple processes in hooks easier. Here, I’ll create a hook on startChallenge function as above.
jscode = """ console.log("Hooked startChallenge() function"); Java.perform(function(){ var newstart = Java.use("jakhar.aseem.diva.MainActivity"); newstart.startChallenge.overload("android.view.View").implementation = function(v){ //enter any implementation of startChallenge you want //for demo I'm just sending an alert on console send("MainActivity.startChallenge() is now started"); console.log("You clicked...but in vain!"); var ret = this.startChallenge.overload("android.view.View").call(this); }; }); """ import frida,sys process = frida.get_usb_device().attach("jakhar.aseem.diva") script = process.create_script(jscode) print("*** Running Hook on startChallenge() now!") script.load() sys.stdin.read()
Now, every time user clicks on any button to start the challenge, the execution stops and our custom output is printed instead.
We, run this script using the command below:
python3 startChallenge.py
Let’s Play a Game!
All the examples demonstrated till now are very basic. There are advanced hooking techniques to perform various different functions whose references I’ll mention at the end. One such challenge I found was on 11×256’s blog. In example #1, we have to intercept the APK, see what’s happening behind the white screen, change its implementation and modify its behaviour. finally, we’ll check logcat to see if our hook worked and the sum of our custom defined integers is thrown or not.
Follow the link here and download the sample apk.
First, after running the application in the emulator we saw just a plain white screen. That means something must probably be happening in the background.
We’ll use drozer to see activities here:
run app.activity.info -a com.example.a11x256.frida_test
As you can see, my_activity is present. This means this is the activity responsible for the full white front screen.
Now, we’ll use objection to watch what this class is actually doing. (Full objection tutorial here.)
objection --gadget com.example.a11x256.frida_test explore android hooking watch class com.example.a11x256.frida_test --dump-args --dump-return --dump-backtrace
Here, observe that fun() is being called. This has two int parameters, so, presumably, these two integers are getting performed a mathematical operation on.
Now, we write a code in javascript:
console.log("Script loaded successfully "); Java.perform(function x() { //Silently fails without the sleep from the python code console.log("Inside java perform function"); //get a wrapper for our class var my_class = Java.use("com.example.a11x256.frida_test.my_activity"); //replace the original implmenetation of the function `fun` with our custom function my_class.fun.implementation = function (x, y) { //print the original arguments console.log("original call: fun(" + x + ", " + y + ")"); //call the original implementation of `fun` with args (2,5) var ret_value = this.fun(2, 5); return ret_value; } });
This code does nothing but defines fun() function and specifies 2 and 5 as our own integers on which some mathematical function will be performed. but before that, the script also intercepts and displays the original call and obviously the original integers!
Let’s fire it up using frida:
frida -U -l 11x256.js -f com.example.a11x256.frida_test
As we can see, the original call had two integers namely, 50 and 30.
Let’s quickly check logcat and see what is happening in the background.
adb logcat | grep frida_test
As we can see in the screenshot down below, a mathematical Sum of type Double is being repeatedly called. This is similar to the behaviour of the app we just installed that was calling a method called fun after every second. Hence, it is safe to conclude that fun() is adding two integers. Original numbers to be added were 50 and 30, which we not only intercepted and dumped but also changed to 2 and 5 and the sum of 2 and 5 is now being called as evident in logcat.
References
Hooking is a pentester’s best buddy and reverse engineer’s most handy tool. It has numerous applications and to write custom scripts to perform various functions like sniffing crypto APIs, decrypting AES etc, a tester needs to have complete knowledge of javascript, reverse engineering APKs and java. To get good at frida and hooking (dynamic instrumentation), I’ll mention the following references:
- 11×256’s blog here
- Frida docs here
- Apps to perform frida demo here along with DIVA and our custom APK here
- Josh Spicer’s blog here
Author: Harshit Rajpal is an InfoSec researcher and left and right brain thinker. Contact here
Which application is used in demo for root detection? Any other test applications to practice root detection technique…
Awesome