Techniques
Intercepting Traffic with Burp Suite
Most of what a mobile app does is visible in its network traffic: how it authenticates, what data it sends back, which endpoints it talks to. Getting that traffic into Burp Suite takes three things to line up:
- The app’s traffic has to reach Burp.
- The app has to trust Burp’s certificate, or TLS breaks.
- The app must not be pinning a certificate, or it rejects Burp regardless.
Work through them in order. Pinning bypass does not help if the routing is wrong.
That three-step shape is the same on Android and iOS. Only the middle step really differs: Android has its user and system CA stores (and the Android 14 APEX move below), while iOS installs a configuration profile and then needs full trust enabled in Certificate Trust Settings. Routing is a proxy setting either way, and pinning bypass is the same Objection and Frida on both. The procedure here is the Android side; iOS gets its own section once that material lands.
Prerequisites
A rooted device or emulator, set up as in Android Lab Setup, and Burp Suite listening. Root is not strictly required for the routing step, but it is required to install the CA where modern Android will trust it, so assume it throughout.
Step 1: Route Traffic to Burp
Point Burp’s proxy at an interface the device can reach, then point the device at Burp.
In Burp: Proxy → Proxy settings → Proxy listeners. Either bind the listener to All interfaces, or bind it to your machine’s LAN IP. Do not leave it on 127.0.0.1 for a physical device, because the device cannot reach your loopback.
Then route the device. Pick whichever of these fits your setup:
Emulator, at launch. Applies to all traffic on the emulator:
emulator -avd pentest -http-proxy 127.0.0.1:8080
Any device, through Wi-Fi settings. Long-press the connected network → Modify → Advanced → Proxy: Manual, then enter your machine’s IP and Burp’s port. Applies only to Wi-Fi, and some apps ignore it.
Physical device, over ADB reverse. No shared network needed. The device’s own localhost:8080 is tunnelled to Burp on your machine:
adb reverse tcp:8080 tcp:8080
adb shell settings put global http_proxy 127.0.0.1:8080
# undo with:
adb shell settings delete global http_proxy
Any device, through a VPN app. A VPN app routes every connection on the device through its local VPN service, so it catches apps that ignore the Wi-Fi proxy setting. ReThink DNS lets you enter a custom proxy endpoint: set the app to use Other DNS → Proxy DNS and point it at your machine’s IP and Burp’s port. No root needed, and it covers the whole device rather than one network type.
To confirm routing works, open a plain HTTP site in the device browser. It should appear in Burp’s HTTP history. At this point HTTPS will throw a certificate error on the device: that is expected, and the next step fixes it.
Step 2: Make the App Trust Burp’s CA
Burp generates its own CA and signs a fresh certificate for each site on the fly. The device rejects them because Burp’s CA is not in its trust stores. So the CA has to be installed as trusted, and Android sorts trusted CAs into two stores that behave very differently:
- User store: what “install a certificate” in Settings writes to. Any app can install here, no root needed.
- System store: shipped with the OS, in
/system/etc/security/cacerts/. Writing here needs root and a writable system partition.
Since Android 7 (API 24), apps do not trust user-store CAs by default. An app trusts the user store only if its network security config opts in, which almost none do. So a user-store install is enough for the browser and for old apps, but for a real target you almost always need the CA in the system store.
Export Burp’s CA
Proxy → Proxy settings → Import / export CA certificate → Certificate in DER format. Save it, then convert to PEM:
openssl x509 -inform DER -in cacert.der -out cacert.pem
The Quick Way: User Store
This is enough for browser traffic, and for apps that still trust the user store.
adb push cacert.pem /sdcard/Download/burp.crt
On the device: Settings → Security → Encryption & credentials → Install a certificate → CA certificate → accept the warning → pick the file. On some builds the path is Settings → Security → Install from storage.
If the target app now shows in Burp over HTTPS, you are done. If it fails, the app is ignoring the user store, so move to the system store.
The Reliable Way: System Store (Android 7 to 13)
System CAs are stored under a filename derived from the certificate’s subject hash, with a .0 extension. Compute the filename, then copy the PEM into the store with that name:
# The filename Android expects
hash=$(openssl x509 -inform PEM -subject_hash_old -in cacert.pem | head -1)
echo "$hash" # e.g. 9a5ba575
# Make the system partition writable, then install
adb root
adb remount
adb push cacert.pem /sdcard/burp.pem
adb shell su -c "cp /sdcard/burp.pem /system/etc/security/cacerts/${hash}.0"
adb shell su -c "chmod 644 /system/etc/security/cacerts/${hash}.0"
adb reboot
subject_hash_old (not subject_hash) is what Android uses; the wrong one gives a filename the system ignores. If adb remount fails, the emulator was not started with -writable-system, or verification was not disabled: both are covered in the lab setup note.
Android 14 and Later
Android 14 moved the system CA store. It is no longer a plain directory you can drop a file into: the trusted CAs live in an APEX module (/apex/com.android.conscrypt/), and a plain file copy does not update them.
The practical answer is a Magisk module that overlays the CA into the runtime store. Options:
- Magisk-Trust-User-Certs and similar modules promote whatever you install in the user store into the system store at boot. Install the CA through Settings as usual, flash the module, reboot, and the user CA is now trusted system-wide.
- A Frida or Objection runtime patch (below) sidesteps trust stores entirely by changing the app’s behaviour instead of the OS. On Android 14 this is often the fastest route.
Confirm
Browse an HTTPS site on the device. It should now appear in Burp with no certificate warning. Check that the system store picked it up: Settings → Security → Encryption & credentials → Trusted credentials → System tab should list “PortSwigger” (or your CA’s name).
Step 3: Defeat Certificate Pinning
If HTTPS works in the browser but the target app’s traffic never reaches Burp, or shows up as TLS errors, the app is pinning. Pinning means the app ships the certificate or public key it expects and checks the server against it directly. A CA the device trusts is not enough: the presented certificate has to be the pinned one, and Burp’s is not.
You cannot satisfy a pin, only bypass it. Three routes:
Objection
Objection wraps Frida with ready-made bypasses. With a Frida server running on the device (see Dynamic Instrumentation with Frida for how to match versions and push the right build):
objection -g com.example.app explore
# then, at the prompt:
android sslpinning disable
It hooks the common pinning implementations at runtime: OkHttp’s CertificatePinner, the default TrustManager, WebViewClient, and others. Traffic starts flowing into Burp without touching the APK. This works for most apps.
Frida with a dedicated script
When Objection’s generic hooks miss, a maintained script catches more implementations:
frida -U -f com.example.app -l frida-multiple-unpinning.js
The widely used one is frida-multiple-unpinning (search the CodeShare directory). If even that misses, the app is doing something custom: decompile it, find the class doing the check, and hook that method directly. Frida’s own logging shows you which hooks fired and which did not.
Patching the APK
When runtime hooking is blocked, or you need a device without Frida attached, patch the app itself and remove the pinning from its code:
-
Decompile with
apktool d app.apk. -
Add a network security config that trusts the user store, and point the manifest at it:
<!-- res/xml/network_security_config.xml --> <network-security-config> <base-config cleartextTrafficPermitted="true"> <trust-anchors> <certificates src="system" /> <certificates src="user" /> </trust-anchors> </base-config> </network-security-config><!-- AndroidManifest.xml, on <application> --> android:networkSecurityConfig="@xml/network_security_config" -
Repack, sign, and install. A repacked APK is unsigned, so it has to be signed with your own key (
keytoolto generate one,apksignerto sign) before Android will install it.
The config alone defeats apps that pin only through the network security config’s <pin-set>. An app pinning in its own code needs the smali for that check edited out as well, or one of the runtime bypasses above instead.
DNS Spoofing with an Invisible Proxy
Some apps ignore the proxy settings entirely, or hardcode a hostname and open their own TLS connection. One route around both: make the device resolve the app’s hostname to your machine, and have Burp serve the HTTPS itself.
-
Map the app’s domains to your machine’s IP with dnsmasq:
# dnsmasq.conf address=/hextree.io/192.168.178.37 address=/api.hextree.io/192.168.178.37 log-queriesdocker run --name dns-spoof --rm -p 0.0.0.0:53:53/udp \ -v "$PWD/dnsmasq.conf:/etc/dnsmasq.conf" \ andyshinn/dnsmasqUnlisted domains are forwarded upstream, so the rest of the device keeps working.
-
Force the device to use your DNS. Android’s normal DNS settings accept only encrypted DNS or the network’s own servers, so the clean way is a VPN app again: in ReThink DNS, set Other DNS → Proxy DNS and add an entry pointing at your dnsmasq host. Check that spoofing works by opening
chrome://net-internalsin the device browser and looking up the hostname in the DNS tab; the resolved address should be your machine’s. -
Add invisible proxy listeners in Burp on ports 80 and 443 (Proxy settings → Proxy listeners → add, bind to all interfaces, then under Request handling enable Support invisible proxying). An invisible listener acts as a full HTTP(S) server instead of a forward proxy: it parses the
Hostheader or SNI value and forwards the request to the real destination, so the device needs no proxy configuration at all.
With the hostname resolving to your machine and Burp’s CA installed as in step 2, the app’s TLS terminates at Burp and the traffic appears in the history. This setup also covers tools that open raw HTTPS sockets the system proxy does not capture.
Flutter Apps
A Flutter app’s networking runs in Dart, and Dart breaks the standard setup in two ways:
- Dart’s
HttpClientdoes not read the system proxy settings. The app opens its own TCP connection straight to the server, so the traffic never reaches Burp no matter how routing is configured. - Flutter ships its own TLS stack, BoringSSL compiled into
libflutter.so, and checks certificates against its own embedded trust list. The system and user CA stores, and the network security config, are all ignored, so the CA install in step 2 does nothing for the Flutter layer.
Both parts need their own fix.
Routing: iptables
On a rooted device, DNAT rules rewrite the destination of the app’s outbound connections before the packets leave the device:
# Redirect HTTP and HTTPS to the machine running Burp
adb shell su -c 'iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to-destination 192.168.1.10:8080'
adb shell su -c 'iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to-destination 192.168.1.10:8080'
# On an emulator, use 10.0.2.2 as the destination instead: that address is the emulator's alias for the host machine
The app connects thinking it talks to the real server, and the kernel hands the connection to Burp. Two setup details:
- The redirected traffic arrives as raw TCP with no proxy handshake, so the Burp listener on 8080 must have invisible proxying enabled (the setting is covered in the DNS spoofing section above).
- Re-running the same
-Acommand adds a duplicate rule instead of replacing anything, and duplicates make connections behave unpredictably. List what is loaded withiptables -t nat -L OUTPUT -n -v, and delete a rule by repeating it with-Dinstead of-A.
TLS: disable Flutter’s certificate check
The disable-flutter-tls-verification Frida script (search the CodeShare directory) patches the BoringSSL verification in libflutter.so at runtime, so the app accepts Burp’s certificate:
frida -U -f com.example.app -l disable-flutter-tls-verification.js
The alternative is reFlutter, which repacks the APK with a modified Flutter engine that points at a proxy of your choice and skips TLS verification. It avoids needing Frida at runtime, at the cost of shipping a re-signed build.
When Traffic Still Will Not Appear
| Symptom | Likely cause |
|---|---|
| Nothing in Burp at all, even HTTP | Routing. Listener bound to loopback for a physical device, or wrong IP/port on the device. |
| HTTP works, HTTPS shows TLS errors | CA not trusted. User-store install on an app that ignores it, or the wrong subject_hash_old filename. |
| Browser HTTPS works, the app does not | Certificate pinning. Go to step 3. |
| Works on Android 12, fails on Android 14 | The CA store move. Use a Magisk trust module, or a runtime bypass. |
| App uses a socket you never see | Not HTTP. gRPC, a raw TLS socket, or Flutter, which ignores the system proxy and needs a Frida-based approach of its own. |
Burp only sees HTTP(S). An app talking over a raw socket, or a Flutter app that bypasses the system proxy entirely, needs Frida to redirect its traffic, not a proxy setting.