Original address: tinyhack.com/2021/03/07/…
Written by Tinyhack.com
Release date: March 7, 2021
Reverse-engineering a release of a Flutter application is not easy because there are no tools and the Flutter engine itself changes quickly. Until now, if you were lucky enough to have a Flutter application built with a particular version of the Flutter SDK, you could dump the application’s class and method names using Darter or Doldrums.
If you’re very lucky, this is what happened the first time I needed to test the Flutter App: you don’t even need to reverse engineer the App. If the application is very simple and uses a simple HTTPS connection, you can use an intercepting Proxy such as Burp or Zed Attack Proxy to test all features. The application I just tested uses an extra layer of encryption over HTTPS, which is why I needed to actually reverse engineer it.
In this article, I will only give examples for the Android platform, but everything written here is generic and applicable to other platforms. TLDR is: we do not update or create a snapshot parser, but simply recompile the Flutter engine and replace it with our target application.
Application of Flutter after compilation
The few articles and databases I have found so far on Flutter reverse engineering are.
- Reverse engineering Flutter for Android (explains the basic situation of snapshots format, is introduced, the Doldrums, so far only support snapshot version 8 ee4ef7a67df9845fba331734198a953)
- Reverse-engineering the Flutter application (Part 1) is a very good article that explains the internal structure of Dart. Unfortunately, the code is not provided, and Part 2 has not been provided as of this writing.
- Darter: Dart snapshot parser a snapshot dump c8562f0ee0ebc38ba217c7955956d1cb version of the tool.
The main code consists of two libraries libflutter. So (the flutter engine) and libapp.so (your code). You might be wondering what actually happens if you try to open a libapp.so (aot-compiled Dart code) with a standard disassembler. This is just native code, right? If you use IDA, initially, all you see is this bunch of bytes.
If you use other tools like binary Ninja will try to do some linear scanning, you can see a lot of ways. None of the methods are named, and there are no string references you can find. There are no references to external functions (whether liBC or other libraries), and no direct calls to kernel syscall (such as Go).
With tools like Darter Dan Doldrums, you can dump class names and method names, and you can find the implementation address of the function. Here is an example of a dump using Doldrums. This is great for reversing applications. You can also use Frida to hook at these addresses, dump memory or method parameters.
Snapshot Format Problem
A particular tool can dump only snapshots of a particular version because the snapshot format is unstable and is designed to run with a particular version. Unlike some other formats that can skip unknown or unsupported formats, the snapshot format is very intolerant. If you can’t parse one section, you can parse the next.
Basically, the snapshot format looks like this.
…… Each chunk has no specific length, and the header of the tag has no specific format (so you can’t just do a pattern match and expect to know the start of a chunk). Everything is just numbers. There is no documentation for this snapshot other than the source code itself.
In fact, the format doesn’t even have a version number. This format is identified by the snapshot version string. The version string is generated from the source code hash of the snapshot-related file. We assume that if the file is changed, the format is also changed. This is true most of the time, but not always (for example, if you edit a comment, the snapshot version string changes).
My initial idea was just to modify Doldrums or Darter to the version I needed by looking at the DIff in the Dart source code. But as it turns out, this isn’t easy: enums are sometimes inserted in the middle (meaning I need to shift all constants by a number). Dart also uses a lot of bitwise manipulation, using C++ templates. For example, when I looked at the code for Doldums, I saw something like this.
def decodeTypeBits(value):
return value & 0x7f
Copy the code
Dart.googlesource.com/sdk//+/2beb… I think I can quickly check that this constant in the code (whether it has changed in the new version) is not a simple integer.
class ObjectPool : public Object {
using TypeBits = compiler::ObjectPoolBuilderEntry::TypeBits;
}
struct ObjectPoolBuilderEntry {
using TypeBits = BitField<uint8_t, EntryType, 0.7>;
}
Copy the code
You can see that this Bitfield is implemented as a Bitfield template class. This particular bit is easy to read, but if you see kNextBit, you need to go back to all the previous bit definitions. I know this isn’t hard for experienced C++ developers, but still: to keep track of the changes between these versions, you need to do a lot of manual checking.
My conclusion is this. I don’t want to maintain Python code. The next time the update application is retested, they can use a newer version of the Flutter SDK and create a snapshot version. And for the specific work THAT I’m doing. I needed to test two apps with two different versions of Flutter: one that was already released in the app Store and one that was about to be released.
Rebuild the Flutter engine
The Flutter engine (libflutter. So) is a library independent of libapp.so, the main application logic code, which is a separate framework on iOS. The idea is very simple.
- Download the version of the engine we want
- Modify it to print class names, methods, and so on, rather than writing our own snapshot parser.
- Replace the original libflutter. So library with our patch version.
- profit
The first step is already difficult: how do I find the corresponding snapshot version? This table from Darter can help us, but it is not updated with the latest version. For other versions, we need to hunt and test if it has a matching snapshot number. There are instructions to recompile the Flutter engine, but there was a minor incident during compilation that required us to modify the snapshot version of the Python script. Also: The inside of the Dart itself is not that easy to manipulate.
Most of the older versions I tested didn’t compile correctly. You need to edit the DEPS file to make it compile. In my case: the difference is small, but I need to search for this online. For some reason, that particular submission is not available and I need to use a different version. Note: Don’t blindly apply this patch, basically check these two points.
- If a submission is missing, find the one closest to the release date.
- If something refers to
_internal
Yeah, you probably should_internal
Remove the part of.
diff --git a/DEPS b/DEPS
index e173af55a.. 54ee961ec 100644
--- a/DEPS
+++ b/DEPS
@ @ + 196-196, 7, 7 @ @ deps = {
Var('dart_git') + '/dartdoc.git@b039e21a7226b61ca2de7bd6c7a07fc77d4f64a9',
'src/third_party/dart/third_party/pkg/ffi':
- Var('dart_git') + '/ffi.git@454ab0f9ea6bd06942a983238d8a6818b1357edb',
+ Var('dart_git') + '/ffi.git@5a3b3f64b30c3eaf293a06ddd967f86fd60cb0f6',
'src/third_party/dart/third_party/pkg/fixnum':
Var('dart_git') + '/fixnum.git@16d3890c6dc82ca629659da1934e412292508bba',
@ @ + 468-468, 7, 7 @ @ deps = {
'src/third_party/android_tools/sdk/licenses': {
'packages': [
{
- 'package': 'flutter_internal/android/sdk/licenses',
+ 'package': 'flutter/android/sdk/licenses',
'version': 'latest',
}
],
Copy the code
Now we can start editing the snapshot file to see how it works. But as mentioned earlier: if we modify the snapshot file: the snapshot hash value changes, so we need to return a static version number in third_party/dart/tools/make_version.py to fix this. If you touch any of the VM_SNAPSHOT_FILES files, change the snapshot_hash = MakeSnapshotHashString() line to your specific version with a static string.
What happens if we don’t patch the version? The app won’t start. So, after patching OS::PrintErr(“Hello World”) and recompiling the code, we can test the replacement.so file and run it.
I’ve done a lot of experiments (such as trying FORCE_INCLUDE_DISASSEMBLER), so I don’t have a clean fix to share, but I can give you some hints.
- In the runtime/vm/clustered_snapshot. Cc, We can modify the Deserializer: : ReadProgramSnapshot (ObjectStore * object_store) to Print class table isolation – > class_table () – > Print ()
- In runtime/ VM /class_table.cc, we can modify void ClassTable::Print() to Print more information.
For example, print the function name.
const Array& funcs = Array::Handle(cls.functions());
for (intptr_t j = 0; j < funcs.Length(a); j++) { Function& func = Function::Handle(a); func = cls.FunctionFromIndex(j);
OS::PrintErr("Function: %s", func.ToCString());
}
Copy the code
Side note: SSL certificate
Another problem with the Flutter app is that it does not trust the user’s installed root CERT. This has been a problem for Pentesting, and a tutorial on how to patch binaries (directly or using Frida) has been written to address this problem. To quote TLDR from this blog post.
- Flutter uses Dart, which does not use the system’s CA shop
- The list of cas used by Dart is compiled into the application.
- Dart is proxy-aware on Android, so use ProxyDroid and iptables.
- Disable chain validation by checking the session_verify_cert_chain function in x509. Cc.
This can be easily done by recompiling the Flutter engine. We only need to modify the source code according to the sample (third_party/boringssl/SRC/SSL handshake. Cc), without the need to find assembly in the compiled code byte pattern.
Confuse Flutter
Use the instructions provided here to confuse the Flutter/Dart application. This will make inversion a little more difficult. Note that only the names are obfuscated, and no high-level control flow obfuscation is performed.
conclusion
I am lazy and recompiling the Flutter engine is the shortcut I take instead of writing a proper snapshot parser. Of course, others have similar ideas about hacking runtime engines when reversing other techniques, such as a PHP module that hooks Eval to reverse a confused PHP script.
Translation via www.DeepL.com/Translator (free version)