Android Anti-Instrumentation & SSL Pinning Bypass (Frida/Objection)

Tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks

This page provides a practical workflow to regain dynamic analysis against Android apps that detect/root‑block instrumentation or enforce TLS pinning. It focuses on fast triage, common detections, and copy‑pasteable hooks/tactics to bypass them without repacking when possible.

Detection Surface (what apps check)

  • Root checks: su binary, Magisk paths, getprop values, common root packages
  • Frida/debugger checks (Java): Debug.isDebuggerConnected(), ActivityManager.getRunningAppProcesses(), getRunningServices(), scanning /proc, classpath, loaded libs
  • Native anti‑debug: ptrace(), syscalls, anti‑attach, breakpoints, inline hooks
  • Early init checks: Application.onCreate() or process start hooks that crash if instrumentation is present
  • TLS pinning: custom TrustManager/HostnameVerifier, OkHttp CertificatePinner, Conscrypt pinning, native pins

Step 1 — Quick win: hide root with Magisk DenyList

  • Enable Zygisk in Magisk
  • Enable DenyList, add the target package
  • Reboot and retest

Many apps only look for obvious indicators (su/Magisk paths/getprop). DenyList often neutralizes naive checks.

References:

  • Magisk (Zygisk & DenyList): https://github.com/topjohnwu/Magisk

Step 2 — 30‑second Frida Codeshare tests

Try common drop‑in scripts before deep diving:

  • anti-root-bypass.js
  • anti-frida-detection.js
  • hide_frida_gum.js

Example:

frida -U -f com.example.app -l anti-frida-detection.js

These typically stub Java root/debug checks, process/service scans, and native ptrace(). Useful on lightly protected apps; hardened targets may need tailored hooks.

  • Codeshare: https://codeshare.frida.re/

Automate with Medusa (Frida framework)

Medusa provides 90+ ready-made modules for SSL unpinning, root/emulator detection bypass, HTTP comms logging, crypto key interception, and more.

git clone https://github.com/Ch0pin/medusa
cd medusa
pip install -r requirements.txt
python medusa.py

# Example interactive workflow
show categories
use http_communications/multiple_unpinner
use root_detection/universal_root_detection_bypass
run com.target.app

Tip: Medusa is great for quick wins before writing custom hooks. You can also cherry-pick modules and combine them with your own scripts.

Step 3 — Bypass init-time detectors by attaching late

Many detections only run during process spawn/onCreate(). Spawn‑time injection (-f) or gadgets get caught; attaching after UI loads can slip past.

# Launch the app normally (launcher/adb), wait for UI, then attach
frida -U -n com.example.app
# Or with Objection to attach to running process
aobjection --gadget com.example.app explore  # if using gadget

If this works, keep the session stable and proceed to map and stub checks.

Step 4 — Map detection logic via Jadx and string hunting

Static triage keywords in Jadx:

  • ā€œfridaā€, ā€œgumā€, ā€œrootā€, ā€œmagiskā€, ā€œptraceā€, ā€œsuā€, ā€œgetpropā€, ā€œdebuggerā€

Typical Java patterns:

public boolean isFridaDetected() {
    return getRunningServices().contains("frida");
}

Common APIs to review/hook:

  • android.os.Debug.isDebuggerConnected
  • android.app.ActivityManager.getRunningAppProcesses / getRunningServices
  • java.lang.System.loadLibrary / System.load (native bridge)
  • java.lang.Runtime.exec / ProcessBuilder (probing commands)
  • android.os.SystemProperties.get (root/emulator heuristics)

Step 5 — Runtime stubbing with Frida (Java)

Override custom guards to return safe values without repacking:

Java.perform(() => {
  const Checks = Java.use('com.example.security.Checks');
  Checks.isFridaDetected.implementation = function () { return false; };

  // Neutralize debugger checks
  const Debug = Java.use('android.os.Debug');
  Debug.isDebuggerConnected.implementation = function () { return false; };

  // Example: kill ActivityManager scans
  const AM = Java.use('android.app.ActivityManager');
  AM.getRunningAppProcesses.implementation = function () { return java.util.Collections.emptyList(); };
});

Triaging early crashes? Dump classes just before it dies to spot likely detection namespaces:

Java.perform(() => {
  Java.enumerateLoadedClasses({
    onMatch: n => console.log(n),
    onComplete: () => console.log('Done')
  });
});

// Quick root detection stub example (adapt to target package/class names) Java.perform(() => { try { const RootChecker = Java.use(ā€˜com.target.security.RootCheck’); RootChecker.isDeviceRooted.implementation = function () { return false; }; } catch (e) {} });

Log and neuter suspicious methods to confirm execution flow:

Java.perform(() => {
  const Det = Java.use('com.example.security.DetectionManager');
  Det.checkFrida.implementation = function () {
    console.log('checkFrida() called');
    return false;
  };
});

Bypass emulator/VM detection (Java stubs)

Common heuristics: Build.FINGERPRINT/MODEL/MANUFACTURER/HARDWARE containing generic/goldfish/ranchu/sdk; QEMU artifacts like /dev/qemu_pipe, /dev/socket/qemud; default MAC 02:00:00:00:00:00; 10.0.2.x NAT; missing telephony/sensors.

Quick spoof of Build fields:

Java.perform(function(){
  var Build = Java.use('android.os.Build');
  Build.MODEL.value = 'Pixel 7 Pro';
  Build.MANUFACTURER.value = 'Google';
  Build.BRAND.value = 'google';
  Build.FINGERPRINT.value = 'google/panther/panther:14/UP1A.231105.003/1234567:user/release-keys';
});

