Zygisk-based reFlutter

I developed a Zygisk module for rooted Android phones with Magisk (and Zygisk enabled). This module allows you to “reFlutter” your Flutter App at runtime, simplifying the testing and reverse engineering processes.
我为具有 Magisk(并启用了 Zygisk)的 root Android 手机开发了一个 Zygisk 模块。该模块允许您在运行时“重新 Flutter”您的 Flutter 应用程序,从而简化测试和逆向工程过程。

If you don’t want to read the detail, the release is available at:
如果您不想阅读详细信息,可在以下位置获得该版本:

https://github.com/yohanes/zygisk-reflutter

Zygisk-based reFlutter

reFlutter reFlutter 的

Before discussing zygisk-reflutter and how it works, I want to discuss what is reFlutter, how it works, and why you need it. and what is the problem that a zygisk-based solution solves?
在讨论 zygisk-reflutter 及其工作原理之前,我想先讨论一下 reFlutter 是什么,它是如何工作的,以及为什么需要它。基于 zygisk 的解决方案解决了什么问题?

I have discussed the problems of reversing a Flutter-based app in this 2021 post: Reverse Engineering a Flutter app by recompiling Flutter Engine. My proposed solution is to recompile the flutter engine (libflutter.so) because the binary format of flutter is not stable and not documented.
我在这篇 2021 年的帖子中讨论了反转基于 Flutter 的应用程序的问题:通过重新编译 Flutter Engine 对 Flutter 应用程序进行逆向工程。我建议的解决方案是重新编译 flutter 引擎 ( libflutter.so ),因为 flutter 的二进制格式不稳定且没有文档。

In 2022, a similar concept was introduced by someone from PT Swarm (detailed in their write-up). They created reFlutter, which includes a GitHub action to recompile all released Flutter engines, and a Python script that:
2022 年,PT Swarm 的某个人引入了类似的概念(详见他们的文章)。他们创建了 reFlutter,其中包括一个用于重新编译所有已发布的 Flutter 引擎的 GitHub 操作,以及一个 Python 脚本:

  1. Extracts the current hash of libflutter used in an APK or IPA.
    提取 APK 或 IPA 中使用的 libflutter 的当前哈希值。
  2. Downloads a prebuilt library matching the hash (created by the GitHub action).
    下载与哈希匹配的预构建库(由 GitHub 操作创建)。
  3. Patches libflutter.so with a user-provided proxy IP address.
    使用用户提供的代理 IP 地址 libflutter.so 修补程序。
  4. Replaces the original libflutter.so in the APK with the patched version.
    将 APK 中的原始 libflutter.so 替换为修补版本。

To use this tool, what we need to do is:
要使用这个工具,我们需要做的是:

  1. Acquire the APK or IPA.
    获取 APK 或 IPA。
  2. Run reflutter <APK/IPA>. 运行 reflutter <APK/IPA> 。
  3. Re-sign the APK/IPA. 重新签署 APK/IPA。
  4. Install the APK/IPA, removing the original app if necessary.
    安装 APK/IPA,必要时删除原始应用程序。

This tool is great, and I have been using it since it was released. But there are two problems that I encountered when using this tool:
这个工具很棒,自从它发布以来我一直在使用它。但是我在使用此工具时遇到了两个问题:

  1. Some app checks their signature, and repacking APK will change the signature (usually, I will resort to in-memory patching with Frida)
    一些应用程序会检查他们的签名,重新打包 APK 会更改签名(通常,我会求助于 Frida 的内存中修补)
  2. It takes quite some time to extract APKs from a device, reflutter, re-sign, remove the app, and reinstall the new one
    从设备中提取 APK、重新颤动、重新签名、删除应用程序并重新安装新应用程序需要相当长的时间
  3. If we need to compare the unpatched and patched binary, we need to reinstallt he app
    如果我们需要比较未修补和修补的二进制文件,我们需要重新安装应用程序

What I want to have is a tool that can replace this library at runtime, solving the above problems.
我想要的是一个可以在运行时替换这个库的工具,解决上述问题。

Magisk and Zygisk Magisk 和 Zygisk

