热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

通过重新编译Flutter引擎对Flutter应用程序进行逆向分析

 我们知道,要想对flutter应用程序的发布版本进行逆向分析是一件非常困难的事情,原因主要有两个,一是缺乏相应的工具,二是flutter引擎本身也经常发生变化。幸运的是,如果待逆向的flutter应

 

我们知道,要想对flutter应用程序的发布版本进行逆向分析是一件非常困难的事情,原因主要有两个,一是缺乏相应的工具,二是flutter引擎本身也经常发生变化。幸运的是,如果待逆向的flutter应用是用特定版本的Flutter SDK构建的,则可以借助于darter或Doldrums来转储该应用程序的类名和方法名。

如果您的运气足够好,就像我第一次测试Flutter App时那样,甚至根本就无需对App进行逆向工程。如果应用程序本身非常简单,并且使用简单的HTTPS连接,则可以使用拦截代理(如Burp或Zed Attack Proxy)来测试其全部功能。但是,这次需要测试的应用程序在HTTPS的基础上使用了额外的加密层,所以,我不得不对其进行逆向分析。

在这篇文章中,虽然只介绍了Android平台的例子,但这里介绍的方法都是通用的,也适用于其他平台。简单来说,我们的方法就是:不需要更新或创建快照解析器,而是只要重新编译flutter引擎,并将其替换到目标应用中即可。

 

Flutter编译的应用程序

目前,我发现了一些有关Flutter逆向工程的文章和存储库:


  • Reverse engineering Flutter for Android:讲解了快照格式的基本知识,介绍了Doldrums,截至目前只支持快照版本8ee4ef7a67df9845fba331734198a953。

  • Reverse engineering Flutter apps (Part 1):这是一篇非常棒的文章,它详细解释了Dart的内部结构,不幸的是,文章没有提供相应的代码。截至目前为止,该文章的续篇还没有发表。

  • darter: Dart snapshot parser:这是一个转储快照版本c8562f0ee0ebc38ba217c7955956d1cb的工具。

我们知道,Flutter应用程序的代码主要由两个库组成,其中libflutter.so库存放的是flutter引擎,而libapp.so库则用于存放用户编写的代码。读者可能会问:如果用标准的反汇编器打开一个libapp.so(经过AOT编译的Dart代码),将会看到什么呢?只会看到本地代码,对吗?实际上,如果使用IDA打开这个库的话,最初看到的,只是一堆字节而已。

如果使用其他工具,比如Binary Ninja,这些工具可能还会进行一些线性扫描,因此,我们会看到很多方法。但是,所有的方法都没有命名,我们也无法找到任何字符串引用。同时,libapp.so既不会引用外部函数(无论是libc还是其他库),也没有直接调用内核的syscall(比如Go)。

如果使用Darter dan Doldrums这样的工具,我们不仅可以转储类名和方法名,还可以找到函数的实现地址。下面是一个使用Doldrums进行转储的例子。这对逆向分析应用程序极为有用。同时,我们还可以使用Frida在这些地址处设置hook,以转储内存或方法参数。

 

快照的格式问题

一个特定工具只能转储快照的特定版本的原因是:快照格式不稳定,它被设计成由特定版本的运行时来运行。对于某些格式来说,如果遇到未知或不受支持的部分,它们会直接放弃;相比之下,快照格式则显得“宽容的多”:如果无法解析某个部分,就会继续解析下一个部分。

快照的格式大体是这样的:……,就像您看到的那样,这里并没有为每个块显式地指定其长度,也没有为标记的头部指定特定格式(所以,我们无法通过模式匹配来找到块的起始部分)。一切都只是数字。除了源代码本身,再也找不到与快照相关的其他文档。

实际上,这种格式连一个版本号都没有:格式是由快照版本字符串进行标识的。版本字符串是通过对快照相关文件的源代码计算其哈希值而得到的。因此,如果文件发生变化,那么格式也会随之发生变化。这在大多数情况下都是正确的,但也并非总是如此(例如:如果您编辑一个注释,快照版本字符串就会改变)。

