Following our last post on Flutter, Jordi Ventayol, one of Build38’s Security Engineers will dig in a bit deeper into the security that the Flutter framework offers. In this more technical post we will analyze the obfuscation of the code once compiled, as well as different alternatives.
Before going into the obfuscation of any Application, it’s interesting to extract data that provides us with information. For the Flutter Application, we can consider strings, method names, and class names, since from these, we can clearly understand what the section of code containing these meaningful strings is doing.
If we compile the Application with the default command: “flutter build apk” and then analyze the strings of the binary ‘libapp.so’using the “strings” command, (which as we mentioned in the previous post is where our compiled code is located (ref. part 1 of this blog serie)), we can extract the following list of strings:
Regarding strings of code embedded as strings:
Regarding class and method names we implemented in our code:
As can be seen, the default Flutter compilation neither hides the data nor removes the meaning of the function names. Hence, an attacker could begin the reverse engineering process with almost the same information the original developer had.
Obfuscation in Flutter
As most readers of this blog post may know, obfuscation involves hiding data and the structure of compiled code through transformations. Thus, when an attacker tries to reverse engineer the Application, the more obfuscated the code, the harder it is to understand. This means an attacker would need to invest time to de-obfuscate the code to understand the data or function being analyzed. From this, we can infer that the more obfuscation, the more time and resources an attacker would need to de-obfuscate the code.
One of the most basic obfuscation techniques involves renaming function and class names with characters to eliminate their inherent meaning.
We’d all agree that a function like:
is much easier to understand than:
This is just a very basic obfuscation method, which involves removing the meaning of strings as method and class names. However, there are countless other methods that allow us not only to obfuscate Application data like String obfuscation or Mixed-Boolean-Arithmetic to hide constants but also the execution flow, such as Control Flow Flattening or Function Inlining.
Well, Flutter allows us to compile the Application with obfuscation using the following command: “flutter build apk –obfuscate”. Let’s compile it to analyze the obfuscation provided by the framework itself.
If we analyze the strings in the binary compiled with obfuscation, as we did with the default compilation, regarding the code’s strings, we find:
We can see the same strings as the App compiled without obfuscation.
As for the names of the methods and classes, they no longer appear. This means that Flutter removed the meaning of class and method names but did not apply any transformation to the text strings in the code.
At this point, we could say that the code is somewhat more protected, as an attacker would have to reverse engineer the “PoCfunction” and “B38changeText” functions to understand their operation. Remember that previously, just by reading the method name, an attacker would know that the “B38changeText” function modifies the text displayed on the screen. Now, they at least need to understand the internal instructions of this routine.
Is this obfuscation enough?
It’s important to note that some decompilers, like Ghidra, were unable to retrieve the text strings from the binary, even though they are located in the .rodata section as defined by the ‘ELF’ standard. This is due to the peculiarities of the library compiled for Flutter, which we will discuss in the next post in this series. This was not the case with IDA Pro, which does correctly identify these character strings, but with the particularity that they seem not to be referenced in the code.
How is this possible?
The binary incorporates the text strings, but they appear unused; there is no subroutine in the .text section (where the executable code is located) that references these character strings.
If the reader is familiar with compiling Android Applications natively, they might compare this type of compilation with the obfuscation option we get when compiling with Gradle using the ‘proguard’ tool with the minifyEnabled = true option.
The only observable functions are the following 5 sections marked as “exports“, indicating that other binaries will access them:
Thus, the names of methods and classes are gone, and the text strings still appear but are not referenced. It seems we will need to start analyzing the code’s structure from an execution flow standpoint, as from a data perspective, there seems to be little more we can extract.
In summary, Flutter’s built-in obfuscation capabilities provide a valuable layer of security, but developers should remain vigilant about protecting sensitive strings and be aware of the variations in different tools.
In our upcoming posts, we will continue to explore Flutter’s security features to provide a comprehensive understanding of its strengths and potential vulnerabilities. Stay tuned for more insights about Flutter’s framework and for more information on how to protect your Mobile Applications, don’t hesitate to contact us here.