# Android Mitm Setup

> Intercept a mobile Android app's HTTPS traffic on Android 7+ where TLS pinning is in force. Walks through rootable emulator selection, mitmproxy CA installation into the system trust store, proxy routing, and Frida-based pinning bypass. Use when you need to see what an Android app is sending over the wire — for security research, API documentation, or interoperability work.

- Skill: `abedegno/android-mitm-setup` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add abedegno/android-mitm-setup`
- Raw SKILL.md: https://api.skillmd.com/api/skills/abedegno/android-mitm-setup/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: abedegno (https://skillmd.com/u/abedegno)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/abedegno/android-mitm-setup

---


# Android MITM Setup

Intercepting a mobile app's HTTPS traffic on a modern Android device requires four things working together: a rootable emulator (so you can write to the system trust store), mitmproxy on the host (decoding and re-issuing the TLS), a CA certificate the device actually trusts (system store, not user store, since Android 7+), and a Frida-injected runtime hook that disables the app's TLS-pinning logic. None of these pieces is hard; together they're the half-day-of-stack-overflow that this skill collapses into a checklist.

This skill is the **dynamic counterpart** to the static decompilation skill in [`android-reverse-engineering`](https://github.com/SimoneAvogadro/android-reverse-engineering-skill). Use both: jadx reads the APK, this gets you the wire traffic.

## When this skill applies

- Target is an Android app on API 24+ (Android 7+, where user-store CAs stopped being trusted for app traffic by default).
- The app uses HTTPS — most do.
- The app pins certificates (OkHttp `CertificatePinner`, `TrustManager`, or platform default after Android 7).
- You have legal grounds to inspect the app's traffic (own software, written permission, disclosure programme, interoperability under applicable law).

If the app doesn't pin, you can skip the Frida step. If it uses gRPC or QUIC, mitmproxy 9+ has experimental support but you may need extra config.

## Workflow

### Phase 1: Pick a rootable emulator image

Use the **Android Studio emulator** (`avdmanager` / `emulator`) with a system image flavour that allows `adb root`. The flavour matters:

- ✅ **Google APIs** (e.g. `system-images;android-34;google_apis;arm64-v8a`) — rootable, includes Play Services so most apps work.
- ❌ **Google Play** (`google_apis_playstore`) — *not* rootable; Play Integrity / SafetyNet hardened. Spending an afternoon discovering this is a rite of passage.
- ✅ **AOSP / default** — rootable but no Play Services; some apps refuse to start.

Create the AVD once:

```bash
avdmanager create avd \
    --name Pixel_API_34_GoogleAPIs \
    --package "system-images;android-34;google_apis;arm64-v8a" \
    --device pixel_6
```

Boot with a writable system partition so `/system` edits stick:

```bash
emulator -avd Pixel_API_34_GoogleAPIs -writable-system
```

If you want to use a physical device, you need a userdebug Android build (custom ROM like LineageOS) or Magisk on a stock build. The Magisk path is documented elsewhere; the emulator is faster to set up and easier to throw away.

### Phase 2: Generate the mitmproxy CA and install it into the system store

Start mitmproxy once on the host so it generates `~/.mitmproxy/`:

```bash
mitmproxy -p 8080
# Press q to quit — the cert files now exist in ~/.mitmproxy/
ls ~/.mitmproxy/
#   mitmproxy-ca-cert.cer  mitmproxy-ca-cert.pem  mitmproxy-ca.p12  ...
```

Compute the cert's old-style subject hash. Android's system CA directory uses `<hash>.0` as the filename:

```bash
HASH=$(openssl x509 -inform PEM -subject_hash_old -in ~/.mitmproxy/mitmproxy-ca-cert.cer | head -1)
echo "$HASH"
```

Push the cert into the system store. On Android 10+ this requires `-writable-system` (see Phase 1) AND `adb remount`:

```bash
adb root
adb remount
adb push ~/.mitmproxy/mitmproxy-ca-cert.cer /system/etc/security/cacerts/${HASH}.0
adb shell chmod 644 /system/etc/security/cacerts/${HASH}.0
adb reboot
```

On Android 14+ the system CA directory moved to an APEX (`/apex/com.android.conscrypt/cacerts/`). `mitmproxy` documents the current path; check their docs if the steps above don't take.

After reboot, verify:

```bash
adb shell ls /system/etc/security/cacerts/ | grep "$HASH"
adb shell settings list global | grep cacerts   # sanity
```

### Phase 3: Route emulator traffic through mitmproxy

Set the global proxy on the emulator to the host bridge alias `10.0.2.2:8080`:

```bash
adb shell settings put global http_proxy 10.0.2.2:8080
```

Start mitmproxy on the host:

```bash
mitmproxy -p 8080
```

Open Chrome on the emulator and visit `https://example.com` — you should see the flow in mitmproxy and the page should load (TLS now terminates at mitmproxy with the new CA the device trusts).

To revert proxy:

```bash
adb shell settings put global http_proxy :0
```

### Phase 4: Bypass TLS pinning with Frida

