Safety & scope reminder: everything in this module is written for authorized, lab-only testing (intentionally vulnerable APKs or apps you have written permission to test). I will show practical Frida examples for instrumentation and analysis in a controlled environment. I won’t provide operational instructions intended to bypass protections on real production apps or to exploit customers’ devices. Use these techniques only in your isolated lab and with written consent.
This module explains the architecture of runtime instrumentation, how to plan safe instrumentation sessions, how to discover and hook targets (Java and native), practical Frida patterns (tracing, intercepting, modifying data), how to handle obfuscation and native code, and how to detect & reason about anti-instrumentation defenses. It also covers hygiene (evidence collection, reproducibility) and how to design lab exercises that validate vendor claims without attacking production.
6.0 Learning objectives
After this module you will be able to:
- Explain Frida architecture and how it interacts with Android processes.
- Prepare an emulator or device for Frida-based labs (lab-only setup).
- Discover runtime targets (classes, methods, native exports) and trace execution.
- Write Frida scripts to log method arguments, return values, and to change behavior in a controlled test APK.
- Hook native functions with Frida-Gum and read/modify memory safely.
- Understand common anti-instrumentation techniques and how to detect them.
- Collect reproducible evidence (logs, traces, script outputs) that can be audited by third parties.
6.1 Frida: architecture & core concepts (concise)
- Frida is a dynamic instrumentation toolkit that injects a JavaScript runtime into a target process and allows you to script hooks at runtime.
- frida-server runs on the device (root or appropriate privileges needed for many operations); the desktop Frida client communicates with it over USB/TCP.
- Frida-Gum is the low-level API used to instrument native code (intercept exports, inline hooks, read/write memory).
- Java.use / Java.perform: Frida exposes a bridge to the Java runtime (ART), letting you get references to classes and methods.
- Interceptor.attach: attaches to native function addresses or exports.
- frida-trace: automatic tracing tool that can instrument many functions by signature/name patterns for quick reconnaissance.
6.2 Lab preparation (safe, authorized)
Only perform these steps in a lab you control.
- Pick a test APK — DVIA, InsecureBankv2, or a custom lab app with known behaviors. Do not target production apps.
- Device choice:
- Emulator (AVD) — easy to reset and snapshot. Some Frida features work without device root on modern emulators.
- Real device — gives realistic behavior; often needs root or Magisk for frida-server.
- Install matching Frida versions:
- Ensure frida-tools on your host matches frida-server version on the device. Example: if
pip install frida-tools==15.x, deploy the corresponding frida-server binary onto device. - Place
frida-serverin/data/local/tmp/and give it executable permissions (lab-only).
- Ensure frida-tools on your host matches frida-server version on the device. Example: if
- Start frida-server (lab-only):
- Run frida-server on device/emulator; confirm
frida-ps -Ufrom host lists processes.
- Run frida-server on device/emulator; confirm
- Prepare artifacts & snapshots:
- Take a snapshot of AVD or a full device image before instrumentation so tests are reproducible.
- Record the exact frida binary versions, device model, Android version, and APK hash.
6.3 Discovering runtime targets — reconnaissance at runtime
Before hooking, discover interesting classes/methods and native exports.
6.3.1 Java layer discovery
- Use frida’s Java enumeration to list classes and methods (interactive JS script). High-level pattern:
Java.perform(function () {
Java.enumerateLoadedClasses({
onMatch: function(className) {
if (className.indexOf('com.example') !== -1) {
console.log(className);
}
},
onComplete: function() { console.log('done'); }
});
});
- In lab, look for classes related to:
Auth,Login,CertificatePinner,PlayIntegrityManager,KeyStore,OkHttpClient,Retrofit,TokenStore, or vendor-specific names.
6.3.2 Native layer discovery
- Use
nm,readelf, orfridanative enumeration to list exported symbols. Within Frida you can enumerate modules and their exports, e.g.:
Module.enumerateExportsSync("libexample.so").forEach(function(exp) {
console.log(exp.name, exp.type, exp.address);
});
- Look for exported
Java_-style symbols (Java_com_company_pkg_Class_method) that indicate JNI bridges.
6.3.3 Quick automated tracing
- frida-trace is useful for one-off broad reconnaissance:
frida-trace -U -i "Java_*" com.target.app(lab-only). It will instrument matching functions and log calls — good to see hot paths.
6.4 Hooking Java methods (patterns & examples)
Below are lab-safe examples showing how to log information and modify behavior in a test APK. These are intended for intentionally vulnerable apps and educational labs.
6.4.1 Simple logger: log arguments & return values
This script logs calls to com.example.auth.LoginManager.login(String, String):
// lab_hook_login.js (lab-only)
Java.perform(function() {
var LoginManager = Java.use("com.example.auth.LoginManager");
LoginManager.login.overload('java.lang.String','java.lang.String').implementation = function(username, password) {
console.log("[+] login called with:", username, password);
var ret = this.login(username, password); // call original
console.log("[+] login returned:", ret);
return ret;
};
});
Run from host:
frida -U -f com.example.vulnapp -l lab_hook_login.js --no-pause
What to capture: console output, timestamps, and any sensitive data that appears (for evidence, hash and store). Don’t share real credentials.
6.4.2 Modify return values (lab-only demonstration)
To change a method’s return value in a test app (e.g., force an “isDeviceRooted” check to return false in lab):
Java.perform(function() {
var RootChecker = Java.use("com.example.security.RootChecker");
RootChecker.isDeviceRooted.implementation = function() {
var orig = this.isDeviceRooted();
console.log("[*] original rooted:", orig);
return false; // lab-only: force not rooted behavior
};
});
Important: Use this only to test detection/reporting in your lab builds. Do not apply to production apps.
6.5 Intercepting network libraries (OkHttp/Retrofit)
Many apps use OkHttp. You can intercept Request objects and Response bodies in lab to observe tokens or headers.
Java.perform(function() {
var RequestBuilder = Java.use('okhttp3.Request$Builder');
var RealCall = Java.use('okhttp3.internal.connection.RealCall'); // depends on okhttp version
// Hook client newCall(Request) or OkHttpClient.newCall
var OkHttpClient = Java.use('okhttp3.OkHttpClient');
OkHttpClient.newCall.overload('okhttp3.Request').implementation = function(request) {
console.log("[HTTP] Request URL:", request.url().toString());
var headers = request.headers().toString();
console.log("[HTTP] Headers:", headers);
return this.newCall(request); // call original
};
});
For intercepting responses, find the callback or Response.body().string() and hook accordingly. Beware: converting the response body to string may consume streams — in labs replicate logic carefully.
6.6 Hooking native functions with Frida-Gum
For .so instrumentation (e.g., native encryption function), use Interceptor.attach or NativeFunction.
Example: attach to an exported native function Java_com_example_crypto_encrypt:
var moduleName = "libexample.so";
var exp = Module.findExportByName(moduleName, "Java_com_example_crypto_encrypt");
if (exp) {
Interceptor.attach(exp, {
onEnter: function(args) {
console.log("encrypt called");
// args[0], args[1] are pointers — use Memory.readUtf8String if appropriate
try {
var input = Memory.readUtf8String(args[1]);
console.log("input:", input);
} catch (e) { /* non-string arg */ }
},
onLeave: function(retval) {
// read return pointer if needed
}
});
}
For inline hooks at arbitrary addresses or for reading/writing process memory, Frida-Gum provides Memory.readByteArray, Memory.writeUtf8String, etc. Use carefully in lab to avoid crashing the test app.
6.7 Dealing with obfuscation & hidden call sites
Obfuscation complicates finding targets. Strategies in lab:
- Search by types, not names: look for
OkHttpClientorCipherusage rather than class names. - Trace from entry points: hook Activity lifecycle methods (
onCreate,onResume) and log call stacks to discover call paths. - Use frida-trace to identify frequently-used methods, then refine with hand-crafted hooks.
- Instrument JNI bridges: many obfuscated apps still call native functions with
Java_*exports; hooking these can reveal higher-level intent.
Example: instrument Activity.onCreate and print a stack trace to see what it calls:
Java.perform(function() {
var Activity = Java.use('android.app.Activity');
Activity.onCreate.overload('android.os.Bundle').implementation = function(bundle) {
console.log("Activity onCreate: " + this.getClass().getName());
Thread.backtrace(Java.vm.getEnv(), Backtracer.ACCURATE).forEach(function(frame) {
console.log(frame.toString());
});
this.onCreate(bundle);
};
});
6.8 frida-trace & automated traces (fast reconnaissance)
frida-trace generates instrumentation hooks automatically based on patterns:
frida-trace -U -i "java.lang.String.*" -f com.example.vulnapp
Use it to rapidly discover functions that handle strings, crypto, or network. It is noisy; capture output, prune uninteresting traces, and then create targeted scripts.
6.9 Persistence, script packaging & frida-compile
- Use
frida-compile(part of Frida tooling) to bundle multiple JS modules into a single file for deployment in repeated labs. - Store scripts in a versioned private repo with metadata (what APK version it targets, Frida version used, device image snapshot ID).
6.10 Anti-instrumentation & detection (what to look for)
Apps may include techniques to detect Frida, Xposed, or other instrumentation. In static analysis you might have seen these; at runtime they manifest as behavior or logs.
Common anti-instrumentation techniques:
- Detecting loaded processes or files: checking
/proc/<pid>/cmdlineforfrida-serveror scanning process list for suspicious names. - Timing checks: detecting delays introduced by hooks.
- Detecting debugger or ptrace:
Debug.isDebuggerConnected()or nativeptracechecks. - Self-checking code integrity: verifying checksums of loaded code or native libraries at runtime.
- Dynamic code integrity: verifying stack/return addresses or using anti-hook primitives.
How to detect these behaviors (lab)
- Hook common detection APIs and log calls (e.g.,
Debug.isDebuggerConnected) — if called early during startup, app performs anti-debug. - Log any exceptions or unusual stack traces during instrumentation attempts (they can hint at tamper detection).
- Inspect native code for
ptrace,prctl, or/procreads — static patterns you flagged in Module 3.
Note: presence of anti-instrumentation in client code is a signal of defense but does not guarantee security. You should map detection to server-side evidence and design telemetry.
6.11 Countermeasures & defensive testing (what developers should do)
Developers should assume instrumentation tools exist and design for detection & resilience:
- Server-side validation: never trust client-only decisions—validate attestation tokens server-side.
- Telemetry: log suspicious instrumented behavior and enforce graduated policies.
- Harden sensitive code: move critical checks to hardware-backed keystore or server.
- Implement tamper-detection reporting: allow clients to report suspicious conditions that the server can correlate.
For red-teaming in lab, use Frida to validate that telemetry triggers as expected when instrumentation runs against a test build.
6.12 Evidence collection, logging & reproducibility
Always capture:
- Frida script used (exact content and version).
- frida-server binary version and host frida-tools version.
- Device/emulator snapshot ID and APK checksum.
- Frida console output & timestamps.
- Any network captures, logcat extracts, and modified APKs (if used) — all hashed (SHA-256) and stored securely.
When reporting a vendor claim (e.g., they bypassed root checks), request equivalent artifacts from them and reproduce in your lab using the same Frida script & device image where possible.
6.13 Practical lab exercises (authorized, controlled)
Lab 6-A — Java method discovery & logging
Goal: enumerate loaded classes for com.yourlab namespace and log calls to login function.
Steps (high-level):
- Install test APK on emulator.
- Start frida-server and confirm
frida-ps -U. - Run the simple logger script above (
lab_hook_login.js) and interact with app. - Collect console logs and save with timestamp, frida version, and APK SHA256.
Expected outcome: you’ll see login arguments & returns and can map where tokens are created.
Lab 6-B — Intercept OkHttp requests
Goal: identify auth headers and tokens sent by OkHttp in the test app.
Steps:
- Hook
OkHttpClient.newCallas shown earlier. - Reproduce the login flow and capture the logged Request URL and headers.
- Correlate with Burp capture (if proxying) for full request/response context.
Expected outcome: Full picture of request headers & tokens for lab verification.
Lab 6-C — Hook a native crypto wrapper
Goal: find and log the plaintext input to a native encrypt function in a test library.
Steps:
- Enumerate module exports for
libcrypto.soin the APK. - Attach to
Java_com_example_crypto_encryptand log args (careful with pointer types). - Reproduce API that uses this function and collect the logs.
Expected outcome: You observe cleartext being passed to native encryptor (lab-only), enabling you to evaluate whether moving crypto to native is correctly implemented.
6.14 Common mistakes & best practices
- Don’t run Frida on production devices — risk of service disruption, data leakage, or legal issues.
- Match versions: mismatched Frida server/client leads to hard-to-debug failures.
- Avoid destructive hooks: modifying memory or return values carelessly can crash the process and corrupt logs used in evidence.
- Prefer non-invasive observation first: log arguments and returns before attempting modifications.
- Record everything: scripts, outputs, and environment metadata; these are essential to validate findings.
6.15 Deliverables for Module 6
- Frida script library (lab-only): documented scripts for enumeration, Java hooks, OkHttp hooks, native attach examples.
- Instrumentation playbook: step-by-step lab procedure template (what to snapshot, what logs to collect, checklists).
- Evidence template: fields to include when reporting an instrumentation-based finding (script, frida versions, APK sha256, device snapshot id, logcat, pcap).
- Anti-instrumentation detection report: list of indicators and mapping to code locations (from static analysis).
6.16 Next steps & suggested readings/tools
- Practice on DVIA and InsecureBankv2 to build comfort with Frida.
- Read Frida docs and study Frida-Gum API to understand native memory APIs.
- Pair Frida sessions with Burp captures and server-side validation to form complete, auditable findings.
