首页
社区
课程
招聘
[原创]Flutter APP逆向实践-初级篇
发表于: 2022-7-4 22:11 77946

[原创]Flutter APP逆向实践-初级篇

2022-7-4 22:11
77946

很长一段时间对于Flutter的app逆向都比较头疼,它不像纯Java app那样可以使用jadx-gui看源码,也不能像原生native那样可以用ida看字符串交叉引用。

分析flutter的时候,没有交叉引用,那真是想它他拖进回收站。

最近又遇到了几个flutter开发的app,对它进行了一些研究,总结了以下的分析流程。

本文以iOS app为例子作为讲解,Android 的flutter app和iOS 的flutter app分析方法类似。

抓包,使用frida-ios-dump进行砸壳后,得到目标app的ipa文件,reFlutter对ipa重打包后得到release.RE.ipa,使用ios-app-signer对release.RE.ipa进行自签名,安装到手机上并运行,得到dump.dart文件,根据dump.dart里的类名、函数名、函数相对_kDartIsolateSnapshotInstructions的偏移,配合ida静态分析+frida动态分析,分析出加密算法。

python dump.py app名

打开ipa能看到Frameworks目录下有App.framework, Flutter.framework两个框架,表示这个app是flutter开发的。

reflutter XXXX.ipa

如果没有iOS付费开发者账号,使用免费开发者账号的话,需要先使用xcode新建一个demo工程,并运行在iOS上,然后才能使用ios-app-signer重签名

xcode -> Window -> Devices and Simulators -> INSTALLED APPS,有个+号,可以把上一步重签名的ipa安装到手机上

Devices and Simulators 界面有个Open Console按钮,可以打开控制台,查看dump.dart的路径

reFlutter dump file: /private/var/mobile/Containers/Data/Application/F4F2810A-C863-4732-B871-480BFD1C101B/Documents/dump.dart

使用scp命令把dump.dart 拷贝到本地,可以看到里面包含了类名,函数名,相对_kDartIsolateSnapshotInstructions的偏移

使用ida加载App文件,看Exports窗口,有如下几个导出符号,而且运气比较好,很多函数都有符号,Precompiled_xxx

去dump.dart搜索signsafe字符串,没有搜索到

根据前面抓包signsafe的内容10a7d81a264e79640ce3433f1b03d992,这是一个32位的字符串,猜的可能是md5相关的算法

于是去dump.dart里搜索md5字符串, 搜索到3处md5相关的类

对三个md5相关的函数,进行hook,

手机上操作一下,可以看到updateHash被调用

去ida中看看PrecompiledapiSign_7813函数

对上图画红框的5个函数进行hook

得到以下日志,省略了部分无关的日志,以......代替

由于结果有比较明显特征,从日志可以猜出大概算法,有md5(32位字符串或16个字节), base64(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/= 结果在这个字符串里), hmac_sha1(40位字符串或20字节)

分析以上的例子,因为运气好,dump.dart里面有很多业务相关的函数符号,所以降低了很大的难度。

对抗以上的逆向方法也很简单,flutter官方就有解决方案。

加上 --obfuscate --split-debug-info两个参数就能抹去这些类名和函数名

https://github.com/AloneMonkey/frida-ios-dump

https://github.com/Impact-I/reFlutter

https://github.com/DanTheMan827/ios-app-signer

https://frida.re

 
 
 
 
 
 
 
 
 
 
 
 
 
 
Library:'package:pointycastle/digests/md5.dart' Class: MD5Digest extends MD4FamilyDigest implements Type: Digest {
  FactoryConfig factoryConfig = sentinel ;
  Function 'get:algorithmName': getter const. String: null {
               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000cc8a1c
       }
  Function 'get:digestSize': getter const. String: null {
               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000ccd5f4
       }
  Function 'MD5Digest.': constructor. String: null {
               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000002828
       }
  Function 'resetState':. String: null {
               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000cc4d98
       }
  Function 'processBlock':. String: null {
               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000ce4ea0
       }
      }
