This is a short, practical introduction to dynamic instrumentation with Frida on Android. The goal is not to glorify breaking apps, but to show how easily a pentest device can change client behavior and why production systems should assume that client input is untrusted.
We will set up a rooted Android emulator, install Frida, and use runtime hooks to modify network responses, database writes, and share actions. The point is to underline two policies that matter in real systems:
- require non-rooted devices for production access
- require verified store builds for production access
Everything else is defense-in-depth.
The Pirate Ship: Rooted Emulator
Though possible to hack without it, having a rooted device or emulator image is essential to using Frida effectively.
Frida’s tools communicate with a server running on a device to enable hooking into code at runtime.
The modes of operation and installation on devices are:
- Injected on the device filesystem (requiring rooted/jailbroken access)
- Embedded a gadget inside the application (repacking)
Repackaging an application and embedding a Frida gadget is possible but more error prone. Rooting an Android emulator is easy and risk free.
It’s Magisk
Magisk is a common tool used to provide root access on Android devices.
Magisk has a github repository for installing on an emulator specifically. The instructions are straight forward (some minor manual steps), so in summary:
- backup your emulator
ramdisk.img - use the repo to patch the running emulator
- move the resulting patched
ramdisk.imgback to the emulator location
You can check that the device has been rooted inside the Magisk app or with a root check application.
Frida
Frida is a suite of tools that provides an API for instrumenting code in native applications.
Installing Frida on the host system is as simple as pip install frida-tools.
Installing Frida server on our rooted device means obtaining a binary for the
platform architecture, copying, and running it.
Frida Loader is an Android
application that will manage that for you. Grab the APK from the
latest release
and install with adb install.
When you run the launcher it will give you an option to download, install, and run the latest and greatest.
To check the server is functioning, first find the device. frida-ls-devices
should return something like:
Id Type Name
------------- ------ ---------------------
local local Local System
emulator-5554 usb Android Emulator 5554
socket remote Local Socket
To view a list of processes on the device use frida-ps -D emulator-5554
or frida-ps -U for the USB device.
Hooking Treasure: Targeting an App
Now that we have a rooted device with Frida installed, we can install the target app and make sure it’s available by checking the package name:
adb shell pm list packages | grep jokeapp (package:com.github.ryjen.jokeapp)
Note that I am using a development debug build for clarity. In reality the app would be installed from an app store and may require disassembly to find target code.
Hooking is a Frida scripting API that allows replacing code at runtime.
Our goal is to intercept and return spoofed data for:
- the network API for a random joke
- saving a joke to the database
- sharing a joke
As an analogy, I will call these use cases the 3 parrots. They will echo back a message we tell them (reminds me of the hypno-parrot emoji).
Walk the Plank: Write Scripts
Writing Frida hooks is easy when you have the source code and a debug build. In the real world, you must locate the obfuscated methods after disassembling them. The process typically looks as follows:
- Disassemble the application APK in a tool called JADX-GUI
- Scan the resources and code for strings related to the targets (service, network, API, client, remote, database, repository, etc.)
- Use Frida to trace execution of target code using regex
(
frida-trace -U -j 'x4.a!*' Jiver) - Use the application at points of execution to verify and gather more information (like parameters)
- Once you have located the target code to hook or have the source code, you can start writing scripts to exploit potential vulnerabilities.
Network Parrot
The following Frida script intercepts the network service and returns spoofed responses:
const Types = {
Service: "com.github.ryjen.jokeapp.data.api.JokeService",
Response: "com.github.ryjen.jokeapp.data.model.JokeResponse",
Continuation: "kotlin.coroutines.Continuation",
};
Java.perform(function () {
var Service = Java.use(Types.Service);
var Response = Java.use(Types.Response);
// Overload a private kotlin suspend function to return a hacked network response
Service.getRandomJoke.overload(Types.Continuation).implementation = function (
continuation
) {
var actual = this.getRandomJoke(continuation);
// suspend functions pass a flag to indicate it will not return
if (actual.toString() == "COROUTINE_SUSPENDED") {
return actual;
}
// log the actual data
console.log(actual);
// return the spoofed data
return Response.$new("hackID", "This app is HACKED", 200);
};
});
The basic premise of the code is:
- Define java types used
- Use the java instrumentation to perform the hooking
- Define the java classes in the application binary to use
- Overload the API method in the network service classes
- Log the actual value
- Return the spoofed response
Kotlin coroutines internally pass an intrinsic parameter to signal the function
has suspended instead of returned. In Frida we need to handle this by checking
for COROUTINE_SUSPENDED before performing the overload.
Running frida -U --no-pause -f com.github.ryjen.jokeapp -l network.js makes the
app parrot back the spoofed response on every network call.

