Flutter mobile application testing
Before Android 6.0, to intercept the HTTP request of the application, you only need to set the proxy to your debugging proxy server like BurpSuite in the Wi-Fi settings and install the CA certificate of the debugging proxy, and you can successfully capture the packet.
But after Android 7.0, Network security configuration was added. Just like Certificate Pinning, if you don’t specify the allowed CA in network_security_config.xml, Android OS will not let network traffic go through the proxy server.
A researcher wrote an automated tool and put it on Github. After unpacking the APK file, put in the network_security_config.xml that allows all CAs, and the traffic can be received by the proxy.
Flutter is different. Flutter uses its own runtime engine to execute dart bytecodes, there is a libflutter.so in asserts/lib/, each architutre will execute the libfultter.so of its respective platform to execute kernel_blob.bin (Non-Release Mode ) / libapp.so (Release Mode).
You can directly restore source codes from kernel_blob.bin, and there is no way for release mode.
Flutter uses the BoringSSL to establish a TLS connection. The source code can be found in the official Dart repo.
To intercept the HTTP request of the Flutter application, there are several things to be done:
- Intercept network traffic and send it to debugging proxy
- Bypass Flutter’s CA certificate validation <- Our Goal
- Modify request / response
Intercept network traffic and send it to debugging proxy
There are Rooted and Non-Rooted methods:
Rooted
- iptables: a lot of command to type.
- ProxyDroid: automatically generate iptables commands to apply.
Non-Rooted
This part is suitable for the relatively high version of Android, after all, there are a bunch of functions missing after the phone is rooted.
VPN2SOCK
- use tun2sock + VPN to intercept traffic, PoC written by myself, and full of bugs.
LDPlayer
- Built-in Root / ADB
- Available with Android 5.1 / 7.1
- I’m using this, I’m using Android 5.1, just open ProxyDroid.
Bypass Flutter’s CA certificate validation
According to the error message in this article, TLS handshake failed appears on line 352 of handshake.cc because of CERTIFICATE_VERIFY_FAILED.
Check the source code of handshake.cc and find that flutter obtains the boolean returned by session_verify_cert_chain to determine whether the check certificate is valid.
Then let session_verify_cert_chain always return true.
I found that session_verify_cert_chain is located in ssl_x509.cc:362, and there are 2 return false in ssl_x509.cc:391 and ssl_x509.cc:411, just patch these two places to return true.
Patching libflutter.so @ armeabi-v7a
- It’s 32 bits ARM
- Just patch ARM 32bits is fine. Most mobile phones (arm64, x86, x86_64) have an ARM 32-bit runtime for compatibility. LDPlayer and my Galaxy S10 also have it.
First open lib/armeabi-v7a/libflutter.so with Ghidra.
In order to find session_verify_cert_chain, I took a look at the source code. This method has 3 inputs and a macro OPENSSL_PUT_ERROR to print out the error message. Defined in err.h:422, the error message will show the file name and line number. so let’s look for the string ssl_x509.cc in Ghidra, and find a function of xrefs and it has 3 inputs.
Ghidra -> Search -> Program Text, search for ssl_x509.cc.
There are 7 xrefs
In function FUN_012c68b4, there are 3 inputs, and there is one OPENSSL_PUT_ERROR call with line number 0x186 are also in session_verify_cert_chain in ssl_x509.cc.
Let’s take a look of disassembled code.
Just replace the return 0 of L18 and uVar4 = 0 of L62 with 1.
Obtain OpCode 0x01 0x24 of movs r4, 1 from Online ARM Assambler and change it with Hex Editor, or directly in Ghidra, right-click on movs r4, 0x0 and select Patch Instruction, change 0x0 to 0x1, and then save.
Put libflutter.so back into the apk, delete all lib directory except armeabi-v7a, re-sign with apksigner, and install it on the phone.
That’s all.