Reverse Engineering Flutter apps: NullBreaker CTF
We have a conducted a CTF recently - NullBreaker CTF in association with eHackify and YAS, and in this post, I’ll be discussing about reverse engineering one of the android challenge, TapTap.
Files:
Debug mode apk
arm64-v8 release
armeabi-v7a release
Flutter / Dart
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.
Challenge
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 kernel_blob.bin
in /assets/flutter_assets/
directory of the decompressed APK.
Use apktool to extract the components of the apk.
1 | 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 Sup
, NullBreaker CTF
, main()
and other keywords which we already know from the APK.
1 | 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 setState()
of _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 getevent
command.
And then send touch events using adb input tap
, 850 times.
1 | import os |
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.
Python implementation:
1 | 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.
nbCTF{4nDr0iD_f1u773r_r3v3rS1nG}
App source code: main.dart
will be changed once the challenge repo goes public.