Magisk is a suite of open source software for customizing Android. You will need a phone with unlockable bootloader to use this. There are several features of Magisk, but the one that is useful for this is: Zygisk.
Magisk 是一套用于自定义 Android 的开源软件。您需要一部带有可解锁引导加载程序的手机才能使用它。Magisk 有几个功能,但对此有用的是:Zygisk。

Zygisk can inject a code that can run in every Android applications’ processes. To do this: we can create a shared library making use of Zygisk API, package it in a zip file and install it using Magisk Manager.
Zygisk 可以注入可以在每个 Android 应用程序的进程中运行的代码。为此:我们可以使用 Zygisk API 创建一个共享库,将其打包到 zip 文件中并使用 Magisk Manager 进行安装。

The only documentation for Zygisk is the sample code in this repository:
Zygisk 的唯一文档是此存储库中的示例代码:

https://github.com/topjohnwu/zygisk-module-sample

My understanding of Zygisk comes from studying various open-source Zygisk module repositories. The Zygisk API is straightforward, but diagnosing issues can be challenging.
我对 Zygisk 的了解来自于对各种开源 Zygisk 模块存储库的研究。Zygisk API 简单明了,但诊断问题可能具有挑战性。

For example, creating a JNI project in Android Studio links to libandroid by default, which is fine unless you use a companion process. The companion process will stop working when you connect to it (solved by removing -landroid).
例如,在 Android Studio 中创建 JNI 项目默认链接到 libandroid,除非您使用配套进程,否则这很好。当您连接到配套进程时,它将停止工作(通过删除 -landroid 解决)。

As I am not a Zygisk expert, my approach might not be optimal. I am open to suggestions for improvement. The GUI part of the app is also not very good. I am not a front end Android programmer and half of the GUI the code was written with the help of Copilot.
由于我不是 Zygisk 专家,我的方法可能不是最佳的。我愿意接受改进建议。该应用程序的 GUI 部分也不是很好。我不是前端 Android 程序员,一半的 GUI 代码是在 Copilot 的帮助下编写的。

Library Replacement 库更换

Assuming that we have a replacement flutter library available (downloaded from the release page of reFlutter), we can hook android_dlopen_ext using pltHookRegister (a Zygisk API) and pass in the new library.
假设我们有一个可用的替换 flutter 库(从 reFlutter 的发布页面下载),我们可以 android_dlopen_ext 使用 pltHookRegister (一个 Zygisk API) 钩住并传入新库。

Zygisk-based reFlutter

How do I know to hook android_dlopen_ext? The easy method is just by guessing. But i found by tracing the calls from System.loadlibrary:
我怎么知道钩 android_dlopen_ext 子?简单的方法就是猜测。但是我发现通过跟踪来自以下 System.loadlibrary 电话的电话:

  • System.loadlibrary,will call: Runtime.nativeLoad (implemented in libcore/ojluni/src/main/native/Runtime.c).
    System.loadlibrary ,将调用: Runtime.nativeLoad (在 libcore/ojluni/src/main/native/Runtime.c 中实现)。
  • Runtime.nativeLoad will call JVM_NativeLoad (in art/openjdkjvm/OpenjdkJvm.cc).
    Runtime.nativeLoad 将调用 JVM_NativeLoad (in art/openjdkjvm/OpenjdkJvm.cc )。
  • JVM_NativeLoad will call LoadNativeLibrary in art/runtime/jni/java_vm_ext.cc
    JVM_NativeLoad 会打电话 LoadNativeLibrary 进来 art/runtime/jni/java_vm_ext.cc
  • LoadNativeLibrary will call OpenNativeLibrary (in art/libnativeloader/native_loader.cpp)
    LoadNativeLibrary 将调用 OpenNativeLibrary (in art/libnativeloader/native_loader.cpp )
  • OpenNativeLibrary will call android_dlopen_ext (in bionic/libdl/libdl.cpp)
    OpenNativeLibrary 将调用 android_dlopen_ext (in bionic/libdl/libdl.cpp )

However, due to Android security, I was unable to load .so from /data/local/tmp/ or its subdirectories, even when I verify that the .so file is readable and executable. But if the library.so is in the app’s data directory, then android_dlopen_ext will work.
但是,由于 Android 安全性,我无法从 /data/local/tmp/ .so 或其子目录加载 .so,即使我验证 .so 文件是可读和可执行的。但是,如果 library.so 在应用程序的数据目录中,则 android_dlopen_ext 可以工作。

