mimic/docs/pinning.md at main · CooperSheroy/mimic · GitHub
Skip to content

Latest commit

 

History

History
116 lines (87 loc) · 4.45 KB

pinning.md

File metadata and controls

116 lines (87 loc) · 4.45 KB

Certificate pinning

mimic captures traffic through a proxy (mitmproxy). A pinned app rejects the proxy's cert, so the proxy sees nothing and there's nothing to capture. This page covers getting past that.

What pinning does

Normal TLS authenticates the server to the client: the app checks that the server's cert is signed by a trusted CA. To MITM it, you add your own CA to the device trust store and the proxy presents a cert signed by that CA. Pinning hardcodes the exact cert (or public-key hash) the app will accept, so a proxy cert signed by any other CA, including one you control, gets rejected.

So two things are true:

Pinning stops capture, not replay. It only makes traffic hard to observe. Once you can see a request, the auth bundle in it replays the same as it would for an unpinned app. Pinning adds no replay resistance; that's what DPoP is for (see dpop.md).

You can't beat it by handing the app a cert. The pinned value is a public cert, and presenting it in a handshake needs the matching private key, which sits on the server. The fix is on the app side: stop the app from checking, by hooking its TLS verification at runtime with Frida.

mimic doesn't reimplement those hooks; they change across iOS versions and TLS stacks. mimic unpin drives the upstream that maintains them, httptoolkit/frida-interception-and-unpinning, and handles the mimic-specific part: baking your mitmproxy CA and proxy address into the scripts and printing the command to run.

Prerequisites

mimic doctor        # see the "optional — only for mimic unpin" section
  • git, to fetch the unpinning scripts.
  • frida, to run the hooks (pipx install frida-tools).
  • objection, for the no-jailbreak path only (pipx install objection).
  • A mitmproxy CA at ~/.mitmproxy/mitmproxy-ca-cert.pem. Running mimic record once generates it.

Path A: jailbroken device

The phone runs frida-server and you attach over USB. No repackaging, no signing.

mimic record                       # one terminal — starts the proxy
mimic unpin com.example.app        # bundle id → prints a frida command

unpin fetches the scripts, bakes in your CA and this Mac's LAN IP:8080, and prints:

frida -U \
    -l mimic-unpin/frida-scripts/config.js \
    -l mimic-unpin/frida-scripts/ios/ios-connect-hook.js \
    -l mimic-unpin/frida-scripts/ios/ios-disable-detection.js \
    -l mimic-unpin/frida-scripts/native-tls-hook.js \
    -l mimic-unpin/frida-scripts/native-connect-hook.js \
    -f com.example.app

Run it, use the app, then mimic hosts and mimic gen <host> as usual.

Path B: stock device (Frida gadget)

No jailbreak. You inject the Frida gadget into a decrypted IPA, re-sign, and sideload.

mimic unpin ./MyApp.ipa --codesign <TEAM_ID>

unpin bakes the config, runs objection patchipa to inject the gadget, and writes mimic-unpin/MyApp-patched.ipa. It stops there and prints the signing and install step, since that's what tools like Sideloadly handle:

  • drag the patched IPA into Sideloadly, or
  • pymobiledevice3 apps install mimic-unpin/MyApp-patched.ipa

Then attach the scripts to the gadget (unpin prints the frida -U command) and capture as usual.

The catches

  • You need a decrypted IPA. Your own app build is fine. An App Store app is FairPlay-encrypted, and decrypting it takes a jailbroken device once (or a pre-decrypted IPA). "No jailbreak" often means "no jailbreak, if someone already decrypted it."
  • Free Apple certs expire after 7 days. TrollStore (iOS 17.0.x and below) installs with real entitlements permanently and skips the weekly re-sign.
  • Apps with binary-integrity checks detect the re-signed bundle and refuse to run.

Framework notes

  • React Native uses NSURLSession and honors the system proxy. The standard hooks work.
  • Flutter ships its own BoringSSL and ignores the system proxy, so SecTrustEvaluate hooks never fire and a device proxy captures nothing. It needs reFlutter to patch the engine; mimic unpin won't handle Flutter apps as they are.

What still won't work

  • DPoP / sender-constrained tokens. The per-request signing key never leaves the device, so captured requests don't replay. See dpop.md.
  • Hardware attestation (App Attest, DeviceCheck). Out of scope.