ByteBlind Security
Back to blog
FlutterFridaSSL Pinning

Intercepting Flutter App Traffic When Burp Shows You Nothing

Richard · March 2, 2026

If you’ve ever pointed Burp at a Flutter app and watched the traffic tab stay completely empty, you’re not missing a step in your proxy setup. Flutter apps bundle their own network stack, BoringSSL, compiled directly into the binary, instead of using the platform’s. The usual “install a CA cert and set the proxy” approach never actually touches it.

Why the normal approach fails

A native Android app built on OkHttp or the platform HttpsURLConnection will respect the system proxy settings, and it’ll trust a user-installed CA certificate too, at least on API 24 and above, assuming the app opts into that through its network security config.

Flutter apps do neither of those by default. The Dart networking layer talks straight to a statically linked BoringSSL, which ships with its own trust store baked into the binary at compile time. Your CA cert might as well not exist as far as the app is concerned, it’s not even part of the conversation.

That’s really the whole reason people get stuck here. It’s not that the proxy setup was done wrong, it’s that Flutter apps were never going to look at the platform’s trust store to begin with.

Getting visibility with Frida

The fix is to hook the certificate verification function directly in the BoringSSL library the app ships, rather than trying to get the app to trust your proxy at all. In practice that means finding the loaded library exporting ssl_crypto_x509_session_verify_cert_chain, or whatever the equivalent symbol is for the Flutter engine version you’re actually looking at, then hooking it with Frida and forcing a return value that tells the app the certificate chain checked out fine.

Once that’s neutralized, pointing the emulator or device’s system proxy at Burp works the way you’d expect on a normal app. The traffic still terminates fine, and Burp can decrypt it, because the one thing standing in the way was that verification call, not the proxy configuration itself.

Interceptor.attach(
  Module.findExportByName('libflutter.so', 'ssl_crypto_x509_session_verify_cert_chain'),
  {
    onLeave(retval) {
      retval.replace(1);
    },
  },
);

The exact symbol name shifts between Flutter engine versions, so the first step is always confirming what’s actually exported before writing the hook. Don’t assume last year’s script works against this year’s build, dump the exports fresh with something like frida-trace or nm against the target libflutter.so and confirm the symbol’s actually there before you go hunting for why a hook silently isn’t firing.

Why the symbol keeps moving

Worth understanding why this isn’t a stable target the way a lot of native Android pinning checks are. Flutter’s engine gets rebuilt and versioned independently of the app itself, and the way BoringSSL gets compiled into libflutter.so can shift between engine releases, sometimes the function gets renamed, sometimes it gets inlined somewhere else entirely, sometimes symbol stripping in a release build hides it from a plain export listing altogether. None of that means the approach stops working, it just means the one-liner hook above is a starting point for a specific engine version, not a permanent script you paste into every Flutter target you touch afterward.

The broader lesson

This is a good example of why “run a scanner and see what lights up” breaks down on Android. Nothing about this required a zero-day or a fuzzer. It required understanding why the traffic wasn’t showing up in the first place, and being willing to go one layer deeper than the tooling defaults expect, past the proxy, past the platform trust store, down into the binary the app actually shipped with.