背景
由于Flutter本身开发标准一致,降低了RD人员适配多端的工作量,且本身具有很好的安全性,因此越来越多的应用尝试使用Flutter。而逆向Flutter应用却不像Java一样简单,常用的IDA、JEB等工具完全失效。
本文主要通过一个实际例子,讲解逆向分析Flutter的具体步骤和方法论
逆向步骤简述
- 确定APP是否为Flutter应用
- Android:
lib/xxx/libapp.so
lib/xxx/libflutter.so
- IOS:
Payload/Frameworks/Flutter.framework
- Android:
- 获取逆向关键信息
- 将libapp.so或app等相关Flutter代码拖入IDA进行分析
- [可选] 重命名函数(混淆时使用)
- 获取
_kDartIsolateSnapshotInstructions
偏移 - 利用frida动静结合进行算法分析
例子
以某🐟为例子
- 确认是否为Flutter应用
- reFlutter
- 安装
pip3 install reflutter
- 使用
reflutter example.apk
/reflutter example.ipa
- 重签名后在真机上安装并运行应用
- 获取
- Android
adb -d shell "cat /data/data/<PACKAGE_NAME>/dump.dart" > dump.dart
- IOS
scp /private/var/mobile/Containers/Data/Application/<UUID>/dump.dart .
- Android
Library:'package:anyapp/navigation/DeepLinkImpl.dart' Class: Navigation extends Object {
String* DeepUrl = anyapp://evil.com/ ;
Function 'Navigation.': constructor. (dynamic, dynamic, dynamic, dynamic) => NavigationInteractor {
Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000009270
}
Library:'package:anyapp/auth/navigation/AuthAccount.dart' Class: AuthAccount extends Account {
PlainNotificationToken* _instance = sentinel;
Function 'getAuthToken':. (dynamic, dynamic, dynamic, dynamic) => Future<AccessToken*>* {
Code Offset: _kDartIsolateSnapshotInstructions + 0x00000000003ee548
}
- IDA分析
- IDA运行以下脚本,重命名函数 [遇到混淆时使用]
import sys, os, json
import idaapi, ida_kernwin
try:
import flure
except ImportError:
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
idaapi.require("flure.code_info")
idaapi.require("flure.ida.patch_names")
from flure.code_info import CodeInfo
from flure.ida.patch_names import create_ida_folders
if __name__ == "__main__":
function_info_file = ida_kernwin.ask_file(False, f"*.json", "Flutter snapshot function name filename")
if function_info_file is not None:
with open(function_info_file, 'r') as fp:
code_info = CodeInfo.load(json.load(fp))
create_ida_folders(code_info)
如果混淆相对严重,也可使用BinDiff或者Diaphora进行新旧版本比对
- 获取
_kDartIsolateSnapshotInstructions
值readelf -Ws libapp.so
let kDartIsolateSnapshotInstructions = Module.findExportByName("Flutter", "kDartIsolateSnapshotInstructions")
- Frida分析
/**
* 利用第二步dump出来的信息,查找关键的函数偏移地址之后进行HOOK,并追踪调用链路
**/
function hook_flutter_function() {
let kDartIsolateSnapshotInstructions = Module.findExportByName("App", "kDartIsolateSnapshotInstructions")
let sign = kDartIsolateSnapshotInstructions.add(0x0000000000xxxxx); //从dump.dart中获取对应地址
Interceptor.attach(sign, {
onEnter(args) {
console.log("sign:", Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n'));
}
})
}
利用上述Frida打印的调用链路,进入IDA进行分析,获取对应的函数地址并进行下一步Hook分析
/**
* Precompiled_xxxx_xxxx 由IDA解析获取,前提条件是函数名没有混淆
**/
function hook_flutter_function_ida() {
let flutter_fun = DebugSymbol.fromName("Precompiled_xxxx_xxxx")
Interceptor.attach(flutter_fun.address, {
onEnter(args) {
this.log = []
this.log.push(flutter_fun.name + " onEnter:\r\n")
for(let i = 0; i < 8; i++) {
try {
this.log.push(hexdump(args[i]), "\r\n");
} catch (error) {
this.log.push((args[i]), "\r\n");
}
}
}, onLeave(retval) {
this.log.push(flutter_fun.name + " onLeave:\r\n")
try {
this.log.push(hexdump(retval), "\r\n");
} catch (error) {
this.log.push((retval), "\r\n");
}
this.log.push("=======================")
console.log(this.log);
}
})
}
参考
The Current State & Future of Reversing Flutter™ Apps
Guardsquare/flutter-re-demo