Complement with stubs for file existence checks and identifiers (TelephonyManager.getDeviceId/SubscriberId, WifiInfo.getMacAddress, SensorManager.getSensorList) to return realistic values.

SSL pinning bypass quick hook (Java)

Neutralize custom TrustManagers and force permissive SSL contexts:

Java.perform(function(){
  var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
  var SSLContext = Java.use('javax.net.ssl.SSLContext');

  // No-op validations
  X509TrustManager.checkClientTrusted.implementation = function(){ };
  X509TrustManager.checkServerTrusted.implementation = function(){ };

  // Force permissive TrustManagers
  var TrustManagers = [ X509TrustManager.$new() ];
  var SSLContextInit = SSLContext.init.overload('[Ljavax.net.ssl.KeyManager;','[Ljavax.net.ssl.TrustManager;','java.security.SecureRandom');
  SSLContextInit.implementation = function(km, tm, sr){
    return SSLContextInit.call(this, km, TrustManagers, sr);
  };
});

Notes

  • Extend for OkHttp: hook okhttp3.CertificatePinner and HostnameVerifier as needed, or use a universal unpinning script from CodeShare.
  • Run example: frida -U -f com.target.app -l ssl-bypass.js --no-pause

Step 6 — Follow the JNI/native trail when Java hooks fail

Trace JNI entry points to locate native loaders and detection init:

frida-trace -n com.example.app -i "JNI_OnLoad"

Quick native triage of bundled .so files:

# List exported symbols & JNI
nm -D libfoo.so | head
objdump -T libfoo.so | grep Java_
strings -n 6 libfoo.so | egrep -i 'frida|ptrace|gum|magisk|su|root'

Interactive/native reversing:

  • Ghidra: https://ghidra-sre.org/
  • r2frida: https://github.com/nowsecure/r2frida

Example: neuter ptrace to defeat simple anti‑debug in libc:

const ptrace = Module.findExportByName(null, 'ptrace');
if (ptrace) {
  Interceptor.replace(ptrace, new NativeCallback(function () {
    return -1; // pretend failure
  }, 'int', ['int', 'int', 'pointer', 'pointer']));
}

See also: Reversing Native Libraries

Step 7 — Objection patching (embed gadget / strip basics)

When you prefer repacking to runtime hooks, try:

objection patchapk --source app.apk

Notes:

  • Requires apktool; ensure a current version from the official guide to avoid build issues: https://apktool.org/docs/install
  • Gadget injection enables instrumentation without root but can still be caught by stronger init‑time checks.

Optionally, add LSPosed modules and Shamiko for stronger root hiding in Zygisk environments, and curate DenyList to cover child processes.

For a complete workflow including script-mode Gadget configuration and bundling your Frida 17+ agent into the APK, see:

Frida Tutorial — Self-contained agent + Gadget embedding

References:

  • Objection: https://github.com/sensepost/objection

Step 8 — Fallback: Patch TLS pinning for network visibility

If instrumentation is blocked, you can still inspect traffic by removing pinning statically:

apk-mitm app.apk
# Then install the patched APK and proxy via Burp/mitmproxy
  • Tool: https://github.com/shroudedcode/apk-mitm

  • For network config CA‑trust tricks (and Android 7+ user CA trust), see:

    {{#ref}} make-apk-accept-ca-certificate.md {{#endref}}

    {{#ref}} install-burp-certificate.md {{#endref}}

Handy command cheat‑sheet

# List processes and attach
frida-ps -Uai
frida -U -n com.example.app

# Spawn with a script (may trigger detectors)
frida -U -f com.example.app -l anti-frida-detection.js

# Trace native init
frida-trace -n com.example.app -i "JNI_OnLoad"

# Objection runtime
objection --gadget com.example.app explore

# Static TLS pinning removal
apk-mitm app.apk

Universal proxy forcing + TLS unpinning (HTTP Toolkit Frida hooks)

Modern apps often ignore system proxies and enforce multiple layers of pinning (Java + native), making traffic capture painful even with user/system CAs installed. A practical approach is to combine universal TLS unpinning with proxy forcing via ready-made Frida hooks, and route everything through mitmproxy/Burp.

Workflow

  • Run mitmproxy on your host (or Burp). Ensure the device can reach the host IP/port.
  • Load HTTP Toolkit’s consolidated Frida hooks to both unpin TLS and force proxy usage across common stacks (OkHttp/OkHttp3, HttpsURLConnection, Conscrypt, WebView, etc.). This bypasses CertificatePinner/TrustManager checks and overrides proxy selectors, so traffic is always sent via your proxy even if the app explicitly disables proxies.
  • Start the target app with Frida and the hook script, and capture requests in mitmproxy.

Example

# Device connected via ADB or over network (-U)
# See the repo for the exact script names & options
frida -U -f com.vendor.app \
  -l ./android-unpinning-with-proxy.js \
  --no-pause

# mitmproxy listening locally
mitmproxy -p 8080

Notes

  • Combine with a system-wide proxy via adb shell settings put global http_proxy <host>:<port> when possible. The Frida hooks will enforce proxy use even when apps bypass global settings.
  • This technique is ideal when you need to MITM mobile-to-IoT onboarding flows where pinning/proxy avoidance is common.
  • Hooks: https://github.com/httptoolkit/frida-interception-and-unpinning

References

Tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks