吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4967|回复: 54
收起左侧

[原创] 从零开始的 Windows Flutter x64 应用逆向

  [复制链接]
MicroBlock 发表于 2025-11-8 03:08
本帖最后由 MicroBlock 于 2025-11-8 03:21 编辑

闲来无事,想研究一下之前一直都看到就放弃的 Flutter,看看是不是真的有那么困难。

Flutter 运行机制初探

在开始逆向之前,我们先了解一下 Flutter 的基本运行原理。
查阅资料后发现,Flutter 在 Release 模式下使用 AOT(Ahead-Of-Time)编译,将代码直接编译成原生二进制;只有在 Debug 模式下才会使用 JIT(Just-In-Time)运行。
我原本以为 Flutter 始终使用 JIT 运行。网上很多人说"Flutter 相当于自带 VMP",其实这种说法并不准确。

现有工具的局限性

目前市面上已有的 Flutter 逆向工具,比如 reFlutter 和 blutter,都只支持安卓和 iOS 平台的 ARM 架构,对于 Windows 平台和 x64 架构完全没有支持。这就是为什么我们需要自己动手打造工具来从内存中提取所需信息,也是本文标题"从零开始"的由来(笑)。

我选择的研究目标是 Reqable,版本为 3.0.22。

目标程序初步分析

打开 Reqable 的安装目录,几个关键的二进制文件引起了我的注意:

  • flutter_windows.dll - 这相当于安卓平台下的 libflutter.so,是 Flutter 引擎的 Windows 版本
  • Reqable.exe - 主要执行文件,实际上只是个启动器
  • data/app.so - 包含实际的 Dart 编译代码

首先,我使用 ImHex 查看了 flutter_windows.dll,直接搜索 +0000

从这里可以看到,样本使用的 Dart 版本是 3.3.4,Snapshot Hash 是 ee1eb666c76a5cb7746faf39d0b97547

为了确认版本信息,我检查了 app.so 中的魔数 0xdcdcf5f5 后面的数据:

同时还能看到编译配置信息:

product no-code_comments dwarf_stack_traces_mode no-lazy_dispatchers dedup_instructions no-tsan no-asserts x64 windows no-compressed-pointers null-safety

平台差异分析

要开发适用于 Windows x64 平台的 Flutter 逆向工具,首先需要理解它与 Android ARM 平台的关键差异。

Flutter 使用了自己定制的一套 ABI,它会占用两个通用寄存器来存储上下文相关的数据:一个指向 Object Pool(对象池),另一个指向 dart::Thread。这对于逆向工作来说是个很好的突破口 - 对于 Dart 这种虚拟机语言,只要我们能获取到它的堆数据,就能从中提取出大部分函数、字面量等信息。

因此,了解 x64 平台下 Flutter 的特殊 ABI 实现就变得至关重要。

深入 Dart 源码

幸运的是,Flutter 的 Dart 部分是开源的。Dart 的源码可以在 GitHub 上找到。由于目标应用使用的是 Dart 3.3.4,我们需要切换到对应的分支。

instructions_x64.cc 文件中,我找到了 Flutter 在 x64 平台下的 ABI 实现:Thread Pointer 存储在 R14 寄存器中,ObjectPool Pointer 存储在 R15 寄存器中。

理论上,我们只需要编写代码从 ObjectPool 中提取各种数据就可以了。听起来很简单,对吧?但实际情况要复杂得多。

数据读取的挑战

为了读取 ObjectPool 的数据,我们显然需要 ObjectPool 的内存结构。来分析一下这附近的源码:

显然,所有继承 Object 的对象都仅仅是一个壳子,真正的数据保存在 UntaggedXXX 里面。而 untag() 其实只是返回一个内部指针 - 1 的值,大概是某种神秘的优化。在 UntaggedObjectPool 中,我们可以看到其真正的结构:

整理后即为:

struct UntaggedObjectPool {
    AtomicBitFieldContainer<uword> tags_;  // 8 bytes, 继承自 UntaggedObject
    intptr_t length_;                      // 8 bytes
    Entry entries[length_];                // 变长数组
    uint8_t* entry_bits[length_];          // 类型信息位图
};

这是一个变长对象,使用 entry_bits 数组来存储每个条目的类型信息。

基于这个理解,我最初尝试编写了这样的代码:

auto proc = blook::Process::self();

for(auto& thread: proc->threads()) {
    auto context = thread.capture_context();
    if (memory_accessible(context->r14) && memory_accessible(context->r15)) {
        auto thread = reinterpret_cast<dart::Thread*>(context->r14);
        if (thread->os_thread()->id() == GetCurrentThreadId()) {
            std::println("Existing Flutter thread detected! Thread ID: {}", 
                        thread->os_thread()->id());
        }
    }
}

但这段代码完全不起作用。经过调试发现,Flutter 在与外部代码交互时,会把 ABI 恢复到正常状态。由于 Windows 系统暂停线程是在内核调度时完成的,我们捕获到的上下文都是 ABI 恢复正常的状态,自然找不到 R14 和 R15 中的关键数据。

巧妙的解决方案

既然无法通过线程上下文捕获获取数据,我决定换个思路:直接在 app.so 的代码中设置断点。

static blook::VEHHookManager::VEHHookHandler bp = 
    blook::VEHHookManager::instance().add_breakpoint(
        blook::VEHHookManager::HardwareBreakpoint{
            .address = reinterpret_cast<void*>(*ptr),
        },
        [](blook::VEHHookManager::VEHHookContext &ctx) {
            auto thread = reinterpret_cast<dart::Thread*>(
                ctx.exception_info->ContextRecord->R14);
            auto object_pool = (dart::ObjectPoolPtr)
                ctx.exception_info->ContextRecord->R15;
            // ...
        });

