找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: 活动 交友 discuz
查看: 53|回复: 0

Android-Flutter逆向

[复制链接]

2万

主题

137

回帖

13万

积分

管理员

积分
139366
发表于 2024-8-6 10:42:49 | 显示全部楼层 |阅读模式 IP:山东省青岛市 联通

登录后更精彩...O(∩_∩)O...

您需要 登录 才可以下载或查看,没有账号?立即注册

×
Android-Flutter逆向


0x01 引言
因为WMCTF2023的一道用Flutter写的Android游戏题而引出这篇文章,刚好一直想研究一下Flutter,这次趁这个机会研究一下,并且介绍一种基于Patch Flutter动态链接库实现辅助解析Flutter逆向工程符号的方法。这个方法理论上支持任意Flutter版本,而且具有高度定制化的特点,缺点是需要根据特定的版本重新编译,并且需要替换动态链接库,存在被检测到的可能。

0x02 Flutter
Flutter是谷歌使用Dart语言开发的高性能、跨端UI框架,可以通过一套代码,支持iOS、Android、Windows/MAC/Linux等多个平台,且能达到原生性能。 Flutter也可以与平台原生代码进行混合开发。
在开发中,Flutter应用会在一个 VM(程序虚拟机)中运行,从而可以在保留状态且无需重新编译的情况下,热重载相关的更新。对于发行版 (Release) ,Flutter 应用程序会直接编译为机器代码,即AOT(Ahead Of Time)。

# Flutter体系结构Flutter体系结构是分层设计的,类似于Android的体系结构。
  • 最上层是Framework层,即Flutter框架层,开发者可以通过 Flutter框架层 与 Flutter 交互,该框架提供了以 Dart 语言编写的现代响应式框架。Flutter框架相对较小,因为一些开发者可能会使用到的更高层级的功能已经被拆分到不同的软件包中,使用 Dart和 Flutter的核心库实现,
  • Flutter引擎是一个用于高质量跨平台应用的可移植运行时,由C/C++编写。它实现了Flutter的核心库,包括动画和图形、文件和网络I/O、辅助功能支持、插件架构,以及用于开发、编译和运行Flutter应用程序的Dart运行时和工具链。引擎将底层C++代码包装成 Dart代码,通过dart:ui暴露给 Flutter框架层。
  • Flutter可以通过一套代码在多个平台使用依靠着嵌入层,嵌入层采用了适合当前平台的语言编写,例如 Android使用的是 Java和 C++, iOS 和 macOS 使用的是 Objective-C 和 Objective-C++,Windows 和 Linux 使用的是 C++。嵌入层提供一个程序入口,程序由此可以与底层操作系统进行协调。



# Flutter App架构


#Flutter编译模式以下部分内容引自:Flutter’s Compilation Patterns. People who built App with Flutter must…

Flutter使用Dart作为应用程序开发编程语言,因此Flutter的编译模式与Dart的编译模式相关。下面这张表总结了Dart的编译模式。

  • Script::最常见的JIT模式。就像Node.js一样,可以通过Dart VM命令行工具直接执行Dart源代码。
  • Script Snapshot: JIT模式。与Script模式不同,Script Snapshot会将源代码打包成代码的Token形式,这可以节省了在编译时词法分析器所花费的时间。
  • Application Snapshot: JIT模式。Dart的Application Snapshot有点像运行时的转储。它包含了从源代码解析的类和函数,所以运行时可以更快地进行加载和执行。但是这种快照与架构相关,在IA_32上生成的快照无法在X64平台上运行。
  • AOT:AOT模式。在这种模式下,Dart源代码会被翻译成汇编文件,然后汇编文件由汇编器为不同架构编译成二进制代码。
对于Flutter,其在上述编译模式的基础上进行了调整
  • Script和Script Snapshot:与Dart的模式一样,但Flutter从未使用过。
  • Kernel Snapshot:对应用代码进行中间字节码(Dart kernel格式)快照。通过避免Dart代码重新编译来实现移动端的快速启动,类似于Java字节码与JVM,核心快照是不依赖于体系架构的。
  • Core JIT:Dart编译代码的一种二进制格式。程序数据和指令打包成特定的二进制格式,供 Dart运行时加载。实际上该模式 是一种 AOT 模式。
  • AOT Assembly:即Dart的AOT模式,完全AOT预编译的本地代码。

