Reverse Engineering Flutter apps: NullBreaker CTF
Flutter is Google’s framework for building natively-compiled applications for mobile, web and desktop from a single code base. It follows a “write once, run anywhere” design, so you could basically write the app once in dart, and use flutter to run it anywhere.
Dart is also created by Google, It is OOP language and has a syntax similar like java. Dart could compile in both Ahead Of Time (AOT) to native code like C/C++ and also to proprietary byte-code, to be later executed by some Just-In-Time(JIT) compiler like Java or JS.
We provided a Debug-mode apk, which has a button and a counter which increases it’s value as we tap it.
We can’t get the app source by just extracting like java compiled APKs. Flutter has this cool feature called “hot-reloading” which enables developers to change code in runtime, and only the new code that has been changed will be updated in the app, without the need to compile it again. DartVM takes care of all this work.
Hence, in debug mode apks, we can find the source code of the app including the comments made. All of the app code is present in the
/assets/flutter_assets/ directory of the decompressed APK.
Use apktool to extract the components of the apk.
java -jar .\Tools\apktool_2.4.1.jar d .\taptap-debug.apk
Using strings you could extract the Dart code, but you’ll need to find the relevant code from the garbage strings.
Try to search strings like
main() and other keywords which we already know from the APK.
strings kernel_blob.bin | grep "Sup?" -m1 -B20 -A25
Here we could find the main class
MyHomePage and the
_incrementCounter() function which is called everytime the button is clicked. On looking closer at the
_incrementCounter(), we could find an array named “secret” with some gibberish characters, followed by a little crypto to decode the flag. It’s only executed if the value of
_counter goes above 850.
As we know the count of taps it takes to print the flag, we could possibly sent touch-events using ADB, if we’re too lazy to reverse the crypto. :D
We could use ADB to map out the counter button coordinate using the
And then send touch events using
adb input tap, 850 times.
But since this is a very time consuming task, we will reverse the encoding on the flag.
On looking closely on the decoding function, if the
_counter goes above 850, it takes each element from the array and convert it into it’s respective ASCII code, add 1200 to it and performs a bitwise XOR with the whole number. It is then converted back to character and concatenated with the _flag variable.
secret = ['\xa7', '\xab', '\xca', '\xbd', '\xcf', '\x92', ']', '\xa7', '\xcd', '\x9b', 'Y', '\xa0', '\xcd', '\xb6', '\xaf', 'X', '\x9c', '^', '^', 'Z', '\x9b', '\xb6', '\x9b', 'Z', '\x9f', 'Z', '\x9b', '\xba', 'X', '\xa7', '\xce', '\x94']
And we get the flag.
App source code: main.dart
will be changed once the challenge repo goes public.