Flutter与so文件动调

这是2024-CtfNewStar的Week5 Ohn_flutter!!!一个Flutter的安卓题目,这里只讲如何恢复符号表和so文件动调,具体WP请看CtfNewStar

Flutter是谷歌的移动UI框架,Flutter 使用编译型语言 Dart 开发,代码会被编译为原生机器码(而非通过 JavaScript 桥接),运行速度接近原生应用。

工具准备

blutter 环境配置

前置知识

Flutter 应用的结构

Flutter 应用的核心文件通常包括:

  • libapp.so(Android)或 App.framework(iOS):包含 Dart 编译后的原生代码。

  • flutter_assets/:存放资源文件(图片、字体、配置文件等)。

  • isolate_snapshot_datavm_snapshot_data:Dart 虚拟机的快照数据(用于启动应用)。

  • libflutter.so(Android):Flutter 引擎库。

恢复符号表

配置好blutter环境以后,打开x64 Native Tools Command Prompt(这是visual studio2022 自带的一个终端,它可以在Visual Studio文件夹中找到)

然后运行blutter.py并提供libapp.solibflutter.so的文件夹路径以及输出文件夹路径

1
python .\blutter.py ..\chall\lib\arm64-v8a\ .\output

以这道题目为例

1
python blutter.py C:\Users\LENOVO\Desktop\app-release\lib\arm64-v8a C:\Users\LENOVO\Desktop\app-release\lib\arm64-v8a\output

后面的输出路径如果没有在给出的位置找到相关文件夹的话会自动生成

要注意一下so文件架构是否支持

然后你会得到

在”asm\ohn_flutter”目录下,把这个文件夹在vscode里面打开,我们可以看到汇编形式的源代码,可以尝试用关键的函数地址搜索,找到程序的主逻辑处。

开始恢复符号表

blutter 反编译得到 asm 文件夹和 ida_script 文件夹,ida_script 在 IDA 里先加载 ida_dart_struct.h 头文件再运行 addNames.py 脚本可得到大部分符号

具体步骤

1. 加载二进制文件到 IDA

2. 导入 Dart 结构头文件

  1. 在 IDA 菜单栏点击 File → Load file → Parse C header

  2. 选择 ida_script/ida_dart_struct.h

  3. 勾选 Add to imported types,点击 Parse
    → 成功后会在 Structures 窗口看到 Dart 相关结构(如 Dart_IsolateDart_Code 等)

3. 运行符号恢复脚本

  1. 在 IDA 菜单栏点击 File → Script file

  2. 选择 ida_script/addNames.py

  3. 等待脚本执行完成

完成后仍然有大量混淆用IDA自带的去goomba插件(右键-De-obfuscate)或D810都可以去除部分混淆

SO文件调试

步骤一:将 android_server 文件 push 到手机中并执行

复制IDA目录下的\dbgsrv目录下的android_serverandroid_server64到真机中并执行:

1
2
3
4
5
6
7
8
//复制到真机中  
adb push android_server /data/local/tmp
adb push android_server64 /data/local/tmp
//修改应用权限使其可执行
Cd /data/local/tmp
chmod 777 android_server android_server64
//执行所需要的版本
./android_server64

当然你也可以通过指令进行h重命名

1
adb shell "mv /data/local/tmp/android_server32 /data/local/tmp/新文件名"

步骤二:端口转发

默认启动端口为23946。

另开一个cmd执行如下指令:

1
adb forward tcp:23946 tcp:23946

第一个端口对应电脑端,第二个端口对应手机端。

执行后,将会通过电脑端的23946端口转发数据到手机端的23946端口。

步骤三:启动要调试的应用程序

你可以直接在手机进行启动,或者输入以下命令进行启动

1
2
3
4
adb shell am start -D -n 包名/活动名  
//例如
//adb shell am start -D -n
com.example.ohn_flutter/.MainActivity

步骤四:使用IDA附加进程

用IDA打开想要调试的so库,调试器选择Remote ARM Linux/Android debugger

在菜单栏的Debugger -> Debugger options中设置程序暂停的时机

在菜单栏的Debugger -> Process options中设置主机和端口

默认端口127.0.0.1

在菜单栏的Debugger -> Attach to Process中选择要调试的进程进行附加

需要注意的是,在安装 APK 时,检查对应 apk 的属性,需要满足以下两个属性均为 true,可以通过使用 apktool 解包后修改重新打包实现,也可以使用面具模块或 xposed 模块实现。

1
2
android:debuggable="true"  
android:extractNativeLibs="true"

果不想这么复杂,直接把 so 复制到 /data/app/包名/lib 里面,就会优先加载这个目录里面的 so,或者直接在 IDA 中的 module 中选择 base.apk,然后定位到需要调试的地址,然后手动按 P 将其转为 code,然后就可以下断点调试。

在 IDA 中直接 F9 运行,此时 APP 将会运行起来,IDA 将会弹出下列界面,点击 same 就可以了。

为什么要将两个属性设置为True呢?

android:extractNativeLibs="true"

安装 APK 时系统会把里面的 .so 解压到磁盘,例如:

1
/data/app/com.xxx.xxx-xxxx/lib/arm64/libtarget.so
  • 进程加载时,linker 会从这个路径用 mmap 映射到内存。
  • 因为有实际的文件路径,IDA 动态调试时 attach 到进程,就能看到这个映射关系(maps 里会有一行带 libtarget.so 的路径)。
  • 这样 IDA 就可以直接自动关联你的 .so 文件,地址对齐也正确。

android:extractNativeLibs="false"

  • 这种情况下,APK 安装后不会把 .so 文件解压出来,而是 直接在 APK 压缩包里

    当程序运行时,linker 会用一种特殊的方式:

    • 打开 base.apk
    • 定位 zip 里的 .so
    • mmap 那段压缩包里的数据
    • 然后根据 ELF 头信息建立 soinfo 结构,把段映射到内存里
  • 所以内存里依然有 ELF 格式的 .so,但是磁盘上没有独立文件,/proc/<pid>/maps 里只会显示类似:

1
/data/app/com.xxx.xxx-xxxx/base.apk!lib/arm64/libtarget.so
  • 因为没有真实文件,IDA attach 的时候就没法直接知道应该如何和你本地的 libtarget.so 对齐。

android:debuggable="true" 这个属性呢?

这个属性和前面 extractNativeLibs 不一样,它完全是 跟调试器 attach 权限相关的

  • 它是 AndroidManifest.xml 里的 application 节点属性。

  • 决定了这个应用能不能被外部调试器(如 IDA、gdb、Android Studio Debugger)attach。