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

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

2022-7-4 22:11
74000

Flutter APP逆向实践-初级篇

0. 前言

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

 

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

 

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

 

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

1. 简述流程

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

2. 前期准备

2.1 抓包,确定需要分析的signsafe算法

2.2 frida-ios-dump砸壳

python dump.py app名

 

 

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

2.3 reFlutter重打包

reflutter XXXX.ipa

 

2.4 ios-app-signer 重签名

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

 

2.5 xcode安装重打包的ipa

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

2.6 查看dump.dart日志

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的偏移

 

2.7 ida加载App.framework下的App文件

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

 

3. signsafe详细分析流程

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

 

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

 

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
       }
      }
1
2
3
4
5
6
7
8
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
       }
      }
1
2
3
4
5
Library:'package:crypto/src/md5.dart' Class: _MD5@404143612 extends Hash {
  Function 'startChunkedConversion':. String: null {
               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000c4f2d4
       }
      }

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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));
        }
    })
}

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

 

 

去ida中看看PrecompiledapiSign_7813函数

 

 

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function hook_addr(addr, name) {
    Interceptor.attach(addr, {
        onEnter(args) {
            this.log = []
            this.log.push(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(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);
        }
    })
}
 
function hook_apisign() {
    let Precompiled_Hmac_Hmac__7814 = DebugSymbol.fromName("Precompiled_Hmac_Hmac__7814")
    let Precompiled_Hmac_convert_37042 = DebugSymbol.fromName("Precompiled_Hmac_convert_37042")
    let Precompiled____base64Encode_5267 = DebugSymbol.fromName("Precompiled____base64Encode_5267")
    let Precompiled_Hash_convert_37041 = DebugSymbol.fromName("Precompiled_Hash_convert_37041")
    let Precompiled_Digest_toString_34431 = DebugSymbol.fromName("Precompiled_Digest_toString_34431")
    hook_addr(Precompiled_Hmac_Hmac__7814.address, Precompiled_Hmac_Hmac__7814.name);
    hook_addr(Precompiled_Hmac_convert_37042.address, Precompiled_Hmac_convert_37042.name);
    hook_addr(Precompiled____base64Encode_5267.address, Precompiled____base64Encode_5267.name);
    hook_addr(Precompiled_Hash_convert_37041.address, Precompiled_Hash_convert_37041.name);
    hook_addr(Precompiled_Digest_toString_34431.address, Precompiled_Digest_toString_34431.name);
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Precompiled_Hmac_Hmac__7814 onEnter:
,            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
10c0afba9  03 6d 00 00 00 00 00 c0 fb 0a 0c 01 00 00 00 00  .m..............
10c0afbb9  00 00 00 14 00 00 00 44 32 33 41 42 43 40 23 35  .......D23ABC@#5
10c0afbc9  36 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04  6...............
 
。。。。。。
 
Precompiled_Hmac_convert_37042 onEnter:
,            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
10c0afd79  07 6d 00 00 00 00 00 90 fd 0a 0c 01 00 00 00 00  .m..............
10c0afd89  00 00 00 9a 00 00 00 61 70 69 2e 78 78 78 2e 63  .......api.xxx.c
10c0afd99  6e 2f 78 78 78 78 2f 61 70 69 3f 70 68 6f 6e 65  n/xxxx/api?phone
10c0afda9  3d 31 33 38 30 30 31 33 38 30 30 30 26 74 78 79  =13800138000&txy
10c0afdb9  7a 6d 3d 26 75 72 69 3d 61 70 69 78 78 78 78 2f  zm=&uri=apixxxx/
10c0afdc9  61 70 69 2f 75 73 65 72 2f 73 65 6e 64 73 6d 73  api/user/sendsms
10c0afdd9  63 6f 64 65 00 00 00 00 00 00 00 00 00 00 00 04  code............
 
。。。。。。
 
Precompiled____base64Encode_5267 onEnter:
,            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
10c0b0789  03 6d 00 00 00 00 00 a0 07 0b 0c 01 00 00 00 00  .m..............
10c0b0799  00 00 00 28 00 00 00 f6 41 84 fc 8b 76 4a f3 03  ...(....A...vJ..
10c0b07a9  a0 fe d9 2f e8 5d 85 0f ee 6d b2 00 00 00 00 04  .../.]...m......
。。。。。。
,Precompiled____base64Encode_5267 onLeave:
,            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
10c0b0859  03 55 00 00 00 00 00 38 00 00 00 00 00 00 00 39  .U.....8.......9
10c0b0869  6b 47 45 2f 49 74 32 53 76 4d 44 6f 50 37 5a 4c  kGE/It2SvMDoP7ZL
10c0b0879  2b 68 64 68 51 2f 75 62 62 49 3d 00 00 00 00 00  +hdhQ/ubbI=.....
 
。。。。。。
,Precompiled_Digest_toString_34431 onLeave:
,            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
10c0b0d19  03 55 00 00 00 00 00 40 00 00 00 00 00 00 00 31  .U.....@.......1
10c0b0d29  30 61 37 64 38 31 61 32 36 34 65 37 39 36 34 30  0a7d81a264e79640
10c0b0d39  63 65 33 34 33 33 66 31 62 30 33 64 39 39 32 00  ce3433f1b03d992.

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

1
md5("9kGE/It2SvMDoP7ZL+hdhQ/ubbI=") -> 10a7d81a264e79640ce3433f1b03d992
1
2
3
4
5
00000000  f6 41 84 fc 8b 76 4a f3 03 a0 fe d9 2f e8 5d 85  |öA.ü.vJó. þÙ/è].|
00000010  0f ee 6d b2                                      |.îm²|
 
进行 base64 可以得到
9kGE/It2SvMDoP7ZL+hdhQ/ubbI=
1
hmac_sha1("D23ABC@#56", "api.xxx.cn/xxxx/api?phone=13800138000&txyzm=&uri=apixxxx/api/user/sendsmscode")    //已打码xxxx

4. 总结

分析以上的例子,因为运气好,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


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞16
打赏
分享
打赏 + 70.00雪花
打赏次数 2 雪花 + 70.00
 
赞赏  Editor   +50.00 2022/07/22 恭喜您获得“雪花”奖励,安全圈有你而精彩!
赞赏  珍惜Any   +20.00 2022/07/04 Yang神牛逼
最新回复 (19)
雪    币: 29
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_oowzftna 2022-7-4 22:18
2
0
大佬kDartIsolateSnapshotInstructions这个的地址怎么得到呀
雪    币: 5995
活跃值: (3470)
能力值: ( LV6,RANK:96 )
在线值:
发帖
回帖
粉丝
Imyang 1 2022-7-4 22:19
3
0
mb_oowzftna 大佬kDartIsolateSnapshotInstructions这个的地址怎么得到呀
let kDartIsolateSnapshotInstructions = Module.findExportByName("App", "kDartIsolateSnapshotInstructions")
雪    币: 5995
活跃值: (3470)
能力值: ( LV6,RANK:96 )
在线值:
发帖
回帖
粉丝
Imyang 1 2022-7-4 22:20
4
0
mb_oowzftna 大佬kDartIsolateSnapshotInstructions这个的地址怎么得到呀
ida也可以查看kDartIsolateSnapshotInstructions的偏移
雪    币: 1929
活跃值: (12830)
能力值: ( LV9,RANK:190 )
在线值:
发帖
回帖
粉丝
珍惜Any 2 2022-7-4 22:20
5
0
火钳刘明
雪    币: 212
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_ezphxtar 2022-7-4 22:32
6
0
mb_oowzftna 大佬kDartIsolateSnapshotInstructions这个的地址怎么得到呀
也可以用 readelf -Ws libapp.so
来查看
雪    币: 646
活跃值: (4417)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
猫盾科技 2022-7-4 23:02
7
0
占个楼
雪    币: 4116
活跃值: (1019)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Papaya. 2022-7-5 00:15
8
0
强啊
雪    币: 576
活跃值: (2035)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kakasasa 2022-7-5 01:52
9
0
mark,感谢分享
雪    币: 13430
活跃值: (4758)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tDasm 2022-7-5 08:20
10
1
没看到你这个分析同Flutter有什么关系?唯一的关联是这个app是用Flutter开发的?
雪    币: 1154
活跃值: (3399)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
王麻子本人 2022-7-5 11:00
11
0
yang神乱杀
雪    币: 1669
活跃值: (3982)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小黄鸭爱学习 2022-7-5 11:57
12
0
reFlutter 这个工具目前看还行,长远来看不太可靠。我已经遇到过没有version版本标志位的fultter so,后面估计还有更多安全变种。reFlutter作者每次更新就加个适配dart vm的版本号,不知道作者怎么看。
雪    币: 8731
活跃值: (5483)
能力值: ( LV13,RANK:296 )
在线值:
发帖
回帖
粉丝
sunfishi 4 2022-7-5 12:21
13
0
根据SnapshotHashString辨别dart version并不可靠,开发者可以修改dart源码实现自定义SnapshotHashString
雪    币: 286
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
MrSean 2022-7-5 13:20
14
0
sunfishi 根据SnapshotHashString辨别dart version并不可靠,开发者可以修改dart源码实现自定义SnapshotHashString
mason大佬牛b
雪    币: 0
活跃值: (353)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Erlösung 2022-7-13 15:45
15
0
[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 报错上面的这个,求解,谢谢
雪    币: 1874
活跃值: (4879)
能力值: ( LV8,RANK:146 )
在线值:
发帖
回帖
粉丝
Simp1er 2022-7-14 11:48
16
0
Yang神中级篇和高级篇什么时候更新
雪    币: 227
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_wvvozxbm 2022-7-21 18:22
17
0
大佬这个APP有木有样本地址
雪    币: 4599
活跃值: (2467)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
rehai 2022-11-16 14:15
18
0
要是apk的话怎么得到kDartIsolateSnapshotInstructions呢
雪    币: 2321
活跃值: (4364)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
Dyingchen 2024-1-20 10:33
20
0
有符号的话还能处理一下,抹去类名和函数名之后完全是在大海捞针
游客
登录 | 注册 方可回帖
返回