第1章:掉进兔子洞
首先,我将介绍Flutter堆栈的一些背景知识及其工作原理。
您可能已经知道:Flutter是从头开始构建的,具有自己的渲染管道和小部件库,从而使其真正跨平台,并具有一致的设计,无论在什么设备上运行都可以感觉到。
与大多数平台不同,flutter框架的所有基本渲染组件(包括动画,布局和绘画)都在中完全向您公开package:flutter
。
您可以从Wiki / The-Engine-architecture的官方架构图中看到这些组件
从逆向工程的角度来看,最有趣的部分是Dart层,因为这是所有应用程序逻辑所在的位置。
但是Dart层是什么样的?
Flutter将Dart编译为本机汇编代码,并使用尚未公开深入记录的格式,更不用说完全反编译和重新编译了。
为了进行比较,其他平台(例如React Native)仅捆绑了缩小的javascript,这很容易检查和修改,此外,Android上Java的字节码已被详细记录,并且有许多免费的反编译器。
尽管没有混淆(默认情况下)或加密,但是Flutter应用程序目前仍然非常难以逆向工程,因为它需要深入了解Dart内部知识才能刮擦表面。
从知识产权的角度来看,这使得Flutter变得非常出色,几乎可以防止窥探代码。
接下来,我将向您展示Flutter应用程序的构建过程,并详细说明如何对它产生的代码进行反向工程
快照
Dart SDK具有高度的通用性,您可以在许多不同的平台上以许多不同的配置嵌入Dart代码。
运行Dart的最简单方法是使用dart
可执行文件,该可执行文件像脚本语言一样直接读取dart源文件。它包括我们称为前端的主要组件(解析Dart代码),运行时(提供运行代码的环境)以及JIT编译器。
您还可以dart
用来创建和执行快照,这是Dart的预编译形式,通常用于加快常用的命令行工具(如pub
)的速度。
ping@debian:~/Desktop$ time dart hello.dart
Hello, World!
real 0m0.656s
user 0m0.920s
sys 0m0.084s
ping@debian:~/Desktop$ dart --snapshot=hello.snapshot hello.dart
ping@debian:~/Desktop$ time dart hello.snapshot
Hello, World!
real 0m0.105s
user 0m0.208s
sys 0m0.016s
如图所见,使用快照时,启动时间大大缩短。
默认的快照格式是kernel,相当于AST的Dart代码的中间表示形式。
在调试模式下运行Flutter应用时,Flutter工具会创建内核快照,并使用调试运行时+ JIT在您的android应用中运行该快照。这使您能够通过热重载在运行时调试应用程序并实时修改代码。
对于我们来说不幸的是,由于对RCE的关注日益增加,在移动行业中,使用您自己的JIT编译器已不受欢迎。iOS实际上阻止您完全执行像这样的动态生成的代码。
但是,还有两种类型的快照,app-jit
和app-aot
,它们包含编译后的机器代码,它们可以比内核快照更快地初始化,但它们不是跨平台的。
快照的最终类型app-aot
,仅包含机器代码,不包含内核。这些快照是使用中提供的gen_snapshots
工具生成的flutter/bin/cache/artifacts/engine/<arch>/<target>/
,稍后再介绍。
但是,它们不仅仅是Dart代码的编译版本,实际上,它们是在调用main之前VM堆的完整“快照”。这是Dart的一项独特功能,也是与其他运行时相比,其初始化速度如此之快的原因之一。
Flutter将这些AOT快照用于发行版本,您可以在使用以下内容构建的Android APK的文件树中看到包含它们的文件flutter build apk
:
ping@debian:~/Desktop/app/lib$ tree .
.
├── arm64-v8a
│ ├── libapp.so
│ └── libflutter.so
└── armeabi-v7a
├── libapp.so
└── libflutter.so
在这里,您可以看到两个libapp.so文件,它们是作为ELF二进制文件的a64和a32快照。
在gen_snapshots
这里输出ELF /共享对象的事实可能会引起误解,它不会将dart方法公开为可以在外部调用的符号。而是,这些文件是“群集快照”格式的容器,但在单独的可执行部分中包含编译的代码,以下是它们的结构:
ping@debian:~/Desktop/app/lib/arm64-v8a$ aarch64-linux-gnu-objdump -T libapp.so
libapp.so: file format elf64-littleaarch64
DYNAMIC SYMBOL TABLE:
0000000000001000 g DF .text 0000000000004ba0 _kDartVmSnapshotInstructions
0000000000006000 g DF .text 00000000002d0de0 _kDartIsolateSnapshotInstructions
00000000002d7000 g DO .rodata 0000000000007f10 _kDartVmSnapshotData
00000000002df000 g DO .rodata 000000000021ad10 _kDartIsolateSnapshotData
AOT快照采用共享对象形式而不是常规快照文件的原因是因为gen_snapshot
在应用启动时需要将生成的机器代码加载到可执行内存中,而最好的方法是通过ELF文件。
有了这个共享.text
库,链接器将把该节中的所有内容加载到可执行内存中,从而允许Dart运行时随时调用它。
您可能已经注意到有两个快照:VM快照和Isolate快照。