Flutter Windows Thick Client SSL Pinning Bypass
I recently worked on a Flutter-based application and learned that it is different from other hybrid frameworks like React Native or Electron JS. After reading multiple blogs, I realized that there is no research or blog for Windows applications built with Flutter.
Although I was able to intercept and set up a proxy in Android and iOS applications with the help of existing research, I could not figure out how to do that for Windows apps. Even the Frida script needed modification to work for Windows apps and allow traffic interception in Burp Suite. I decided to develop a Flutter app myself with the help of some Flutter and Dart tutorials.
Table Of Content
· Basics
· Sending HTTP Request
· Proxy Setup
· Resolve SSL Error
· Using Proxy
Basics
If you are unfamiliar with how Flutter works, such as what VM or snapshot is, I would recommend doing some research. There are existing blogs and resources available that can help you understand the basics. I have personally followed some of these blogs and have found them helpful in understanding how a Flutter app works. Additionally, it’s worth noting that the concepts remain the same across different platforms.
- Reverse engineering Flutter apps (Part 1)
- The Current State & Future of Reverse Engineering Flutter™ Apps
- Reverse Engineering a Flutter app by recompiling Flutter Engine
- Flutter Reverse Engineering and Security Analysis
When dealing with Windows applications, it’s important to note that there are three main files:
- Exe: This is the main executable file that loads the Flutter engine and other necessary files.
- flutter_windows.dll: This is the Flutter engine, which can be decompiled using tools like IDA or Ghidra. You can find the Flutter engine on the GitHub repository here.
- data/app.so: This is the main file that contains the developer’s written code, and it’s not easy to reverse the code. This file also includes the Dart SDK from the GitHub repository, which is available here.
So, I found this .so file in a Windows app and I was like, “What’s the point?” But then I did some digging on Flutter’s GitHub repo and it turns out, it’s actually pretty useful.
Flutter applications that are AOT compiled us a
.so
on Windows, Linux desktop, and Android. This file contains only your Dart code. Separately, the executable is compiled from the platform specific entry point in the windows directory, along with your plugins and this depends on a dll (flutter engine) which contains the actual engine as well as the support for loading AOT compiled Dart code.
Sending HTTP Request
Let’s focus on SSL and proxy interception when it comes to Flutter applications. There are two popular methods to send HTTP requests in a Flutter app:
1. HttpClient Class — This class is part of the dart-io and allows the sending of HTTP requests. However, it’s recommended to avoid using this class as it works with low-level API and doesn’t support all platforms. Nonetheless, it is still used extensively in Flutter apps for major platforms such as desktop and mobile applications.
2. Http Package — This is an additional package created by the Dart team, which supports all platforms including the web. It is another popular method to send requests in a Flutter-based application.
The HttpClient Class has proxy support by default enabled. In the windows, we can set the environment variable like below.
setx HTTP_PROXY localhost:8080
setx HTTPS_PROXY localhost:8080
By default, the HttpClient will utilize this proxy and not read the proxy settings from Windows. In cases where the developer wants to disable proxy support completely, even setting environment variables like the one mentioned above will not work.
By default the HttpClient uses the proxy configuration available from the environment, see findProxyFromEnvironment. To turn off the use of proxies set the findProxy property to null.
It is rare to find an app where the developer has already added a proxy. However, they can add a proxy directly within the code.
httpClient.findProxy = (uri) {
return "PROXY localhost:8080;";
};
The HTTP package doesn’t provide any API to add or remove proxy support. However, it uses IOClient as a default and can read the proxy from the environment variable, just like the HTTPClient class. Similarly to HTTPClient, I haven’t found any method to remove the proxy or ignore it from the environment variable.
It is possible that if an application uses the HTTP package, it will mostly support proxies with environment variables since there is no way to ignore them.
Proxy Setup
I have created different applications to test for different scenarios. You can find the application here on my GitHub.
Let’s run the win_ssl_httpclient_no_config.exe the app does not have any proxy configuration set and uses HTTP Client class, no configuration means by default the app will support proxy from Windows env.
Let’s run the app without proxy and we can see the request is successful.

Set the proxy environment in Windows like the below image.

