CLI tool
worawit/blutter avatar
worawit/blutter

B(l)utter: reverse engineering Android Flutter libapp.so with a compiled Dart AOT runtime

Flutter Mobile Application Reverse Engineering Tool

2,695 stars370 forksC++MIT

At a glance

What is it?
B(l)utter rebuilds a Dart AOT runtime so it can read symbols and object pools out of an Android Flutter app's libapp.so. It is arm64 Android only, tied to recent Dart versions, and it builds a compiler toolchain the first time you run it.
Who is it for?
Use B(l)utter if you are reverse engineering an arm64 Android Flutter app built on a recent Dart version and you are comfortable with a C++20 toolchain, because the first run compiles a Dart runtime against your target's version. Skip it for iOS binaries, ipa or apk input, and obfuscated apps, all of which the README lists as unfinished.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 44 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The gap B(l)utter fills in Flutter app analysis

A release Flutter app ships its Dart code as an AOT snapshot inside libapp.so. There is no Dart source in the APK and no bytecode a normal decompiler can lift. The README describes B(l)utter as a "Flutter Mobile Application Reverse Engineering Tool by Compiling Dart AOT Runtime", which is the whole idea: instead of trying to parse the snapshot blind, the tool builds the Dart runtime that matches the target's Dart version and uses it to interpret the snapshot. The audience is engineers doing security review, malware analysis or interoperability work on a shipped Flutter binary, not app developers debugging their own code. The supported surface is narrow and the README says so up front: "Currently the application supports only Android libapp.so (arm64 only)" and it works "only against recent Dart versions".

What blutter.py does with lib/arm64-v8a, and what lands in out_dir

The entry point is a Python script, not the C++ binary. You point it at the extracted lib/arm64-v8a directory of an APK and an output directory. The README states that blutter.py "will automatically detect the Dart version from the flutter engine and call executable of blutter to get the information from libapp.so". If no matching executable exists, the script checks out Dart source and compiles it, which is where the build time goes. The repository layout reflects this split: bin holds prebuilt executables named blutter_dartvm<ver>_<os>_<arch>, blutter holds the C++ source that must be built against the Dart VM library, packages holds static Dart runtime libraries, and build and dartsdk are scratch directories the README says can be deleted after a build finishes. The output is four artifacts. asm/ contains libapp assemblies with symbols. blutter_frida.js is a Frida script template for the target application. objs.txt is a complete nested dump of objects from the object pool, and pp.txt lists all Dart objects in the object pool. Those last two are the practical payoff: recovered class and object structure that the stripped binary does not expose on its own.

Installing B(l)utter and running it on a first libapp.so

The README recommends Linux and notes testing only on Debian sid/trixie, because the code uses the C++20 formatting library and needs g++ 13 or newer, or Clang 16 or newer. It also warns that you must use a Debian or Ubuntu release whose own main repository provides gcc 13; backporting gcc to an older release does not work. On Debian, the dependency set is installed in one command.

bash
apt install python3-pyelftools python3-requests git cmake ninja-build \
    build-essential pkg-config libicu-dev libcapstone-dev

On macOS Sequoia the README lists XCode plus Homebrew packages and two Python modules.

bash
brew install cmake ninja pkg-config icu4c capstone
pip3 install pyelftools requests

On Ventura and Sonoma the same command applies with llvm@16 added, since clang 16 is the floor. Windows is a different path: install git, Python 3 and Visual Studio with the C++ desktop workload, then run the repository's own setup script, which fetches libcapstone and libicu4c into external/.

bash
python scripts\init_env_win.py

After that you work from an "x64 Native Tools Command Prompt". The first real use is one command against an extracted library directory.

bash
python3 blutter.py path/to/app/lib/arm64-v8a out_dir

If the Dart version is not covered by an executable in bin, expect the script to fetch and compile Dart before any analysis output appears. When it finishes, out_dir should contain asm/, blutter_frida.js, objs.txt and pp.txt. To move to a newer B(l)utter, the README gives git pull followed by the same command with --rebuild, which forces the executable to be rebuilt rather than reused.

Where B(l)utter stops: obfuscation, iOS and non-APK input