There are two kind of libraries provided by the reFlutter project: only for proxying and for class dumping. To make the explanation simpler, I will only discuss the proxying case.
reFlutter 项目提供了两种库:仅用于代理和类转储。为了简化解释,我将只讨论代理案例。

So to make this work, I made an app that:
因此,为了实现这项工作,我制作了一个应用程序:

  1. Lists all installed Android apps.
    列出所有已安装的 Android 应用。
  2. Extracts the Flutter hash from Flutter apps upon selection.
    选择时从 Flutter 应用中提取 Flutter 哈希。
  3. Allows for downloading libflutter.so from reFlutter for chosen apps.
    允许从 reFlutter 下载所选应用程序的 libflutter.so。
  4. Creates PACKAGENAME.txt containing the hash .so, if “enable proxy” is selected.
    如果选择了“启用代理”,则创建包含哈希 .so 的PACKAGENAME.txt。
  5. Sets up a Proxy IP.
    设置代理 IP。

Please note that the app is not clean: it does not
请注意,该应用程序并不干净:它没有

When the Zygisk module is loaded, it:
加载 Zygisk 模块后,它:

  • Checks for PACKAGENAME.txt in the ZygiskReflutter app files directory, reading the content if available.
    检查 ZygiskReflutter 应用文件目录中的PACKAGENAME.txt,读取内容(如果可用)。
  • If the target app lacks the library, copies the .so and patches the IP with the desired proxy IP (done once unless the IP changes).
    如果目标应用缺少库,请复制 .so 并使用所需的代理 IP 修补 IP(除非 IP 更改,否则执行一次)。

Since accessing another app directory requires root access, I am using the companion feature of Zygisk to perform this action. Another method that can also work is this:
由于访问另一个应用程序目录需要root访问权限,因此我正在使用Zygisk的配套功能来执行此操作。另一种也可以使用的方法是:

  • Store libraries and configurations inside /data/local/tmp
    将库和配置存储在 /data/local/tmp 中
  • When the app is started, copy the data from /data/local/tmp to the app files directory
    启动应用后,将数据从 /data/local/tmp 复制到应用文件目录

I didn’t realize that we can easily get an app data directory during preSpecialize step, so I might use this approach in the future.
我没有意识到我们可以在 preSpecialize 步骤中轻松获取应用程序数据目录,因此我将来可能会使用这种方法。

Installation and usage 安装和使用

To install this, you will need to install both the zip file (as zygisk module) and the app as ordinary APK. You will find a list of app package, scroll (or filter) to find the app. Click it. It will show “Finding hash”.
要安装它,您需要将 zip 文件(作为 zygisk 模块)和应用程序作为普通 APK 安装。您将找到应用程序包的列表,滚动(或筛选)以查找该应用程序。它将显示“查找哈希值”。

If the app is supported, it will enable the download button. This will download the library (each library is around 10 megabytes, so try using a fast internet connection). Currently I didn’t handle the case when download is corrupted.
如果该应用程序受支持,它将启用下载按钮。这将下载库(每个库大约10兆字节,因此请尝试使用快速的互联网连接)。目前我没有处理下载损坏的情况。

In case that happen, delete the file from: /data/data/com.tinyhack.zygiskreflutter/files), and download manually from github.
如果发生这种情况,请从以下位置删除文件:/data/data/com.tinyhack.zygiskreflutter/files),然后从 github 手动下载。

Zygisk-based reFlutter
Download proxy lib 下载代理库

Once it is downloaded, you can enable proxy
下载后,您可以启用代理

Zygisk-based reFlutter
Proxy can be enabled now
现在可以启用代理

Happy hacking 快乐的黑客

This was a weekend project, so this is not a very clean implementation. I am going for a holiday tomorrow to escape from Chiang Mai’s pollution and might continue this project later (but so far its good enough for me).
这是一个周末项目,所以这不是一个非常干净的实现。我明天要去度假,以逃避清迈的污染,以后可能会继续这个项目(但到目前为止,它对我来说已经足够了)。

原文始发于Tinyhack:Zygisk-based reFlutter

版权声明:admin 发表于 2024年4月14日 下午2:53。
转载请注明:Zygisk-based reFlutter | CTF导航

相关文章