daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

android-anti-instrumentation-and-ssl-pinning-bypass.md (29147B)


      1 ---
      2 title: "Android Anti-Instrumentation & SSL Pinning Bypass (Frida/Objection)"
      3 section: "Mobile"
      4 sectionSlug: "mobile-pentesting"
      5 sourcePath: "src/mobile-pentesting/android-app-pentesting/android-anti-instrumentation-and-ssl-pinning-bypass.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/android-app-pentesting/android-anti-instrumentation-and-ssl-pinning-bypass.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Android Anti-Instrumentation & SSL Pinning Bypass (Frida/Objection)
     14 
     15 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.
     16 
     17 ## Detection Surface (what apps check)
     18 
     19 - Root checks: su binary, Magisk paths, getprop values, common root packages
     20 - Frida/debugger checks (Java): Debug.isDebuggerConnected(), ActivityManager.getRunningAppProcesses(), getRunningServices(), scanning /proc, classpath, loaded libs
     21 - Native anti‑debug: ptrace(), syscalls, anti‑attach, breakpoints, inline hooks
     22 - Early init checks: Application.onCreate() or process start hooks that crash if instrumentation is present
     23 - TLS pinning: custom TrustManager/HostnameVerifier, OkHttp CertificatePinner, Conscrypt pinning, native pins
     24 
     25 ## Bypassing Anti-Frida Detection / Stealth Frida Servers
     26 
     27 **phantom-frida** rebuilds Frida from source and applies ~90 patches so common Frida fingerprints disappear while the stock Frida protocol remains compatible (`frida-tools` can still connect). Target: apps that grep `/proc` (cmdline, maps, task comm, fd readlink), D-Bus service names, default ports, or exported symbols.<sup>[[13]](#references)</sup>
     28 
     29 Phases:
     30 - **Source patches:** global rename of `frida` identifiers (server/agent/helper) and rebuilt helper DEX with a renamed Java package.
     31 - **Targeted build/runtime patches:** meson tweaks, memfd label changed to `jit-cache`, SELinux labels (e.g., `frida_file`) renamed, libc hooks on `exit`/`signal` disabled to avoid hook-detectors.
     32 - **Post-build rename:** exported symbol `frida_agent_main` renamed after the first compile (Vala emits it), requiring a second incremental build.
     33 - **Binary hex patches:** thread names (`gmain`, `gdbus`, `pool-spawner`) replaced; optional sweep removes leftover `frida`/`Frida` strings.
     34 
     35 Detection vectors covered:
     36 - **Base (1–8):** process name `frida-server`, mapped `libfrida-agent.so`, thread names, memfd label, exported `frida_agent_main`, SELinux labels, libc hook side-effects, and D-Bus service `re.frida.server` are renamed/neutralized.
     37 - **Extended (9–16):** change listening port (`--port`), rename D-Bus interfaces/internal C symbols/GType names, temp paths like `.frida`/`frida-`, sweep binary strings, rename build-time defines and asset paths (`libdir/frida`). D-Bus interface names that are part of the wire protocol stay unchanged in base mode to avoid breaking stock clients.
     38 
     39 Build/usage (Android arm64 example):
     40 ```bash
     41 python3 build.py --version 17.7.2 --name myserver --port 27142 --extended --verify
     42 adb push output/myserver-server-17.7.2-android-arm64 /data/local/tmp/myserver-server
     43 adb shell chmod 755 /data/local/tmp/myserver-server
     44 adb shell /data/local/tmp/myserver-server -D &
     45 adb forward tcp:27142 tcp:27142
     46 frida -H 127.0.0.1:27142 -f com.example.app
     47 ```
     48 Flags: `--skip-build` (patch only), `--skip-clone`, `--arch`, `--ndk-path`, `--temp-fixes`; WSL helper: `wsl -d Ubuntu bash build-wsl.sh`.
     49 
     50 ## Step 1 — Quick win: hide root with Magisk DenyList
     51 
     52 - Enable Zygisk in Magisk
     53 - Enable DenyList, add the target package
     54 - Reboot and retest
     55 
     56 Many apps only look for obvious indicators (su/Magisk paths/getprop). DenyList often neutralizes naive checks.<sup>[[1]](#references)</sup>
     57 
     58 References:
     59 - Magisk (Zygisk & DenyList): https://github.com/topjohnwu/Magisk<sup>[[9]](#references)</sup>
     60 
     61 ### Play Integrity / Zygisk detections (post‑SafetyNet)
     62 
     63 Newer banking/ID apps tie runtime checks to Google Play Integrity (SafetyNet replacement) and can also crash if Zygisk itself is present.<sup>[[15]](#references)</sup> Quick triage tips:
     64 
     65 - Temporarily disable Zygisk (toggle off + reboot) and retry; some apps crash as soon as Zygote injection loads.
     66 - If attestation blocks login, patch Google Play Services with PlayIntegrityFix/Fork + TrickyStore or use ReZygisk/Zygisk‑Next only when testing. Keep the target in DenyList and avoid LSPosed modules that leak props.
     67 - For one‑off runs, use KernelSU/APatch (no Zygote injection) to stay under Zygisk heuristics, then attach Frida.
     68 
     69 ## Step 2 — 30‑second Frida Codeshare tests
     70 
     71 Try common drop‑in scripts before deep diving:
     72 
     73 - anti-root-bypass.js
     74 - anti-frida-detection.js
     75 - hide_frida_gum.js
     76 
     77 Example:
     78 
     79 ```bash
     80 frida -U -f com.example.app -l anti-frida-detection.js
     81 ```
     82 
     83 These typically stub Java root/debug checks, process/service scans, and native ptrace(). Useful on lightly protected apps; hardened targets may need tailored hooks.<sup>[[1]](#references)[[2]](#references)</sup>
     84 
     85 - Codeshare: https://codeshare.frida.re/<sup>[[2]](#references)</sup>
     86 
     87 ## Automate with Medusa (Frida framework)
     88 
     89 Medusa provides 90+ ready-made modules for SSL unpinning, root/emulator detection bypass, HTTP comms logging, crypto key interception, and more.<sup>[[10]](#references)</sup>
     90 
     91 ```bash
     92 git clone https://github.com/Ch0pin/medusa
     93 cd medusa
     94 pip install -r requirements.txt
     95 python medusa.py
     96 
     97 # Example interactive workflow
     98 show categories
     99 use http_communications/multiple_unpinner
    100 use root_detection/universal_root_detection_bypass
    101 run com.target.app
    102 ```
    103 
    104 Tip: Medusa is great for quick wins before writing custom hooks. You can also cherry-pick modules and combine them with your own scripts.
    105 
    106 ## Automate with Auto-Frida (spawn-mode + consolidated hooks)
    107 
    108 Auto-Frida is a Frida automation toolkit that focuses on repeatable setup plus **auto-detection** of protections and **consolidated bypass script generation**. It is useful when apps run checks very early or when multiple bypass modules would otherwise double-hook the same APIs.<sup>[[11]](#references)</sup>
    109 
    110 Key automation ideas:
    111 - **Spawn-mode analysis** to install hooks before `Application.onCreate()` so early SSL pinning, root, emulator, or anti-Frida checks are caught.
    112 - **Protection detection + auto-bypass**: detection results drive the generation of a single consolidated script that hooks each Java method/native symbol once, reducing crashes from overlapping hooks.
    113 - **Frida server lifecycle checks**: validate server health (process + port `27042` + `frida-ps` handshake) before downloading/restarting to keep runs stable.
    114 
    115 Quick start:
    116 ```bash
    117 git clone https://github.com/ommirkute/Auto-Frida.git
    118 cd Auto-Frida
    119 pip install -r requirements.txt
    120 python auto_frida.py
    121 ```
    122 
    123 Notes
    124 - Auto-Frida can auto-install `frida`/`frida-tools` if missing and supports multi-device selection.
    125 - Generated scripts can be executed immediately or merged with your custom hooks after analysis.
    126 
    127 ## Step 3 — Bypass init-time detectors by attaching late
    128 
    129 Many detections only run during process spawn/onCreate(). Spawn‑time injection (-f) or gadgets get caught; attaching after UI loads can slip past.
    130 
    131 ```bash
    132 # Launch the app normally (launcher/adb), wait for UI, then attach
    133 frida -U -n com.example.app
    134 # Or with Objection to attach to running process
    135 aobjection --gadget com.example.app explore  # if using gadget
    136 ```
    137 
    138 If this works, keep the session stable and proceed to map and stub checks.
    139 
    140 ## Step 4 — Map detection logic via Jadx and string hunting
    141 
    142 Static triage keywords in Jadx:<sup>[[1]](#references)[[5]](#references)</sup>
    143 - "frida", "gum", "root", "magisk", "ptrace", "su", "getprop", "debugger"
    144 
    145 Typical Java patterns:
    146 
    147 ```java
    148 public boolean isFridaDetected() {
    149     return getRunningServices().contains("frida");
    150 }
    151 ```
    152 
    153 Common APIs to review/hook:
    154 - android.os.Debug.isDebuggerConnected
    155 - android.app.ActivityManager.getRunningAppProcesses / getRunningServices
    156 - java.lang.System.loadLibrary / System.load (native bridge)
    157 - java.lang.Runtime.exec / ProcessBuilder (probing commands)
    158 - android.os.SystemProperties.get (root/emulator heuristics)
    159 
    160 ## Step 5 — Runtime stubbing with Frida (Java)
    161 
    162 Override custom guards to return safe values without repacking:<sup>[[1]](#references)</sup>
    163 
    164 ```javascript
    165 Java.perform(() => {
    166   const Checks = Java.use('com.example.security.Checks');
    167   Checks.isFridaDetected.implementation = function () { return false; };
    168 
    169   // Neutralize debugger checks
    170   const Debug = Java.use('android.os.Debug');
    171   Debug.isDebuggerConnected.implementation = function () { return false; };
    172 
    173   // Example: kill ActivityManager scans
    174   const AM = Java.use('android.app.ActivityManager');
    175   AM.getRunningAppProcesses.implementation = function () { return java.util.Collections.emptyList(); };
    176 });
    177 ```
    178 
    179 Triaging early crashes? Dump classes just before it dies to spot likely detection namespaces:
    180 
    181 ```javascript
    182 Java.perform(() => {
    183   Java.enumerateLoadedClasses({
    184     onMatch: n => console.log(n),
    185     onComplete: () => console.log('Done')
    186   });
    187 });
    188 ```
    189 
    190 Quick root detection stub example (adapt to target package/class names):<sup>[[12]](#references)</sup>
    191 
    192 ```javascript
    193 Java.perform(() => {
    194   try {
    195     const RootChecker = Java.use('com.target.security.RootCheck');
    196     RootChecker.isDeviceRooted.implementation = function () { return false; };
    197   } catch (e) {}
    198 });
    199 ```
    200 
    201 Log and neuter suspicious methods to confirm execution flow:
    202 
    203 ```javascript
    204 Java.perform(() => {
    205   const Det = Java.use('com.example.security.DetectionManager');
    206   Det.checkFrida.implementation = function () {
    207     console.log('checkFrida() called');
    208     return false;
    209   };
    210 });
    211 ```
    212 
    213 ## Bypass emulator/VM detection (Java stubs)
    214 
    215 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.<sup>[[12]](#references)</sup>
    216 
    217 Quick spoof of Build fields:
    218 ```javascript
    219 Java.perform(function(){
    220   var Build = Java.use('android.os.Build');
    221   Build.MODEL.value = 'Pixel 7 Pro';
    222   Build.MANUFACTURER.value = 'Google';
    223   Build.BRAND.value = 'google';
    224   Build.FINGERPRINT.value = 'google/panther/panther:14/UP1A.231105.003/1234567:user/release-keys';
    225 });
    226 ```
    227 
    228 Complement with stubs for file existence checks and identifiers (TelephonyManager.getDeviceId/SubscriberId, WifiInfo.getMacAddress, SensorManager.getSensorList) to return realistic values.
    229 
    230 ## SSL pinning bypass quick hook (Java)
    231 
    232 Neutralize custom TrustManagers and force permissive SSL contexts:<sup>[[12]](#references)</sup>
    233 ```javascript
    234 Java.perform(function(){
    235   var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
    236   var SSLContext = Java.use('javax.net.ssl.SSLContext');
    237 
    238   // No-op validations
    239   X509TrustManager.checkClientTrusted.implementation = function(){ };
    240   X509TrustManager.checkServerTrusted.implementation = function(){ };
    241 
    242   // Force permissive TrustManagers
    243   var TrustManagers = [ X509TrustManager.$new() ];
    244   var SSLContextInit = SSLContext.init.overload('[Ljavax.net.ssl.KeyManager;','[Ljavax.net.ssl.TrustManager;','java.security.SecureRandom');
    245   SSLContextInit.implementation = function(km, tm, sr){
    246     return SSLContextInit.call(this, km, TrustManagers, sr);
    247   };
    248 });
    249 ```
    250 
    251 Notes
    252 - Extend for OkHttp: hook okhttp3.CertificatePinner and HostnameVerifier as needed, or use a universal unpinning script from CodeShare.
    253 - Run example: `frida -U -f com.target.app -l ssl-bypass.js --no-pause`
    254 
    255 ### LSPosed layered unpinning and pre-load Flutter patching
    256 
    257 When LSPosed is already available on the test device, [SSL Kill Switch](https://github.com/0xdad0/ssl-kill-switch-lsposed) can apply persistent hooks without repacking the APK or attaching Frida. Install and activate the module, restrict its **LSPosed scope** to the authorized packages, restart those processes, install/trust the interception CA as required, and route traffic through the proxy. Only its optional transparent-routing feature invokes `su` for `iptables`.<sup>[[20]](#references)</sup>
    258 
    259 The Java bypass is applied twice: from `initZygote()` for boot-classpath implementations and again from `handleLoadPackage()` with the target application's class loader. This lets one module cover the following layers while preserving each hooked method's return contract.<sup>[[20]](#references)</sup>
    260 
    261 - Replace the `TrustManager[]` passed to `SSLContext.init()` while preserving `KeyManager[]`; suppress Conscrypt and Network Security Configuration checks. Void validators can return immediately, but methods returning a cleaned chain must return the unverified input as the expected array or `List<X509Certificate>` instead of `null`.
    262 - Force named hostname verifiers to succeed and replace verifier arguments passed to `HttpsURLConnection`.
    263 - No-op OkHttp/TrustKit pinners, then also replace the finished OkHttp client's verifier and pinner fields. This second layer survives internal method-signature changes that prevent a direct `CertificatePinner` hook from matching.
    264 - Continue WebView TLS failures and suppress later error callbacks. Redirecting process-wide `SslErrorHandler.cancel()` calls to `proceed()` also catches overrides that explicitly cancel without calling `super`; equivalent hooks can use Cordova or Tencent X5 handler types.
    265 
    266 Flutter needs a different path because BoringSSL is statically linked into `libflutter.so` and its verifier is normally not exported. The original NVISO technique locates `ssl_verify_peer_cert` with byte signatures; SSL Kill Switch moves that idea to a pre-load file-patching flow.<sup>[[19]](#references)[[20]](#references)</sup>
    267 
    268 1. Intercept every available `Runtime.loadLibrary0` overload, falling back to `System.loadLibrary()` and also covering absolute-path `System.load()` calls.
    269 2. Resolve `libflutter.so` through the supplied class loader, `nativeLibraryDir`, or base/split APK ZIP entries; extract it to app cache when `extractNativeLibs=false`.
    270 3. Pattern-scan a copy with byte/nibble wildcards, overwrite the verifier prologue with an architecture-specific immediate-return stub, and verify the written bytes. Use the target function's semantic success value (`0` for `ssl_verify_peer_cert`, but `1` for Boolean chain-verification routines).
    271 4. Load the patched copy in the original class-loader namespace and suppress the original load. If no signature matches, allow the original library load rather than corrupting an unknown Flutter build.
    272 
    273 Current implementation caveats are important during testing:<sup>[[20]](#references)</sup>
    274 
    275 - Java hooks and the Kotlin Flutter patch affect every process selected in **LSPosed module scope**; the module UI's per-category/domain selections do not gate these active paths. The Flutter mode selector is also ignored and the alternative native C++ engine is disabled.
    276 - The UI's nominal per-application redirect does not emit `-m owner --uid-owner UID`, so it actually redirects device-wide TCP 80/443 traffic. It also does not cover QUIC/HTTP/3 over UDP 443 or exclude traffic already destined for the proxy.
    277 - Global removal and `flushAll()` delete/flush `OUTPUT` entries but leave the added `POSTROUTING` `MASQUERADE` rules. Inspect the complete NAT table and remove residual test rules manually.
    278 
    279 ### mTLS interception: bypass server pinning without breaking client auth
    280 
    281 For **mTLS** apps, `SSLContext.init(KeyManager[], TrustManager[], SecureRandom)` controls **two different trust decisions**:
    282 - **`TrustManager[]`** validates the **server** certificate.
    283 - **`KeyManager[]`** presents the **client** certificate/private key.
    284 
    285 If you replace **both** arrays with a generic “trust all” hook, the app may accept Burp's certificate but **stop sending its client certificate**, so the handshake still fails. In mTLS scenarios, keep the original `KeyManager[]` and replace **only** `TrustManager[]`.<sup>[[17]](#references)[[18]](#references)</sup>
    286 
    287 ```javascript
    288 Java.perform(function () {
    289   var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
    290   var SSLContext = Java.use('javax.net.ssl.SSLContext');
    291   var TrustAll = Java.registerClass({
    292     name: 'com.ht.TrustAll', implements: [X509TrustManager], methods: {
    293       checkClientTrusted: function () {}, checkServerTrusted: function () {},
    294       getAcceptedIssuers: function () { return []; }
    295     }
    296   });
    297   var init = SSLContext.init.overload('[Ljavax.net.ssl.KeyManager;','[Ljavax.net.ssl.TrustManager;','java.security.SecureRandom');
    298   init.implementation = function (km, tm, sr) {
    299     return init.call(this, km, Java.array('javax.net.ssl.TrustManager', [TrustAll.$new()]), sr);
    300   };
    301 });
    302 ```
    303 
    304 ### mTLS client certificate extraction from live keystore reloads
    305 
    306 A common Android mTLS pattern is:
    307 1. generate an app keypair,
    308 2. store the private key + issued client cert in **PKCS12** (`.p12`), often with a runtime-derived password,
    309 3. reload that keystore on every request to build a `KeyManager`.
    310 
    311 That password can be strong at rest and still be useless during runtime: the app must eventually call `KeyStore.load(...)`, `getCertificate(...)`, and `getKey(alias, password)` in-process. Hook the method/constructor that receives the **decrypted `KeyStore`**, alias, and password (often a custom `KeyManager` wrapper) and dump the live material instead of brute-forcing the `.p12` offline.<sup>[[17]](#references)[[18]](#references)</sup>
    312 
    313 Quick triage:
    314 - `privateKey.getEncoded()` returns **bytes** → software/JCE key, usually exportable.
    315 - `privateKey.getEncoded()` returns **`null`** → likely `AndroidKeyStore`/TEE-backed, so direct key export is blocked and you need a different approach.
    316 
    317 <details>
    318 <summary>Frida example: dump client cert/private key from a decrypted PKCS12-backed KeyStore</summary>
    319 
    320 ```javascript
    321 Java.perform(function () {
    322   var CKM = Java.use('com.example.app.ClientKeyManager');
    323   var Base64 = Java.use('android.util.Base64');
    324   var X509Certificate = Java.use('java.security.cert.X509Certificate');
    325 
    326   CKM.$init.implementation = function (ks, alias, password) {
    327     this.$init(ks, alias, password);
    328     var cert = Java.cast(ks.getCertificate(alias), X509Certificate);
    329     var certPem = Base64.encodeToString(cert.getEncoded(), 0);
    330     var key = ks.getKey(alias, password);
    331     var raw = key.getEncoded();
    332     console.log('alias=' + alias + ' password=' + password);
    333     console.log('CERT=' + certPem);
    334     console.log('KEY=' + (raw ? Base64.encodeToString(raw, 0) : 'null'));
    335   };
    336 });
    337 ```
    338 
    339 </details>
    340 
    341 If the key is exportable, convert the dumped PEM key + certificate into a Burp-compatible client bundle:
    342 
    343 ```bash
    344 openssl pkcs12 -export -out client-cert.pfx -inkey privateKey.key -in cert.pem
    345 ```
    346 
    347 Useful extra hook points when Frida is attached **before enrollment**:
    348 - `KeyPairGenerator.generateKeyPair()`
    349 - `KeyStore.setKeyEntry()`
    350 - custom registration code that signs a nonce before the server issues the client certificate
    351 
    352 ### OkHttp4 / gRPC / Cronet pinning (2024+)
    353 
    354 Modern stacks pin inside newer APIs (OkHttp4+, gRPC over Cronet/BoringSSL). Add these hooks when the basic SSLContext hook hangs:<sup>[[14]](#references)</sup>
    355 
    356 ```javascript
    357 Java.perform(() => {
    358   try {
    359     const Pinner = Java.use('okhttp3.CertificatePinner');
    360     Pinner.check.overload('java.lang.String', 'java.util.List').implementation = function(){};
    361     Pinner.check$okhttp.implementation = function(){};
    362   } catch (e) {}
    363 
    364   try {
    365     const CronetB = Java.use('org.chromium.net.CronetEngine$Builder');
    366     CronetB.enablePublicKeyPinningBypassForLocalTrustAnchors.overload('boolean').implementation = function(){ return this; };
    367     CronetB.setPublicKeyPins.overload('java.lang.String', 'java.util.Set', 'boolean').implementation = function(){ return this; };
    368   } catch (e) {}
    369 });
    370 ```
    371 
    372 If TLS still fails, drop to native and patch BoringSSL verification entry points used by Cronet/gRPC:
    373 
    374 ```javascript
    375 const customVerify = Module.findExportByName(null, 'SSL_CTX_set_custom_verify');
    376 if (customVerify) {
    377   Interceptor.attach(customVerify, {
    378     onEnter(args){
    379       // arg0 = SSL_CTX*, arg1 = mode, arg2 = callback
    380       args[1] = ptr(0); // SSL_VERIFY_NONE
    381       args[2] = NULL;  // disable callback
    382     }
    383   });
    384 }
    385 ```
    386 
    387 ## Step 6 — Follow the JNI/native trail when Java hooks fail
    388 
    389 Trace JNI entry points to locate native loaders and detection init:<sup>[[1]](#references)</sup>
    390 
    391 ```bash
    392 frida-trace -n com.example.app -i "JNI_OnLoad"
    393 ```
    394 
    395 Quick native triage of bundled .so files:
    396 
    397 ```bash
    398 # List exported symbols & JNI
    399 nm -D libfoo.so | head
    400 objdump -T libfoo.so | grep Java_
    401 strings -n 6 libfoo.so | egrep -i 'frida|ptrace|gum|magisk|su|root'
    402 ```
    403 
    404 Interactive/native reversing:
    405 - Ghidra: https://ghidra-sre.org/<sup>[[6]](#references)</sup>
    406 - r2frida: https://github.com/nowsecure/r2frida<sup>[[7]](#references)</sup>
    407 
    408 Example: neuter ptrace to defeat simple anti‑debug in libc:
    409 
    410 ```javascript
    411 const ptrace = Module.findExportByName(null, 'ptrace');
    412 if (ptrace) {
    413   Interceptor.replace(ptrace, new NativeCallback(function () {
    414     return -1; // pretend failure
    415   }, 'int', ['int', 'int', 'pointer', 'pointer']));
    416 }
    417 ```
    418 
    419 See also:
    420 [Reversing Native Libraries](/hacktricks/mobile-pentesting/android-app-pentesting/reversing-native-libraries)
    421 
    422 ## Step 7 — Objection patching (embed gadget / strip basics)
    423 
    424 When you prefer repacking to runtime hooks, try:
    425 
    426 ```bash
    427 objection patchapk --source app.apk
    428 ```
    429 
    430 Notes:
    431 - Requires apktool; ensure a current version from the official guide to avoid build issues: https://apktool.org/docs/install<sup>[[8]](#references)</sup>
    432 - Gadget injection enables instrumentation without root but can still be caught by stronger init‑time checks.
    433 
    434 Optionally, add LSPosed modules and Shamiko for stronger root hiding in Zygisk environments, and curate DenyList to cover child processes.<sup>[[12]](#references)</sup>
    435 
    436 For a complete workflow including script-mode Gadget configuration and bundling your Frida 17+ agent into the APK, see:
    437 
    438 [Frida Tutorial — Self-contained agent + Gadget embedding](/hacktricks/mobile-pentesting/android-app-pentesting/frida-tutorial/overview)
    439 
    440 References:
    441 - Objection: https://github.com/sensepost/objection<sup>[[3]](#references)</sup>
    442 
    443 ## Step 8 — Fallback: Patch TLS pinning for network visibility
    444 
    445 If instrumentation is blocked, you can still inspect traffic by removing pinning statically:<sup>[[1]](#references)</sup>
    446 
    447 ```bash
    448 apk-mitm app.apk
    449 # Then install the patched APK and proxy via Burp/mitmproxy
    450 ```
    451 
    452 - Tool: https://github.com/shroudedcode/apk-mitm<sup>[[4]](#references)</sup>
    453 - For network config CA‑trust tricks (and Android 7+ user CA trust), see:
    454 
    455 [Make Apk Accept Ca Certificate](/hacktricks/mobile-pentesting/android-app-pentesting/make-apk-accept-ca-certificate)
    456 
    457 [Install Burp Certificate](/hacktricks/mobile-pentesting/android-app-pentesting/install-burp-certificate)
    458 
    459 
    460 ## LSPosed/Xposed Hooking Abuse (Telephony/SMS)
    461 
    462 On rooted devices, LSPosed/Xposed modules can hook Java telephony/SMS APIs at runtime, keeping the APK unmodified on disk while fully controlling what the app sees. This is commonly abused to bypass SIM‑binding flows that trust local telephony APIs or local SMS provider state.<sup>[[16]](#references)</sup>
    463 
    464 Key primitives
    465 - **Suppress outgoing verification SMS** while exfiltrating the token by short‑circuiting `SmsManager.sendTextMessage` in `beforeHookedMethod`.
    466 - **Spoof MSISDN/line number** by forcing `TelephonyManager.getLine1Number()` and `SubscriptionInfo.getNumber()` to return an attacker‑controlled value.
    467 - **Plant a fake “Sent” record** in the SMS provider so apps that check local SMS history see a successful send even if the carrier never received it.
    468 
    469 Example: block SMS dispatch and capture content
    470 ```java
    471 XposedHelpers.findAndHookMethod(
    472   "android.telephony.SmsManager",
    473   lpparam.classLoader,
    474   "sendTextMessage",
    475   String.class, String.class, String.class, PendingIntent.class, PendingIntent.class,
    476   new XC_MethodHook() {
    477     protected void beforeHookedMethod(MethodHookParam param) {
    478       String body = (String) param.args[2];
    479       // exfiltrate body to operator channel
    480       param.setResult(null); // suppress real SMS send
    481     }
    482   }
    483 );
    484 ```
    485 
    486 Example: spoof device phone number
    487 ```java
    488 XposedHelpers.findAndHookMethod(
    489   "android.telephony.TelephonyManager",
    490   lpparam.classLoader,
    491   "getLine1Number",
    492   new XC_MethodHook() {
    493     protected void afterHookedMethod(MethodHookParam param) {
    494       param.setResult(spoofedMsisdn);
    495     }
    496   }
    497 );
    498 ```
    499 ```java
    500 XposedHelpers.findAndHookMethod(
    501   "android.telephony.SubscriptionInfo",
    502   lpparam.classLoader,
    503   "getNumber",
    504   new XC_MethodHook() {
    505     protected void afterHookedMethod(MethodHookParam param) {
    506       param.setResult(spoofedMsisdn);
    507     }
    508   }
    509 );
    510 ```
    511 
    512 Example: inject a fake “Sent” SMS record
    513 ```java
    514 ContentValues v = new ContentValues();
    515 v.put("address", dest);
    516 v.put("body", body);
    517 v.put("type", 2);   // sent
    518 v.put("status", 0); // success
    519 context.getContentResolver().insert(Uri.parse("content://sms/sent"), v);
    520 ```
    521 
    522 ## Handy command cheat‑sheet
    523 
    524 ```bash
    525 # List processes and attach
    526 frida-ps -Uai
    527 frida -U -n com.example.app
    528 
    529 # Spawn with a script (may trigger detectors)
    530 frida -U -f com.example.app -l anti-frida-detection.js
    531 
    532 # Trace native init
    533 frida-trace -n com.example.app -i "JNI_OnLoad"
    534 
    535 # Objection runtime
    536 objection --gadget com.example.app explore
    537 
    538 # Static TLS pinning removal
    539 apk-mitm app.apk
    540 ```
    541 
    542 ## Universal proxy forcing + TLS unpinning (HTTP Toolkit Frida hooks)
    543 
    544 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.
    545 
    546 Workflow
    547 - Run mitmproxy on your host (or Burp). Ensure the device can reach the host IP/port.
    548 - 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.
    549 - Start the target app with Frida and the hook script, and capture requests in mitmproxy.
    550 
    551 Example
    552 ```bash
    553 # Device connected via ADB or over network (-U)
    554 # See the repo for the exact script names & options
    555 frida -U -f com.vendor.app \
    556   -l ./android-unpinning-with-proxy.js \
    557   --no-pause
    558 
    559 # mitmproxy listening locally
    560 mitmproxy -p 8080
    561 ```
    562 
    563 Notes
    564 - 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.
    565 - This technique is ideal when you need to MITM mobile-to-IoT onboarding flows where pinning/proxy avoidance is common.
    566 - Hooks: https://github.com/httptoolkit/frida-interception-and-unpinning
    567 
    568 ## References
    569 
    570 - [1] [Reversing Android Apps: Bypassing Detection Like a Pro](https://www.kayssel.com/newsletter/issue-12/)
    571 - [2] [Frida Codeshare](https://codeshare.frida.re/)
    572 - [3] [Objection](https://github.com/sensepost/objection)
    573 - [4] [apk-mitm](https://github.com/shroudedcode/apk-mitm)
    574 - [5] [Jadx](https://github.com/skylot/jadx)
    575 - [6] [Ghidra](https://ghidra-sre.org/)
    576 - [7] [r2frida](https://github.com/nowsecure/r2frida)
    577 - [8] [Apktool install guide](https://apktool.org/docs/install)
    578 - [9] [Magisk](https://github.com/topjohnwu/Magisk)
    579 - [10] [Medusa (Android Frida framework)](https://github.com/Ch0pin/medusa)
    580 - [11] [Auto-Frida (Android Frida automation toolkit)](https://github.com/ommirkute/Auto-Frida)
    581 - [12] [Build a Repeatable Android Bug Bounty Lab: Emulator vs Magisk, Burp, Frida, and Medusa](https://www.yeswehack.com/learn-bug-bounty/android-lab-mobile-hacking-tools)
    582 - [13] [phantom-frida (stealth Frida server builder)](https://github.com/TheQmaks/phantom-frida)
    583 - [14] [Frida OkHttp4 SSL pinning bypass script](https://github.com/Zero3141/Frida-OkHttp-Bypass)
    584 - [15] [XDA guide to strong Play Integrity bypass (2025)](https://xdaforums.com/t/updated-11-17-2025-guide-get-strong-integrity-fix-banking-apps-revolut-google-wallet-android-16-working.4753805/)
    585 - [16] [Weaponizing LSPosed: Remote SMS Injection and Identity Spoofing in Modern Payment Ecosystems](https://www.cloudsek.com/blog/weaponizing-lsposed-remote-sms-injection-and-identity-spoofing-in-modern-payment-ecosystems-2)
    586 - [17] [How to Bypass mTLS on Android with Frida](https://kiratliygt.medium.com/how-to-bypass-mtls-on-android-with-frida-45c5e71373e8)
    587 - [18] [Demo-mTLS- lab app/server](https://github.com/YigitK-1/Demo-mTLS-)
    588 - [19] [NVISOsecurity disable-flutter-tls-verification](https://github.com/NVISOsecurity/disable-flutter-tls-verification)
    589 - [20] [SSL Kill Switch - LSPosed Module](https://github.com/0xdad0/ssl-kill-switch-lsposed)