怎么老是你flutter
最近碰到这个挺多次的了,就想着弄懂一下😋
简介
Flutter 是谷歌的移动 UI 框架,建构在Dart VM之上,使用Dart语言开发的移动应用开发框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。
现在很多app都是用的该框架构建,而该原生框架是建立在app的native层,所以逆向时需要解包。
Dart VM
Dart VM 是一个虚拟机,它为高级编程语言提供了执行环境,但它并不意味着在 Dart VM 上执行。它既能JIT即时编译、也能 AOT 提前编译。
在Dart VM下,AOT将源码转换成机器码打包进libapp.so,AOT 提前跑完后半段并把结果序列化成快照。
特点
无法常规抓包
- Dart语言网络请求不通过代理发送
有的抓包软件是作为中间代理,拦截网关从而获得流量包,而因为Dart语言的这个特点使得app无法正常抓包。
Flutter 的 HTTP 实现默认不读取 Android/iOS 的系统代理设置,流量直接走原始 Socket。
- 自带SSL校验
Flutter 引擎把 Google 的 BoringSSL 静态编译进 libflutter.so
,校验逻辑 在 ssl_crypto_x509_session_verify_cert_chain()
等函数里完成。
它只信任预制在代码或系统目录里的特定公钥/指纹,用户安装的 Burp/Charles 根证书即使放进系统受信区也照样被拒绝。
无法正常反编译
如果按照正常的套路把apk拖到jadx里面会发现MainActivity里什么也没有
- Dart 代码被 AOT 编译成原生机器码,而不是常见的字节码
flutter在release模式下直接把Dart编译成ARM/x86机器码存放到libapp.so中。
- 代码的形态以Dart快照形式存在
辨别
怎么看它是不是flutter框架呢?
熟悉安卓的朋友都知道, assets文件是资源
当你解包打开lib会发现有这两个so文件,有这里两个so文件,很容易辨别
在发布版本的apk文件里面 libapp.so 保存的是快照,libflutter.so则是引擎,其中包括一个dart的虚拟机。
对抗
抓包
《安卓逆向这档事》番外实战篇3-拨云见日之浅谈Flutter逆向 - 吾爱破解 - 52pojie.cn
【Flutter逆向】记一次某灰产APP(Sm**th)的逆向学习-CSDN博客
前两个方法都比较麻烦,我都理解了一下但没有试过。
hook函数过证书校验
找so文件里找可以绕过ssl验证的函数,用脚本hook,使函数返回true
1 | function hook_dlopen() { |
reflutter
这个还挺麻烦的,还有手动签名什么的
Reqable或者proxyPin
这里我下载了Reqable,只选了独立模式
这里的证书配置的话,如果有MT那很容易,记得把证书文件权限改为777就可以直接使用了
反编译
libapp.so文件里面不止包含了AOT代码,还有Dart快照、Dart VM中的一些信息。
下面我随便拿了一个文件,试了一下readelf -s 命令
_kDartVmSnapshotData
: 代表 isolate 之间共享的 Dart 堆 (heap) 的初始状态。有助于更快地启动 Dart isolate,但不包含任何 isolate 专属的信息。_kDartVmSnapshotInstructions
:包含 VM 中所有 Dart isolate 之间共享的通用例程的 AOT 指令。这种快照的体积通常非常小,并且大多会包含程序桩 (stub)。_kDartIsolateSnapshotData
:代表 Dart 堆的初始状态,并包含 isolate 专属的信息。_kDartIsolateSnapshotInstructions
:所有 Dart 业务代码编译后的 ARM64 机器码。_kDartSnapshotBuildId
: 快照格式版本+Git Hash,调试/校验用
_kDartIsolateSnapshotInstructions即为我们需要重视的
由于Dart语言不断的发展,不同版本的Dart引擎其快照格式不同,不论是动态还是静态工具都在更迭。
动态逆向
reflutter
Impact-I/reFlutter: Flutter Reverse Engineering Framework
利用reflutter等工具编译修改过的libflutter.so并且重新打包到APK中,在启动APP的过程中,由修改过的引擎动态链接库将快照数据获取并且保存。
如果对应的Flutter版本reFlutter
还未更新那就只能自己尝试Patch。、
自己patch
教程在下面这个链接里
Android-Flutter逆向 | LLeaves Blog
- snapshotHash
我们在libapp.so文件里可以看到关于快照版本的hash,但是具体是多少位看的好麻烦,直接用reflutter里面的脚本运行出来
1 | python .\get_snapshot_hash.py D:\test\xxx\lib\arm64-v8a\libapp.so |
- Engine_commit
这个Engine_commit则在libflutter.so中找这个我找的好费劲还没找到::>_<::
有了这两个值之后,在下面的表格里就可以找到flutter和dart的版本
reFlutter/scripts/enginehash.tmp.csv at main · Impact-I/reFlutter
蒽,后面的编译好麻烦不是我现在的重点,先溜了。。。
工具
1 | pip3 install reflutter |
reflutter工具实践之–xx一番赏app_reflutter教程-CSDN博客
得到release.RE.apk之后是要对齐和签名,但是我找的的两个题这两条路都不太通,主要原因是reflutter目前覆盖到3.13左右,我的题是3.19的要手动编译即上面那种方法,等哪天实践成功了回来补一下。
经过最后一条adb的命令就可以还原dart代码了,然后接下来就能分析了
flutter逆向助手
flutter逆向助手是reflutter的上位替代,省去了重新打包的麻烦,使用起来简单很多。直接选择文件之后就能得到反编译的文件。
[原创]flutter逆向助手-基于reflutter的简易化dart解析工具-Android安全-看雪论坛-安全社区|非营利性质技术交流社区
但是它依然没有解决我的问题,因为支持的版本依旧很低〒▽〒
reflutter和flutter逆向助手停止更新,flutterSDK过高,两个工具都无法破解,所以又有一个工具blutter,这是一个静态的
静态逆向
blutter的配置
1 | git clone https://github.com/worawit/blutter --depth=1 |
其实主要的命令是这几个,还需要ninja环境
使用最后一个命令的时候,要在x64 Native Tools Command Prompt中运行才能编译
最后文件会放在output中
asm 对dart语言的反编译结果,里面有很多dart源代码的对应偏移
ida_script so文件的符号表还原脚本
blutter_frida.js目标应用程序的 frida 脚本模板
objs.txt对象池中对象的完整(嵌套)转储,对象池里面的方法和相应的偏移量
pp.txt对象池中的所有 Dart 对象
嗯还有一个点是,生成的addNames.py这个脚本,好像只能在ida7.x或者ida8.x下成功,但是相对于动态的两种,这已经能解决ctf中的绝大部分题目了。
练习
2025 Nepctf
这道题,没办法正常解出来,报错了
ai说是libapp.so 里找不到符号 _kDartVmSnapshotData,里面是Dart 的一些东西。
ida打开在字符串里面意外发现了 _kDartVmSnapshotData,因为到0x346的时候突然出现了一堆0,所以报错了
1 | def extract_snapshot_hash_flags(libapp_file): |
可以看到extract_dart_info里面的源码,是连续读取的我们修改一下改成从0x354开始读,然后继续尝试一下
蒽依旧报错这次是因为**.rodata
段**没找到,分析一下so
看了一下正常的.rodata、.text、.bss段都被删去了,嘶难搞再跑到源码那分析了一下找.rodata 段的代码,代码的作用是找flutter的版本信息和Dart SDK的版本号
出问题的地方应该是这边
修一下,经过好久的编译错误终于成功了,在官方wp里面找到了ida9还原函数名的方法
1 | python blutter.py --dart-version 3.8.1_android_arm64 D:\test\nep\flutterpro\flutterPro\lib\arm64-v8a\libapp.so .\output |
在output文件夹里面asm/flutterpro/main.dart可以看到有两个回调函数其中ontap点击事件在ida里面分析,找到主要事务函数分析
不断ai
大概就是指这一部分构成一个8*8的样子,构成了之后应该会有一点操作,点进这部分下面的一个sub_203624函数继续分析,一点一点分析实在是太慢了,然后就想hook去看看,结果很久很久一直不成功,后来在一个文章里面看到要用真机😊
1 | while ( 1 ) |
在这个函数里面,可以发现是有矩阵在相乘的
我看 不论是官方的还是其它大师的wp都是hook出了矩阵的key,在blutter给的frida脚本上修改
1 |
|
所以第一次加密这边是一个矩阵相乘
再往下分析一下
创建另一个DartObjectPool的结构体,然把v6修改成池子,变成了这样
点进去就会发现又一长串的字符串,可能是密文
从相乘后往下可以发现还有一个sub_2030D8函数,点进去里面也有很多类似于v6的东西,会发现里面有写莫名其妙的字符串和密文差不多
所以可能逻辑就是
input -> len -> 矩阵相乘 -> RCNB -> compare
1 | import rcnb |
1 | from z3 import Solver, Int, sat |
2025 WMCTF
bultter一下,还原符号
额好难分析我要死掉了,等我慢慢分析完整在写上来