A practical guide for security researchers and penetration testers on intercepting HTTPS traffic from Flutter applications packaged with Android App Bundles.

Introduction

Modern Android applications are increasingly distributed as Android App Bundles (AABs) rather than traditional monolithic APKs. When a user installs an app from the Google Play Store, the Play Store dynamically assembles and delivers a set of split APKs — each one responsible for a specific slice of the application: the base code, architecture-specific native libraries, language resources, and screen density assets.

This architectural shift has meaningful implications for mobile application security testing. Whereas a classic APK could be pulled from a device, decompiled, and inspected as a single artifact, split APKs require the tester to understand which component holds the code or library of interest — because the pieces are distributed across multiple files.

Flutter applications compound this challenge significantly. Unlike traditional Android apps written in Java or Kotlin, Flutter compiles application logic into native machine code using the Dart runtime. All network communication — including TLS/HTTPS — is handled deep inside the Flutter engine library (libflutter.so), which operates entirely outside the Android networking stack. As a result, tools like Burp Suite, mitmproxy, or Charles Proxy that rely on Android's network security configuration, system certificate trust store, or Java-level hooks cannot intercept Flutter traffic out of the box.

Furthermore, many production Flutter apps implement certificate pinning directly within the Flutter engine or via Dart code, explicitly rejecting connections to any host presenting an unexpected certificate — including your Burp Suite CA certificate. This makes traffic interception with a standard proxy setup impossible without first patching the app.

This is where ReFlutter comes in. ReFlutter is an open-source tool specifically designed to patch the Flutter engine binary inside an APK, disabling its SSL certificate verification so that traffic can be intercepted by a proxy. The challenge — which this guide addresses in full — is that when an app is distributed as split APKs, you must correctly identify which APK file contains libflutter.so before running ReFlutter. Running it against the wrong APK will produce a patched file that changes nothing meaningful.

This article walks through the complete, step-by-step process: from understanding split APK structure, to identifying the Flutter engine, patching it, signing the result, and installing the split APK set back onto a device for traffic capture.

Understanding the Application Structure

When you extract or pull a Flutter app distributed as an Android App Bundle from a device (for example, using adb shell pm path <package> followed by adb pull), you will typically find a directory containing several files. In the case examined throughout this guide, the extracted application contains the following:

app.metadata
base.apk
base.digests
base.dm
lib/
split_config.arm64_v8a.apk
split_config.en.apk
split_config.xxhdpi.apk

Understanding what each of these files represents is essential before you begin any patching work.

base.apk is the core of the application. It contains the AndroidManifest.xml, the compiled Dalvik bytecode (classes.dex files), and shared resources that are common to all device configurations. In traditional monolithic APK installations, this file would contain everything. In a split APK installation, it contains the universal base, but critically — it may not contain architecture-specific native libraries like libflutter.so.

split_config.arm64_v8a.apk is an architecture-specific configuration split. This APK delivers native shared libraries compiled for the arm64-v8a ABI (64-bit ARM processors, which covers virtually all modern Android devices). In Flutter apps, this is where libflutter.so and libapp.so almost always reside.

split_config.en.apk is a language configuration split. It contains string resources and locale-specific assets for English. These splits exist for every supported language and allow the Play Store to deliver only the languages relevant to the user's device settings.

split_config.xxhdpi.apk is a screen density configuration split. It delivers drawable resources — icons, images — optimized for extra-extra-high-density screens. Like language splits, these exist in multiple densities (mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi) and are selectively delivered.

app.metadata is a metadata file used by the Android package manager to track the installed split set. It records which splits are installed together and is not directly relevant to patching.

base.digests contains cryptographic digests of the APK contents used by the Android runtime for integrity verification.

base.dm is a Dalvik metadata file (also called an .art or .dm file) that can contain ART compiler hints or pre-verified class information to speed up app startup. It is not relevant to the patching process.

lib/ may contain a small number of shared libraries needed at install time, but in split APK setups, the bulk of native libraries are inside the architecture-specific split.

With this structure in mind, the most important insight is this: do not assume that libflutter.so is inside base.apk. Proceeding on that assumption is the single most common mistake when attempting to patch Flutter split APK applications.

Identifying Where Flutter Libraries Exist

Before running any patching tool, you must determine exactly which APK contains the Flutter engine. This is a mandatory verification step — skipping it will cause the patching process to fail silently or produce an APK that appears patched but changes nothing at runtime.

The identification is straightforward. Use unzip with the list flag (-l) to inspect the contents of the architecture-specific split without fully extracting it, then pipe the output through grep to filter for native library paths:

bash

unzip -l split_config.arm64_v8a.apk | grep lib

