Welcome to the Frida instrumentation workshop for Android! In this workshop, you will learn how to use Frida, a powerful dynamic instrumentation tool, to manipulate the behavior of Android apps at runtime.
-
Presented at: NSConclave2023
- Dynamic binary instrumentation (DBI) can be thought of as a technique used by a spy organization to gather information about the inner workings of another organization.
Imagine that there is a secret spy organization that wants to learn more about the operations of a rival organization. One way they could do this is by sending in a spy to gather information from inside the rival organization. This spy could observe meetings, read documents, and gather other types of information about the rival organization's activities.
Now imagine that the rival organization has a number of different programs that it uses to run its operations. These programs might be used to manage finances, communicate with employees, or perform other tasks. The spy organization could use dynamic binary instrumentation to analyze and modify these programs while they are running in order to gather more information about the rival organization's activities.
In this analogy, the rival organization is like a compiled program, and the spy organization is like a DBI tool. Just as the spy gathers information by observing and interacting with the rival organization, the DBI tool gathers information by analyzing and modifying the compiled program.
Before you get started, you will need to make sure you have the following prerequisites:
- A computer running macOS, Linux, or Windows
- A device running Android 7.0 or newer, or an emulator running Android 7.0 or newer
- The Frida Python package installed on your computer (pip install frida)
- ADB (Android Debug Bridge) installed and configured on your computer
- JadX-GUI
Once you have everything set up, you're ready to get started! Here is a brief outline of what we will cover in this workshop:
- Introduction to Frida and dynamic instrumentation
- Setting up a Frida server on your Android device
- Injecting Frida into an Android app
- Manipulating the behavior of an Android app using Frida
- Advanced techniques for Android instrumentation with Frida
Let's get started!
Frida is a dynamic instrumentation tool that allows you to manipulate the behavior of running processes on a device. It works by injecting a JavaScript library into the process, which gives you access to the process's memory and allows you to manipulate its behavior in real-time.
Dynamic instrumentation is a powerful technique that can be used for a wide range of purposes, such as reverse engineering, penetration testing, and automating tasks. In this workshop, we will focus on using Frida for reverse engineering Android apps.
Before you can use Frida to instrument an Android app, you will need to set up a Frida server on your Android device. The Frida server is a small daemon that runs on your device and allows Frida to communicate with it.
-
Get CPU architecture of your device/emulator
-
adb shell getprop ro.product.cup.abi
-
adb shell getprop ro.product.cup.abilist
To set up the Frida server on your device, follow these steps:
- Download the Frida server based on your device architecture from the Frida github releases https://github.com/frida/frida/releases
- Transfer the binary to your Android device using ADB (adb push frida-server /data/local/tmp/)
- Connect to your device using ADB (adb shell)
- Once the Frida server is transffered, you can start it by running the following command:
/data/local/tmp/frida-server &
The Frida server should now be running on your device and ready for use.
Next step is to setup frida on the system,run following to install frida on the system.
pip3 install frida-tools
Now that the Frida server is set up on your device, you can use Frida to instrument an Android app.
To interact with android app using Frida, follow these steps:
- Make sure the Frida server is running on your device
- Find the package name of the app you want to instrument (e.g. com.example.app)
- Run the following command to list the processes running on your device:
frida-ps -U
- Find the package name of the app you want to instrument in the list of processes.
- Run the following command to inject Frida into the app's process:
frida -U -f com.example.app -l script.js
Where com.example.app is the package name of the app, and script.js is the name of the JavaScript script that will be injected into the app's process.
The Frida library should now be injected into the app's process, and you should see a message in the terminal indicating that Frida is running.
- Run
frida -U -f <Package name>
- This creates an interactive shell
- Write your scripts inside this shell
- Write the frida script inside the .js file
- Pass it as an argument using -l option
- Run
frida -U -f <Package name> -l <agent.js>
- The frida script will be excuted prallaly with target application execution
- Using Python and JS allows for quick development with a risk-free API. Frida can help you easily catch errors in JS and provide you an exception rather than crashing.
- Create a python file to spawn the target process
- Write an agent in JS for the hooking logic
- Use JS inside your python file to interact with process
Now that Frida is injected into the app's process, you can use it to manipulate the app's behavior. Frida provides a powerful JavaScript API that allows you to enumerate the app's classes, methods, and fields, and to manipulate their behavior at runtime.
Here is a simple example of how you can use Frida to change the behavior of an Android app:
Java.perform(function() {
// Find the class we want to manipulate
var targetClass = Java.use("com.example.app.TargetClass");
// Replace the implementation of the target method with our own
targetClass.targetMethod.implementation = function(arg1, arg2) {
console.log("targetMethod called with args: " + arg1 + ", " + arg2);
// Call the original implementation of the method
var result = this.targetMethod(arg1, arg2);
// Return the result of the original method
return result;
};
});
In this example, we are using Frida to find the TargetClass
class in the app, and to replace the implementation of the targetMethod
method with our own code. Our code logs the arguments passed to the method, calls the original implementation of the method, and returns the result.
Using Frida in this way allows you to modify the behavior of the app in real-time, without having to modify the app's code or rebuild it.
Frida provides many advanced features that allow you to perform more sophisticated manipulations of Android apps. Here are a few examples of what you can do with Frida:
- Hook into method calls made by the app, and manipulate the arguments or return values
- Bypassing client side encryption
- Client side checks such as ROOT detection, SSL pinning, Emulator detection, Biomatric Authentication
I hope this section has given you a good introduction to Frida and dynamic instrumentation on Android. With Frida, you can unlock the full potential of Android apps and use them in ways that were never intended by their developers. Let's dive into more details, Happy hacking!
-
Here is an example of how to use the Frida CLI to list the process names and IDs on a device:
-
Download: com.hpandro.androidsecurity_1.3.apk
# frida-ps -Uai
PID Name Identifier
------------------------ ---------------------------------------
10780 Android Security Testing hpandro.android.security
11856 Camera com.motorola.ts.camera
12231 Chrome com.android.chrome
6148 Exchanges com.exchanges
5926 Google com.google.android.googlequicksearchbox
24359 Google Play Store com.android.vending
26998 Motorola Notifications com.motorola.ccc.notification
11402 Photos com.google.android.apps.photos
11696 YouTube com.google.android.youtube
- If you have multiple devices connected you should run following
# frida-ls-devices
Id Type Name OS
---------- ------ ------------------ ---------------------
local local ns Kali GNU/Linux 2022.4
ZF6223RG45 usb motorola one power Android 10
socket remote Local Socket
- With -D option you can specify which device you want to interect with
# frida-ps -D ZF6223RG45
PID Name
----- -------------------------------------------------------------------------------------------------
30004 .dataservices
30020 .qtidataservices
1508 ATFWD-daemon
10780 Android Security Testing
11856 Camera
6148 Exchanges
5926 Google
24359 Google Play Store
13047 Moto Audio
26998 Motorola Notifications
- Few more important flags are listed below
-f TARGET, --file TARGET
spawn FILE
-F, --attach-frontmost
attach to frontmost application
-n NAME, --attach-name NAME
attach to NAME
-N IDENTIFIER, --attach-identifier IDENTIFIER
attach to IDENTIFIER
-p PID, --attach-pid PID
attach to PID
- Let's create a Hello world script to understand the basics.
// helloscript.js
Java.perform(()=>{
console.log("hello world, bye");
});
-
Inside the block of code passed to Java.perform(), you can use the Frida JavaScript API to interact with the Android runtime and perform various tasks, such as enumerating loaded classes, hooking method calls, and modifying field values.
-
Java.perform() is use to hook into the JVM
- We have access to methods and classes in the memory
# frida -U -f hpandro.android.security -l helloscript.js
____
/ _ | Frida 16.0.7 - 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 motorola one power (id=ZF6223RG45)
Spawned `hpandro.android.security`. Resuming main thread!
[motorola one power::hpandro.android.security ]-> hello world, bye
- Enumerate loaded classes with frida
- List methods and properties of a class
- Hook the target function
- Dumping function parameters
- Playing around with instances & Modifying the parameter values
- Modifying the function behaviour
- List classes of target application
Java.enumerateLoadedClasses(callbacks): enumerate classes loaded right now, where callbacks is an object specifying:
onMatch(name, handle): called for each loaded class with name that may be passed to use() to get a JavaScript wrapper.
onComplete(): called when all classes have been enumerated
// frida -U -f hpandro.android.security -l listclasses.js
Java.perform(()=>{
Java.enumerateLoadedClasses({
onMatch : function(name, handle){
if(name.includes("AES")){
console.log(name);
}
},
onComplete : function(){
console.log("--- done ---");
}
});
});
frida -U -f com.example.helloapp -l listclasses.js
frida -U -f hpandro.android.security -l listclasses.js
hpandro.android.security.ui.activity.task.encryption.AESActivity
Java.use()
Java.use(className):
dynamically get a JavaScript wrapper for className that you can instantiate objects from by calling$new()
on it to invoke a constructor.- Mention the class name inside
Java.use()
- We can use CONST or var to store
Java.use()
- Mention the class name inside
// frida -U -f hpandro.android.security -l list-methods-and-properties.js
Java.perform(()=>{
console.log("--- ok ---");
const activityclass = Java.use("hpandro.android.security.ui.activity.task.encryption.AESActivity");
console.log(activityclass);
console.log(Object.getOwnPropertyNames(activityclass).join("\n\t"));
});
frida -U -f hpandro.android.security -l list-methods-and-properties.js
Java.enumerateMethods(query):
enumerate methods matching query, specified as "class!method"
.
// frida -U -f hpandro.android.security -l list-methods-and-properties-1.js
Java.perform(() => {
const groups = Java.enumerateMethods('*AES*!*decrypt*')
console.log(JSON.stringify(groups, null, 2));
});
-
Decompile APK using Jadx and identify target function to hook
-
Write coresponding frida script
-
check if function is called
// frida -U -f owasp.mstg.uncrackable1 -l func-call.js
Java.perform(()=>{
const rootcheckclass = Java.use("sg.vantagepoint.a.c");
rootcheckclass.a.implementation = function(){
console.log("a() function called!");
return true;
}
});
- modify the function's return values
// frida -U -f owasp.mstg.uncrackable1 -l func-call.js
Java.perform(()=>{
const rootcheckclass = Java.use("sg.vantagepoint.a.c");
rootcheckclass.a.implementation = function(){
console.log("a() function called!");
return false;
}
});
// frida -U -f owasp.mstg.uncrackable1 -l crackme1-rootbypass.js
Java.perform(()=>{
const rootcheckclass = Java.use("sg.vantagepoint.a.c");
rootcheckclass.a.implementation = function(){
console.log("--bypass c.a()--");
return false;
}
rootcheckclass.b.implementation = function(){
console.log("--bypass c.b()--");
return false;
}
rootcheckclass.c.implementation = function(){
console.log("--bypass c.c()--");
return false;
}
});
frida -U -f owasp.mstg.uncrackable1 -l crackme1-rootbypass.js
frida -U -f owasp.mstg.uncrackable1 -l dump-function-parameters.js
// frida -U -n "Uncrackable1" -l dump-function-parameters.js
Java.perform(()=>{
const classa = Java.use("sg.vantagepoint.uncrackable1.a");
classa.a.overload("java.lang.String").implementation = function(s){
console.log(s.toString());
return true;
}
});
- changed return true for method a of class a
- And dumped function parameters
- We need to work with existing instance sometimes
-
Scan application memory to find the instances
-
Java.choose(className, callbacks):
enumerate live instances of theclassName
class by scanning the Java heap, where callbacks is an object specifying: -
onMatch(instance):
called with each live instance found with a ready-to-use instance. -
onComplete():
called when all instances have been enumerated
-
Java.perform(function() {
// We are scanning the application memory for existing instances of the Class.
Java.choose('com.example.classname', {
// onMatch - Callback => If an instance has been found,
onMatch: function(instance) {
// code if an instance has been found
send("An instance of the class has been found = " + instance)
// Do operations on the found instance
},
// onComplete - Callback => Finished scanning the app memory regarding to the instance.
onComplete: function() {
send("Frida has finished scanning the application memory for class instances")
}
})
})
- dice game
- fridaFunc
- clickMe
- Spoof geo location of android google maps
// frida -U -f com.google.android.apps.maps
Java.perform(() => {
var Location = Java.use('android.location.Location');
Location.getLatitude.implementation = function() {
return 26.4679656;
}
Location.getLongitude.implementation = function() {
return 74.6343847;
}
})
-
$new
- Instantiate objects by calling $new()
// frida -U -f owasp.mstg.uncrackable1 -l decrypt-and-show.js
Java.perform(()=>{
// base64 decode
const base64 = Java.use("android.util.Base64");
var arrayofbytes = base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=",0);
// b function
const bfunclass = Java.use("sg.vantagepoint.uncrackable1.a");
var encKey = bfunclass.b("8d127684cbc37c17616d806cf50473cc");
// a function
const afunclass = Java.use("sg.vantagepoint.a.a");
var decryptedarray = afunclass.a(encKey, arrayofbytes);
// convert to string and show
const strclass = Java.use("java.lang.String");
var decryptedpass = strclass.$new(decryptedarray);
console.log(decryptedpass);
});
frida -U -f owasp.mstg.uncrackable1 -l decrypt-and-show.js
-
Here we have task to identify the encryption logic and try to decrypt the string presented at the start of activity.
-
Download: com.hpandro.androidsecurity_1.3.apk
// frida -U -f hpandro.android.security -l "hp andro/aes-decrypt.js"
Java.perform(function x() {
var ScopeObject = Java.use("hpandro.android.security.ui.activity.task.encryption.AESActivity");
var scopeInstance = ScopeObject.$new();
console.log(scopeInstance.decryptStrAndFromBase64('dfdfdf','hpAndro','UhIF1tR4HCQOdf1SXQXVOIeT7L4yd76Yh0K7ULfGJCkOcjB7OrAciDuA0/HlpfeR'));
})
- Perform above task with
Java.choose()
- Perform DES task in hpAndro
-
Here we have task to identify the encryption logic and try to decrypt the string presented at the start of activity.
-
Download: com.hpandro.androidsecurity_1.3.apk
// frida -U -f hpandro.android.security -l "/hp andro/rsa-decrypt.js"
Java.perform(function () {
let RSAActivity = Java.use("hpandro.android.security.ui.activity.task.encryption.RSAActivity");
RSAActivity["decrypt"].implementation = function (privateKey, bArr) {
console.log('decrypt is called' + ', ' + 'privateKey: ' + privateKey + ', ' + 'bArr: ' + bArr);
let ret = this.decrypt(privateKey, bArr);
const strclass = Java.use("java.lang.String");
var decryptedpass = strclass.$new(ret);
console.log('decrypt ret value is ' + decryptedpass);
return ret;
};
});
- Often we deal with cross platform apps in our pentests. This tricks comes in really handy to debug the application logic.
Java.perform(function() {
Java.choose("android.webkit.WebView", {
"onMatch": function(o) {
try {
// Use a Runnable to invoke the setWebContentsDebuggingEnabled
// method in the same thread as the WebView
var Runnable = Java.use('java.lang.Runnable');
var MyRunnable = Java.registerClass({
name: 'com.example.MyRunnable',
implements: [Runnable],
methods: {
'run': function() {
o.setWebContentsDebuggingEnabled(true);
console.log('WebView Debugging should be enabled');
}
}
});
var runnable = MyRunnable.$new();
o.post(runnable);
} catch (e) {
console.log("Execution failed " + e.message);
}
},
"onComplete": function() {
console.log("Execution completed")
}
})
}
);
- Setup the Damn Vulnerable Bank
Username | Password | Account Number | Beneficiaries | Admin privileges |
---|---|---|---|---|
user1 | password1 | 111111 | 222222, 333333, 444444 | No |
user2 | password2 | 222222 | None | No |
user3 | password3 | 333333 | None | No |
user4 | password4 | 444444 | None | No |
admin | admin | 999999 | None | Yes |
-
Bypass basic frida detection
-
Start the frida server on different port
./frida -l 0.0.0.0:1337
-
Connect to frida using
-H
flagfrida -H 192.168.56.101:1337 -f com.app.damnvulnerablebank
-
Bypass root detection using frida
-
Dump the encrypted and plain-text response
-
Get
accesstoken
from shared preference -
bypass fingerprint check while Sending money
-
Get data from clipboard
-
Log everything!
- https://frida.re/docs/home/
- https://github.com/RavikumarRamesh/hpAndro1337
- https://github.com/rewanthtammana/Damn-Vulnerable-Bank
- https://github.com/OWASP/owasp-mastg/tree/master/Crackmes/Android
- https://github.com/WithSecureLabs/android-keystore-audit/blob/master/frida-scripts
- https://github.com/apkunpacker/FridaScripts/
- https://www.youtube.com/watch?v=JK8Azi7f13E
- https://www.youtube.com/watch?v=0QIJRjdnT2I