Note: This post is a hands-on companion to the Frida Gadget setup guide and the JADX decompilation guide — read those first if you haven’t already.
Frida’s JavaScript API lets you inspect and modify a running Android app without rebuilding it. The core idea is simple: you get a reference to a Java class, then you replace one or more of its methods with your own implementation.
The snippets below assume you already have Frida running against your target app (see the setup guide linked above). They use the Frida 16+ API, where Java.perform() is still the entry point for all Java-level work.
This post is the second in a series on practical Frida techniques. The companion posts cover the JavaScript API reference, community Android hooking patterns, and automated reversing workflows.
The core pattern: Java.use and implementation
The starting point for almost every Java hook is Java.use(). It returns a JavaScript wrapper around the named class, and from there you can override methods by assigning to .implementation.
Java.perform(function () {
var Utils = Java.use('com.example.app.Utils');
Utils.process.implementation = function (data) {
console.log('process called with: ' + data);
return this.process(data);
};
});
A few things to note here. First, everything runs inside Java.perform(), which ensures the Java VM is ready. Second, calling this.process(data) invokes the original method — that’s how you intercept rather than replace. Third, the class name must be fully qualified.
Overloads: picking the right signature
Java methods are often overloaded — the same name with different parameter lists. If you assign .implementation directly on an overloaded method, Frida throws an error telling you to be explicit. You then pick one explicitly with overload(), passing the fully-qualified type names as strings.
Java.perform(function () {
var Utils = Java.use('com.example.app.Utils');
Utils.process.overload('java.lang.String', 'int')
.implementation = function (data, count) {
console.log('two-arg process: ' + data + ', ' + count);
return this.process(data, count);
};
});
The Frida JavaScript API docs list the shorthand names for primitives (int, boolean, and so on), but reference types always need their full package path. Getting a signature wrong is the most common reason a hook silently never fires — double-check it against the decompiled source.
Constructors and live objects
Constructors are hooked through $init, and you can also reach objects that already exist on the heap with Java.choose().
Java.perform(function () {
var Utils = Java.use('com.example.app.Utils');
Utils.$init.overload('int').implementation = function (count) {
console.log('Utils created with count=' + count);
return this.$init(count);
};
Java.choose('com.example.app.Utils', {
onMatch: function (instance) {
console.log('live instance: ' + instance);
},
onComplete: function () {}
});
});
One caveat: Java.choose() only finds objects currently on the heap. If the object you want hasn’t been created yet, you’ll need to hook its constructor or the method that creates it instead.
Going native: hooking C/C++ functions
Not everything lives in Java. For native libraries, use Module.findExportByName() to locate a function and Interceptor.attach() to hook it.
var openAddr = Module.findExportByName('libc.so', 'open');
Interceptor.attach(openAddr, {
onEnter: function (args) {
console.log('open() path: ' + Memory.readUtf8String(args[0]));
},
onLeave: function (retval) {}
});
For a deeper look at reading native code, see the Frida cheat sheet and the Smali basics post, which covers what to do when the Java layer isn’t where the logic lives.
Spawn vs. attach: when to use each
Frida can either spawn a process (starting the app under instrumentation from the first instruction) or attach to one that’s already running. Spawning is what you want when you need to hook early startup code or constructors that run before you’d manage to attach.
| Mode | When to use it | Example |
|---|---|---|
| Spawn | Hooking startup code, $init, or anything that runs before you could attach |
frida -U -f com.example.app -l hook.js |
| Attach | Inspecting or modifying a running app | frida -U com.example.app -l hook.js |
| Overloads | Selecting one signature among several | .overload('java.lang.String', 'int') |
Key takeaways
Java.use()gives you a scriptable wrapper around a Java class; assign to.implementationto hook a method.- Overloaded methods need an explicit
overload()with fully-qualified type names. - Hook constructors with
$initand find live objects withJava.choose(). - Drop to
Interceptor.attach()for native code in.solibraries. - Spawn the app when you need to catch startup; attach when it’s already running.
References
Was this useful?
Thanks for the feedback.
2 comments