想要分析 Flutter App,首先需要将函数逆向回来,这里介绍两种常见的方法,分别使用不同的开源工具,这里以 gskinner 的 demo 为例:
https://github.com/gskinnerTeam/flutter-wonderous-app
https://github.com/Impact-I/reFlutter
reflutter 的原理是重打包 apk,patch libapp.so,在运行时将 dart 函数信息打印出来,生成 dump.dart,同时注入代理代码,使 flutter 使用我们自己指定的代理,从而可以抓包。
使用方法很简单,pip 安装即可 pip install reflutter
安装成功后执行 reflutter <apk>
选择 2,输入自己代理服务器的地址,reflutter 注入的默认端口是 8083,使用抓包工具需要手动改端口。
reflutter 会自动识别 libapp 的快照版本,重打包 apk
完成后得到的是未签名的 apk,需要自己进行对齐和签名,这里需要注意必须 debug 签名才能生成 dump.dart,可以先用 debug 签运行,生成 dump.dart 后再用 apk 本身的签名签回去(防止部分功能有签名校验),这样就可以正常使用了。
官方推荐使用 uber-apk-signer 同时实现对齐 & 签名 https://github.com/patrickfav/uber-apk-signer
java -jar uber-apk-signer.jar --allowResign -a release.RE.apk
dump.dart 会生成在 app 的私有目录,虽然 reflutter 会授权 777,但因为上级目录没有权限,没办法直接 pull 出来,这里可借助模拟器 root 后再 adb pull
/data/data/com.gskinner.flutter.wonders/dump.dart,或者利用 su 获取 root shell 后 cat /data/data/com.gskinner.flutter.wonders/dump.dart > /sdcard/dump.dart 再 pull
这个文件本质可以当成 json 来处理,但是缺少了逗号,可以将 "}" 替换成 "}, ",头尾再加上 [],方便编辑器解析。
这里面是 dart 的函数信息,还有偏移地址,其中基址可以通过 readelf 得到:
_kDartIsolateSnapshotInstructions + offset 就是对应 IDA 中的函数地址,可以直接用 frida 去 hook,reflutter 提供了 frida.js 可以直接使用。
虽然 reflutter 提供了函数信息,但对于实际分析来讲,很难分析出函数的功能,只能靠函数名去推测或者结合 IDA 来分析。
https://github.com/worawit/blutter
这个工具更简单粗暴一点,但是没有代理绕过注入
建议使用 linux 系统进行操作,其中涉及到编译环节,需要安装 g++ 13 以上的版本
blutter 的输入是 apk 解压后 so 目录,安装完依赖后直接执行
python3 blutter.py arm64-v8a out
blutter 会自动根据版本下载依赖和工具,这里等待时间比较长,完成后会有以下产物:
其中 asm 就是逆向出来的汇编代码,bluter_frida.js 可以直接使用 hook 对应函数。
ida_script 中有头文件和脚本,导入头文件后执行脚本可以还原 ida 中的函数名,其中 addNames.py 部分函数名有 “#”,需要手动替换成别的字符。
这时再去 IDA 分析就稍微明朗一点,至少有函数名,但其实还是不建议直接看 IDA,建议看 asm 再配合 hook,分析起来简单点。
在分析 asm 中的反汇编代码时,当遇到匿名闭包时(这里的举例是
wonders/logic/collectibles_logic.dart),具体的逻辑可以根据地址在同一个 dart 文件找到,blutter 的产物中还有一个 pp.txt 文件,这里可以根据地址找到对应的对象,比如下图的相加操作是 0x17 lsl #12,代表 0x17 左移 12 位,对 16 进制相当于移 3 位,即 0x17000,下面的 ldr 是从内存中加载数据到寄存器,ldr 0xda8 即从 0x17000 开始再偏移 0xda8,即 0x17da8,在 pp.txt 中根据这个地址就能找到这个函数,对应的地址是 0x58a1fc
利用这个方法,可以得出逆向大部分对象的实际内容,例如字符串、函数等。
前面提到 flutter 默认走自己的代理,不会走系统代理,所以 WiFi 配置代理也抓不到 flutter 发出的流量,这里有两种方案:
用 reflutter,由于 patch 的时候已经注入了代理,直接正常运行就能抓包了;
用 vpn,全局代理,这样 vpn 在 flutter 的上层,可以感知到流量,但是需要进行一些绕过。
首先由于 flutter 本身有证书校验的逻辑,用 vpn 相当于普通的配置代理,flutter 有自己的证书信任列表,我们还需要绕过证书校验
flutter 使用的是 boringssl 库,我们需要找到这个库证书校验的代码,这里先打开 github 仓库 https://github.com/google/boringssl
ssl 校验的代码在 ssl/ssl_x509.cc 函数, ssl_crypto_x509_session_verify_cert_chain 里,返回值就是校验是否通过。
我们需要在 libflutter.so 找到编译后的函数,这里可以搜索字符串 ssl_client 来定位。
点进去看,这个逻辑和源码逻辑是一样的,所以 5DC570 就是函数地址。
用 frida hook 这个函数,直接把返回值改成 true 就能绕过证书校验。
这时候就能抓到所有的包了,UA 是 dart 的就是用 flutter 发出的请求。
总结
Flutter 目前已经成为主流的多平台开发框架,今年也将重登 Google I/O 大会,相信以后选择使用 Flutter 开发的应用会越来越多。区别于传统开发模式,Flutter 开发出来的 App 逆向分析难度更大,恶意行为也更难被发现,实际分析起来还是需要多方面结合。