
You set Burp as the device proxy, install the CA, open the app, and Burp's HTTP history stays empty. Nothing is broken. The app is proxy-unaware: it never reads the system HTTP proxy setting, or it talks over raw sockets or QUIC that an HTTP proxy does not handle. Asking the app politely to use your proxy will never work here.
So you stop asking and start forcing. This post walks transparent redirection with the exact commands (iptables NAT, DNSChef, a tun/VPN), getting that traffic into mitmproxy or Burp, and the pinning bypass you still need afterward to actually read it. The most common time sink is treating those as one problem when they are two. It all maps to MASVS-NETWORK and the MASTG network test cases.
An app is proxy-unaware when it does not honor the operating system's HTTP proxy configuration, so its traffic bypasses any proxy you set in Wi-Fi settings. There are a few distinct causes, and the cause determines the fix:
The first three need forced redirection at the network layer. The last needs a pinning bypass. The two are independent, and on a stubborn app you usually solve both.
On a rooted Android device, iptables NAT rewrites the packet path in the kernel, below the app, so its proxy settings become irrelevant. Start mitmproxy in transparent mode on your machine, then redirect ports 80 and 443 on the device:
# on your machine
$ mitmproxy --mode transparent --showhost
Proxy server listening at *:8080
# on the rooted device (adb shell, as root)
# iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to-destination 192.168.1.50:8080
# iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to-destination 192.168.1.50:8080
# iptables -t nat -L OUTPUT -n
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DNAT tcp -- anywhere anywhere tcp dpt:80 to:192.168.1.50:8080
DNAT tcp -- anywhere anywhere tcp dpt:443 to:192.168.1.50:8080The two DNAT lines confirm the rules are live; from here every TCP connection on 80/443 lands in your transparent mitmproxy regardless of what the app's HTTP client thinks. Burp offers the same idea through its invisible proxy listener. When you finish, flush the chain with iptables -t nat -F OUTPUT so the device leaves the engagement clean.
Use DNS spoofing or a tun/VPN interface when iptables alone is not enough: a non-rooted device, an iOS device, or an app using non-HTTP protocols. DNSChef answers the app's DNS queries with your machine's IP, pulling its connections to a host where your proxy listens:
$ dnschef --fakeip 192.168.1.50 --fakedomains api.targetapp.com -i 0.0.0.0
_
| |
__| |_ __ ___ ___| |__ ___ f
[*] DNSChef started on interface: 0.0.0.0
[*] Using the following nameservers: 8.8.8.8
[*] Cooking replies for domain api.targetapp.com with IP 192.168.1.50
[23:14:07] cooked A api.targetapp.com -> 192.168.1.50 <- app's lookup hijackedThe cooked line is the confirmation the app's resolver took your answer. The tun/VPN approach is the most general: a local VPN profile (or a tool that creates a tun interface) routes every packet through your machine, so it works on iOS and non-rooted Android because it operates at the OS VPN layer rather than needing iptables. Pair DNSChef (to control where the app connects) with a transparent mitmproxy (to terminate TLS) for full coverage of stubborn HTTP apps.
Redirection only gets packets to your proxy. If the app pins, it still refuses to trust your interception CA, and every connection dies at the TLS handshake. The fastest fix is objection's one-liner; when it misses a custom implementation, attach a targeted Frida script and watch it land:
$ frida -U -f com.target.app -l unpin.js --no-pause
____
/ _ | Frida 16.2.1 - dynamic instrumentation toolkit
| (_| |
> _ | Spawning com.target.app...
[Pixel::com.target.app ]-> [+] TrustManagerImpl.verifyChain() hooked
[+] OkHttp CertificatePinner.check() neutralized
[+] Handshake to api.targetapp.com now succeeds -> visible in mitmproxyOnce those hooks land, the redirected traffic decrypts and appears in your proxy. On iOS, hook NSURLSession and the lower-level SecTrust APIs; for deeper pinning, patch the smali after apktool d and repackage. We cover where pinning sits in the mobile pentest checklist and the broader workflow in what mobile app penetration testing is.
On a recent assessment of a logistics app, I spent close to an hour convinced an iptables rule was wrong. The redirect was fine and mitmproxy was logging inbound connections the whole time, but every one died at the TLS handshake because the app pinned. I kept rewriting NAT rules that were already correct. The moment the Frida unpin script landed, the traffic appeared instantly, fully decrypted. The fix was never in the redirection layer.
That is the trap with proxy-unaware apps: testers see no readable traffic, assume the proxy is broken, and burn time on Burp settings when the real issue is pinning, or the reverse. Confirm each layer independently. Watch mitmproxy for an inbound connection first to prove redirection works, then worry about the handshake. The decision table below maps each symptom to the right tool so you do not chase the wrong layer.
For raw sockets, custom binary protocols, or QUIC, an HTTP proxy cannot parse the wire format, so you intercept at the boundary where the app hands data to the OS. For QUIC the cheapest move is to drop UDP/443 so the app falls back to TCP/TLS, which you already know how to intercept:
# force QUIC apps down to TCP/TLS, then intercept normally
# iptables -A OUTPUT -p udp --dport 443 -j DROPFor raw sockets or custom protocols, hook the app's socket write/read functions or its TLS library with Frida and log the plaintext buffers before encryption and after decryption, which sidesteps both pinning and the unusual format at once. Remember that an app's SDKs can use their own networking and pin separately from the host, covered in our mobile SDK security testing methodology. This same forced-interception capability is part of the runtime testing that agentic pentesting automates.
Everything above is also a remediation checklist read backwards. The point of pinning is not to stop a rooted attacker (it cannot) but to force them to patch the binary rather than run a stock script. Configure it so a one-liner does not win:
<pin-set> in res/xml/network_security_config.xml with backup pins, and set cleartextTrafficPermitted="false" so a DNSChef redirect to plain HTTP fails.CertificatePinner so the pin is not solely in config that a smali patch can strip.None of this stops a determined tester, and it should not pretend to. It raises the cost from a ten-second objection command to a binary patch, which is exactly the friction MASVS-RESILIENCE asks for on apps that need it.