For most apps using OkHttp `CertificatePinner` or platform `TrustManager`, the default-trust path will *now* show pinning errors in the app: the app trusts the system CA store (Phase 2) but rejects any cert not matching its hardcoded pin.

The fix is to hook the pinning check at runtime. Steps:

```bash
# 1. Download frida-server matching your emulator architecture from
#    https://github.com/frida/frida/releases
#    (e.g. frida-server-16.x.x-android-arm64.xz)

# 2. Push and launch
adb push frida-server-android-arm64 /data/local/tmp/frida-server
adb shell chmod 755 /data/local/tmp/frida-server
adb shell "/data/local/tmp/frida-server &"

# 3. Install Frida tooling on the host
pipx install frida-tools     # or: pip install frida-tools

# 4. Verify connection
frida-ps -U | head
```

Then inject a pin-bypass script. **Don't paste hook source into this skill** — pinning bypass scripts evolve fast and Codeshare keeps them up to date. The canonical starting points:

- `frida-multiple-unpinning` on Frida Codeshare: <https://codeshare.frida.re/@akabe1/frida-multiple-unpinning/>
- `objection`: <https://github.com/sensepost/objection> — wraps Frida with a higher-level CLI; `objection -g <package> explore` then `android sslpinning disable` covers ~80% of cases.

Launch the app with the script:

```bash
frida -U -f com.example.app -l unpinning.js --no-pause
```

If the standard scripts don't take, the app has likely been R8-shrunk and the class/method signatures the script targets are now obfuscated to single-letter names. Typical resolution:

1. Decompile the APK ([`android-reverse-engineering`](https://github.com/SimoneAvogadro/android-reverse-engineering-skill) plugin) and search for `CertificatePinner` / `checkServerTrusted` / `okhttp3` patterns.
2. Identify the obfuscated class/method that calls them.
3. Write a targeted hook on that specific class. See `references/tls-pinning-bypass.md` for the structure (not the source — pinning bypass for a specific APK should not be in a public repo).

### Phase 5: Capture and document the contract

With traffic flowing through mitmproxy you should now see:

- Auth flows (Auth0, OAuth, SAML) — the IdP host plus PKCE / state / nonce / audience.
- Backend BFF / API gateway calls (Azure APIM, AWS API Gateway, CloudFront).
- Feature-flag / experimentation calls (LaunchDarkly, Optimizely).
- Telemetry (Firebase, Sentry, Datadog RUM).
- WebSocket upgrades (game servers, real-time channels).

Save flows for replay:

```bash
mitmdump -p 8080 -w session.flows         # record
mitmdump -nr session.flows                # replay
mitmdump -nr session.flows -f "~u /api/"  # filter
```

This is the input to [`mobile-auth-replay`](../mobile-auth-replay/SKILL.md) if you want to drive the same contract from a desktop tool.

## Common pitfalls

- **"Google Play" image instead of "Google APIs".** Cannot `adb root`. Re-create the AVD.
- **Forgot `-writable-system` at boot.** `/system` is read-only; cert push fails. Re-boot with the flag.
- **CA installed in user store via Settings → Security.** Doesn't work on Android 7+ for app traffic. Must be system store.
- **Auth host fronted by a TLS-fingerprint WAF (Akamai, Cloudflare Bot Management).** Irrelevant on the emulator itself — the real app's TLS stack passes. It matters in [`mobile-auth-replay`](../mobile-auth-replay/SKILL.md) where you replay from desktop. Note it now, address it there.
- **gRPC traffic shows as binary garbage.** mitmproxy can decode gRPC with the `--set http2_force_decode_for_gprc=true` flag and a `.proto` (if you have one). Otherwise you'll see HTTP/2 envelopes with protobuf bodies — read them with `protoc --decode_raw` per message.

## References

- [`references/emulator-setup.md`](references/emulator-setup.md) — AVD creation, image selection, `-writable-system` gotchas.
- [`references/ca-cert-install.md`](references/ca-cert-install.md) — CA install per Android version (7, 10, 11, 14+ APEX).
- [`references/tls-pinning-bypass.md`](references/tls-pinning-bypass.md) — when default scripts work, when you need targeted hooks, how to identify the obfuscated class.

## Pairs with

- [Simone Avogadro's `android-reverse-engineering`](https://github.com/SimoneAvogadro/android-reverse-engineering-skill) — static decompilation. Run that first to read the APK; this captures what it *does*.
- [`mobile-auth-replay`](../mobile-auth-replay/SKILL.md) — once you've seen the auth flow in mitmproxy, drive it from a desktop tool.
- [`find-production-sourcemap`](../find-production-sourcemap/SKILL.md) — if the app is a webview wrapping a JS bundle, sometimes you can skip mitm entirely.

## Scope reminder

These techniques produce traffic captures that can include credentials, tokens, PII, and other sensitive data even from your *own* account. Treat them like passwords: don't paste into bug reports, don't commit `session.flows` files to public repos, redact before sharing. If you're disclosing a finding to a vendor, send the redacted reproduction steps, not the raw flows.

