Hooking¶
Intercepting function calls at runtime to read arguments, modify return values, or replace functionality entirely. Frida is the standard tool for Android hooking. Xposed provides a framework-level alternative.
Frida¶
Frida injects a JavaScript engine into the target process. Scripts can hook any Java method, native function, or system call.
Setup¶
adb push frida-server /data/local/tmp/
adb shell chmod 755 /data/local/tmp/frida-server
adb shell su -c "/data/local/tmp/frida-server &"
pip install frida-tools
Basic Java Hooking¶
Intercept a Java method, read arguments, modify return value:
Java.perform(function() {
var targetClass = Java.use("com.target.app.LoginActivity");
targetClass.checkPassword.implementation = function(password) {
console.log("Password entered: " + password);
var result = this.checkPassword(password);
console.log("Result: " + result);
return true;
};
});
Hooking Overloaded Methods¶
When a method has multiple signatures:
targetClass.send.overload("java.lang.String", "int").implementation = function(msg, code) {
console.log("send(" + msg + ", " + code + ")");
return this.send(msg, code);
};
Hooking Constructors¶
targetClass.$init.overload("java.lang.String").implementation = function(param) {
console.log("Constructor called with: " + param);
this.$init(param);
};
Native Function Hooking¶
Hook native (C/C++) functions in shared libraries:
Interceptor.attach(Module.findExportByName("libnative.so", "decrypt"), {
onEnter: function(args) {
console.log("decrypt called");
console.log("arg0: " + Memory.readUtf8String(args[0]));
},
onLeave: function(retval) {
console.log("returned: " + Memory.readUtf8String(retval));
}
});
Common Hooking Tasks¶
SSL Pinning Bypass¶
The most frequent use case. Multiple approaches depending on the pinning implementation:
Java.perform(function() {
var CertificatePinner = Java.use("okhttp3.CertificatePinner");
CertificatePinner.check.overload("java.lang.String", "java.util.List").implementation = function(hostname, peerCertificates) {
console.log("Bypassing pin for: " + hostname);
};
});
For comprehensive pinning bypass, Objection automates this:
Root Detection Bypass¶
Hook root check methods to return false. The example below covers basic RootBeer checks, but production malware uses multi-layered detection (file checks, property reads, native library probes). 8kSec's advanced root detection bypass guide covers sophisticated detection mechanisms and Frida-based bypasses beyond basic library hooks:
Java.perform(function() {
var RootBeer = Java.use("com.scottyab.rootbeer.RootBeer");
RootBeer.isRooted.implementation = function() {
console.log("Root check bypassed");
return false;
};
});
Emulator Detection Bypass¶
Java.perform(function() {
var Build = Java.use("android.os.Build");
Build.FINGERPRINT.value = "google/walleye/walleye:8.1.0/OPM1.171019.011/4448085:user/release-keys";
Build.MODEL.value = "Pixel 2";
Build.MANUFACTURER.value = "Google";
Build.BRAND.value = "google";
Build.PRODUCT.value = "walleye";
Build.HARDWARE.value = "walleye";
});
Crypto Key Extraction¶
Hook encryption functions to dump keys and plaintext:
Java.perform(function() {
var SecretKeySpec = Java.use("javax.crypto.spec.SecretKeySpec");
SecretKeySpec.$init.overload("[B", "java.lang.String").implementation = function(keyBytes, algorithm) {
console.log("Algorithm: " + algorithm);
console.log("Key: " + bytesToHex(keyBytes));
return this.$init(keyBytes, algorithm);
};
var Cipher = Java.use("javax.crypto.Cipher");
Cipher.doFinal.overload("[B").implementation = function(input) {
console.log("Cipher input: " + bytesToHex(input));
var result = this.doFinal(input);
console.log("Cipher output: " + bytesToHex(result));
return result;
};
});
DEX Loading Interception¶
Capture dynamically loaded DEX files (packed apps):
Java.perform(function() {
var DexClassLoader = Java.use("dalvik.system.DexClassLoader");
DexClassLoader.$init.implementation = function(dexPath, optimizedDir, libraryPath, parent) {
console.log("Loading DEX: " + dexPath);
this.$init(dexPath, optimizedDir, libraryPath, parent);
};
var InMemoryDexClassLoader = Java.use("dalvik.system.InMemoryDexClassLoader");
InMemoryDexClassLoader.$init.overload("java.nio.ByteBuffer", "java.lang.ClassLoader").implementation = function(buf, loader) {
console.log("In-memory DEX loaded, size: " + buf.remaining());
var bytes = new Uint8Array(buf.remaining());
var file = new File("/data/local/tmp/dumped_" + Date.now() + ".dex", "wb");
file.write(bytes.buffer);
file.flush();
file.close();
this.$init(buf, loader);
};
});
Advanced Frida: Memory Operations¶
Beyond hooking functions, Frida provides direct memory manipulation APIs for native-level analysis. 8kSec's memory operations series covers the full API:
| API | Purpose |
|---|---|
Memory.scan() / Memory.scanSync() |
Scan process memory for byte patterns |
Memory.alloc() |
Allocate memory in the target process |
Memory.copy() / Memory.dup() |
Copy and duplicate memory regions |
Memory.protect() |
Change memory page permissions (RWX) |
Memory.patchCode() |
Patch executable code at runtime |
8kSec's Frida Stalker guide covers instruction-level tracing using Stalker APIs, enabling real-time observation of code execution at the assembly level. This is particularly useful for analyzing obfuscated native code in families like Mandrake that use OLLVM.
Anti-Frida Detection and Bypass¶
Malware actively detects Frida to prevent dynamic analysis. Understanding each detection vector is necessary to bypass them.
Detection Techniques¶
| Detection Method | Indicator | Implementation |
|---|---|---|
| Port scanning | TCP port 27042 (default frida-server port) | Socket connect to localhost:27042, if open assume Frida is present |
| Process name | "frida-server" in process list | Read /proc/*/cmdline or ps output looking for "frida" |
| Memory maps scanning | "frida" strings in /proc/self/maps |
Open /proc/self/maps and scan for "frida-agent", "frida-gadget", or "frida-server" |
| Named pipes | Frida's linjector pipe names | Enumerate /proc/self/fd/ for pipes matching "linjector" pattern |
| Loaded libraries | frida-agent*.so in memory |
dlopen enumeration or /proc/self/maps check for frida-agent shared objects |
| pthread_create hooking | Thread creation patterns | Frida spawns threads during injection -- detect abnormal thread count or thread naming patterns |
| D-Bus protocol detection | Frida's internal D-Bus communication | Send D-Bus AUTH message to suspected Frida port and check for a valid reply |
| Inline hook detection | Modified function prologues | Read the first bytes of commonly hooked functions (like open, strcmp) and compare against known-good prologues |
/proc/self/maps Scanning (Most Common)¶
The most widespread Frida detection reads /proc/self/maps line by line, searching for Frida-related strings. Malware typically runs this in a background thread on a timer:
Java.perform(function() {
var Runtime = Java.use("java.lang.Runtime");
Runtime.exec.overload("java.lang.String").implementation = function(cmd) {
if (cmd.indexOf("maps") !== -1 || cmd.indexOf("frida") !== -1) {
console.log("Blocked maps/frida scan: " + cmd);
return this.exec("echo");
}
return this.exec(cmd);
};
});
For native-level maps scanning, hook libc.so open and filter /proc/self/maps access:
Interceptor.attach(Module.findExportByName("libc.so", "open"), {
onEnter: function(args) {
var path = Memory.readUtf8String(args[0]);
if (path && path.indexOf("/proc/self/maps") !== -1) {
this.shouldBlock = true;
}
},
onLeave: function(retval) {
if (this.shouldBlock) {
retval.replace(-1);
}
}
});
D-Bus Protocol Detection¶
Some malware sends a raw D-Bus AUTH handshake to suspected Frida ports. If Frida responds, the app terminates:
Interceptor.attach(Module.findExportByName("libc.so", "connect"), {
onEnter: function(args) {
var sockAddr = args[1];
var port = (Memory.readU8(sockAddr.add(2)) << 8) | Memory.readU8(sockAddr.add(3));
if (port === 27042) {
console.log("Blocked connect to Frida port");
this.shouldBlock = true;
}
},
onLeave: function(retval) {
if (this.shouldBlock) {
retval.replace(-1);
}
}
});
Bypass Strategies¶
| Strategy | How | Trade-offs |
|---|---|---|
| Rename frida-server | cp frida-server fs-15.x and run the renamed binary |
Simple but only evades name-based detection |
| Non-default port | frida-server -l 0.0.0.0:1234 |
Defeats port 27042 scanning only |
| Frida Gadget injection | Embed frida-gadget.so directly into the APK's lib folder |
No frida-server process, survives process name checks |
| Hook detection functions | Intercept open(), fopen(), access() calls targeting /proc/self/maps |
Comprehensive but can be detected by syscall-level checks |
| Magisk + Shamiko | Use Shamiko to hide root and Frida from process | Hides at zygote level, effective against most checks |
| Stalker-based tracing | Use Frida Stalker instead of Interceptor to avoid inline hook artifacts | Slower but undetectable by prologue checking |
| Kernel-level hiding | Custom kernel module to filter /proc/self/maps entries |
Most thorough, requires custom kernel |
| Patch detection out | Remove Frida detection entirely from the APK (see Patching) | Permanent fix, avoids the cat-and-mouse entirely |
Frida Gadget (Rootless Injection)¶
When root is unavailable or frida-server is detected, inject Frida Gadget directly into the APK:
apktool d target.apk -o target_patched/
cp frida-gadget-16.x.x-android-arm64.so target_patched/lib/arm64-v8a/libfrida-gadget.so
Add a System.loadLibrary call in the main activity's Smali to load the gadget at startup:
const-string v0, "frida-gadget"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
Reassemble, sign, and install. The app loads Frida Gadget on launch without needing frida-server.
Xposed Framework¶
Xposed operates at the ART (Android Runtime) level, replacing method entry points in the runtime's internal method table. When a hooked method is called, ART redirects execution to the Xposed callback before (or instead of) the original implementation.
LSPosed is the modern Xposed implementation for Android 8.1+, installed as a Magisk module. It uses Riru or Zygisk to inject into the zygote process, which means hooks are active from the moment an app process is forked.
How ART Method Hooking Works¶
Xposed replaces the entry_point_from_quick_compiled_code field in ART's ArtMethod struct. When the VM calls a hooked method, it jumps to Xposed's trampoline instead of the original compiled code. The trampoline invokes registered callbacks, then optionally calls the original method.
This is fundamentally different from Frida's approach: Frida injects into a running process and patches code in memory, while Xposed modifies the runtime's method dispatch table at process creation time.
LSPosed Module Structure¶
An Xposed module is an Android app with a xposed_init file declaring the entry class:
public class HookModule implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if (!lpparam.packageName.equals("com.target.malware")) return;
XposedHelpers.findAndHookMethod(
"com.target.malware.SecurityCheck",
lpparam.classLoader,
"isRooted",
new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) {
return false;
}
}
);
}
}
For before/after hooks instead of full replacement:
XposedHelpers.findAndHookMethod(
"javax.crypto.Cipher",
lpparam.classLoader,
"doFinal",
byte[].class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
byte[] input = (byte[]) param.args[0];
XposedBridge.log("Cipher.doFinal input: " + bytesToHex(input));
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
byte[] output = (byte[]) param.getResult();
XposedBridge.log("Cipher.doFinal output: " + bytesToHex(output));
}
}
);
Frida vs. Xposed¶
| Aspect | Frida | Xposed / LSPosed |
|---|---|---|
| Hook timing | Attaches to running process | Active from process creation (zygote fork) |
| Persistence | Script must be re-run each session | Hooks survive app restarts and reboots |
| Iteration speed | Instant -- edit JS script, re-attach | Requires module rebuild and device reboot |
| Module changes | Immediate | Requires reboot (or soft reboot via LSPosed manager) |
| Language | JavaScript (with Java.perform bridge) | Java / Kotlin |
| Root requirement | Yes (frida-server) or Gadget injection | Yes (Magisk + LSPosed) |
| Detection surface | Detectable via port, maps, thread artifacts | Lower profile -- no separate server process |
| Native hooks | Full support (Interceptor, Stalker) | Limited -- primarily targets Java/ART methods |
| Interactive exploration | REPL, live scripting, object inspection | No REPL -- compile, install, reboot cycle |
| Best for | Dynamic exploration, one-off analysis | Persistent monitoring, long-running malware observation |
Xposed is particularly useful when analyzing malware that actively detects Frida, since LSPosed hooks leave fewer artifacts. It is also preferable for long-running observation sessions where restarting Frida scripts is impractical.
Family-Specific Hooking¶
Certain malware families require targeted hooks to extract key data:
| Family | What to Hook | Purpose |
|---|---|---|
| Cerberus lineage | javax.crypto.Cipher.doFinal |
Decrypt C2 communication and overlay inject URLs |
| GodFather v3 | VirtualApp framework APIs | Intercept virtualized banking app interactions |
| Anatsa | AccessibilityService.onAccessibilityEvent | Observe ATS command sequence |
| Mandrake | OLLVM-protected native functions via Stalker | Trace obfuscated control flow |
| SharkBot | DGA algorithm function | Predict future C2 domains |
| Vultur | AlphaVNC initialization | Capture screen streaming setup |
| SpyNote | Socket/DataOutputStream | Intercept RAT command protocol |
| Necro | BitmapFactory + pixel extraction | Capture steganographic payload |
| Gigabud | libstrategy.so native functions |
Intercept UI interaction commands |
| BTMOB RAT | WebView loadUrl / evaluateJavascript |
Capture injected phishing pages |
| LightSpy | Plugin loader + light2.db SQLite |
Intercept plugin download and C2 config |
| FluHorse | Dart FFI bridge in libapp.so |
Hook the Dart-to-native boundary for credential interception |
| Rafel RAT | DevicePolicyManager + Cipher.doFinal |
Intercept admin commands and ransomware encryption |
| KoSpy | Firebase Firestore getDocument |
Capture C2 configuration delivery |
| All packed families | DexClassLoader, InMemoryDexClassLoader | Dump decrypted DEX payload |
Accessibility Service Monitoring¶
For families using accessibility-based ATS, hook the accessibility service to observe the full fraud sequence:
Java.perform(function() {
Java.enumerateLoadedClasses({
onMatch: function(className) {
try {
var cls = Java.use(className);
if (cls.class.getSuperclass() &&
cls.class.getSuperclass().getName() === "android.accessibilityservice.AccessibilityService") {
cls.onAccessibilityEvent.implementation = function(event) {
console.log("[A11y] " + event.getEventType() + " pkg=" + event.getPackageName() + " text=" + event.getText());
this.onAccessibilityEvent(event);
};
}
} catch(e) {}
},
onComplete: function() {}
});
});
reFrida¶
reFrida is a browser-based Frida IDE that replaces the typical workflow of editing scripts in a text editor and running them via CLI. It connects to a running frida-server and provides a full development environment in the browser.
Key capabilities:
- Monaco editor with Frida API autocompletion, syntax highlighting, and inline documentation
- Visual interceptor builder -- select a class and method from a tree view, and reFrida generates the hook code automatically. Useful for quickly building hooks without memorizing overload signatures.
- Built-in disassembler -- disassemble native functions directly from the browser, navigate to cross-references, and set hooks on specific instructions
- Memory search -- scan process memory for strings, byte patterns, or values with a visual interface (wraps
Memory.scanwith result highlighting) - Stalker integration -- configure and run Stalker traces with visual output of executed basic blocks and call graphs
- Script management -- save, load, and organize scripts per target application
reFrida is particularly effective for malware analysis workflows where you need to rapidly iterate on hooks, inspect memory regions, and trace native code execution without switching between multiple terminal sessions.
Task-Oriented Hooking Strategies¶
Beyond family-specific hooks, certain analysis goals map to standard hook points regardless of the malware family:
| Analysis Goal | What to Hook | Why |
|---|---|---|
| Intercept overlay injection | WindowManager.addView, WindowManager.LayoutParams |
Banking trojans overlay fake login screens on top of legitimate apps |
| Capture C2 traffic | OkHttpClient.newCall, HttpURLConnection.connect, URL.openConnection |
Intercept HTTP-based C2 before SSL encryption |
| Extract encryption keys | SecretKeySpec.$init, Cipher.doFinal, Mac.doFinal |
Dump keys and plaintext at the crypto API boundary |
| Monitor SMS exfiltration | SmsManager.sendTextMessage, SmsManager.sendMultipartTextMessage |
Catch outbound SMS used for OTP forwarding or premium abuse |
| Track file system activity | File.$init, FileOutputStream.write, SharedPreferences.edit |
Observe config drops, payload writes, and preference changes |
| Capture screen recording | MediaProjection.createVirtualDisplay, ImageReader.acquireLatestImage |
Detect VNC/screen streaming setup used by RAT families |
| Monitor accessibility abuse | AccessibilityService.onAccessibilityEvent, performAction, performGlobalAction |
Observe ATS commands (clicks, scrolls, gestures) during automated fraud |
| Intercept dynamic loading | DexClassLoader.$init, InMemoryDexClassLoader.$init, ClassLoader.loadClass |
Capture unpacked or stage-2 payloads at load time |
| Track permission abuse | DevicePolicyManager.lockNow, DevicePolicyManager.resetPassword |
Detect device admin abuse (screen lock, wipe threats) |
| DNS/domain resolution | InetAddress.getByName, InetAddress.getAllByName |
Capture DGA output or C2 domain resolution |
| WebView injection | WebView.loadUrl, WebView.evaluateJavascript, WebViewClient.shouldInterceptRequest |
Intercept injected phishing pages and JavaScript payloads |
| Clipboard theft | ClipboardManager.setPrimaryClip, ClipboardManager.getPrimaryClip |
Detect clipboard monitoring for crypto wallet address swapping |
Tools¶
| Tool | Purpose |
|---|---|
| Frida | Runtime instrumentation |
| reFrida | Browser-based Frida IDE with visual interceptor builder, disassembler, and Stalker integration |
| Objection | Frida-powered automation (SSL bypass, root bypass, etc.) |
| LSPosed | Xposed framework for modern Android |
| frida-dexdump | Dump DEX from memory via Frida |
| r2frida | Radare2 + Frida integration |
| medusa | Extensible Frida wrapper for common hooking tasks |
SSL Pinning: Current State¶
Google now recommends against SSL pinning in Android security best practices. 8kSec's analysis covers why: pinning is trivially bypassed with Frida, creates maintenance burden, and provides minimal security benefit for most threat models since the Android platform's certificate transparency and Play Integrity checks provide stronger guarantees. For malware analysis, pinning bypass remains a routine first step (see SSL Pinning Bypass above).