在开发阶段,开发Android App时,为了实现热重载技术加速UI的开发,Flutter在这个阶段使用Kernel Snapshot 模式,即核心快照模式。在编译生成的app-deug.apk 中的资源目录下存在isolate_snapshot_data vm_snapshot_data 以及kernel_blob.bin ,前两个文件分别用于加速isolate启动,加速dart_vm启动,最后一个文件为业务代码的字节码。在lib目录中还存在libflutter.so,即flutter动态链接库,与实际业务代码无关。


在发布阶段,在生产模式下应用程序需要更快地执行。因此,Flutter 在编译应用程序代码时选择了 AOT 模式。但是,由于平台特性不同,编译模式也有很大不同。我们可以将这些总结为一个表格。

在Android上,Core JIT和AOT Assembly 两种编译模式都支持。默认使用AOT Assembly 模式,将会生成libapp.so放入apk包中的lib目录下,由dart代码编译而来,除此之外还有libflutter.so,也就是flutter动态链接库,与业务代码无关。

0x03 Flutter逆向

# 快照使用readelf -s命令读取[backcolor=var(--notion-orange_background)]保存快照信息的libapp.so将会输出下面的内容
[Bash shell] 纯文本查看 复制代码
Symbol table '.dynsym' contains 6 entries:
Num:    Value          Size Type    Bind   Vis      Ndx Name
 0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
 1: 000000000014c000 29728 OBJECT  GLOBAL DEFAULT    7 _kDartVmSnapshotInstructi
 2: 0000000000153440 0x22bd30 OBJECT  GLOBAL DEFAULT    7 _kDartIsolateSnapshotInst
 3: 0000000000000200 15248 OBJECT  GLOBAL DEFAULT    2 _kDartVmSnapshotData
 4: 0000000000003dc0 0x147af0 OBJECT  GLOBAL DEFAULT    2 _kDartIsolateSnapshotData
 5: 00000000000001c8    32 OBJECT  GLOBAL DEFAULT    1 _kDartSnapshotBuildId


  • _kDartVmSnapshotData: 代表 isolate 之间共享的 Dart 堆 (heap) 的初始状态。有助于更快地启动 Dart isolate,但不包含任何 isolate 专属的信息。
  • _kDartVmSnapshotInstructions:包含 VM 中所有 Dart isolate 之间共享的通用例程的 AOT 指令。这种快照的体积通常非常小,并且大多会包含程序桩 (stub)。
  • _kDartIsolateSnapshotData:代表 Dart 堆的初始状态,并包含 isolate 专属的信息。
  • _kDartIsolateSnapshotInstructions:[backcolor=var(--notion-orange_background)]包含由 Dart isolate 执行的 AOT 代码。

其中_kDartIsolateSnapshotInstructions 是最为重要的,因为包含了所有要执行的AOT代码,即业务相关的代码。
Dart VM中所有的代码都运行在一些isolate内,isolate可以看作是一个隔离的Dart执行环境,有自己的全局状态和通常自己的执行线程(mutator线程)。isolate被组织成isolate group ,同一个组内的isolate共享同一个垃圾回收堆,用于存储该isolate组分配的对象。

在Flutter 中,不会使用多个isolate,除了始终存在的 VM isolate之外,只使用一个isolate

Isolate中维护了堆栈变量,函数调用栈帧,用于GC、JIT等辅助任务的子线程等, 而这里的堆栈变量就是要被序列化到磁盘上的东西,即IsolateSnapshot。此外像dart预置的全局对象,比如null,true,false等等等是由VMIsolate管理的,这些东西需序列化后即VmSnapshot。最初快照不包括机器代码,但是后来在开发AOT编译器时添加了此功能。开发 AOT 编译器和带代码的快照的动机是允许在由于平台级别限制而无法进行 JIT 的平台上使用 VM。带代码的快照的工作方式与普通快照几乎相同,但略有不同:它们包含一个代码部分,与快照的其余部分不同,它不需要反序列化。此代码段的铺设方式允许它在映射到内存后直接成为堆的一部分。dart源码中runtime/vm/app_snapshot.cc处理快照的序列化和反序列化



#一般逆向方法一般情况下要想获取更多关于业务代码相关的信息,暂时只有两种方法
  • 编译修改过的libflutter.so并且重新打包到APK中,在启动APP的过程中,由修改过的引擎动态链接库将快照数据获取并且保存。本文主要采用这种方法。
上面的两种方法,分别对应着静态和动态,但是有着同样的缺点,高度依赖于引擎版本,不同版本的Dart引擎其快照格式不同,所以静态的方法就需要频繁跟着版本更新迭代,成本极高,而动态也需要重新编译对应版本的链接库。同时如果APP作者抹除版本信息和hash信息,则无从下手,而且重打包APK极易被检测到。

对于第一种方法相关项目有:
  • 更新:Blutter工具,挺好用,根据不同编码的引擎编译不同版本的工具静态dump出源码信息,无需运行时dumphttps://github.com/worawit/blutter
对于第二种方法相关项目有:


#编译Flutter动态链接库以上两种方法有着各自的缺点,但是要选择肯定选择动态的,但是如果对应的Flutter版本reFlutter还未更新那就只能自己尝试Patch。
以下编译基于已经明确Flutter引擎使用的dart版本的前提,即APP并未抹除或修改snapshotHash的条件下。使用Ubuntu20.04 + Python3.8.8作为编译环境。
  • 要为特定 Flutter版本创建补丁,我们必须确定该版本中使用的 Dart SDK的特定版本。可以从下面的链接中找到对应的版本,在这里访问https://storage.googleapis.com/f ... releases_linux.json 即可获取对应版本的DartSDK,但是在这之前需要知道Flutter版本。

      [Bash shell] 纯文本查看 复制代码
      https://storage.googleapis.com/flutter_infra_release/releases/releases_windows.json
      [url]https://storage.googleapis.com/flutter_infra_release/releases/releases_macos.json[/url]
      [url]https://storage.googleapis.com/flutter_infra_release/releases/releases_linux.json[/url]


      对于未抹除hash信息的libapp.so而言,可以在其二进制文件中找到关于快照版本的hash,即snapshotHash ,在这里是7dbbeeb8ef7b91338640dca3927636 。然后通过

      通过搜索libflutter.so,找到flutter引擎的hash ,在这里是1ac611c64eadbd93c5f5aba5494b8fc3b35ee952

      完成后就可以在reFlutter/scripts/enginehash.tmp.csv at main · Impact-I/reFlutter (github.com)中根据两个hash值准确定位到Flutter版本为3.13.0 对应的dart版本为3.1.0
      [Bash shell] 纯文本查看 复制代码
      {
          "hash": "efbf63d9c66b9f6ec30e9ad4611189aa80003d31",
          "channel": "stable",
          "version": "3.13.0",
          "dart_sdk_version": "3.1.0",
          "dart_sdk_arch": "x64",
          "release_date": "2023-08-16T17:31:30.644124Z",
          "archive": "stable/linux/flutter_linux_3.13.0-stable.tar.xz",
          "sha256": "a2fc218708b2eaa395ed108f43fa40242e4c12299730e3e745b966962ada95af"
      }


  • 拉取depot_tools 工具并配置环境变量,其含有编译过程中必需的gclient

      [Bash shell] 纯文本查看 复制代码
      git clone [url]https://chromium.googlesource.com/chromium/tools/depot_tools.git[/url]
      export PATH=$PATH:$(pwd)/depot_tools


  • 创建准备放置自定义flutter引擎源码的文件夹并创建.gclient

      [Bash shell] 纯文本查看 复制代码
      mkdir myengine
      cd myengine
      touch .gclient


  • fork一份flutter引擎到自己的github,flutter/engine: The Flutter engine (github.com),然后将下面内容填入.gclient

      [Bash shell] 纯文本查看 复制代码
      solutions = [
        {
          "managed": False,
          "name": "src/flutter",
          "url": "git@github.com:LLeavesG/engine.git",
          "custom_deps": {},
          "deps_file": "DEPS",
          "safesync_url": "",
        },
      ]

  • 使用gclient sync 让他自己拉取相关源码

      [Bash shell] 纯文本查看 复制代码
      cd myengine
      gclient sync --with_branch_heads --with_tags --verbose


  • 完成后需要将源码切换到特定版本分支

      [Bash shell] 纯文本查看 复制代码
      cd myengine/src/flutter
      git pull [email]git@github.com[/email]:LLeavesG/engine.git 1ac611c64eadbd93c5f5aba5494b8fc3b35ee952
      git reset --hard 1ac611c64eadbd93c5f5aba5494b8fc3b35ee952


  • 安装编译必需的依赖ninja-build

      [Bash shell] 纯文本查看 复制代码
      apt-get install ninja-build

  • 这里只编译Android arm64 release 的版本,等待编译完成即可在myengine/src/out/android_release_unopt_arm64/lib.stripped 下找到去符号的libflutter.so


      [AppleScript] 纯文本查看 复制代码
      cd myengine/src
      ./flutter/tools/gn --android --unoptimized --runtime-mode=release --android-cpu=arm64
      ninja -C out/android_release_unopt_arm64 -j 8



💡可能出现的问题
[Bash shell] 纯文本查看 复制代码
Exception: GOMA was specified but was not found. Set the GOMA_DIR environment variable, install goma at $HOME/goma following the instructions at [url]https://github.com/flutter/flutter/wiki/Compiling-the-engine[/url], or run this script with the --no-goma flag to do a non-goma-enabled build

[Bash shell] 纯文本查看 复制代码
myengine/src/flutter/tools/gn
line 279 注释
# gn_args.update(setup_goma(args))




#Patch libflutter.so辅助逆向工程在这里给出两种Patch方案:分别来自于
在这里给出两种方案的patch文件,可以根据下面的命令直接将patch应用到对应版本或者手动修改。

[Bash shell] 纯文本查看 复制代码
cp patch_3_1_0.patch myengine/src/flutter/third_party/dart
git apply -v patch_3_1_0.patch



首先是Ostorlab的Patch方案,部分代码存在问题,无法在实际测试中工作,所以进行了部分调整。关键部分在于修改code->untag()->monomorphic_unchecked_entry_point_ 将其改为instructions_table_.rodata()->entries()[instructions_table_.rodata()->first_entry_with_code +instructions_index_ - 1].pc_offset; 即为了获取具体类中方法相对于_kDartIsolateSnapshotInstructions的偏移,否则会拿到在实际运行过程中内存地址。在reFlutter的Patch方案中也需要这样patch。
[Diff] 纯文本查看 复制代码
diff --git a/runtime/vm/app_snapshot.cc b/runtime/vm/app_snapshot.cc
index dc08a4137d3..e4e0270e33f 100644
--- a/runtime/vm/app_snapshot.cc
+++ b/runtime/vm/app_snapshot.cc
@@ -1239,12 +1239,50 @@ class FunctionDeserializationCluster : public DeserializationCluster {
   }
 
   void PostLoad(Deserializer* d, const Array& refs, bool primary) {
+    OS::Print("Patch: Function List START\n");
     if (d->kind() == Snapshot::kFullAOT) {
       Function& func = Function::Handle(d->zone());
       for (intptr_t i = start_index_, n = stop_index_; i < n; i++) {
         func ^= refs.At(i);
         auto const code = func.ptr()->untag()->code();
         ASSERT(code->IsCode());
+
+        // auto& rCode = Code::Handle(code);
+        auto& rClass = Class::Handle(func.Owner());
+        auto& rLib = Library::Handle(rClass.library());
+        auto& rlibName = String::Handle(rLib.url());
+
+
+        JSONWriter js;
+        // Open empty object so output is valid/parsable JSON.
+        js.OpenObject();
+
+        js.PrintProperty("method_name", func.UserVisibleNameCString());
+        auto& codee = Code::Handle(func.CurrentCode());
+        js.PrintProperty("offset", static_cast<intptr_t>(codee.MonomorphicUncheckedEntryPoint()));
+        // js.PrintProperty("offset", (intptr_t)code->untag()->monomorphic_unchecked_entry_point_);
+        js.PrintProperty("library_url", rlibName.ToCString());
+        js.PrintProperty("class_name", rClass.UserVisibleNameCString());
+        js.CloseObject();
+
+        char* buffer = nullptr;
+        intptr_t buffer_length = 0;
+        js.Steal(&buffer, &buffer_length);
+
+        const auto &path = "/data/data/com.example.dino_run/dart.txt"; 
+        OS::Print("Using Path %s\n", path);
+        FILE* file;
+        // Write to the file
+        file = fopen(path, "a");
+        if (file != NULL) {
+          fwrite(buffer, sizeof(char), buffer_length, file);
+          fwrite("\n", sizeof(char), 1, file);
+          fclose(file);
+          OS::Print("Successfully wrote to the file '%s'.\n", path);
+        } else {
+          OS::Print("Failed to open the file '%s' for writing.\n", path);
+        }
+        
         if (!Code::IsUnknownDartCode(code)) {
           uword entry_point = code->untag()->entry_point_;
           ASSERT(entry_point != 0);
@@ -8613,8 +8651,9 @@ void Deserializer::ReadInstructions(CodePtr code, bool deferred) {
   code->untag()->entry_point_ = entry_point;
   code->untag()->unchecked_entry_point_ = entry_point + unchecked_offset;
   code->untag()->monomorphic_entry_point_ = monomorphic_entry_point;
-  code->untag()->monomorphic_unchecked_entry_point_ =
-      monomorphic_entry_point + unchecked_offset;
+  auto &offset =  instructions_table_.rodata()->entries()[instructions_table_.rodata()->first_entry_with_code +instructions_index_ - 1].pc_offset;
+  code->untag()->monomorphic_unchecked_entry_point_ = offset;
+      // monomorphic_entry_point + unchecked_offset;
 #else
   ASSERT(!deferred);
   InstructionsPtr instr = image_reader_->GetInstructionsAt(Read<uint32_t>());
diff --git a/tools/make_version.py b/tools/make_version.py
index a8322079dde..0a73f3e9806 100755
--- a/tools/make_version.py
+++ b/tools/make_version.py
@@ -85,7 +85,7 @@ def FormatVersionString(version,
         version_time = 'Unknown timestamp'
     version = version.replace('{{COMMIT_TIME}}', version_time)
 
-    snapshot_hash = MakeSnapshotHashString()
+    snapshot_hash = '7dbbeeb8ef7b91338640dca3927636de'
     version = version.replace('{{SNAPSHOT_HASH}}', snapshot_hash)
 
     return version


reFlutter的Patch方案更加复杂(进行部分调整,否则会crash),但是Dump出的数据较为美观规范,但是会出现Dump不完整的情况。
[Diff] 纯文本查看 复制代码
diff --git a/runtime/vm/app_snapshot.cc b/runtime/vm/app_snapshot.cc
index dc08a4137d3..a0342e3bd20 100644
--- a/runtime/vm/app_snapshot.cc
+++ b/runtime/vm/app_snapshot.cc
@@ -8614,7 +8614,7 @@ void Deserializer::ReadInstructions(CodePtr code, bool deferred) {
   code->untag()->unchecked_entry_point_ = entry_point + unchecked_offset;
   code->untag()->monomorphic_entry_point_ = monomorphic_entry_point;
   code->untag()->monomorphic_unchecked_entry_point_ =
-      monomorphic_entry_point + unchecked_offset;
+      instructions_table_.rodata()->entries()[instructions_table_.rodata()->first_entry_with_code + instructions_index_-1].pc_offset;
 #else
   ASSERT(!deferred);
   InstructionsPtr instr = image_reader_->GetInstructionsAt(Read<uint32_t>());
diff --git a/runtime/vm/class_table.cc b/runtime/vm/class_table.cc
index 3d5c3a89354..c277ae0b99a 100644
--- a/runtime/vm/class_table.cc
+++ b/runtime/vm/class_table.cc
@@ -4,6 +4,7 @@
 
 #include "vm/class_table.h"
 
+#include <cstdlib>
 #include <memory>
 
 #include "platform/atomic.h"
@@ -14,10 +15,11 @@
 #include "vm/object_graph.h"
 #include "vm/raw_object.h"
 #include "vm/visitor.h"
+#include <sys/stat.h>
 
 namespace dart {
 
-DEFINE_FLAG(bool, print_class_table, false, "Print initial class table.");
+DEFINE_FLAG(bool, print_class_table, true, "Print initial class table.");
 
 ClassTable::ClassTable(ClassTableAllocator* allocator)
     : allocator_(allocator),
@@ -194,7 +196,10 @@ void ClassTable::Validate() {
   }
 }
 
-void ClassTable::Print() {
+void ClassTable::Print()  { 
+  // OS::PrintErr("reFlutter");
+  char pushArr[160000]={0};
+
   Class& cls = Class::Handle();
   String& name = String::Handle();
 
@@ -205,7 +210,201 @@ void ClassTable::Print() {
     cls = At(i);
     if (cls.ptr() != nullptr) {
       name = cls.Name();
-      OS::PrintErr("%" Pd ": %s\n", i, name.ToCString());
+
+      auto& funcs = Array::Handle(cls.functions());
+      if (funcs.Length() > 1000) {
+        continue;
+      }
+      char classText[250000] = {0};
+      pushArr[0] = 0;
+      String& supname = String::Handle();
+      name = cls.Name();
+      strcat(classText, cls.ToCString());
+      Class& supcls = Class::Handle();
+      supcls = cls.SuperClass();
+      if (!supcls.IsNull()) {
+        supname = supcls.Name();
+        strcat(classText, " extends ");
+        strcat(classText, supname.ToCString());
+      }
+      
+      const auto& interfaces = Array::Handle(cls.interfaces());
+      auto& interface = Instance::Handle();
+      for (intptr_t in = 0; in < interfaces.Length(); in++) {
+        interface ^= interfaces.At(in);
+        if (in == 0) {
+          strcat(classText, " implements ");
+        }
+        if (in > 0) {
+          strcat(classText, " , ");
+        }
+        strcat(classText, interface.ToCString());
+      }
+      strcat(classText, " {\n");
+      const auto& fields = Array::Handle(cls.fields());
+      auto& field = Field::Handle();
+      auto& fieldType = AbstractType::Handle();
+      String& fieldTypeName = String::Handle();
+      String& finame = String::Handle();
+      // Instance& instance2 = Instance::Handle();
+      for (intptr_t f = 0; f < fields.Length(); f++) {
+        field ^= fields.At(f);
+        finame = field.name();
+        fieldType = field.type();
+        fieldTypeName = fieldType.Name();
+        strcat(classText, "  ");
+        strcat(classText, fieldTypeName.ToCString());
+        strcat(classText, " ");
+        strcat(classText, finame.ToCString());
+        if (field.is_static()) {
+          // instance2 ^= field.StaticValue();
+          strcat(classText, " = ");
+          // strcat(classText, instance2.ToCString());
+          strcat(classText, " ;\n");
+        } else {
+          strcat(classText, " = ");
+          strcat(classText, " nonstatic;\n");
+        }
+      }
+      for (intptr_t c = 0; c < funcs.Length(); c++) {
+        auto& func = Function::Handle();
+        func = cls.FunctionFromIndex(c);
+        String& signature = String::Handle();
+        signature = func.InternalSignature();
+        auto& codee = Code::Handle(func.CurrentCode());
+        if (!func.IsLocalFunction()) {
+          strcat(classText, " \n  ");
+          strcat(classText, func.ToCString());
+          strcat(classText, " ");
+          strcat(classText, signature.ToCString());
+          strcat(classText, " { \n\n              ");
+          char append[70];
+          snprintf(
+              append, sizeof(append),
+              " Code Offset: _kDartIsolateSnapshotInstructions + 0x%016" PRIxPTR
+              "\n",
+              static_cast<uintptr_t>(codee.MonomorphicUncheckedEntryPoint()));
+          strcat(classText, append);
+          strcat(classText, "       \n       }\n");
+        } 
+        else {
+          auto& parf = Function::Handle();
+          parf = func.parent_function();
+          String& signParent = String::Handle();
+          signParent = parf.InternalSignature();
+          strcat(classText, " \n  ");
+          strcat(classText, parf.ToCString());
+          strcat(classText, " ");
+          strcat(classText, signParent.ToCString());
+          strcat(classText, " { \n\n          ");
+          char append[80];
+          snprintf(
+              append, sizeof(append),
+              " Code Offset: _kDartIsolateSnapshotInstructions + 0x%016" PRIxPTR
+              "\n",
+              static_cast<uintptr_t>(codee.MonomorphicUncheckedEntryPoint()));
+          strcat(classText, append);
+          strcat(classText, "       \n       }\n");
+        }
+      }
+      strcat(classText, " \n      }\n\n");
+      const Library& libr = Library::Handle(cls.library());
+      if (!libr.IsNull()) {
+        auto& owner_class = Class::Handle();
+        owner_class = libr.toplevel_class();
+        auto& funcsTopLevel = Array::Handle(owner_class.functions());
+        char pushTmp[1000];
+        String& owner_name = String::Handle();
+        owner_name = libr.url();
+        snprintf(pushTmp, sizeof(pushTmp), "'%s',", owner_name.ToCString());
+        if (funcsTopLevel.Length() > 0 && strstr(pushArr, pushTmp) == NULL) {
+          strcat(pushArr, pushTmp);
+          strcat(classText, "Library:");
+          strcat(classText, pushTmp);
+          strcat(classText, " {\n");
+        
+          for (intptr_t c = 0; c < funcsTopLevel.Length(); c++) {
+            auto& func = Function::Handle();
+            func = owner_class.FunctionFromIndex(c);
+            String& signature = String::Handle();
+            signature = func.InternalSignature();
+            auto& codee = Code::Handle(func.CurrentCode());
+            if (!func.IsLocalFunction()) {
+              strcat(classText, " \n  ");
+              strcat(classText, func.ToCString());
+              strcat(classText, " ");
+              strcat(classText, signature.ToCString());
+              strcat(classText, " { \n\n              ");
+              char append[70];
+              snprintf(append, sizeof(append),
+                       " Code Offset: _kDartIsolateSnapshotInstructions + "
+                       "0x%016" PRIxPTR "\n",
+                       static_cast<uintptr_t>(
+                           codee.MonomorphicUncheckedEntryPoint()));
+              strcat(classText, append);
+              strcat(classText, "       \n       }\n");
+            } else {
+              auto& parf = Function::Handle();
+              parf = func.parent_function();
+              String& signParent = String::Handle();
+              signParent = parf.InternalSignature();
+              strcat(classText, " \n  ");
+              strcat(classText, parf.ToCString());
+              strcat(classText, " ");
+              strcat(classText, signParent.ToCString());
+              strcat(classText, " { \n\n          ");
+              char append[80];
+              snprintf(append, sizeof(append),
+                       " Code Offset: _kDartIsolateSnapshotInstructions + "
+                       "0x%016" PRIxPTR "\n",
+                       static_cast<uintptr_t>(
+                           codee.MonomorphicUncheckedEntryPoint()));
+              strcat(classText, append);
+              strcat(classText, "       \n       }\n");
+            }
+          }
+          strcat(classText, " \n      }\n\n");
+        }
+      }
+      struct stat entry_info;
+      int exists = 0;
+      if (stat("/data/data/", &entry_info) == 0 &&
+          S_ISDIR(entry_info.st_mode)) {
+        exists = 1;
+      }
+      if (exists == 1) {
+        pid_t pid = getpid();
+        char path[64] = {0};
+        snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
+        FILE* cmdline = fopen(path, "r");
+        if (cmdline) {
+          char chm[264] = {0};
+          char pat[264] = {0};
+          char application_id[64] = {0};
+          fread(application_id, sizeof(application_id), 1, cmdline);
+          snprintf(pat, sizeof(pat), "/data/data/%s/dump.dart", application_id);
+          do {
+            FILE* f = fopen(pat, "a+");
+            fprintf(f, "%s", classText);
+            fflush(f);
+            fclose(f);
+            snprintf(chm, sizeof(chm), "/data/data/%s", application_id);
+            chmod(chm, S_IRWXU | S_IRWXG | S_IRWXO);
+            chmod(pat, S_IRWXU | S_IRWXG | S_IRWXO);
+          } while (0);
+          fclose(cmdline);
+        }
+      }
+      // if (exists == 0) {
+      //   char pat[264]={0};
+      //   do {
+      //     FILE* f = fopen(pat, "a+");
+      //     // if(classText)
+      //     fprintf(f, "%s", classText);
+      //     fflush(f);
+      //     fclose(f);
+      //   } while (0);
+      // }
     }
   }
 }
diff --git a/runtime/vm/dart.cc b/runtime/vm/dart.cc
index 8692cf38957..b4ae44d4fa0 100644
--- a/runtime/vm/dart.cc
+++ b/runtime/vm/dart.cc
@@ -977,7 +977,7 @@ ErrorPtr Dart::InitializeIsolateGroup(Thread* T,
     }
   }
 
-  if (FLAG_print_class_table) {
+  if (true) {
     IG->class_table()->Print();
   }
 
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 0438b7a5acb..aff62e94673 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -2076,7 +2076,7 @@ DART_EXPORT Dart_Handle Dart_RunLoop() {
     TransitionNativeToVM transition(T);
     return Api::NewHandle(T, I->StealStickyError());
   }
-  if (FLAG_print_class_table) {
+  if (true) {
     HANDLESCOPE(Thread::Current());
     I->group()->class_table()->Print();
   }
diff --git a/tools/make_version.py b/tools/make_version.py
index a8322079dde..0a73f3e9806 100755
--- a/tools/make_version.py
+++ b/tools/make_version.py
@@ -85,7 +85,7 @@ def FormatVersionString(version,
         version_time = 'Unknown timestamp'
     version = version.replace('{{COMMIT_TIME}}', version_time)
 
-    snapshot_hash = MakeSnapshotHashString()
+    snapshot_hash = '7dbbeeb8ef7b91338640dca3927636de'
     version = version.replace('{{SNAPSHOT_HASH}}', snapshot_hash)
 
     return version


#更新:Blutter工具使用具体使用方法参见https://github.com/worawit/blutter,下面说明可能存在的问题及解决方案,下面的图为使用该工具静态dump出的相关信息导入IDA中的分析结果,较为直观。

环境需求:g++ ≥ 13  cmake>= 3.19 最好使用Kali 2022及以上或Ubuntu22.04及以上
💡
git工具无法稀疏clone  的--sparse 参数,提示error: failed to initialize sparse-checkout 解决方案:升级git到高版本,不使用稀疏clone将会导致一辈子都拉不下dart SDK源码
[Bash shell] 纯文本查看 复制代码
add-apt-repository ppa:git-core/ppa 
apt update
apt install git

💡
CMake Error at CMakeLists.txt:3 (cmake_minimum_required):CMake 3.19 or higher is required.  You are running version 3.16.3解决:CMake 安装升级更高版本,CMake 3.19.2 or higher is required. You are running version 3.10.2


0x04 IDA符号恢复对于第一种patch方案,编写了IDApython脚本利用dump出的数据(改名格式为json)进行符号恢复,对于第二种reFlutter的Patch方案则存在现成的脚本
flutter-re-demo
[Python] 纯文本查看 复制代码
import json
import sys, os, json
import idaapi, ida_kernwin
import idautils
import idc
import ida_funcs


count = 0
dup_offset = {}

function_info_file = "dart.json"
function_info_file = ida_kernwin.ask_file(False, f"*.json", "Flutter snapshot function name filename")

def replace_str(str):
    SMALL_FUNC_MAPPING = {
        " ": "__space__",
        "=": "__equals__",
        "|": "__or__",
        "&": "__and__",
        "^": "__xor__",
        "+": "__add__",
        "*": "__mul__",
        "-": "__sub__",
        "<": "__inf__",
        ">": "__sup__",
        "%": "__mod__",
        "/": "__fiv__",
        "~": "__bnot__",
    }
    replaced_string = ''.join(SMALL_FUNC_MAPPING.get(char, char) for char in str)
    return replaced_string


with open(function_info_file, "r") as file:
    lines = file.readlines()

for line in lines:
    json_str = line.strip()
    count += 1
    try:
        data = json.loads(json_str)
        method_name = data["method_name"]
        offset = data["offset"]
        lib_name = data['library_url']
        class_name = data["class_name"]
        full_func_name = f"{class_name}::{method_name}"
    
            
        if offset not in dup_offset:
            dup_offset[offset] = [full_func_name]
        else:
            if full_func_name not in dup_offset[offset]:
                dup_offset[offset].append(full_func_name)
        
    except json.JSONDecodeError:
        print("Invalid JSON:", json_str)
        continue
    
exported_entries = {}
for entry in idautils.Entries():
    exported_entries[entry[3]] = entry[2]
      
base_addr = exported_entries['_kDartIsolateSnapshotInstructions']  
offset_info = {}

for each in dup_offset:
    if len(dup_offset[each]) == 1:
        func_addr = each + base_addr
        func_name = replace_str(dup_offset[each][0])
        offset_info[each] = dup_offset[each]
        if not ida_funcs.add_func(func_addr, idc.BADADDR):
            print(func_name + " : " + str(hex(func_addr)) + " failed make function")
        given_name = idc.set_name(func_addr, func_name)

for each in offset_info:
    func_addr = each + base_addr
    if not ida_funcs.add_func(func_addr, idc.BADADDR):
        func_name = offset_info[each]
        print(func_name + " : " + str(hex(func_addr)) + " failed make function again")


print('done')



0x05 参考



from:
Android-Flutter逆向 | LLeaves Blog (lleavesg.top)


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|时间戳|加密|CTF WiKi|CTF平台汇总|CTF show|ctfhub|棱角安全|rutracker|攻防世界|php手册|peiqi文库|CyberChef|猫捉鱼铃|手机版|小黑屋|cn-sec|IOTsec-Zone|在线工具|分享屋 ( 鲁ICP备2021028754号 )

GMT+8, 2024-9-19 09:48

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表