Bypassing SSL pinning on Android Flutter Apps with Ghidra
TL-DR
Android Apps built with the Flutter framework validate the secure connections and honour the Proxy settings in a different fashion when compared to apps written in dex. A binary dubbed libflutter.so seems to contain the dependencies responsible for establishing remote connections. This post shows the steps to patch the binary to bypass ssl pinning on Android apps (armeabi-v7a).
This binary (libflutter.so) seems to comprise the Flutter engine that is compiled (AOT). With that in mind I left the 2 patched binaries (armeabi-v7a and x86_64) to be used by security researchers when assessing Android Flutter apps that are using Dart 2.10.5 (stable). I tested these binaries on other Flutter apps using the same Dart version and they seemed to work just fine.
Additionally you can try to download the patched binary for your platform and replace within the app you want to analyze — remember to sign them before use and bear in mind that the Dart version influences the engine so different versions might not work.
Introduction
Flutter is an open-source SDK created and maintained by Google to ease the development of Mobile and Web applications. One of its main characteristics is the use of Dart as the programming language, changing some of the behaviors normally found on Android apps — i.e. Android Flutter apps don’t honour the Android proxy settings nor trust on the Android TrustManager. Some overview of its architecture can be found here.
Motivation
I’ve always been fond of bypassing SSL certificate pinning on Android apps since long ago when I was a security consultant. At that time I could never really understand how Frida worked but I recall trying to bypass ssl pinning by modifying smali code for known libs like okhttp.
Recently a friend of mine, Vinicius mentioned he was facing some challenges with an app that was built using Flutter. The main challenge arises from the fact that Flutter apps don’t trust the Android TrustManager. He also said that Frida could be used to instrument the boringssl library, one of the dependencies of the libflutter.so.
As I mentioned I’ve never been a fan of Frida as its abstraction always confused my shallow knowledge of binary analysis and code instrumentation. When Vinicius shared with me the libflutter.so alongside this blog post as a reference for intercepting traffic of Android Flutter Applications I found an opportunity to eliminate the use of Frida for this case and patch the binary to avoid certificate verification failures.
Analysis
Learning from Jeroen Beckers’s post that the exception was generated by the handshake.cc from the boringssl — that is also because Vinicius mentioned the app was suppressing errors which was making it harder to instrument with Frida.
With the method ssl_crypto_x509_session_verify_cert_chain being responsible to perform the validation and return a boolean value. Knowing that it’s a matter of trying to change its return to be always true (1):
In this analysis I used the armeabi-v7a ISA to depict the following steps.
Steps for armeabi-v7a
We will use Ghidra for this binary patching. Assuming you’ve already disassembled the apk (apktool d command) and got the libflutter.so from the related lib folder (/lib/armeabi-v7a/libflutter.so).
Note that for different instruction set architectures the patching process might differ. For x86_64 ISA, a very similar approach can be used while for arm64-v8a the return of the function is quite different (on Dart 2.10.5).
Open the libflutter.so using Ghidra. You will be asked if you would like Ghidra to analyze the binary. Hit Yes followed by Analyze.
After some (good) time of disassembling the instructions with Ghidra, it will present disassembled code that can be searched and read with its C representation. Knowing that the certificate validation happens in this piece of code:
Search for the references linking to this file [ Search | For Strings ]
The disassembler shows that the analyzed libflutter.so has 8 cross references (XREF) to the aforementioned file. Going over all of them, it’s possible to detect a function very similar to the ssl_crypto_x509_session_verify_cert_chain due to the parameters definition presented in the disassembled code:
Investigating the disassembled code and the instructions further, it’s possible to note that the register r4 is being used to hold the return value of the ssl_crypto_x509_session_verify_cert_chain:
Knowing this all we need to do is to change it to be 1 like in the following:
Note that the change we just did reflected in the disassembled code indicating that the function will always return 1 (true):
Save the changes (File | Save “libflutter.so”) and export the binary as following:
Make sure to select ELF format when exporting the file. Hit okay and cross your fingers.
If everything works fine you will see a summary like the following:
Now you just have to replace the patched binary within the disassembled apk, assemble (apktool b) and sign it again. Install the app on your device or emulator and good intercepting. It’s worth to note that as highlighted by Jeroen, “Dart is not proxy aware on Android” so the use of ProxyDroid or iptables is required.
Credits
Last but not least I’d like to give the credits to my friend Vinicius. Without his analysis and opportunity alongside a second shot on testing the app this method wouldn’t be documented.
And of course to Jeroen Beckers’ post that presented the initial steps with Frida and where/how to look.