This command lists every file inside the APK archive and filters for entries containing "lib" in their path. The output, for a Flutter application targeting 64-bit ARM devices, will look like this:

Archive:  split_config.arm64_v8a.apk
    Length      Date    Time    Name
---------  ---------- -----   ----
 12582912  2024-06-01 10:23   lib/arm64-v8a/libflutter.so
  8388608  2024-06-01 10:23   lib/arm64-v8a/libapp.so
---------                     -------
 20971520                     2 files

If you see lib/arm64-v8a/libflutter.so in the output, you have confirmed that the Flutter engine is inside split_config.arm64_v8a.apk, and this is the file you must target with ReFlutter.

libflutter.so is the compiled Flutter engine. It contains all of the Dart runtime, the Skia graphics engine, and critically, the TLS/networking stack that Flutter uses for HTTPS communication. This is the binary that ReFlutter patches to disable certificate verification.

libapp.so is the compiled Dart application code — your app's business logic, UI, and Dart-side networking code. While SSL pinning implemented in pure Dart (e.g., via the http package's badCertificateCallback) lives here conceptually, the engine-level TLS verification that ReFlutter bypasses is in libflutter.so.

As a troubleshooting step, it is also worth running the same unzip -l check against base.apk to confirm that libflutter.so is not present there:

bash

unzip -l base.apk | grep lib

If libflutter.so appears in base.apk instead of the architecture split, you would run ReFlutter against base.apk rather than the split. However, this layout is less common in modern Flutter apps targeting recent versions of the Gradle build system. In practice, the architecture split is the correct target in the overwhelming majority of cases.

Running ReFlutter on the Correct APK

With the target APK identified, you are ready to run ReFlutter. Before proceeding, ensure ReFlutter is installed in your testing environment. It can be installed via pip:

bash

pip3 install reflutter

ReFlutter works by unpacking the target APK, locating libflutter.so, patching specific bytes within the binary to disable TLS certificate validation logic, repacking the APK, and producing a modified APK file. The key insight is that ReFlutter must be executed against the APK that contains libflutter.so — in this case, split_config.arm64_v8a.apk.

Run ReFlutter with the architecture split as the target:

bash

reflutter split_config.arm64_v8a.apk

When executed, ReFlutter will prompt you to enter the IP address and port of your intercepting proxy (your Burp Suite listener). Enter the IP address of your machine on the network that your test device can reach, along with the proxy port (typically 8080):

Enter the IP of your Burp Suite proxy listener: 192.168.1.100
Enter the port of your Burp Suite proxy listener: 8080

ReFlutter patches the Flutter engine to route traffic through the specified proxy address and to disable the certificate verification that would otherwise reject your Burp Suite CA certificate. After the patching process completes, ReFlutter outputs a modified APK file named with the .RE.apk suffix:

split_config.arm64_v8a.RE.apk

This file contains the patched libflutter.so inside an otherwise intact APK structure. However, because the binary contents have been modified, the original APK signature is now invalid. This means the file, in its current state, cannot be installed on an Android device — Android's package manager performs cryptographic signature verification and will reject any APK whose signature does not match its contents.

Note on ReFlutter compatibility: ReFlutter works by patching known byte patterns within the Flutter engine binary. Because these patterns may change between Flutter engine versions, ReFlutter may fail or produce incorrect patches against Flutter apps built with very recent engine versions. If ReFlutter reports that it cannot find the expected patterns, this is likely the cause. See the Troubleshooting section at the end of this article for guidance.

Signing the Patched APK

Android requires that every APK be signed with a valid certificate before it can be installed. When you modify an APK — even by a single byte — the original signature is invalidated. You must re-sign the patched APK with a certificate that the Android package manager will accept.

For testing purposes, the Android debug keystore — automatically generated by Android Studio and the Android SDK — is entirely sufficient. This keystore lives at ~/.android/debug.keystore and uses well-known default credentials. It produces a valid V1/V2 APK signature that Android will accept on a device running in a mode that permits installation from unknown sources (or via adb).

Use apksigner — part of the Android SDK build tools — to sign the patched APK:

bash

apksigner sign --ks ~/.android/debug.keystore --ks-pass pass:android split_config.arm64_v8a.RE.apk

Breaking down the flags:

  • --ks ~/.android/debug.keystore specifies the keystore file to use for signing.
  • --ks-pass pass:android provides the keystore password inline. The debug keystore password is android by default.
  • split_config.arm64_v8a.RE.apk is the file to be signed, in place.

apksigner modifies the APK file in place, embedding the signature directly into the archive. After this command completes, split_config.arm64_v8a.RE.apk is a properly signed APK that Android will accept.