我的第一个想法就是通过查看Dart源代码的差异,将Doldrums或Darter修改为我需要的版本。但事实证明,事情远没有我想的这么简单:枚举有时会插入其中(意味着我需要将所有常量移一个数字)。并且,dart还使用C++模板进行了大量的位操作。例如,当我查看Doldums代码时,遇到了如下所示的内容:

def decodeTypeBits(value):
return value & 0x7f

我想我可以在代码中快速检查这个常量(无论它在新版本中是否改变),结果发现其类型并不是整数:

class ObjectPool : public Object {
using TypeBits = compiler::ObjectPoolBuilderEntry::TypeBits;
}
struct ObjectPoolBuilderEntry {
using TypeBits = BitField;
}

不难看出,这个Bitfield是作为BitField模板类来实现的。这个特殊的位很容易读懂,但是如果想搞清楚kNextBit,则需要回溯之前所有的位定义。我知道对于经验丰富的C++开发者来说,这并非难事,但要想跟踪这些版本之间的变化,还是需要做大量的手工检查的。

我理想中的情况是:无需维护Python代码,下一次更新应用程序需要进行重新测试时,直接使用更新版本的Flutter SDK,并使用另一个快照版本。但是,我面前的事实却是,需要测试两个使用不同的Flutter版本的应用程序:一个是已经在应用商店发布的应用程序,另一个是即将发布的应用程序。

 

重新构建Flutter引擎

flutter引擎(libflutter.so)是一个独立于libapp.so(主应用逻辑代码)的库,在iOS系统中,这是一个独立的框架。这个库的思路非常简单:


  • 下载我们想要的引擎版本;

  • 通过改造该引擎,用于打印类名、方法等,而不是编写我们自己的快照解析器;

  • 用我们的补丁版本替换原来的libflutter.so库;

  • 乐享其成。

实际上,第一步就不是一件轻松的事情:如何才能找到相应的快照版本?虽然darter的这张表可以提供帮助,但是该表并非最新的版本。对于其他版本,我们需要自己寻找并测试它是否有匹配的快照号。关于Flutter引擎的重新编译方法,可以参考这篇资料;需要注意的是,编译过程中可能会出现一些小插曲,为此,我们需要修改快照版本的python脚本。注意,Dart内部的运行机制,就不是那么容易理解和处理了。

我测试过的大部分旧版本都不能正确编译,为此,我们需要编辑DEPS文件。在我的例子中:虽然其差别很小,但借助于网络搜索后,我才找到了这一点。不知何故,相关的提交并不可用,因此,我不得不使用不同的版本。注意:不要盲目应用这个补丁,首先要检查以下两点:


  • 如果某个提交不可用,请查找离发布日期最近的提交;

  • 如果某些代码引用了_Internal,则应删除_Internal部分。