Library:'package:pointycastle/digests/md5.dart' Class: MD5Digest extends MD4FamilyDigest implements Type: Digest {
  FactoryConfig factoryConfig = sentinel ;
  Function 'get:algorithmName': getter const. String: null {
               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000cc8a1c
       }
  Function 'get:digestSize': getter const. String: null {
               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000ccd5f4
       }
  Function 'MD5Digest.': constructor. String: null {
               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000002828
       }
  Function 'resetState':. String: null {
               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000cc4d98
       }
  Function 'processBlock':. String: null {
               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000ce4ea0
       }
      }
Library:'package:crypto/src/md5.dart' Class: _MD5Sink@404143612 extends HashSink {
  Function '_MD5Sink@404143612.': constructor. String: null {
               Code Offset: _kDartIsolateSnapshotInstructions + 0x000000000026309c
       }
  Function 'updateHash':. String: null {
               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000c649f8
       }
      }
Library:'package:crypto/src/md5.dart' Class: _MD5Sink@404143612 extends HashSink {
  Function '_MD5Sink@404143612.': constructor. String: null {
               Code Offset: _kDartIsolateSnapshotInstructions + 0x000000000026309c
       }
  Function 'updateHash':. String: null {
               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000c649f8
       }
      }
Library:'package:crypto/src/md5.dart' Class: _MD5@404143612 extends Hash {
  Function 'startChunkedConversion':. String: null {
               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000c4f2d4
       }
      }
Library:'package:crypto/src/md5.dart' Class: _MD5@404143612 extends Hash {
  Function 'startChunkedConversion':. String: null {
               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000c4f2d4
       }
      }