You can verify the signature is valid with:

bash

apksigner verify split_config.arm64_v8a.RE.apk

If verification returns no output (or Verifies), the APK is correctly signed and ready for installation.

Important: If the application you are testing enforces any form of signature verification (checking that the signing certificate matches an expected value), re-signing with the debug key will cause those checks to fail at runtime. This is a separate protection from SSL pinning and requires a different bypass strategy. For most applications, this is not an issue, but it is worth being aware of.

Installing the Patched Application with Split APKs

This is the step most security testers familiar with monolithic APKs get wrong. You cannot install just the patched architecture split by itself. Split APK applications are not independent packages — they are fragments of a complete installation that Android's package manager assembles together. Installing a single split in isolation will fail with an error similar to:

adb: failed to install split_config.arm64_v8a.RE.apk: Exception occurred while executing 'install':
android.content.pm.PackageManager$NameNotFoundException: ...

This happens because the architecture split does not contain an AndroidManifest.xml with the full package declaration, entry points, permissions, and metadata. The package manager requires base.apk to anchor the installation. Similarly, without the language and density splits, the app will either fail to install or fail to launch correctly because required resources are missing.

The correct approach is to install all splits together in a single adb install-multiple command, substituting the original architecture split with your patched, re-signed version:

bash

adb install-multiple base.apk split_config.arm64_v8a.RE.apk split_config.en.apk split_config.xxhdpi.apk

The critical detail here is that you provide:

  • base.apk — the original, unmodified base split (do not modify this)
  • split_config.arm64_v8a.RE.apk — your patched and re-signed architecture split (replacing the original)
  • split_config.en.apk — the original, unmodified language split
  • split_config.xxhdpi.apk — the original, unmodified screen density split

Android's package manager will treat this as a complete, coherent installation of the application. The patched Flutter engine from split_config.arm64_v8a.RE.apk will be installed alongside all the other required components.

If you have multiple architecture splits available (for example, both split_config.arm64_v8a.apk and split_config.x86_64.apk), include all of them in the install-multiple command, replacing only the one that contains libflutter.so with its patched version.

Successful installation will produce output similar to:

Performing Streamed Install
Success

If you see INSTALL_FAILED_INVALID_APK or signature-related errors, revisit the signing step. If you see errors about conflicting signatures, the original app may already be installed with a different signing certificate — uninstall it first with adb uninstall <package.name> and then retry.

Capturing Traffic with Burp Suite

With the patched application installed, configure your environment to intercept the traffic.

Configure the Burp Suite Proxy Listener

Open Burp Suite and navigate to Proxy > Proxy Settings > Proxy Listeners. Ensure you have a listener active on all interfaces (0.0.0.0) or specifically on the IP address of your machine on the network shared with your test device, on port 8080 (or whichever port you specified to ReFlutter during patching).

Configure the Device's Wi-Fi Proxy

On your Android test device, navigate to Settings > Wi-Fi, long-press the connected network, select Modify Network, and set the proxy to Manual. Enter the IP address of your Burp Suite machine and port 8080. This ensures that HTTP/HTTPS traffic from the device is routed through Burp Suite.

Note: Because ReFlutter patches the Flutter engine to route to the proxy address directly, some configurations may not strictly require the system-level Wi-Fi proxy setting to intercept Flutter HTTPS traffic. However, configuring it at the system level ensures broad coverage and is best practice.

Install the Burp Suite CA Certificate (for Non-Flutter Traffic)

For any non-Flutter HTTPS traffic (from WebViews, standard Android HTTP libraries, etc.), install the Burp Suite CA certificate:

  1. In Burp Suite, navigate to Proxy > Proxy Settings > Import / Export CA Certificate and export the certificate in DER format.
  2. Transfer the certificate to your device and install it via Settings > Security > Install from Storage (exact path varies by Android version and manufacturer).

On Android 7.0 (API 24) and above, user-installed certificates are not trusted by apps by default. If the app has not opted in to user certificates via its network security configuration, you will need a rooted device to install the certificate as a system CA. For Flutter traffic specifically, this is less of a concern because ReFlutter bypasses the Flutter engine's certificate verification directly, regardless of the system trust store.

Verify Traffic Interception

Launch the patched application on the device and perform actions that trigger network requests (logging in, loading data, submitting forms). Switch to Burp Suite's Proxy > HTTP History tab.

If everything is configured correctly, you will see HTTPS requests from the Flutter application appearing in plain text in Burp Suite's history, complete with headers, request bodies, and response payloads. You can now apply Burp Suite's full suite of testing capabilities: repeating requests, modifying parameters, fuzzing endpoints, and analyzing the API surface.

Common Problems and Troubleshooting

