Android Runtime Instrumentation - Hooking Into Apps¶
Overview - Runtime Code Manipulation¶
Instrumentation is where static and dynamic analysis meet. Using tools like Frida and Objection, you can hook into running apps, intercept function calls, modify behavior, and bypass security controls. This is advanced testing - the kind that separates script kiddies from real testers.
Why Instrumentation Matters:
Some vulnerabilities only appear when you can: - Modify runtime behavior - Bypass security checks - Intercept encrypted communications - Manipulate data in memory - Test edge cases not reachable through UI
The Tools:
- Frida: Dynamic instrumentation toolkit (the gold standard)
- Objection: Runtime mobile exploration (built on Frida)
- Xposed: Framework for Android modification (older, still used)
- Magisk: Root solution that can hide from detection
The Process:
- Setup: Install Frida server on device
- Attach: Connect Frida to target app
- Hook: Intercept functions and methods
- Modify: Change behavior, bypass checks
- Test: Verify security controls
- Document: Record techniques and findings
Prerequisites: Understanding of Android Basics, Dynamic Analysis, and basic JavaScript/Python. You need to understand how Android apps work at runtime, what you're hooking, and why.
Table of Contents¶
- Frida Installation and Setup
- Basic Frida Usage
- Hooking Java Methods
- Hooking Native Functions
- Bypassing Security Checks
- SSL Pinning Bypass
- Root Detection Bypass
- Objection Usage
- Advanced Techniques
Frida Installation and Setup¶
Installing Frida¶
# Install Frida client (on your machine)
pip3 install frida-tools
# Verify installation
frida --version
# Download Frida server for Android
# Check device architecture first
adb shell getprop ro.product.cpu.abi
# Download appropriate frida-server from:
# https://github.com/frida/frida/releases
# Example: frida-server-16.0.0-android-arm64
# Push and run frida-server on device
adb push frida-server-16.0.0-android-arm64 /data/local/tmp/frida-server
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "/data/local/tmp/frida-server &"
# Verify connection
frida-ps -U
Alternative: Using Pre-built Scripts¶
# Clone Frida scripts repository
git clone https://github.com/MobSF/Mobile-Security-Framework-MobSF.git
cd Mobile-Security-Framework-MobSF/scripts
# Use setup script (adjust for your environment)
./setup_frida.sh
Basic Frida Usage¶
Listing Processes¶
# List all running processes
frida-ps -U
# List only apps
frida-ps -Ua
# List system processes
frida-ps -U | grep system
Attaching to Running App¶
# Attach to running app by package name
frida -U -f com.example.app
# Attach by PID
frida -U -p 12345
# Spawn app and attach immediately
frida -U -f com.example.app -l script.js
Basic Frida Script¶
// basic_hook.js
console.log("[*] Frida script loaded");
Java.perform(function() {
console.log("[*] Java hooking environment ready");
// Your hooks here
});
Run with:
frida -U -f com.example.app -l basic_hook.js
Hooking Java Methods¶
Hooking Method Calls¶
Java.perform(function() {
// Hook a specific class method
var MainActivity = Java.use("com.example.app.MainActivity");
MainActivity.onCreate.implementation = function(savedInstanceState) {
console.log("[*] onCreate called");
console.log("[*] Arguments: " + savedInstanceState);
// Call original method
this.onCreate(savedInstanceState);
console.log("[*] onCreate completed");
};
});
Intercepting Method Arguments and Return Values¶
Java.perform(function() {
var String = Java.use("java.lang.String");
String.equals.implementation = function(other) {
var result = this.equals(other);
if (result && this.length() > 10) {
console.log("[*] String.equals called:");
console.log("[*] this: " + this);
console.log("[*] other: " + other);
console.log("[*] result: " + result);
}
return result;
};
});
Hooking Multiple Methods¶
Java.perform(function() {
// Hook multiple methods in a class
var HttpURLConnection = Java.use("java.net.HttpURLConnection");
HttpURLConnection.getRequestMethod.implementation = function() {
var method = this.getRequestMethod();
console.log("[*] HTTP Method: " + method);
return method;
};
HttpURLConnection.getURL.implementation = function() {
var url = this.getURL();
console.log("[*] HTTP URL: " + url);
return url;
};
});
Hooking Native Functions¶
Finding Native Libraries¶
Java.perform(function() {
// Enumerate loaded libraries
Process.enumerateModules().forEach(function(module) {
console.log("[*] Module: " + module.name + " @ " + module.base);
});
});
Hooking Native Functions¶
// Hook native function by name
var nativeFunction = Module.findExportByName("libnative.so", "native_function");
Interceptor.attach(nativeFunction, {
onEnter: function(args) {
console.log("[*] native_function called");
console.log("[*] arg[0]: " + args[0]);
},
onLeave: function(retval) {
console.log("[*] native_function returned: " + retval);
}
});
Hooking by Address¶
// Hook function at specific address
var address = ptr("0x12345678");
Interceptor.attach(address, {
onEnter: function(args) {
console.log("[*] Function at " + address + " called");
}
});
Bypassing Security Checks¶
Bypassing Root Detection¶
Java.perform(function() {
// Common root detection methods
// Bypass isDeviceRooted checks
var RootBeer = Java.use("com.scottyab.rootbeer.RootBeer");
RootBeer.isRooted.implementation = function() {
console.log("[*] Root check bypassed");
return false; // Return false even if device is rooted
};
// Bypass isRooted checks
var RootDetection = Java.use("com.example.app.RootDetection");
RootDetection.isRooted.implementation = function() {
return false;
};
});
Bypassing Debugger Detection¶
Java.perform(function() {
// Bypass isDebuggerConnected checks
var Debug = Java.use("android.os.Debug");
Debug.isDebuggerConnected.implementation = function() {
return false;
};
// Bypass isDebuggable checks
var ApplicationInfo = Java.use("android.content.pm.ApplicationInfo");
ApplicationInfo.flags.value = ApplicationInfo.flags.value & ~0x2; // Remove DEBUGGABLE flag
});
Bypassing Certificate Pinning¶
See SSL Pinning Bypass section below.
SSL Pinning Bypass¶
Using Frida Scripts¶
// Universal SSL pinning bypass
Java.perform(function() {
var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");
var SSLContext = Java.use("javax.net.ssl.SSLContext");
var TrustManager = Java.registerClass({
name: "com.example.TrustManager",
implements: [X509TrustManager],
methods: {
checkClientTrusted: function(chain, authType) {},
checkServerTrusted: function(chain, authType) {},
getAcceptedIssuers: function() {
return [];
}
}
});
SSLContext.init.implementation = function(keyManager, trustManager, secureRandom) {
console.log("[*] SSLContext.init called - bypassing certificate validation");
this.init(keyManager, [TrustManager.$new()], secureRandom);
};
});
Using Pre-built Scripts¶
# Use Frida SSL pinning bypass script
frida -U -f com.example.app -l ssl-kill-switch2/frida-script.js
# Or use objection
objection -g com.example.app explore --startup-command "android sslpinning disable"
Root Detection Bypass¶
Comprehensive Root Bypass Script¶
Java.perform(function() {
// Hook common root detection libraries
var rootDetectionClasses = [
"com.scottyab.rootbeer.RootBeer",
"com.kimchangyoun.rootbeerFresh.RootBeerFresh",
"eu.faircode.netguard.Database"
];
rootDetectionClasses.forEach(function(className) {
try {
var RootCheck = Java.use(className);
RootCheck.isRooted.implementation = function() {
console.log("[*] Root check bypassed: " + className);
return false;
};
} catch(e) {
// Class not found, skip
}
});
// Hook file existence checks for root binaries
var File = Java.use("java.io.File");
File.exists.implementation = function() {
var path = this.getAbsolutePath();
var rootBinaries = ["/system/bin/su", "/system/xbin/su", "/system/app/Superuser.apk"];
if (rootBinaries.indexOf(path) !== -1) {
console.log("[*] Root binary check bypassed: " + path);
return false;
}
return this.exists();
};
});
Objection Usage¶
Installing Objection¶
# Install objection
pip3 install objection
# Verify installation
objection --version
Basic Objection Commands¶
# Start objection session
objection -g com.example.app explore
# Once in objection console:
# List loaded classes
android hooking list classes
# Search for classes
android hooking search classes <keyword>
# List class methods
android hooking list class_methods <class_name>
# Hook method execution
android hooking watch class_method <class_name>.<method_name>
# Set return value
android hooking set return_value <class_name>.<method_name> false
# Disable SSL pinning
android sslpinning disable
# Bypass root detection
android root disable
Objection Scripts¶
# Run commands from file
objection -g com.example.app explore -s commands.txt
# Example commands.txt:
# android hooking watch class_method com.example.app.Auth.checkAuth --dump-args --dump-return
# android sslpinning disable
# android root disable
Advanced Techniques¶
Memory Dumping¶
Java.perform(function() {
// Dump strings from memory
Java.choose("java.lang.String", {
onMatch: function(instance) {
var str = instance.toString();
if (str.length > 20 && /[a-zA-Z0-9]{20,}/.test(str)) {
console.log("[*] Potential secret: " + str);
}
},
onComplete: function() {}
});
});
Enumerating Classes and Methods¶
Java.perform(function() {
// Enumerate all classes
Java.enumerateLoadedClasses({
onMatch: function(className) {
if (className.indexOf("com.example.app") !== -1) {
console.log("[*] Found class: " + className);
}
},
onComplete: function() {}
});
});
Dynamic Method Hooking¶
Java.perform(function() {
// Hook all methods in a class
var TargetClass = Java.use("com.example.app.TargetClass");
TargetClass.class.getDeclaredMethods().forEach(function(method) {
var methodName = method.getName();
console.log("[*] Hooking method: " + methodName);
try {
TargetClass[methodName].implementation = function() {
console.log("[*] " + methodName + " called");
return this[methodName]();
};
} catch(e) {
// Method not hookable
}
});
});
Frida Gadget Integration¶
For apps that don't allow Frida attachment, you can inject Frida Gadget:
# Patch APK with Frida Gadget
# 1. Decompile APK
apktool d app.apk -o app_decoded
# 2. Add frida-gadget.so to lib/ directory
cp frida-gadget.so app_decoded/lib/arm64-v8a/libfrida-gadget.so
# 3. Modify AndroidManifest.xml to load gadget
# Add to <application> tag:
# <meta-data android:name="frida.gadget.config" android:value="frida.config" />
# 4. Rebuild and sign
apktool b app_decoded -o app_patched.apk
Instrumentation Checklist¶
- Frida server installed and running on device
- Can attach to target app
- Basic hooks working
- Java methods hooked successfully
- Native functions hooked (if applicable)
- Security checks bypassed
- SSL pinning bypassed (if present)
- Root detection bypassed (if present)
- Findings documented
Related Reading¶
- Dynamic Analysis - Runtime testing techniques
- Network Testing - Network security testing
- Static Analysis - Code analysis
- Component Testing - Component security testing
- Android Basics - Android fundamentals
This guide covers essential runtime instrumentation techniques. Combine with Static Analysis and Dynamic Analysis for comprehensive Android security assessments.