function print_native_stack(addr, context) {
    return ('\r\n[' + addr + '] called from:\n' +
        Thread.backtrace(context.context, Backtracer.ACCURATE)
            .map(DebugSymbol.fromAddress).join('\n') + '\n');
}
function hook_md5() {
    let kDartIsolateSnapshotInstructions = Module.findExportByName("App", "kDartIsolateSnapshotInstructions")
    console.log(kDartIsolateSnapshotInstructions);
    let processBlock = kDartIsolateSnapshotInstructions.add(0x0000000000ce4ea0);
    let updateHash = kDartIsolateSnapshotInstructions.add(0x0000000000c649f8);
    let startChunkedConversion = kDartIsolateSnapshotInstructions.add(0x0000000000c4f2d4);
    Interceptor.attach(processBlock, {
        onEnter(args) {
            console.log("processBlock:", print_native_stack("processBlock", this));
        }
    })
    Interceptor.attach(updateHash, {
        onEnter(args) {
            console.log("updateHash:", print_native_stack("updateHash", this));
        }
    })
    Interceptor.attach(startChunkedConversion, {
        onEnter(args) {
            console.log("startChunkedConversion:", print_native_stack("startChunkedConversion", this));
        }
    })
}
function print_native_stack(addr, context) {
    return ('\r\n[' + addr + '] called from:\n' +
        Thread.backtrace(context.context, Backtracer.ACCURATE)
            .map(DebugSymbol.fromAddress).join('\n') + '\n');
}
function hook_md5() {
    let kDartIsolateSnapshotInstructions = Module.findExportByName("App", "kDartIsolateSnapshotInstructions")
    console.log(kDartIsolateSnapshotInstructions);
    let processBlock = kDartIsolateSnapshotInstructions.add(0x0000000000ce4ea0);
    let updateHash = kDartIsolateSnapshotInstructions.add(0x0000000000c649f8);
    let startChunkedConversion = kDartIsolateSnapshotInstructions.add(0x0000000000c4f2d4);
    Interceptor.attach(processBlock, {
        onEnter(args) {
            console.log("processBlock:", print_native_stack("processBlock", this));
        }
    })
    Interceptor.attach(updateHash, {
        onEnter(args) {
            console.log("updateHash:", print_native_stack("updateHash", this));
        }
    })
    Interceptor.attach(startChunkedConversion, {

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 16
支持
分享
打赏 + 70.00雪花
打赏次数 2 雪花 + 70.00
 
赞赏  Editor   +50.00 2022/07/22 恭喜您获得“雪花”奖励,安全圈有你而精彩!
赞赏  珍惜Any   +20.00 2022/07/04 Yang神牛逼
最新回复 (19)
雪    币: 19
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
大佬kDartIsolateSnapshotInstructions这个的地址怎么得到呀
2022-7-4 22:18
0
雪    币: 6003
活跃值: (3490)
能力值: ( LV6,RANK:96 )
在线值:
发帖
回帖
粉丝
3
mb_oowzftna 大佬kDartIsolateSnapshotInstructions这个的地址怎么得到呀
let kDartIsolateSnapshotInstructions = Module.findExportByName("App", "kDartIsolateSnapshotInstructions")
2022-7-4 22:19
0
雪    币: 6003
活跃值: (3490)
能力值: ( LV6,RANK:96 )
在线值:
发帖
回帖
粉丝
4
mb_oowzftna 大佬kDartIsolateSnapshotInstructions这个的地址怎么得到呀
ida也可以查看kDartIsolateSnapshotInstructions的偏移
2022-7-4 22:20
0
雪    币: 3366
活跃值: (14033)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
5
火钳刘明
2022-7-4 22:20
0
雪    币: 212
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
mb_oowzftna 大佬kDartIsolateSnapshotInstructions这个的地址怎么得到呀
也可以用 readelf -Ws libapp.so
来查看
2022-7-4 22:32
0
雪    币: 593
活跃值: (4562)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
7
占个楼
2022-7-4 23:02
0
雪    币: 4116
活跃值: (1034)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
8
强啊
2022-7-5 00:15
0
雪    币: 576
活跃值: (2035)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
mark,感谢分享
2022-7-5 01:52
0
雪    币: 14846
活跃值: (6078)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
没看到你这个分析同Flutter有什么关系?唯一的关联是这个app是用Flutter开发的?
2022-7-5 08:20
1
雪    币: 846
活跃值: (4010)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
11
yang神乱杀
2022-7-5 11:00
0
雪    币: 2155
活跃值: (4532)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
reFlutter 这个工具目前看还行,长远来看不太可靠。我已经遇到过没有version版本标志位的fultter so,后面估计还有更多安全变种。reFlutter作者每次更新就加个适配dart vm的版本号,不知道作者怎么看。
2022-7-5 11:57
0
雪    币: 8764
活跃值: (5718)
能力值: ( LV13,RANK:296 )
在线值:
发帖
回帖
粉丝
13
根据SnapshotHashString辨别dart version并不可靠,开发者可以修改dart源码实现自定义SnapshotHashString
2022-7-5 12:21
0
雪    币: 298
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
sunfishi 根据SnapshotHashString辨别dart version并不可靠,开发者可以修改dart源码实现自定义SnapshotHashString
mason大佬牛b
2022-7-5 13:20
0
雪    币: 0
活跃值: (353)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
[frida-ios-dump]: HyphenateChat.framework has been loaded. 
[frida-ios-dump]: Flutter.framework has been loaded. 
[frida-ios-dump]: QYSDK.framework has been loaded. 
[frida-ios-dump]: NIMSDK.framework has been loaded. 
[frida-ios-dump]: App.framework has been loaded. 
;;;;;;;;
No such file or directory: '/var/folders/d2/hrwxg_6131jf74x96gyjlv7w0000gn/T/Payload/App.fid'

我这个fultter app dump 报错上面的这个,求解,谢谢
2022-7-13 15:45
0
雪    币: 2270
活跃值: (5537)
能力值: ( LV8,RANK:146 )
在线值:
发帖
回帖
粉丝
16
Yang神中级篇和高级篇什么时候更新
2022-7-14 11:48
0
雪    币: 227
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
17
大佬这个APP有木有样本地址
2022-7-21 18:22
0
雪    币: 5680
活跃值: (3522)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
要是apk的话怎么得到kDartIsolateSnapshotInstructions呢
2022-11-16 14:15
0
雪    币: 3161
活跃值: (5141)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
20
有符号的话还能处理一下,抹去类名和函数名之后完全是在大海捞针
2024-1-20 10:33
0
游客
登录 | 注册 方可回帖
返回
//