本文对该 APP 的逆向仅仅是为了学习和记录更多网络安全方面的思路和技巧,如果有人非法使用本文涉及到的技术或技巧,则由此所带来的一切法律后果都由操作者自行承担,和本文以及作者没有任何关系!
# 前言
我接触逆向的时间也不算长,这是第一次碰到 Flutter 框架的 APP,之前从没接触 dart 语言,而且网上关于 Flutter APP 的逆向相关的文章也很少,这次就记录一下逆向的思路。
# Flutter
Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。 Flutter 可以与现有的代码一起工作。在全世界,Flutter 正在被越来越多的开发者和组织使用,并且 Flutter 是完全免费、开源的。
这是 Flutter 官网对于 Flutter 的介绍。Flutter 可以使用 dart 语言进行移动平台(android、ios)APP 的开发,其基本原理就是在 C++ 层(so)的基础上加了一层 dart 虚拟机,所有的图形渲染、主要逻辑等都在 dart 虚拟机中执行。由于 Flutter 是一个诞生不久的框架,其逆向相关工具比较少,所以逆向难度较高。
# 过程
# 确定目标
本次主要是对 APP 的数据加密算法和签名算法进行逆向,对该 APP 进行抓包,如下图所示。
分析可知,该 APP 的数据包中 data 为发送的有效数据, Body
中和 Header
中分别有一次 sign 验证,所以此次我们的目标就是这三处算法的实现。
# jadx 静态分析
将 APK 拖入 jadx 中,搜索 data
sign
,查看结果并没有得到有效信息,查看 libs 发现有 libflutter.so
libapp.so
等库文件,可以基本确认该 APP 使用了 Flutter 框架。查阅一些资料后发现,flutter 中有些加密函数也是基于 android/ios 原生能力实现,所以我们可以尝试对其 hook 安卓常见的原生加密函数。
# data 加密算法分析
废话不多说,frida 启动,加载算法自吐脚本,直接 hook 到了 data 数据的加密方法,果然,还是调用了原生能力进行数据的加密。可以看到加密方法为 AES/ECB/PKCS5Padding
,密钥的 HEX 为 35727a771e637829613969366e316c71
,使用 AES 解密工具测试一下,成功解密。
遗憾的是并没有发现 sign 算法的调用,于是基本确定 sign 应该是在 dart 层实现,并没有调用原生能力。
# sign 算法分析
sign 参数为 32 位 hexString,猜测应该是某种消息摘要算法。但由于 flutter 在 so 层中加了一层虚拟机,所以对于 so 层的分析就异常困难。将 so 导入 IDA 中,反复分析并没有找到有用的信息(我的 so 层动态函数分析经验本来就不多),于是决定面向搜索引擎逆向,吸取一下前人的经验。
搜索一番后发现,关于 Flutter 逆向的文章寥寥无几,仅有的几篇也都没有什么有效方法,于是卡壳好长时间。后来多方查找,在吾爱破解找到一个 flutter 的逆向分析工具 ——Doldrums,可以从 so 中分析出 dart 层的函数,该工具的 github 地址如下。
Github:https://github.com/rscloura/Doldrums
直接对 libapp.so
进行分析,命令如下:
python3 main.py libapp.so output |
分析后得到的数据如下图所示。
dart 层的数据并没有被混淆,但分析出的数据仅包含类和方法名称以及参数和返回值类型,并没有代码。由于前面已经知道了 sign 参数为 hexstring,所以我们可以直接 hook hex 编码相关的函数。搜索后发现一个 HexEncoder 类,对其进行 hook。
打开 frida,hook 代码如下:
var str_name_so = "libapp.so"; // 要 hook 的 so 名 | |
//var str_name_func = "updateHash"; // 要 hook 的函数名 | |
var n_addr_func = Module.findBaseAddress(str_name_so).add("0x4b9bb0"); | |
console.log("func addr is ---" + n_addr_func); | |
Interceptor.attach(n_addr_func, { | |
// 在 hook 函数之前执行的语句 | |
onEnter: function(args) | |
{ | |
console.log("args:------>") | |
console.log(hexdump(args[0])) | |
//console.log(hexdump(args[1])) | |
}, | |
// 在 hook 函数之后执行的语句 | |
onLeave:function(retval) | |
{ | |
console.log("**********************************") | |
console.log("********************return:------>") | |
console.log("**********************************") | |
console.log(hexdump(retval, {length:200})) | |
} | |
}) |
可以看到确实是调用了该函数,但是不知是什么原因,参数并没有被打印出来。
既然参数打印不出,那我们换个思路,去打印结果。在 sign 计算之前,都会对要签名的字符串进行拼接,我们可以去 hook 拼接的函数,输出结果,这样就能看到拼接后的字符串进而推算出签名算法了。
dart 中,使用 Utf8Encoder 类中的 convert 方法去进行字符串的建立和拼接。hook 该函数,得到结果如下:
可以清楚的看到字符串拼接的结果,Body 中的 sign 参数是 data数据
+ 时间字符串
+ 固定字符串
然后进行了 md5 得到的。Header 中的 sign 参数则是 token
+ 时间字符串
进行 md5 得到的。至此,加密和签名算法全部搞定。
# 总结
本次逆向的 APP 并没有做什么反逆向措施,只是由于使用了 Flutter 框架,缺乏相关的逆向工具和资料导致难以逆向。对于个人开发者或者小公司来说,flutter 编写的应用确实能够在一定程度上防止逆向。
个人建议 dart 层的函数最好在做一次混淆,这样可以更好的防止逆向。