Give the app another shot and you’ll notice an SSL error popping up. If you take a look at the main.dart
file, you’ll see that the app doesn’t have SSL pinning installed. It just sends an HTTPS request. Even though the Burp Suite certificate is installed in the Windows Trusted Certificate, we still get the error.

So, it turns out that the dart doesn’t really care about the trusted certificate on our system and it just uses its own. This means that even if we’ve got the Burp Suite certificate installed in the trusted certificates, the app won’t recognize it.
Resolve SSL Error
While searching for solutions to fix an SSL error, I stumbled upon an article that explains how to solve the same issue for Android and iOS applications.
So basically, the error we’re seeing is coming from this function called ssl_verify_peer_cert
in the handshake.cc
file of the BoringSSL library. The article suggests using reverse engineering to find the offset of the ssl_verify_peer_cert
function in the assembly code, hook it with Frida, and then return the value of 0.
Luckily the boring SSL will be part of the flutter engine instead of dart, So we can reverse the flutter_windows.dll
file using Ghidra to find the correct offset for Windows.
Load the flutter_windows.dll
file into the Ghidra and search for handshake.cc.
In my case, I got the handshake.cc
.

After double clicking on it, we can see all the references to handshake.cc
. In my case, there were around 17 references to this.

After going through the references I managed to find the correct one that looks similar to the ssl_verify_peer_cert
function in my case it was FUN_1804e680d
.
Copy the first few bytes of the function to use it with Frida to hook this particular function.

All we have to do is the same Frida script and modify it for Windows.
Now we just need to run the Frida script on the exe, also available here.
/**
A Frida script that disables Flutter's TLS verification
Modified version of https://github.com/NVISOsecurity/disable-flutter-tls-verification/blob/main/disable-flutter-tls.js
to Support Windows Application
*/
// Configuration object containing patterns to locate the ssl_verify_peer_cert function
// for different platforms and architectures.
var config = {
"windows":{
"modulename": "flutter_windows.dll",
"patterns":{
"x64": [
"41 57 41 56 41 55 41 54 56 57 53 48 83 ec 40 48 89 cf 48 8b 05 ba a4 ba 00"
],
},
},
};
// Flag to check if TLS validation has already been disabled
var TLSValidationDisabled = false;
var flutterLibraryFound = false;
var tries = 0;
var maxTries = 5;
var timeout = 1000;
disableTLSValidation();
// Main function to disable TLS validation for Flutter
function disableTLSValidation() {
// Stop if ready
if (TLSValidationDisabled) return;
tries ++;
if(tries > maxTries){
console.log('[!] Max attempts reached, stopping');
return;
}
console.log(`[+] Attempting to find and hook ssl_verify_peer_cert (${tries}/${maxTries})`)
// Get reference to module. Necessary for iOS, and usefull check for Android
var platformConfig = config["windows"];
var m = Process.findModuleByName(platformConfig["modulename"]);
if (m === null) {
console.log('[!] Flutter library not found');
setTimeout(disableTLSValidation, timeout);
return;
}
else{
// reset counter so that searching for ssl_verify_peer_cert also gets x attempts
if(flutterLibraryFound == false){
flutterLibraryFound = true;
tries = 1;
}
}
if (Process.arch in platformConfig["patterns"])
{
var ranges;
ranges = m.enumerateRanges('r-x')
findAndPatch(ranges, platformConfig["patterns"][Process.arch], Java.available && Process.arch == "arm" ? 1 : 0);
}
else
{
console.log('[!] Processor architecture not supported: ', Process.arch);
}
if (!TLSValidationDisabled)
{
if(tries < maxTries){
console.log(`[!] Flutter library found, but ssl_verify_peer_cert could not be found.`)
}
else
{
console.log('[!] ssl_verify_peer_cert not found. Please open an issue at https://github.com/Anof-cyber/Flutter-Windows/issues');
}
}
}
// Find and patch the method in memory to disable TLS validation
function findAndPatch(ranges, patterns, thumb) {
ranges.forEach(range => {
patterns.forEach(pattern => {
var matches = Memory.scanSync(range.base, range.size, pattern);
matches.forEach(match => {
var info = DebugSymbol.fromAddress(match.address)
console.log(`[+] ssl_verify_peer_cert found at offset: ${info.name}`);
TLSValidationDisabled = true;
hook_ssl_verify_peer_cert(match.address.add(thumb));
console.log('[+] ssl_verify_peer_cert has been patched')
});
if(matches.length > 1){
console.log('[!] Multiple matches detected. This can have a negative impact and may crash the app. Please open a ticket')
}
});
});
// Try again. disableTLSValidation will not do anything if TLSValidationDisabled = true
setTimeout(disableTLSValidation, timeout);
}
function isFlutterRange(range){
var address = range.base
var info = DebugSymbol.fromAddress(address)
if(info.moduleName != null){
if(info.moduleName.toLowerCase().includes("flutter")){
return true;
}
}
return false;
}
// Replace the target function's implementation to effectively disable the TLS check
function hook_ssl_verify_peer_cert(address) {
Interceptor.replace(address, new NativeCallback((pathPtr, flags) => {
return 0;
}, 'int', ['pointer', 'int']));
}
Run the application again with Frida and see that we can intercept the request in the burp suite.
frida -l script.js win_ssl_httpclient_no_config.exe
____
/ _ | Frida 16.2.1 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to Local System (id=local)
Spawning `.\win_ssl_httpclient_no_config.exe`...
[+] Attempting to find and hook ssl_verify_peer_cert (1/5)
[+] ssl_verify_peer_cert found at offset: FlutterDesktopTextureRegistrarMarkExternalTextureFrameAvailable
[+] ssl_verify_peer_cert has been patched
Spawned `.\win_ssl_httpclient_no_config.exe`. Resuming

