基于Zygisk的reFlutter
我开发了一个适用于已root的Android手机(需安装Magisk并启用Zygisk)的Zygisk模块。该模块允许您在运行时"重新Flutter"您的Flutter应用,从而简化测试和逆向工程流程。
如果您不想阅读详细信息,发布版本可在以下地址获取: https://github.com/yohanes/zygisk-reflutter
reFlutter
在讨论zygisk-reflutter及其工作原理之前,我想先讨论什么是reFlutter、它是如何工作的、为什么需要它,以及基于Zygisk的解决方案解决了什么问题。
我在2021年的文章《通过重新编译Flutter引擎逆向工程Flutter应用》中讨论了逆向基于Flutter的应用的问题。我提出的解决方案是重新编译flutter引擎(libflutter.so),因为Flutter的二进制格式不稳定且没有文档记录。
2022年,PT Swarm的某人引入了类似的概念(详见他们的文章)。他们创建了reFlutter,其中包括一个用于重新编译所有已发布Flutter引擎的GitHub action,以及一个Python脚本,该脚本:
- 提取APK或IPA中使用的libflutter的当前哈希值
- 下载与哈希匹配的预构建库(由GitHub action创建)
- 使用用户提供的代理IP地址修补libflutter.so
- 将APK中的原始libflutter.so替换为修补后的版本
使用此工具,我们需要做的是:
- 获取APK或IPA
- 运行reflutter <APK/IPA>
- 重新签名APK/IPA
- 安装APK/IPA,必要时删除原始应用
这个工具很棒,自发布以来我一直在使用它。但使用此工具时我遇到了两个问题:
- 某些应用会检查其签名,重新打包APK会更改签名(通常,我会使用Frida进行内存修补)
- 从设备提取APK、重新Flutter、重新签名、删除应用并重新安装新版本需要相当长的时间
- 如果需要比较未修补和已修补的二进制文件,我们需要重新安装应用
我想要的是一个可以在运行时替换此库的工具,以解决上述问题。
Magisk和Zygisk
Magisk是一套用于自定义Android的开源软件。您需要一部具有可解锁引导加载程序的手机才能使用它。Magisk有几个功能,但对此有用的是:Zygisk。
Zygisk可以注入代码,使其可以在每个Android应用程序的进程中运行。为此:我们可以创建一个使用Zygisk API的共享库,将其打包在zip文件中,并使用Magisk Manager安装。
Zygisk的唯一文档是此存储库中的示例代码: https://github.com/topjohnwu/zygisk-module-sample
我对Zygisk的理解来自研究各种开源Zygisk模块存储库。Zygisk API很简单,但诊断问题可能具有挑战性。
例如,在Android Studio中创建JNI项目默认链接到libandroid,这没问题,除非您使用伴随进程。当您连接到伴随进程时,伴随进程将停止工作(通过移除-landroid解决)。
由于我不是Zygisk专家,我的方法可能不是最优的。我欢迎改进建议。应用的GUI部分也不是很好。我不是前端Android程序员,一半的GUI代码是在Copilot的帮助下编写的。
库替换
假设我们有一个可用的替换flutter库(从reFlutter的发布页面下载),我们可以使用pltHookRegister(一个Zygisk API)挂钩android_dlopen_ext并传入新库。
我怎么知道要挂钩android_dlopen_ext?简单的方法只是猜测。但我通过跟踪System.loadlibrary的调用发现:
- System.loadlibrary将调用:Runtime.nativeLoad(在libcore/ojluni/src/main/native/Runtime.c中实现)
- Runtime.nativeLoad将调用JVM_NativeLoad(在art/openjdkjvm/OpenjdkJvm.cc中)
- JVM_NativeLoad将调用LoadNativeLibrary(在art/runtime/jni/java_vm_ext.cc中)
- LoadNativeLibrary将调用OpenNativeLibrary(在art/libnativeloader/native_loader.cpp中)
- OpenNativeLibrary将调用android_dlopen_ext(在bionic/libdl/libdl.cpp中)
然而,由于Android安全性,我无法从/data/local/tmp/或其子目录加载.so,即使我验证了.so文件可读且可执行。但如果library.so在应用的数据目录中,则android_dlopen_ext将工作。
reFlutter项目提供了两种库:仅用于代理和用于类转储。为使解释更简单,我将仅讨论代理情况。
因此,为了使此工作,我制作了一个应用,该应用:
- 列出所有已安装的Android应用
- 在选择时从Flutter应用提取Flutter哈希
- 允许从reFlutter为选定的应用下载libflutter.so
- 如果选择了"启用代理",则创建包含哈希.so的PACKAGENAME.txt
- 设置代理IP
请注意,该应用并不干净:它没有…
当Zygisk模块加载时,它:
- 检查ZygiskReflutter应用文件目录中是否存在PACKAGENAME.txt,如果可用则读取内容
- 如果目标应用缺少库,则复制.so并使用所需的代理IP修补IP(除非IP更改,否则执行一次)
由于访问另一个应用目录需要root权限,我使用Zygisk的伴随功能来执行此操作。另一种也可以工作的方法是:
- 将库和配置存储在/data/local/tmp内
- 当应用启动时,将数据从/data/local/tmp复制到应用文件目录
我没有意识到我们可以在preSpecialize步骤中轻松获取应用数据目录,因此我将来可能会使用此方法。
安装和使用
要安装此模块,您需要安装zip文件(作为zygisk模块)和作为普通APK的应用。您将找到一个应用包列表,滚动(或过滤)以找到该应用。单击它。它将显示"Finding hash"。
Flutter应用
如果应用受支持,它将启用下载按钮。这将下载库(每个库大约10兆字节,因此请尝试使用快速互联网连接)。目前我没有处理下载损坏的情况。
如果发生这种情况,请从以下位置删除文件:/data/data/com.tinyhack.zygiskreflutter/files,并从github手动下载。
下载代理库
一旦下载完成,您可以启用代理。
代理现在可以启用
黑客愉快
这是一个周末项目,因此这不是一个非常干净的实现。我明天要去度假以逃离清迈的污染,可能稍后会继续这个项目(但到目前为止对我来说已经足够好了)。