Master Android Hooking: From API Hook to Xposed Framework
This article explains the concept of Hook in Android, demonstrates how to intercept View OnClickListener and startActivity using Java reflection and instrumentation, and introduces the Xposed framework for system‑wide method hooking, providing step‑by‑step code examples and practical deployment tips.
1. What is Hook
Hook ("hook") refers to intercepting an event before it reaches its final destination, allowing custom code to run during the interception.
In Android, Hook can embed its code into the target process, becoming part of the process. API Hook redirects the execution result of system APIs. Because Android uses a sandbox where each app runs in an isolated process, Hook provides a way to modify the behavior of other apps.
Use Java reflection to implement API Hook, e.g., modify the ClassLoader to redirect Java function calls.
Below we demonstrate Hooking a View's OnClickListener.
First, examine View's
setOnClickListenermethod; the listener is stored in an internal class
ListenerInfoas
mOnClickListener.
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}Our goal is to replace the original
OnClickListenerwith a custom one.
private void hookOnClickListener(View view) {
try {
// Obtain ListenerInfo object
Method getListenerInfo = View.class.getDeclaredMethod("getListenerInfo");
getListenerInfo.setAccessible(true);
Object listenerInfo = getListenerInfo.invoke(view);
// Get original OnClickListener
Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");
Field mOnClickListener = listenerInfoClz.getDeclaredField("mOnClickListener");
mOnClickListener.setAccessible(true);
View.OnClickListener originOnClickListener = (View.OnClickListener) mOnClickListener.get(listenerInfo);
// Replace with custom listener
View.OnClickListener hookedOnClickListener = new HookedOnClickListener(originOnClickListener);
mOnClickListener.set(listenerInfo, hookedOnClickListener);
} catch (Exception e) {
log.warn("hook clickListener failed!", e);
}
}
class HookedOnClickListener implements View.OnClickListener {
private View.OnClickListener origin;
HookedOnClickListener(View.OnClickListener origin) {
this.origin = origin;
}
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "hook click", Toast.LENGTH_SHORT).show();
log.info("Before click, do what you want to to.");
if (origin != null) {
origin.onClick(v);
}
log.info("After click, do what you want to to.");
}
}After hooking, code runs before and after the original click handling.
Button btnSend = (Button) findViewById(R.id.btn_send);
btnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
log.info("onClick");
}
});
hookOnClickListener(btnSend);We can also hook
startActivityto log parameters before the call.
public void execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
Log.d(TAG, "
执行了startActivity, 参数如下:
" +
"who = [" + who + "],
" +
"contextThread = [" + contextThread + "],
token = [" + token + "],
" +
"target = [" + target + "],
intent = [" + intent + "],
" +
"requestCode = [" + requestCode + "],
options = [" + options + "]");
try {
Method execStartActivity = Instrumentation.class.getDeclaredMethod(
"execStartActivity", Context.class, IBinder.class, IBinder.class,
Activity.class, Intent.class, int.class, Bundle.class);
execStartActivity.setAccessible(true);
return (ActivityResult) execStartActivity.invoke(mBase, who, contextThread, token,
target, intent, requestCode, options);
} catch (Exception e) {
throw new RuntimeException("do not support!!! pls adapt it");
}
}To replace the system
Instrumentation, we obtain the current
ActivityThreadinstance and set a proxy.
public static void attactContext() throws Exception {
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Field currentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
currentActivityThreadField.setAccessible(true);
Object currentActivityThread = currentActivityThreadField.get(null);
Field mInstrumentationField = activityThreadClass.getField("mInstrumentation");
mInstrumentationField.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);
mInstrumentationField.set(currentActivityThread, evilInstrumentation);
}Summary of the Hook process:
Find hook points, preferably static variables or singletons, and target public objects/methods.
Choose an appropriate proxy method; use dynamic proxy for interfaces.
Replace the original object with the proxy ("steal the beam").
Handle API compatibility across Android versions.
2. Xposed
Xposed replaces
/system/bin/app_processso that the Zygote process loads
XposedBridge.jar, allowing it to hook functions in the Zygote and the Dalvik/ART VM.
Installation involves flashing files to system directories (e.g.,
system/bin,
system/framework/XposedBridge.jar,
system/lib, etc.).
META-INF/ (flash‑script.sh configuration)
system/bin/ (replace Zygote files)
system/framework/XposedBridge.jar (jar location)
system/lib & system/lib64 (so files)
xposed.prop (version info)The key point is that
app_process(the Zygote executable) is replaced, so all app processes forked from Zygote are under Xposed control.
Xposed registers native functions to replace Java methods; when the VM reaches a hooked method, it first executes the native hook, then the original Java code.
Startup sequence:
During boot, the init process starts Zygote; the replaced
app_processlaunches Xposed Zygote.
Xposed Zygote loads native libraries and runs
XposedBridge.main, initializing hooks.
Hooks are registered as native functions in the modified VM.
The Zygote then continues its normal work, spawning app processes with hooks in place.
For detailed implementation, refer to the Xposed source code.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.