前言 几个星期前, 出于某些原因我想逆向某个APK文件, 和平常一样, 我使用apktool/dex2jar/jd-gui三件套开始工作. 出乎意料的是, 我没有在classes.dex里面找到任何程序相关的逻辑, 取而代之的是一些”flutter”的代码段.
初探 flutter是谷歌开发的跨平台框架, 初略的探索之后, 我得知程序的逻辑都存在于”libapp.so”文件中.
1 2 3 4 5 6 7 8 9 10 11 12 13 ➜ tree lib lib ├── arm64-v8a │ ├── libapp.so │ └── libflutter.so ├── armeabi-v7a │ ├── libapp.so │ └── libflutter.so └── x86_64 ├── libapp.so └── libflutter.so 3 directories, 6 files
APK解开后, lib目录下面会有两个库文件, “libflutter.so”是框架的运行时支持, “libapp.so”则是逻辑层编译而成的产物.
1 2 3 4 5 6 7 8 9 10 ➜ readelf --symbols --wide libapp.so Symbol table '.dynsym' contains 6 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00001000 12 FUNC GLOBAL DEFAULT 1 _kDartBSSData 2: 00002000 9616 FUNC GLOBAL DEFAULT 2 _kDartVmSnapshotInstructions 3: 00005000 24400 FUNC GLOBAL DEFAULT 3 _kDartVmSnapshotData 4: 0000b000 0x1edf30 FUNC GLOBAL DEFAULT 4 _kDartIsolateSnapshotInstructions 5: 001f9000 0x175bb0 FUNC GLOBAL DEFAULT 5 _kDartIsolateSnapshotData
查看导出符号, 只有上面四个, 但从名称上分析并不像函数. 接下来开始使用IDA分析: 符号”_kDartVmSnapshotData”看起来的确不像是导出函数, 更像是序列化之后的数据, 应该是提供给dart虚拟机解释执行的.
探索 我搜索了很久, 几乎没有关于flutter逆向相关的文章, 只有reverse-engineering-flutter-apps-part-1 这篇文章进行了剖析.
快照 “libapp.so”实际上是dart编译生成的快照文件, 这种快照格式称为”AOT”, 上面提到的四个导出符号就是快照数据的起始偏移:
“_kDartVmSnapshotData”: 虚拟机运行所需的Object等相关数据
“_kDartVmSnapshotInstructions”: 虚拟机运行所需指令相关数据
“_kDartIsolateSnapshotData”: 逻辑层运行所需的Object等相关数据
“_kDartIsolateSnapshotInstructions”: 逻辑层运行所需指令相关数据
官方没有文档公布快照AOT快照的数据格式, 不过幸好flutter和dart都是开源的, 我们可以调试AOT生成/加载的全过程, 就能知道快照里存了什么数据, 在逆向过程中又能如何利用这些数据.
编译 文章中提到了如何单纯的将dart文件转变成”libapp.so”:
1 2 ~/flutter/bin/cache/dart-sdk/bin/dart ~/flutter/bin/cache/artifacts/engine/linux-x64/frontend_server.dart.snapshot --sdk-root ~/flutter/bin/cache/artifacts/engine/common/flutter_patched_sdk_product/ --strong --target=flutter --aot --tfa -Ddart.vm.product=true --output-dill app.dill main.dart gen_snapshot --causal_async_stacks --deterministic --snapshot_kind=app-aot-elf --elf=libapp.so --strip app.dill
上面使用的是flutter sdk中包含的dart相关工具, 我测试之后发现只需要dart sdk就可以完成AOT的生成. 那么接下来, 我们需要编译一个带有调试信息的dart sdk:
1 2 3 4 5 6 7 8 9 10 11 # dev tool git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git export PATH="$PATH:$PWD/depot_tools" # fetch source mkdir dart-sdk; cd dart-sdk fetch dart # build cd sdk ./tools/build.py -m debug --no-goma -a x64 --debug-opt-level 0 all
查看编译生成的产物:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 ➜ tree -L 1 . ├── - ├── app.dill ├── args.gn ├── build.ninja ├── build.ninja.d ├── compressed_observatory_archive.tar ├── dart ├── dart2js_nnbd_strong_outline.dill ├── dart2js_nnbd_strong_platform.dill ├── dart2js_nnbd_strong_platform.dill.d ├── dart2js_outline.dill ├── dart2js_platform.dill ├── dart2js_platform.dill.d ├── dart2js_server_nnbd_strong_outline.dill ├── dart2js_server_nnbd_strong_platform.dill ├── dart2js_server_nnbd_strong_platform.dill.d ├── dart2js_server_outline.dill ├── dart2js_server_platform.dill ├── dart2js_server_platform.dill.d ├── dartdevc.dill ├── dartdevc.js ├── dartdevc.js.map ├── dartdev.dill ├── dart_precompiled_runtime ├── dart_precompiled_runtime_product ├── dart-sdk ├── ddc_outline.dill ├── ddc_outline_sound.dill ├── ddc_platform.dill ├── ddc_platform.dill.d ├── ddc_platform_sound.dill ├── ddc_platform_sound.dill.d ├── dds.dart.snapshot ├── dev_compiler ├── exe.stripped ├── gen ├── gen_kernel_bytecode.dill ├── gen_snapshot ├── gen_snapshot_fuchsia ├── gen_snapshot_host_targeting_host ├── gen_snapshot_product ├── gen_snapshot_product_fuchsia ├── gen_snapshot_product_host_targeting_host ├── icudtl.dat ├── icudtl_extra.dat ├── kernel-service.dart.snapshot ├── libapp.so ├── libentrypoints_verification_test_extension.so ├── libentrypoints_verification_test_extension.so.TOC ├── libffi_test_dynamic_library.so ├── libffi_test_dynamic_library.so.TOC ├── libffi_test_functions.so ├── libffi_test_functions.so.TOC ├── libsample_extension.so ├── libsample_extension.so.TOC ├── libtest_extension.so ├── libtest_extension.so.TOC ├── obj ├── observatory_archive.tar ├── offsets_extractor ├── offsets_extractor_precompiled_runtime ├── out.js ├── out.js.deps ├── out.js.map ├── process_test ├── run_vm_tests ├── toolchain.ninja ├── vm_outline_strong.dill ├── vm_outline_strong_product.dill ├── vm_outline_strong_stripped.dill ├── vm_platform_strong.dill ├── vm_platform_strong.dill.d ├── vm_platform_strong.dill.S ├── vm_platform_strong_product.dill ├── vm_platform_strong_product.dill.d ├── vm_platform_strong_stripped.dill ├── vm_platform_strong_stripped.dill.d └── zlib_bench 5 directories, 73 files
dart-sdk子目录就和我们在官网下载到的版本一样, 但是它里面的程序符号表已被去除, 我们无法基于源码调试, 我们只能使用顶层那些散乱的产物. 根据官网 的描述, 使用dart2native就能生成AOT快照:
1 dart2native bin/main.dart -k aot
在我们的目标产物中, 查看dart2native内容, 发现它只是个脚本文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #!/usr/bin/env bash # Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file # for details. All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. # Run dart2native.dart.snapshot on the Dart VM function follow_links() { file="$1" while [ -h "$file" ]; do # On Mac OS, readlink -f doesn't work. file="$(readlink "$file")" done echo "$file" } # Unlike $0, $BASH_SOURCE points to the absolute path of this file. PROG_NAME="$(follow_links "$BASH_SOURCE")" # Handle the case where dart-sdk/bin has been symlinked to. BIN_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)" SNAPSHOTS_DIR="${BIN_DIR}/snapshots" DART="$BIN_DIR/dart" exec "$DART" "${SNAPSHOTS_DIR}/dart2native.dart.snapshot" $*
脚本使用dart执行dart2native.dart.snapshot, 说明dart2native.dart.snapshot是由dart编写而成的, 具体的源码可以查看github仓库 . 通过阅读源码 , 发现它其实是通过调用gen_snapshot进行的快照生成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 final String kernelFile = path.join(tempDir.path, 'kernel.dill'); final kernelResult = await generateAotKernel(Platform.executable, genKernel, productPlatformDill, sourcePath, kernelFile, packages, defines, enableExperiment: enableExperiment); if (kernelResult.exitCode != 0) { stderr.writeln(kernelResult.stdout); stderr.writeln(kernelResult.stderr); await stderr.flush(); throw 'Generating AOT kernel dill failed!'; } if (verbose) { print('Generating AOT snapshot.'); } final String snapshotFile = (outputKind == Kind.aot ? outputPath : path.join(tempDir.path, 'snapshot.aot')); final snapshotResult = await generateAotSnapshot(genSnapshot, kernelFile, snapshotFile, debugPath, enableAsserts, extraOptions); if (snapshotResult.exitCode != 0) { stderr.writeln(snapshotResult.stdout); stderr.writeln(snapshotResult.stderr); await stderr.flush(); throw 'Generating AOT snapshot failed!'; }
先使用dart生成kernel.dill, 然后使用gen_snapshot将kernel.dill转成AOT, 这也与文章中提到的方法一致.
生成 进入dart-sdk子目录, 编辑hello.dart:
1 2 3 void main() { print('Hello, World!'); }
接着生成kernel.dill:
1 ./bin/dart bin/snapshots/gen_kernel.dart.snapshot --platform lib/_internal/vm_platform_strong_product.dill --aot -Ddart.vm.product=true -o kernel.dill hello.dart
使用gen_snapshot生成AOT快照:
1 ./bin/utils/gen_snapshot --snapshot_kind=app-aot-elf --elf=libapp.so kernel.dill
经历重重关卡, 我们成功将dart源码转成了AOT快照, 那么接下来, 我们只需要使用gdb一步步跟踪快照的生成.