The logging should look something like this in the Frida console after 3 refreshes:
JokeResponse(id=fii3Tv4hFd, joke=Today, my son asked "Can I have a book mark?" and I burst into tears. 11 years old and he still doesn't know my name is Brian., status=200)
JokeResponse(id=IeiyIRSnbxc, joke=I applied to be a doorman but didn't get the job due to lack of experience. That surprised me, I thought it was an entry level position., status=200)
JokeResponse(id=kOfaUvP7Muc, joke=What did the Dorito farmer say to the other Dorito farmer? Cool Ranch!, status=200)
Database Parrot
When the user adds a favorite joke, we want to hook saving to the database to insert spoofed data. The following script accomplishes the task.
const Types = {
Joke: "com.github.ryjen.jokeapp.domain.model.Joke",
Database: "com.github.ryjen.jokeapp.data.repository.joke.JokeRepository",
Continuation: "kotlin.coroutines.Continuation",
};
Java.perform(function () {
var Database = Java.use(Types.Database);
var Joke = Java.use(Types.Joke);
// Overload a private kotlin suspend function to return a hacked network response
Database.addFavorite.overload(Types.Joke, Types.Continuation).implementation =
function (joke, continuation) {
if (continuation.toString() == "COROUTINE_SUSPENDED") {
this.addFavorite(joke, continuation);
return null;
}
var spoofed = Joke.$new("jokeID", "This joke is OWNED", null, true);
console.log(joke);
this.addFavorite(spoofed, continuation);
return null; // kotlin unit
};
});
The difference here is replacing the method call with a spoofed parameter instead of returning one.
Running frida -U --no-pause -f com.github.ryjen.jokeapp -l network.js and saving
a joke adds the spoofed joke to the database.

Again, our logging should log the original data for snooping.
Joke(id=7p41Lmbpjqc, content=What has three letters and starts with gas? A Car., created=null, isFavorite=true)
Joke(id=8p49pWvcxAd, content=Every night at 11:11, I make a wish that someone will come fix my broken clock., created=null, isFavorite=true)
Joke(id=EYo4TCAdUf, content=I tried to write a chemistry joke, but could never get a reaction., created=null, isFavorite=true)
Sharing Parrot
When the user shares a joke, we want to intercept and send a hacked response. This could also be used to pass private application data to an external resource on the device.
const Types = {
Share: "com.github.ryjen.jokeapp.ui.components.Share",
String: "java.lang.String",
Context: "android.content.Context",
};
Java.perform(function () {
var Share = Java.use(Types.Share);
// A message with a exposed data or an attack link
var spoofed = "Please visit hacked link https://example.com";
var shareText = Share.text.overload(Types.Context, Types.String);
shareText.implementation = function (context, content) {
console.log(content);
shareText.call(this, context, spoofed);
};
});
Running frida -U --no-pause -f com.github.ryjen.jokeapp -l share.js and sharing a
joke results in the spoofed content being shared instead. Logging shows the
original joke content as expected.

EOL, Shutdown: Conclusion
The takeaway is simple: assume the client can be manipulated. A rooted device plus Frida can turn a trusted app into a backend probing tool in minutes. The two non‑negotiables for production access are:
- Require non‑rooted devices
- Require verified store builds
When an app is distributed via the Play Store, the Play Integrity API adds a verification layer for user actions. For sensitive systems, backend verification should be mandatory because the client can always lie.
Defensive checklist
- Block access from rooted or jailbroken devices
- Enforce release builds and validate signing certificates
- Verify package identity (name, signature, installer)
- Detect Frida/objection tooling where feasible
- Treat client data as untrusted and validate on the server
- Rate‑limit and monitor unusual backend behavior
Enterprise applications requiring extra security should consider a service like NowSecure provided by the creators of Frida. Other options include VeraCode, Fortify, or Checkmarx.
Dynamic analysis security services (AKA Penetration Testing) run ad-hoc per identified risk can keep you ahead of unknown risks. Static analysis security services in continuous integration keep developers aware of code vulnerabilities.
As vulnerabilities and mitigations are constantly evolving, an application must be pro-active in keeping up to date and knowing its risk factors.
In summary, a pentest device can quickly turn a trusted client into a tool for exploring backend openings. The safe baseline is to assume the client is compromisable, require verified builds on non-rooted devices, and enforce server-side validation for every sensitive action.
** DISCLAIMER: This is partially setting up a penetration testing environment. Do not use on targets without authorization as you would be potentially breaking the law. Bugcrowd has a list of projects accepting testing within limits.