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)