通过硬件断点,我们可以在 Flutter 执行 app.so 中的代码时捕获到正确的上下文。需要注意的是,R15 寄存器存储的不是指针,而是直接的 dart::ObjectPoolPtr 值,这个细节让我调试了好一会儿。

提取和分析数据

获取到 ObjectPool 后,我们就可以提取其中的函数和字符串信息了:

using namespace dart;

if (pool != ObjectPool::null()) {
    const intptr_t length = pool->untag()->length_;
    uint8_t *entry_bits = pool->untag()->entry_bits();

    for (intptr_t i = 0; i < length; i++) {
        auto entry_type = ObjectPool::TypeBits::decode(entry_bits[i]);
        if (entry_type == ObjectPool::EntryType::kTaggedObject) {
            auto &obj = pool.untag()->data()[i].raw_obj_;
            __try {
                if (obj->IsString()) {
                    std::println("String[{}]: content = {}", i, 
                                read_dart_str(dart::StringPtr(obj)));
                } else if (obj->IsFunction()) {
                    auto &func = dart::Function::Handle(dart::FunctionPtr(obj));
                    std::println("Function[{}]: name = {}, native_name = {}, addr = {:x}", 
                                i, read_dart_str(func.name()),
                                read_dart_str(func.native_name()), 
                                func.entry_point());
                } else {
                    std::println("Other Object[{}]: class id = {}", i, 
                                obj->GetClassId());
                }
            } __except (EXCEPTION_EXECUTE_HANDLER) {
                std::println("Failed to read object at index {}", i);
            }
        }
    }
}

输出结果类似这样:

遗憾的是,这个样本开启了代码混淆功能(参考 Flutter Obfuscation),所以即使获取到了函数名和函数地址,实际意义也不大。不过,我们现在至少可以把字符串和其他对象信息注释到 app.so 中,大大提升逆向效率。

实际应用示例

让我们看看 app.so 中是如何访问 ObjectPool 中的对象的:

随便找一个函数,可以看到它访问了 object_pool + 0x974f 处的数据。根据我们之前分析的内存布局:

  • 每个 entry 占 8 字节
  • entry 前面有 16 字节的头部信息
  • 由于 tag pointer 机制,实际地址需要加 1

我们可以计算出它访问的 object 索引:(0x974f - 16 + 1) / 8 = 4840,即第 4840 个对象。

于是,在写个脚本将字符串内容和函数名自动注解到对应地方以后,我们就基本恢复了对 app.so 逆向的能力了。当然,通过这种方式我们还可以获取到所有 dart 类型的信息。对于修改,除了在 Hook/Patch 时对 r14,r15 的使用要格外注意以外,也和普通native逆向没什么区别了。

免费评分

参与人数 40威望 +2 吾爱币 +139 热心值 +36 收起 理由
cnJingfeng + 1 + 1 我很赞同!
cd1688 + 1 + 1 谢谢@Thanks!
Qiaoyuexuan + 1 + 1 谢谢@Thanks!
抱歉、 + 1 用心讨论,共获提升!
zhyyhz + 1 + 1 热心回复!
mjhwzwg6 + 1 + 1 用心讨论,共获提升!
molquark + 1 热心回复!
sunnysab + 1 + 1 用心讨论,共获提升!
heinv123 + 1 用心讨论,共获提升!
zZ181549 + 1 + 1 我很赞同!
wxwx5519 + 1 + 1 收藏收藏
TX8250 + 1 我很赞同!
jccg2003 + 1 + 1 用心讨论,共获提升!
SSzx332600 + 1 我很赞同!
gaosld + 1 + 1 热心回复!
Tsquare + 1 + 1 谢谢@Thanks!
su1985 + 1 + 1 我很赞同!
TfiyuenLau + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
trustee + 1 + 1 谢谢@Thanks!
hello95271 + 1 + 1 我很赞同!
ttonott + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
kilkilo502 + 2 谢谢@Thanks!
InfiniteBoy + 1 + 1 用心讨论,共获提升!
taylorchen + 1 用心讨论,共获提升!
神奇的人鱼 + 1 + 1 我很赞同!
klop + 1 + 1 用心讨论,共获提升!
ioyr5995 + 1 + 1 我很赞同!
TFBOYS_GDW + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
爱飞的猫 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
ciker_li + 2 + 1 谢谢@Thanks!
lixyao + 1 谢谢@Thanks!
三滑稽甲苯 + 2 + 1 用心讨论,共获提升!
ZSDXAS + 1 + 1 我很赞同!
RifleNoob233 + 1 + 1 用心讨论,共获提升!
1052481067 + 1 + 1 牛的,弥补了 Flutter Windows 逆向的空白
buluo533 + 1 + 1 用心讨论,共获提升!
weidechan + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
无名 + 2 + 1
Mr.wuki + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
CloverYan + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

cdlanqs 发表于 2025-11-8 06:08
学习了,最近正在学习这方面的
daoye9988 发表于 2025-11-8 07:21
CloverYan 发表于 2025-11-8 08:05
学习了!LZ 的讲解深入浅出,容易理解,让我受益匪浅!
buluo533 发表于 2025-11-8 09:14
跟大佬学习了
dsanke 发表于 2025-11-8 09:35
跟大佬学起来
jiaoyidongxi 发表于 2025-11-8 11:10
好知识,收藏学习了!
laoser 发表于 2025-11-8 12:47
弥补flutter逆向知识
三滑稽甲苯 发表于 2025-11-8 13:13
太有用了,支持一下
666888tzq 发表于 2025-11-8 13:16
先收藏,有需要再细看。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-2-1 15:58

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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