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.
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.
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. Runningmimic recordonce generates it.
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 commandunpin 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.appRun it, use the app, then mimic hosts and mimic gen <host> as usual.
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.
- 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.
- React Native uses
NSURLSessionand honors the system proxy. The standard hooks work. - Flutter ships its own BoringSSL and ignores the system proxy, so
SecTrustEvaluatehooks never fire and a device proxy captures nothing. It needs reFlutter to patch the engine;mimic unpinwon't handle Flutter apps as they are.
- 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.