The TODO list is the honest boundary of the tool. Obfuscated apps are listed as "still missing many functions", so a target built with Dart obfuscation will produce partial symbol recovery, not a clean listing. Reading iOS binaries is not implemented. Accepting an apk or ipa directly is not implemented either, which is why the workflow starts with you extracting lib/arm64-v8a yourself. Code analysis is also shallow by design at this stage: function arguments and return types, and pseudo code for code patterns, are still on the TODO list, so asm/ gives you symbols and structure rather than reconstructed high-level logic. The Frida script template is described as a template, and the TODO asks for more internal classes and object modification, so treat blutter_frida.js as a starting point to edit. The version constraint is the other hard edge. Because the tool builds against a specific Dart runtime, a target on an old or very new Dart version may fall outside what the checked-out source supports, and the README's phrase "recent Dart versions" is the only guidance given.

B(l)utter versus a generic native disassembler

The obvious alternative is loading libapp.so into a general-purpose disassembler such as Ghidra or IDA and working from the raw arm64. That approach asks nothing about Dart versions, runs on any host, and handles iOS Mach-O binaries as easily as Android ELF. What it cannot do is tell you which blob in the snapshot is a Dart class instance or where the object pool entries point. B(l)utter's value is exactly that semantic layer: symbols in asm/, a nested object dump in objs.txt, and a flat object pool listing in pp.txt. The trade is real. A generic disassembler is ready in minutes and never needs gcc 13; B(l)utter may spend a long time compiling a Dart runtime before it prints anything, and it only covers arm64 Android. If your question is about native library loading or a bundled C++ dependency, a disassembler is the better tool. If your question is what the Dart code does, the snapshot is opaque without something like B(l)utter.

Maintenance, rebuild cost and the MIT licence

The repository is not archived and the last push was on 2026-08-18, so recent work exists, but there are no retrieved releases. That matters for how you consume it: there is no versioned artifact to pin, so you take the main branch and the bin/ executables as they come. Upgrading is git pull plus a rebuild, and the README's own update instructions use --rebuild to force it. Budget for the build, not just the clone. A missing executable means checking out Dart source and compiling it, and the README notes that build/ and dartsdk/ can be deleted afterwards, which tells you how much disk those steps consume. The licence is MIT, which permits commercial and closed-source use and modification, but it comes with no warranty. If you redistribute a modified B(l)utter, MIT's terms about carrying the licence and copyright notice apply. This is a description of the licence text, not legal advice; check the LICENSE file and your own counsel for your situation.

Editorial conclusion

Use B(l)utter if you are reverse engineering an arm64 Android Flutter app built on a recent Dart version and you are comfortable with a C++20 toolchain, because the first run compiles a Dart runtime against your target's version. Skip it for iOS binaries, ipa or apk input, and obfuscated apps, all of which the README lists as unfinished. Before relying on it, check which Dart version your target embeds, confirm a prebuilt executable for that version exists in bin, and read TODO to see whether the output you need is on the missing list.

Frequently asked questions

What is B(l)utter and what is it for?

B(l)utter is a Flutter mobile application reverse engineering tool that works by compiling a Dart AOT runtime. It reads an Android libapp.so and produces symbolised assemblies, an object pool dump and a Frida script template.

What does the name blutter mean?

The README gives the project title as "B(l)utter", with the letters b and l set apart in parentheses, which points at the libapp.so file the tool reads. It does not state a separate meaning beyond that styling.

Which platforms and targets does B(l)utter support?

The README states it currently supports only Android libapp.so, arm64 only, and works only against recent Dart versions. Reading iOS binaries is listed as a missing feature in the TODO.

What files does B(l)utter produce in the output directory?

The README lists asm/ with libapp assemblies and symbols, blutter_frida.js as a Frida script template, objs.txt as a complete nested dump of objects from the object pool, and pp.txt with all Dart objects in the object pool.

What compiler does B(l)utter need to build?

The README says the C++20 formatting library is used, so it requires a recent compiler such as g++ 13 or newer, or Clang 16 or newer. On Debian it warns that you must use a release whose own main repository provides gcc 13.

How do I update B(l)utter to a newer version?

The README's update section says to use git pull and then run blutter.py with the --rebuild option to force rebuilding the executable.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. worawit/blutter on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/worawit-blutter.svg)](https://hysenlabs.com/projects/worawit-blutter)