What is FRIDA?
Frida is a free open-source tool to analyze, manipulate, and modify an application running in memory. We can use it to see the output of a function, manipulate its parameters, or even insert new code in the application to help with our testing. All without needing to decompile the application and compile it again.
Today I will explain some of the basic functions FRIDA can do some CTF-like challenges.
FRIDA setup
First, we need to download Frida’s server and client, the client can be downloaded with a simple command
pip3 install frida-tools
for the server, we need to go to Frida’s repo and download the server that is compatible with our phone or emulator. I use Android Studio’s emulator with 86x architecture so that’s what I downloaded.
The server will be put in the /data/local/tmp
directory and run the binary. Make sure that the client and the server are the same version so that we don’t get a version mismatch error.
Challenge 0x1: Frida setup, Hooking a method
This is the only activity in the application, so let’s see its decompiled code
We can see in the highlighted area that when we enter a number and click the button, it takes our number and passes it to the check function with another random number, so let’s see the check
and get_random()
functions:
the get_random
function is simple, and returns a random number from 0 to 99; while the check
function takes our input and makes sure that the first parameter * 2 + 4 equals the second parameter. So all we need to do is pass 2 numbers to the function that evaluates the condition to true, for example, 1,5. How would we do that with Frida? let’s see the code and break it down:
Java.perform(function() {
var maicActivity = Java.use("com.ad2001.frida0x1.MainActivity");
maicActivity.check.implementation = function(i, i2) {
this.check(1, 6);
}
});
Java.perform()
is where we tell Frida to hook itself to the JVM, and it takes a function as a parameter, in this function we write our code.
with Java.use()
we get an instance from the MainActivity
class to be able to access its functions like the check function, its parameter is the absolute path to the class i.e. package_name.class_name
last line, we override the code for the check
function and add our code and whenever the function is called our code will be executed not the original one. In our case, we don’t want to change the code because it has our flag, instead, we need to control the 2 arguments given to the function rather than the original ones. So, we will call the original function but with arguments that satisfy the condition 1 and 5
and click the button with any random numbers, and we get the flag.
$ frida -U -f com.ad2001.frida0x1 -l hook.js
# -U connects to USB connected device
# -f package_name-> starts the application with the package_name
# -l provide the hook script from file
Challenge 0x4: Creating a class instance
In this challenge, we need to create a class instance and call a method not originally called within the application code. We can do that without needing to decompile the application, insert new code, and compile and sign it back again. With Frida life is easy :D
As you can see, there’s nothing in the UI so let’s see the decompiled source code:
Nothing in the MainActivity code as well, but there’s an unused class called Check
let’s see what’s inside of it
Interesting, that’s the flag function we need to call, so how would we call a function that is not called? pretty easy let’s see the code that does the magic
Java.perform(function() {
var check = Java.use("com.ad2001.frida0x4.Check");
var check_instance = check.$new();
console.log(check_instance.get_flag(1337));
});
The first 2 lines we already discussed before so let’s understand the third line. The check
variable here is like a pointer that points to the location of the Check
class in memory, so before we could only call or manipulate static methods and variables without the need for a class instance, now it is a bit different because we have to have an instance to call a class method and we don’t have that instance in memory. That’s what the third line is doing, creating a new instance of the check
class so we are able to execute methods and access its variables. Now all we need to do is get the output, we can connect our code to the layout of the application but that’s a topic for another time, we can log the output on our Frida terminal for the time being.
Challenge 0x5: Invoking methods on an existing instance
This is the challenge, like the last challenge the UI doesn’t provide us with anything, so we need to look at the source code
There’s the flag function, notice it is not static so it needs to be called with a class instance. The first approach we might try is to create an object of the MainActivity
class and like the last challenge call the function, so let’s try that and see
Java.perform(function() {
var mainAct = Java.use("com.ad2001.frida0x5.MainActivity");
var main_instance = mainAct.$new();
console.log(main_instance.flag());
});
let’s run the code and see,
That’s a huge error, so what went wrong?
Creating an instance of MainActivity
or any Android component directly using Frida can be tricky due to Android's lifecycle and threading rules. Android components, like Activity
subclasses, rely on the application context for proper functioning. In Frida, you might lack the necessary context. Android UI components often require a specific thread with an associated Looper
. If you're dealing with UI tasks, ensure you're on the main thread with an active Looper
. Activities are part of the larger Android application lifecycle. Creating an instance of MainActivity
might need the app to be in a specific state, and managing the entire lifecycle through Frida might not be straightforward. In conclusion, it's not a good idea to create an instance of the MainActivity
.
So what should we do? We can just use Frida to get the already-created instance of MainActivity
then call the flag()
method to get our flag. Let’s learn how to do that
Java.perform(function() {
Java.choose('com.ad2001.frida0x5.MainActivity', {
onMatch: function(instance) {console.log(instance.flag(1337));},
onComplete: function() {console.log("done.");}
});
});
Java.choose
: enumerates through instances of the first argument, theMainActivity
class, at runtime.
It has two callbacks:
- onMatch function is executed when the instance is found.
- onComplete function executes when the
Java.choose()
operation ends.