While the CLI tools like frida, frida-trace, etc., are quite useful, there might be times when you’d like to build your own tools harnessing the powerful Frida APIs.
Frida API
Java.available: This API is used to check Frida running on android or not. It is to check if you are actually running on Android. For example, you could create 1 SSL bypassing script that first checks if you’re on Android or iOS, and act accordingly .It specify whether the current process has the a Java VM loaded, i.e. Dalvik or ART return boolean (true or false)
Java.androidVersion: This API return the android version of device that we are using.
Java.enumerateLoadedClasses(callback): This API enumerates classes where object specifying onMatch(name, handle): called for each loaded class with a name that may be passed to use() to get a JavaScript wrapper. onComplete(): called when all classes have been enumerated
console.log("[+] Running Frida Script")
console.log("[+] The android version is: " + Java.androidVersion)
Java.enumerateLoadedClasses({
"onMatch": function(name){
if(name.includes("com.apphacking.certificatepinning")){
console.log(name)
}
},
"onComplete": function(){
console.log("[+] Done")
},
})
For running the script:
frida -U -f com.apphacking.certificatepinning -l .\enumLoadedClasses.js --no-pause
-f → Specify the target package. it’s run the apk. If you don’t specify this option, you should manually run this apk. In case of you don’t want to target application pause, you can use
--no-pause
switch.-U → Connect to frida server with USB connection.
-l → Load JavaScript script
Java.enumerateClassLoaders(callbacks) : This API enumerates the class loaders in Java VM, which have a callback function, which are onMatch: function (loader) and
onComplete
: function ()console.log("[+] Running Frida Script")
console.log("[+] The android version is: " + Java.androidVersion)
Java.enumerateClassLoaders({
"onMatch": function(loader){
console.log("Loader: " + loader)
},
"onComplete": function(){
console.log("[+] Done")
},
})
Enumerate Methods
Java.enumerateMethods()
You can use this script too.
Java.perform(function () {
var targetClass = "com.apphacking.certificatepinning.MainActivity"; // Replace with the desired class name
var targetClassRef = Java.use(targetClass);
var methods = targetClassRef.class.getDeclaredMethods();
methods.forEach(function (method) {
console.log(method.toString());
});
});
Java.perform(fn): ensure that the current thread is attached to the VM and call fn. and the
fnfunction
is called . This function calls VM::AttachCurrentThread
internally, then executes the JavaScript in the fn
callback function to operate the Java runtime, and finally uses VM::DetachCurrentThread
to release resourcesJava.use(className) : It dynamically get a JavaScript wrapper for
className
. Wrapper is basically a function that is intended to call one or more other functions.Java.perform(function() {
var Test = Java.use("com.example.demotest.xyz");
console.log( Test.AClassVariable.value );
});
Java.scheduleOnMainThread(fn) : The callback function is executed on the VM main thread (UI thread). Operating UI elements in Android requires code execution in the main thread, and
scheduleOnMainThread
its role is to execute functions in the main threadJava.openClassFile(filePath) : This APIused for hook dynamic loaded
dex
.Java.choose(className, callbacks) : Scan Java heap in memory and enumerate Java object (className) instances. For example, you can use java.lang.String Scan strings in memory. callbacks provide two parameters: onMatch(instance) and onComplete, which are to find the matching object and scan to complete the call.
Java.perform(
function(){
Java.choose("java.lang.String", {
onMatch: function(instance){
console.log(instance.toString())
},
onCompelet: function(){
console.log("[+] Done")
}
})
}
)
Extracting all strings on this apk
Java.retain(obj) : duplicates the JavaScript wrapper obj for later use outside replacement method
Java.registerClass(spec): Create a new Javaclass and return a wrapper, where the specification is
namestring containing :: Specify the name of the class.
superClass: (Optional) Parent class. java.lang.ObjecOmission to be inherited from t.
implements: (Optional) An array of interfaces implemented by this class.
fields: (Optional) Object, which specifies the name and type of each field to be exposed.
methods: (Optional) Object, which specifies the method to be implemented.
Java.perform(function () {
var hellojni = Java.registerClass({
name: 'com.example.frida_1demo.abcd'
});
console.log(hellojni.hello.value);
});
Java.isMainThread() :This API check the Programmer executing in main thread or not. When a Java program starts up, one thread begins running immediately. This is usually called the main thread of our program, because it is the one that is executed when our program begins.
Java.deoptimizeEverything() : forces the VM to execute everything with its interpreter.
Java.cast(handle, klass) : Create a JavaScript wrapper given the existing instance at the handle of given class klass as returned from
Java.use()
Java.perform(function () {
var targetClass = Java.use("com.example.TargetClass"); // Replace with the desired class name
var obj = targetClass.$new(); // Create an instance of the class
// Perform the cast to String using Java.cast
var strObj = Java.cast(obj, Java.use("java.lang.String"));
// Now you can treat the object as a String and use its methods
var length = strObj.length();
console.log("Length of the string object:", length);
});
Java.array(type, elements) : Used to creates a Java array with elements of the specified type, from a JavaScript array elements
Java.perform(function () {
var intArray = Java.array('int', [1, 2, 3, 4, 5]); // Create an int array
var stringArray = Java.array('java.lang.String', ['apple', 'banana', 'cherry']); // Create a String array
// Access and print array elements
for (var i = 0; i < intArray.length; i++) {
console.log("Element at index", i, "of intArray:", intArray[i]);
}
for (var j = 0; j < stringArray.length; j++) {
console.log("Element at index", j, "of stringArray:", stringArray[j]);
}
});
Example of Method Overwrite
console.log("[+] Starting Frida")
console.log("[+] Android Version: ", Java.androidVersion)
Java.perform(
function(){
let MainActivity = Java.use("com.apphacking.certificatepinning.MainActivity")
MainActivity.onResume.implementation = function() {
send("[+] onResume() is called.")
this.onResume()
}
}
)
Function Overloading
In the context of Frida and Android, function overloading refers to the ability to hook and modify multiple versions of a function with the same name but different parameter signatures. Function overloading is a concept from object-oriented programming languages where you can define multiple functions with the same name but different parameters.
To hook an overloaded function in Frida, you need to provide the exact parameter signature of the function you want to hook. For example, if you have an Android application with two overloaded versions of a function called "doSomething," where one version takes an integer parameter and the other takes a string parameter, you can hook each version separately by specifying the correct parameter type in your Frida script.
Example:
Java.perform(function () {
var targetClass = Java.use('com.example.TargetClass');
// Hook the version of doSomething that takes an integer parameter
targetClass.doSomething.overload('int').implementation = function (param) {
// Your modifications here
console.log('Hooked doSomething(int) with param:', param);
// Call the original method
this.doSomething(param);
};
// Hook the version of doSomething that takes a string parameter
targetClass.doSomething.overload('java.lang.String').implementation = function (param) {
// Your modifications here
console.log('Hooked doSomething(String) with param:', param);
// Call the original method
this.doSomething(param);
};
});
Real World Example:
Java.perform(function () {
var messagingClass = Java.use('com.example.MessagingClass');
// Hook the version of sendMessage that takes a string parameter for text messages
messagingClass.sendMessage.overload('java.lang.String').implementation = function (message) {
console.log('Intercepted text message:', message);
// Call the original method
this.sendMessage(message);
};
// Hook the version of sendMessage that takes an image parameter for image messages
messagingClass.sendMessage.overload('com.example.Image').implementation = function (image) {
console.log('Intercepted image message:', image);
// Call the original method
this.sendMessage(image);
};
// Hook the version of sendMessage that takes a video parameter for video messages
messagingClass.sendMessage.overload('com.example.Video').implementation = function (video) {
console.log('Intercepted video message:', video);
// Call the original method
this.sendMessage(video);
};
});
Calling a Method
Calling static method and virtual method are differently. For static method you can just do this:
let MyClass = Java.use("com.example.package.MyClass")
MyClass.myStaticMethod()
For virtual methods you usually need to create an instance of a class:
let MyClass = Java.use("com.example.package.MyClass")
let MyObjectClass = MyClass.$new()
MyObjectClass .myVirtualMethod()
Tasks
Change
faceNum
and poolSize
parameters.Use Frida to change dice app behavior to return only 6 in dices.
Solution
Create multiple player instance