Flutter Libraries Not Located in base.apk

This is the most frequent source of confusion. If you run ReFlutter against base.apk and the command either fails or produces a patched APK that does not actually intercept traffic, the cause is almost certainly that libflutter.so is not inside base.apk. Always run the unzip -l inspection on all APK files in the split set before deciding which one to target:

bash

unzip -l base.apk | grep libflutter
unzip -l split_config.arm64_v8a.apk | grep libflutter

Target whichever file the grep output identifies as containing libflutter.so.

Architecture Mismatch

If the device is a 32-bit ARM device (or you are using an emulator configured for x86/x86_64), the architecture split you need to patch will be different. Inspect all architecture splits present in your extracted set:

bash

unzip -l split_config.armeabi_v7a.apk | grep libflutter
unzip -l split_config.x86_64.apk | grep libflutter

Patch the split corresponding to the architecture of your target device. If your device uses arm64-v8a (virtually all modern physical devices), split_config.arm64_v8a.apk is the correct target.

Installation Errors with Split APKs

INSTALL_FAILED_INVALID_APK: The APK is not properly signed. Re-run the apksigner command and verify with apksigner verify.

INSTALL_FAILED_UPDATE_INCOMPATIBLE: The package is already installed with a different signing certificate. Uninstall the existing version first:

bash

adb uninstall com.example.targetapp

Then retry the adb install-multiple command.

INSTALL_FAILED_VERSION_DOWNGRADE: The version code of the APK you are installing is lower than the version already installed. Uninstall the existing version and reinstall.

Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES]: The APK has not been signed at all. The apksigner command may not have run successfully.

Split mismatch errors: Ensure all splits in the install-multiple command belong to the same application version. Mixing splits from different builds will produce installation failures.

ReFlutter Failing Due to Flutter Engine Version

ReFlutter locates and patches specific byte sequences within libflutter.so. These sequences are tied to the Flutter engine version. If the application was built with a very recent version of Flutter that ReFlutter does not yet support, the tool will report that it cannot find the expected patterns and will not produce a patched APK.

Workarounds in this scenario include:

  • Check for ReFlutter updates: ReFlutter is actively maintained. A newer version may support the Flutter engine version used by the target app. Check the project's GitHub repository for releases.
  • Manual binary patching: Using a binary analysis tool such as Ghidra or IDA Pro, locate the SSL verification functions within libflutter.so manually and patch the relevant branch instructions. This is significantly more involved but works regardless of ReFlutter's support status.
  • Frida-based dynamic instrumentation: Use Frida to hook the relevant SSL verification functions at runtime. The ssl_logger script and Flutter-specific Frida scripts in the security research community can intercept traffic without modifying the APK.

Traffic Not Appearing in Burp Suite After Successful Installation

If the app launches without crashing but no traffic appears in Burp Suite, verify:

  1. The proxy listener in Burp Suite is active on the correct IP and port.
  2. The IP address entered into ReFlutter during patching matches the IP of the machine running Burp Suite, as seen from the device's network.
  3. The device's Wi-Fi proxy is configured correctly (or that the ReFlutter-patched routing is directing traffic to the correct destination).
  4. The app is actually making network requests during the actions you are performing.

You can verify connectivity by checking whether Burp Suite receives any connection attempts, even if they are rejected, by looking at the Alerts tab.

Conclusion

Analyzing Flutter applications distributed as split APKs requires a clear understanding of how Android App Bundle packaging works and where the Flutter engine actually lives within the split set. The central takeaway of this guide is that libflutter.so — the binary that must be patched by ReFlutter — is almost always located inside the architecture-specific split APK (split_config.arm64_v8a.apk for modern devices), not inside base.apk.

The complete workflow for bypassing SSL pinning in a Flutter split APK application is:

  1. Inspect each APK in the split set with unzip -l to identify which one contains libflutter.so.
  2. Run reflutter against that specific APK, providing your proxy's IP address and port.
  3. Sign the resulting .RE.apk file with the Android debug keystore using apksigner.
  4. Install the complete split set using adb install-multiple, substituting the patched APK in place of the original architecture split.
  5. Configure Burp Suite and the device proxy, then capture decrypted HTTPS traffic.

Understanding this split architecture is not merely a procedural detail — it is foundational knowledge for any mobile application security tester working with modern Android applications. As more apps migrate to AAB distribution, the ability to correctly navigate split APK structures will become an increasingly essential skill. Combining this knowledge with tools like ReFlutter and proxy interceptors like Burp Suite gives security researchers the capability to thoroughly assess the security of Flutter-based mobile applications, including their API communication, authentication mechanisms, and data handling practices.

Connect with me: