如何逆向Flutter应用(反编译)

目前大多数使用Flutter的应用都是采用add2app的方式,在APP中与Flutter相关的内容主要有FlutterEngine、APP产物、资源文件。我们可以在应用市场上寻找一个接入Flutter的应用做实验。(apk可在各大应用市场下载,ipa下载可以在mac上安装Apple Configurator 2进行),apk和ipa中flutter相关产物目录如下:

iOS包文件为ipa,下载后将其后缀重命名为zip进行解压,解压后Payload下即可看到应用文件夹,其中FlutterEngine、APP产物、资源文件分别在如下位置:

Android包文件为apk,下载后将其后缀重命名为zip进行解压,其中FlutterEngine、APP产物、资源文件分别在如下位置:

FlutterEngine各个app都是使用官方或者在官方基础上进行修改,差别不多,我们暂不关心这部分的逆向。资源文件多是图片,字体等无需逆向即可查看的资源。我们主要关心的是使用Dart编写的业务逻辑或者某些框架代码,这部分代码在APP产物中。即:App.framework/App 或 armeabi/libapp.o这两个文件都是动态库,我们先简单看看里面包含什么?

可以看到无论是Android还是iOS,Dart App产物中都包含4个程序段。(来自https://github.com/flutter/flutter/wiki/Flutter-engine-operation-in-AOT-Mode

  • '_kDartVmSnapshotData': 代表 isolate 之间共享的 Dart 堆 (heap) 的初始状态。有助于更快地启动 Dart isolate,但不包含任何 isolate 专属的信息。

  • '_kDartVmSnapshotInstructions':包含 VM 中所有 Dart isolate 之间共享的通用例程的 AOT 指令。这种快照的体积通常非常小,并且大多会包含程序桩 (stub)。

  • '_kDartIsolateSnapshotData':代表 Dart 堆的初始状态,并包含 isolate 专属的信息。

  • '_kDartIsolateSnapshotInstructions':包含由 Dart isolate 执行的 AOT 代码。

看了上面可能还是一脸懵o((⊙﹏⊙))o,为什么分四块,Data与Instructions,Vm与Isolate是什么?为什么使用Snapshot(快照)命名。关于这些问题,推荐一篇博客https://mrale.ph/dartvm/ 。Data与Instructions,Vm与Isolate这些概念两两组合,正好对应上面四个段。也就是VmData、VmInstructions、IsolateData、IsolateInstructions。

先说一下Data与Instructions。首先我们知道的是Flutter编译运行在app上分为JIT和AOT模式,线上只能使用AOT模式,也就是Flutter引入的DartVM包含了执行AOT产物的功能。为了与JIT模式兼容,DartVM采用了所谓快照的方式,即JIT运行时编译后的基本结构与AOT编译的基本结构相同。将类信息、全局变量、函数指令直接以序列化的方式存在磁盘中,称为Snapshot(快照)。

由于快照的序列化格式针对性的为读取速率做了设计,从快照读取也大大提高代码的加载速度(创建所需类信息、全局数据等,可以类比OC Runtime启动加载元类、类信息等)。最开始快照中是不包含机器代码的(即函数体内部的执行逻辑),后来随着AOT模式的开发这部分被加入到快照中了,这些后来者也就是前面说的Instructions。

这里要补充的是,Instructions指的是可执行汇编指令,在.o文件中必须放在text段里,标记为可执行(否则iOS无法加载并执行它)。类信息、全局变量这些内容可以放在data端作为普通数据被加载。(字节的优化50%包体积也是基于此,有兴趣可以看一下文章:https://juejin.im/post/6844904014170030087)。

接着说DartVmSnapshot 与DartIsolateSnapshot。这就涉及Data虚拟机是如何运行业务代码。虚拟是Data代码运行的载体,VM中运行的逻辑都跑在一个抽象的叫做Isolate(隔离)的实体中。你可以把Isolate当做OC里一个带有Runloop的Thread看待(至于他们之间的关系又是一个令人头疼的面试题,这里不展开了)。简要来说Isolate中维护了堆栈变量,函数调用栈帧,用于GC、JIT等辅助任务的子线程等, 而这里的堆栈变量就是要被序列化到磁盘上的东西,即IsolateSnapshot。此外像dart预置的全局对象,比如null,true,false等等等是由VMIsolate管理的,这些东西需序列化后即VmSnapshot。

到这里大致了解Flutter APP产物中的结构。那如何读取他们呢?我们可以从clustered_snapshot.cc中的FullSnapshotReader:: 函数看起,看他是如何反序列化的。

要看懂这部分也是十分费力,另一个大神的分析文章可能会为我们带来很多启示:https://blog.tst.sh/reverse-engineering-flutter-apps-part-1/

我们要看如何读取RawObject对象

每个对象均以包含以下标记的uint32_t开头:

原则上我们自己可以写一个读取的程序进行分析,但是网上有一个使用Python写好的读取程序(只支持读取ELF格式文件,也就是只支持Android包产物的分析):https://github.com/hdw09/darter 基于这个读取工具提供的API我们可以写一个导出应用所有类定义的工具。

这个脚本最终会提取所有指定文件的源码,其中对友商app其中一个类的导出结果如下:

其中标注了类对象 与函数的索引,可以在控制台使用s.refs[xxxxx].x继续跟查。

参考链接


如何逆向Flutter应用(反编译)

Dart Mixin介绍

关于 Dart mixin 的一些理解。理解 mixin 概念的关键在于理解中间类。

Mixins are a way of reusing code in multiple class hierarchies

先来看一个简单例子:

PilotedCraft 拥有 astronauts 字段和 describeCrew() 方法。

mixin 是什么?

维基百科中这样定义 mixin:

In object-oriented programming languages, a Mixin is a class that contains methods for use by other classes without having to be the parent class of those other classes.

即,mixin 是另外一个普通类,我们可以在不继承这个类的情况下从这个类”借用”方法和变量。

Support for the mixin keyword was introduced in Dart 2.1. Code in earlier releases usually used abstract class instead.

从这个角度来讲,mixin 不过是 abstract class

Java tries to make up for this by using Interfaces, but that is not as useful or flexible as mixins.

从这个角度来讲,可以认为 mixin 是带实现的接口。

小节

  • mixin 有点类似 abstract class
  • mixin 有点类似 interface
  • 不能继承 mixin
  • 可以使用 mixinabstract classclass 来作为 mixin

如何使用 mixin?

使用 mixin 的方法很简单:with 关键字后面跟上 mixin 的名字即可。

实现 mixin 的方法同样也很简单:创建一个继承自 Object 的类并且不要声明构造方法。如果想让 mixin 作为普通类使用,使用 class 关键字;如果不想让 mixin 作为普通类使用,使用 mixin 关键字代替 class

on 的用法

The keyword on is used to restrict our mixin’s use to only classes which either extends or implements the class it is declared on. In order to use the on keyword, you must declare your mixin using the mixin keyword

则有如下错误提示:

Error: 'Object' doesn't implement 'B' so it can't be used with 'Y'.

on 关键字限制了 Y 的使用范围:Y 只能用于继承或实现了 B 的类。修复方式是让 Q 继承自 B

mixin 解决了什么问题?

mixin 解决了多重继承中的 Deadly Diamond of Death(DDD) 问题。

多重继承问题简单描述。各个类的继承关系如下:

问题来了,当调用 Musician.perform() 时,到底会调用哪个 perform() 方法是模糊的

来看 mixin 如何解决这个问题。见 Dart for Flutter : Mixins in Dart - Flutter Community - Medium

现在,当调用 Musician.perform() 时,到底会调用哪个 perform() 方法是确定的。在这里是调用 Singer.perform()

mixin 有一套明确的机制来选择调用哪个方法。

假设 Musician 类使用多个 mixin (DancerSinger)。该类有个方法名为 perform()Musician 类继承自 Performer 类。

  • 首先,将 Performer 类置于栈顶
  • 其次,后声明的 mixin 优先于先声明的 mixin。按顺序将 mixin 置于栈中,在这里分别是 DancerSinger
  • 最后,将 Musician 类自己置于栈中。Musician 类中的 perform() 被优先调用

Dart 使用的是单重继承 (Java 也是单重继承,C++ 是多重继承)。多重继承更为强大,但会引起 Deadly Diamond of Death(DDD) 问题。

Java 使用接口(interface)来部分实现多重继承。多重继承的问题是需要在每个类中实现接口(interface),所以并不是一个好的方案。(实际上 Java 已经通过默认方法修复了这个问题)

所以 Dart 中就有了 mixin。

理解 mixin

Mixins in Dart work by creating a new class that layers the implementation of the mixin on top of a superclass to create a new class — it is not “on the side” but “on top” of the superclass, so there is no ambiguity in how to resolve lookups.
Mixins is not a way to get multiple inheritance in the classical sense. Mixins is a way to abstract and reuse a family of operations and state. It is similar to the reuse you get from extending a class, but it is compatible with single-inheritance because it is linear.
StackOverflow

以上这段代码输出 BA

从语义上讲以上这段代码等同于:

继承结构图是这样的:

Since each mixin application creates a new class, it also creates a new interface (because all Dart classes also define interfaces). As described, the new class extends the superclass and includes copies of the mixin class members, but it also implements the mixin class interface.
In most cases, there is no way to refer to that mixin-application class or its interface; the class for Super with Mixin is just an anonymous superclass of the class declared like class C extends Super with Mixin {}. If you name a mixin application like class CSuper = Super with Mixin {}, then you can refer to the mixin application class and its interface, and it will be a sub-type of both Super and Mixin.

理解 mixin 的关键在于它是线性的。

使用场景

  • 在没有共同父类的各个类中共享代码时
  • 在父类中实现某种方法无意义时

在 Dart 语言中,我们经常可以看到对 mixin 关键字的使用,根据字面理解,就是混合的意思。那么,mixin 如何使用,它的使用场景是什么呢。

继续阅读Dart Mixin介绍