diff --git a/DEPS b/DEPS
index e173af55a..54ee961ec 100644
--- a/DEPS
+++ b/DEPS
@@ -196,7 +196,7 @@ deps = {
Var('dart_git') + '/dartdoc.git@b039e21a7226b61ca2de7bd6c7a07fc77d4f64a9',
'src/third_party/dart/third_party/pkg/ffi':
- Var('dart_git') + '/ffi.git@454ab0f9ea6bd06942a983238d8a6818b1357edb',
+ Var('dart_git') + '/ffi.git@5a3b3f64b30c3eaf293a06ddd967f86fd60cb0f6',
'src/third_party/dart/third_party/pkg/fixnum':
Var('dart_git') + '/fixnum.git@16d3890c6dc82ca629659da1934e412292508bba',
@@ -468,7 +468,7 @@ deps = {
'src/third_party/android_tools/sdk/licenses': {
'packages': [
{
- 'package': 'flutter_internal/android/sdk/licenses',
+ 'package': 'flutter/android/sdk/licenses',
'version': 'latest',
}
],

现在,我们就可以开始编辑快照文件,以了解其工作原理了。但是,正如前面提到的:如果我们修改了快照文件,那么,该快照的哈希值就会发生改变,所以,我们需要在third_party/dart/tools/make_version.py中返回一个静态版本号来解决这个问题。对于VM_SNAPSHOT_FILES中的任何一个文件,都需要用静态字符串将snapshot_hash = MakeSnapshotHashString()这一行改为您的特定版本。

如果我们不给版本打补丁的话,结果又会怎么样呢?应用程序将无法启动。所以,在使用OS::PrintErr(“Hello World”)打上补丁并进行重新编译后,我们就可以替换.so文件,然后运行它了。

虽然我进行了多次实验(比如尝试FORCE_INCLUDE_DISASSEMBLER),但是仍然没有找到完美的修改方法,不过,我还是可以分享一些修改建议的。


  • runtime/vm/clustered_snapshot.cc中,我们可以修改Deserializer::ReadProgramSnapshot(ObjectStore* object_store),使其打印类表,即isolate->class_table()->Print()

  • runtime/vm/class_table.cc中,我们可以修改void ClassTable::Print(),使其打印更多的信息。

例如,打印函数名称的代码如下所示:

const Array& funcs = Array::Handle(cls.functions());
for (intptr_t j = 0; j Function& func = Function::Handle();
func = cls.FunctionFromIndex(j);
OS::PrintErr("Function: %s", func.ToCString());
}

 

关于SSL证书

Flutter应用程序的另一个问题是:它并不信任用户安装的根证书。这对于渗透测试来说就是一个问题了,不过,我们可以通过给二进制文件打补丁(直接或使用Frida)来解决这个问题,相关的文章请访问这里,具体方法如下所示:


  • Flutter使用的是Dart,但是Dart并没有使用系统的CA Store;

  • Dart使用的CA列表被编译到应用程序中;

  • Dart在安卓系统上不支持代理,所以使用了ProxyDroid与iptables;

  • 钩住x509.cc中的session_verify_cert_chain函数,以禁用链式验证。

通过重新编译Flutter引擎,可以很容易地实现这一点:我们只需修改源代码(third_party/boringssl/src/ssl/handshake.cc),而不需要在编译后的代码中寻找汇编字节模式了。

 

对Flutter进行混淆处理

使用这里提供的方法,可以对Flutter/Dart应用程序进行相应的混淆处理,从而提高逆向分析的难度。请注意,这里只是对名称进行混淆处理,而没有对控制流进行混淆处理。

 

小结

由于我这个人很懒,所以,我在这里选择了重新编译flutter引擎,而不是编写一个合适的快照解析器。当然,其他人在对其他技术进行逆向分析时,也可以借鉴类似的方法,即黑掉运行时引擎,例如,要对一个经过混淆处理的PHP脚本进行逆向分析时,可以用PHP模块钩住eval函数。


推荐阅读
  • MATLAB字典学习工具箱SPAMS:稀疏与字典学习的详细介绍、配置及应用实例
    SPAMS(Sparse Modeling Software)是一个强大的开源优化工具箱,专为解决多种稀疏估计问题而设计。该工具箱基于MATLAB,提供了丰富的算法和函数,适用于字典学习、信号处理和机器学习等领域。本文将详细介绍SPAMS的配置方法、核心功能及其在实际应用中的典型案例,帮助用户更好地理解和使用这一工具箱。 ... [详细]
  • Android中将独立SO库封装进JAR包并实现SO库的加载与调用
    在Android开发中,将独立的SO库封装进JAR包并实现其加载与调用是一个常见的需求。本文详细介绍了如何将SO库嵌入到JAR包中,并确保在外部应用调用该JAR包时能够正确加载和使用这些SO库。通过这种方式,开发者可以更方便地管理和分发包含原生代码的库文件,提高开发效率和代码复用性。文章还探讨了常见的问题及其解决方案,帮助开发者避免在实际应用中遇到的坑。 ... [详细]
  • 在处理大图片时,PHP 常常会遇到内存溢出的问题。为了避免这种情况,建议避免使用 `setImageBitmap`、`setImageResource` 或 `BitmapFactory.decodeResource` 等方法直接加载大图。这些函数在处理大图片时会消耗大量内存,导致应用崩溃。推荐采用分块处理、图像压缩和缓存机制等策略,以优化内存使用并提高处理效率。此外,可以考虑使用第三方库如 ImageMagick 或 GD 库来处理大图片,这些库提供了更高效的内存管理和图像处理功能。 ... [详细]
  • 为了向用户提供虚拟应用程序,通常会在基础架构中部署StoreFront或Web Interface。为了确保安全的远程访问,通常需要在DMZ中配置Secure Gateway或Access Gateway。本文详细对比了这两种界面工具的功能特性,包括用户管理、安全性、性能优化等方面,为企业选择合适的解决方案提供了全面的参考。 ... [详细]
  • 解决发布版APK构建失败的问题 ... [详细]
  • Flutter 2.* 路由管理详解
    本文详细介绍了 Flutter 2.* 中的路由管理机制,包括路由的基本概念、MaterialPageRoute 的使用、Navigator 的操作方法、路由传值、命名路由及其注册、路由钩子等。 ... [详细]
  • 在Linux系统中避免安装MySQL的简易指南
    在Linux系统中避免安装MySQL的简易指南 ... [详细]
  • 今天我开始学习Flutter,并在Android Studio 3.5.3中创建了一个新的Flutter项目。然而,在首次尝试运行时遇到了问题,Gradle任务 `assembleDebug` 执行失败,退出状态码为1。经过初步排查,发现可能是由于依赖项配置不当或Gradle版本不兼容导致的。为了解决这个问题,我计划检查项目的 `build.gradle` 文件,确保所有依赖项和插件版本都符合要求,并尝试更新Gradle版本。此外,还将验证环境变量配置是否正确,以确保开发环境的稳定性。 ... [详细]
  • 本文详细介绍了如何在Java Web服务器上部署音视频服务,并提供了完整的验证流程。以AnyChat为例,这是一款跨平台的音视频解决方案,广泛应用于需要实时音视频交互的项目中。通过具体的部署步骤和测试方法,确保了音视频服务的稳定性和可靠性。 ... [详细]
  • 在Android 4.4系统中,通过使用 `Intent` 对象并设置动作 `ACTION_GET_CONTENT` 或 `ACTION_OPEN_DOCUMENT`,可以从相册中选择图片并获取其路径。具体实现时,需要为 `Intent` 添加相应的类别,并处理返回的 Uri 以提取图片的文件路径。此方法适用于需要从用户相册中选择图片的应用场景,能够确保兼容性和用户体验。 ... [详细]
  • 本文探讨了Android系统中支持的图像格式及其在不同版本中的兼容性问题,重点涵盖了存储、HTTP传输、相机功能以及SparseArray的应用。文章详细分析了从Android 10 (API 29) 到Android 11 的存储规范变化,并讨论了这些变化对图像处理的影响。此外,还介绍了如何通过系统升级和代码优化来解决版本兼容性问题,以确保应用程序在不同Android版本中稳定运行。 ... [详细]
  • DateTimenowDateTime.now();DateTimetimeDateTime.parse(2018-12-31);if(now.isBefore(time)){Du ... [详细]
  • 怎么入门Android?Android免打包多渠道统计如何实现?含泪整理面经
    热修复技术是Android开发中比较高级的知识点,是中级开发人员通向高级开发中必须掌握的技能。本篇重点讲解热修复热修复的原理,各大热修复框架的比较&#x ... [详细]
  • 看官_在GitHub Actions上进行Flutter 的测试和部署
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了在GitHubActions上进行Flutter的测试和部署相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Android工程师最容易遇到4个瓶颈是什么?附带学习经验
    一些感悟穷人的一次失败,为了还债可能一辈子都翻不了身,为还债一辈子送外卖。你将不再会有精力去思考和投机。穷人的失败可能断送了他所有暴富的机遇和时间&# ... [详细]
author-avatar
浩斯科技
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有