Using Proxy
As mentioned earlier, developers can disable the proxy completely using HTTPClient. This situation is similar to Android and iOS applications. Although mobile apps support proxies from environment variables, we still need to use IP table or VPN as mobile apps are not restricted from reading the environment.
Even if the proxy is disabled for a mobile app, we can still use existing methods to intercept the requests. However, in the case of Windows applications, we need an alternative method as environment variables won’t work in such scenarios.
I was trying to find a different method to avoid using a VPN similar to an iOS device as it requires additional setup. I wasn’t sure if running the VPN server on the VM of the host Windows machine would work. I thought of trying other methods, such as modifying the code to remove the client.findProxy = null;
or finding an alternative method within the Flutter engine or Dart SDK.
I wondered if it would be possible to modify the HTTP Client class to use the Burp IP and port instead of null.
After looking for multiple methods I tried looking at the dark SDK and got this dart code for HTTP Client.
So I was thinking of making some modifications using Ghidra, but turns out it’s a part of the Dart SDK and is located in the app.so
file. That file is really hard to reverse engineer and unfortunately, there’s no tool available for it in the newer versions. I also tried using Frida to get some help, but it was no good. For some reason, it couldn’t find the .so
module in the application process. I guess that’s because it’s not supported in Windows by default.
Then, I tried to get some details from the snapshot using the Flutter engine, but it only worked for Android and iOS applications, not Windows. Maybe we could take a look at the source code to get some other details on how the .so
file works in Windows, but I didn’t really focus on that. My main intention was to deal with the proxy, not the reverse engineering of Flutter.
So, to sum it all up, there’s no method available for the task at hand right now. I decided to give it a shot and deal with it like any other proxy-unaware application.
I tried using Proxifier which mostly works on Windows for proxy unware applications. I configured the Proxifier to use Burp Suite.


Now I tried again and it worked and I got the SSL error so cleary proxy is working, now I can use the same Frida script to intercept the traffic. The only downside is this app is paid and has a 30-day trial.
There are methods to add SSL pinning in Flutter apps, such as adding custom certificates or public key pinning. However, most of these methods only support mobile apps. If the app uses a custom certificate, we can simply replace it with our own certificate from the files, which is the same for both mobile and desktop applications. Although there are methods to keep the certificate within the code instead of a file, in such cases, it might be difficult since it’s not easy or possible to reverse the Dart code like other AOT-compiled binaries.
Twitter — https://twitter.com/ano_f_
GitHub — https://github.com/Anof-cyber/
Linkedin — https://www.linkedin.com/in/sourav-kalal/