混合应用逆向(Flutter / React Native)
SkillDev toolsFlutter/React Native 混合应用逆向。 触发词:Flutter、React Native、混合应用、dart、libapp.so、Hermes、Hermes 字节码、hbc、RN bundle、index.android.bundle、NativeModules、TurboModule、Metro
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the 混合应用逆向(Flutter / React Native) skill
What this skill tells your AI
The instructions your AI receives, as published by dslsdzc/rev-skills in .claude/skills/re-hybrid-app/SKILL.md and read by ahel’s review.
何时使用 / 何时不用
- 用:App 内发现 libflutter.so / libapp.so(Flutter)或 assets/index.android.bundle(React Native)
- 用:需要还原混合框架的业务逻辑(dart 快照 / JS bundle)
- 用:需要定位 JS↔原生桥接(MethodChannel / NativeModules)交换的敏感数据
- 不用:纯 Java/Kotlin 原生 App(走 [[re-apk]])
- 不用:纯原生 so 逻辑(走 [[re-binary-core]])
- 不用:需要运行时 hook——先静态定位再 [[re-frida]]
工具准备
静态分析可免沙箱([[re-analyze/platform-tips]] 最高原则);动态(reFlutter 重打包 / 运行)在受控设备 / 模拟器快照内。所有工具先验证再使用。
引擎识别 —— jadx / unzip(复用 [[re-apk]])
unzip -l app.apk看 assets/ 与 lib/ 结构即可判引擎,跨 OS 安装见 [[re-apk]] 工具准备
blutter —— Flutter AOT 快照静态还原(libapp.so)
- 安装:
git clone https://github.com/worawit/blutter && cd blutter——C++20 项目,需较新编译器(g++>=13 / Clang>=16)+ cmake/ninja/pkg-config/libicu/libcapstone + pyelftools/requests- Debian/Ubuntu:
apt install python3-pyelftools python3-requests git cmake ninja-build build-essential pkg-config libicu-dev libcapstone-dev - Fedora:
dnf install gcc-c++ clang cmake ninja-build pkgconf-pkg-config libicu-devel capstone-devel python3-pyelftools python3-requests git - Arch:
pacman -S gcc cmake ninja pkgconf icu capstone python-pyelftools python-requests git - macOS:
brew install cmake ninja pkg-config icu4c capstone+pip3 install pyelftools requests
- Debian/Ubuntu:
- 用法:
python3 blutter.py libapp.so <输出目录>(自动检测 Dart 版本,必要时自动下载 Dart 源码编译引擎)——目前支持 Android arm64 libapp.so 与较新 Dart 版本 - 产物:
asm/(带符号反汇编)、objs.txt(对象池对象完整 dump)、pp.txt(对象池中所有 Dart 对象)、blutter_frida.js(Frida 脚本模板) - 验证: 输出目录出现
pp.txt且包含目标 App 类名
reFlutter —— Flutter 动态(重打包 + 流量截获)
- 安装:
pip3 install reflutter(Python 3,依赖 frida-tools) - 用法:
reflutter main.apk→ 输入 Burp 代理 IP → 产出release.RE.apk→ uber-apk-signer 重签(java -jar uber-apk-signer.jar --allowResign -a release.RE.apk)→ 安装运行 - 产物: 运行后
adb -d shell "cat /data/data/<包名>/dump.dart" > dump.dart(类/函数清单 + 代码偏移) - 验证:
reflutter -h输出版本与用法;重打包后能安装运行并产出 dump.dart - 注意: Flutter >= 3.24 需手动在设备上设置代理(
adb shell settings put global http_proxy <ip:port>)
dart 工具链(理解快照结构,可选)
- Arch:
pacman -S dart(extra 官方仓库) - Debian/Ubuntu / Fedora: 官方仓库无 dart 包 → 用 Flutter SDK 自带(
snap install flutter或官网 SDK) - macOS:
brew install dart(或brew install --cask flutter);Windows: Flutter SDK zip - 验证:
dart --version
RN bundle 还原 —— hermes-dec(Hermes 字节码)
- 安装:
pip3 install hermes-dec(Python 3,纯标准库);Ubuntu 另有snap install hermes-dec(snap 版命令带hermes-dec.前缀,如hermes-dec.hbc-disassembler,pip 版无前缀);Arch AURyay -S hermes-dec - 命令:
hbc-file-parser(解析 HBC 头 / 版本 / 函数表)、hbc-disassembler(字节码 → .hasm 汇编)、hbc-decompiler(→ 伪 JS) - 验证:
hbc-decompiler --help可用;对样本文件能跑通三步 - 明文 JS bundle 美化:
pip install jsbeautifier(Python 3,js-beautify bundle.js)
操作步骤
按顺序执行,每步产物(so / bundle / hasm / 反编译输出 + sha256)存档。
-
引擎识别:
unzip -l app.apk | grep -E 'libapp|libflutter|assets/|bundle' file lib/arm64-v8a/libapp.so file assets/index.android.bundle # Hermes: "Hermes JavaScript bytecode, version XX"- Flutter:
lib/<abi>/libapp.so(AOT 快照)+libflutter.so(引擎)+assets/flutter_assets/ - RN:
assets/index.android.bundle(明文 JS 或 Hermes HBC);RN 0.70+ 默认 Hermes - 其他: WebView 壳(assets/*.html)/ Cordova / uni-app——按 webview 路径处理
- 识别结论决定分支: Flutter → 步骤 2;RN → 步骤 3
- Flutter:
-
Flutter:libapp.so 快照分析(blutter):
python3 blutter.py lib/arm64-v8a/libapp.so ./flutter_out grep -r "目标类名" ./flutter_out/pp.txt # 类/函数名在快照字符串表里 grep -rn "https://\|api\|token\|secret" ./flutter_out/asm/ | head- blutter 产物给出 Dart 类/函数与对象池结构——业务逻辑按类名追踪(release AOT 保留 dart 层名称,见坑 1)
- 动态补充: reFlutter 重打包 → dump.dart 拿运行期类/函数与偏移 →
frida -U -f <包名> -l frida.js结合_kDartIsolateSnapshotInstructions(readelf -Ws libapp.so)定位 hook 点
-
RN:bundle.js 提取与还原:
# 明文 JS unzip -p app.apk assets/index.android.bundle > bundle.js js-beautify bundle.js > bundle.pretty.js # Hermes 字节码 hbc-file-parser index.android.bundle > meta.txt hbc-disassembler index.android.bundle out.hasm hbc-decompiler index.android.bundle out.js # 线索提取 grep -aoE 'https?://[^"'"'"' ]+' out.js | sort -u # 端点半程 grep -aoE '"[A-Za-z0-9_/+=]{16,}"' out.js | sort -u # 疑似密钥 / base64- hasm 层面看函数调用关系(
LoadConstString与CallN配对);伪 JS 循环/条件不全时以 hasm + 字符串表为准(见坑 2)
- hasm 层面看函数调用关系(
-
原生部分走 [[re-binary-core]]:
libflutter.so/libapp.so(ELF)→ [[re-format-elf]] 解析 → [[re-ghidra]] 反编译引擎层 C++(dart:: VM、通道注册、Skia)与 JNI 桥接(FlutterEngine / PlatformChannel)- RN:
libhermes.so(引擎,可选)、JNI 桥(com.facebook.react包)→ 原生模块逻辑 - 反调试 / 加密 / 证书校验常在此层(如 BoringSSL verify 函数在 libflutter.so)——hook 点见步骤 2
-
逻辑定位(桥接边界):
- Flutter: 全局 grep MethodChannel 通道名(
flutter_前缀常见);PlatformChannel 收发处即敏感数据(token / 配置)交换点 - RN: NativeModules 注册表与
requireNativeComponent/NativeModules.xxx——JS 侧找不到的加解密在原生模块里(转步骤 4) - 从 UI 文案 / 网络字符串反查: 字符串表(Flutter 快照 / JS bundle)→ 对应函数 → 逻辑
- Flutter: 全局 grep MethodChannel 通道名(
Flutter MethodChannel 动态拦截
Flutter 平台通道(PlatformChannel)是 Dart ↔ native 通信主干,拦截可观察全部原生能力调用:
- Java/Kotlin 侧:hook
io.flutter.plugin.common.MethodChannel的 MethodCallHandler——记录 channel 名 / 方法名 / 参数 JSON - channel 名获取:hook
MethodChannel.setMethodCallHandler(onEnter 捕获 channel 实例,含名字)或MethodChannel构造器直接取 channel 名;更底层可 hookBinaryMessenger.send(首参即 channel 名) - engine messenger 层:
io.flutter.embedding.engine.FlutterEngine的 messenger 消息(低层兜底) - 与 Dart 侧静态观察互补:静态找 channel 名与调用点(字符串字面量),动态确认实际流量
- 输出:结构化 JSON(channel / method / args),供 [[re-analyze/analysis-contract]] 数据契约消费(证据存档)
Flutter 专项详见 [[re-flutter]]
React Native / Hermes(RN 字节码与桥接)
识别:HBC 与 RN 产物
.hbc/ Hermes 字节码:文件头 4 字节魔数c61fbc03类(小端),file输出形如 "Hermes JavaScript bytecode, version XX";无魔数、以var __BUNDLE_START_TIME__等 JS 源码开头的是明文 bundle- RN 应用结构:
assets/index.android.bundle(Metro 打包的业务 JS / HBC,RN 0.70+ 默认 Hermes 编译)、lib/<abi>/libhermes.so(Hermes 引擎)、libreactnativejni.so(JNI 桥) - Hermes 与标准 JS 引擎区别:Hermes 是 AOT 预编译字节码(VM 指令、运行期不做 JIT 编译),标准引擎(JSC / V8 类)产物是明文 JS 源码包;二者决定后续走字节码反汇编还是纯 JS 恢复
Hermes 字节码:反汇编思路
- 结构:文件头(魔数 + 版本号)→ 字节码段 → 函数表 / 字符串表;版本号在文件头,直接决定工具兼容性
- 反汇编:hermesc 类工具(
-dump-bytecode系)或 hbc 反汇编类 Python 实现(见「工具准备」)→ 产出 hasm 汇编;字符串表可整体提取(跨段抓可打印串)先拿线索 - 函数表特征:每条函数含 id / 偏移 / 指令数 / 参数个数——按调用关系(LoadConstString 与 CallN 配对)追踪业务入口
JS bundle 提取:Metro 结构与恢复
- Metro 打包结构:模块工厂经
__d(factory, moduleId, deps)注册进全局注册表,__r(moduleId)运行时 require;bundle 顶部有__BUNDLE_START_TIME__与 polyfill 前缀 - 恢复业务 JS:美化(jsbeautify 类工具)→ 按
__d(切分模块边界 → 每模块独立还原(工厂体 + 依赖数组)→ 混淆 / 字符串加密衔接 [[re-script-deob]] - 模块对应:大块模块(字符串长 / 依赖多)常是业务主包,可对照页面功能缩小范围
原生桥接:NativeModules / TurboModule
- JS 侧定位:
NativeModules.xxx访问点 /TurboModuleRegistry.get('Xxx')——桥接名即模块名 - 原生侧映射:JNI 桥
com.facebook.react包下的模块注册表(@ReactMethod注解类方法 /getNativeModules()列表);TurboModule 接口方法签名与 JS 调用名一一对应 - 桥接边界即敏感数据交换点:JS 侧找不到的加解密在原生模块(转 [[re-binary-core]]),与 Flutter MethodChannel 同理
动态:hook JS 运行时([[re-frida]])
- 模块加载点:hook
__d/__r可枚举全部已加载模块与 id、dump 模块工厂源码;运行期再注册的模块即远程下发(热更新)特征 - console 输出:hook console.log / warn 实现,拿业务日志与错误栈
- 桥接层兜底:hook NativeModules 方法调用(入参 / 返回值),观察 JS↔原生交换数据
坑与陷阱
- Hermes 字节码不跨版本:现象——同一样本在一种环境下反汇编正常,换版本或换工具输出乱码、直接报错;原因——HBC 格式随引擎版本演进、不保证向后兼容,反汇编类工具只覆盖特定版本区间;对策——先取版本(
file输出 / libhermes.so 内版本字符串 / 构建时间),选对应版本的 hermesc 类工具或更新反汇编工具,版本不匹配时优先换工具而非手动修字节 - bundle 加密 / 混淆(Metro 加密层):现象——bundle 解出后是密文 / 乱码,或模块函数名不可读;原因——构建管线在 Metro 输出层加密(bundle 内嵌解密器,运行期解密)或启用混淆插件;对策——在 bundle 内找解密器特征(XOR 循环 / 自解密函数 / 硬编码密钥),静态还原解密器整体解密,或 [[re-frida]] 在解密函数返回处 dump 明文,混淆部分衔接 [[re-script-deob]]
- 字节码与明文 JS 混合(部分页面走远程 bundle):现象——本地 bundle 是明文 JS 且内容正常,但关键页面逻辑缺失,或运行期出现本地没有的模块;原因——混合交付:部分页面打包进 assets,敏感 / 更新页走远程下发 bundle(热更新、灰度),本地包只含壳与加载逻辑;对策——先核对本地 bundle 完整性(
__d模块数与页面功能对比),抓包 / 文件系统快照归档远程 bundle,再按明文或 HBC 路径处理
跨域联合
- [[re-mobile]]: 混合应用分支固定调用本技能(引擎识别后按框架分流)
- [[re-apk]] / [[re-ios]]: 容器侧静态(manifest、签名、iOS 等价物)
- [[re-binary-core]]: 原生部分——[[re-format-elf]] + [[re-ghidra]] 反编译 libapp.so / libflutter.so 与 RN JNI 桥
- [[re-frida]]: 运行时 hook(桥接层、证书校验、反检测)
- [[re-analyze/platform-tips]]: 默认沙箱、工具解析 ≠ 加载器视图(快照偏移 / 工具版本差异)
- 本技能被 [[re-analyze]] 的 triage「移动 App 分析」路径引用(re-mobile → re-hybrid-app)
常见坑与陷阱
- Flutter AOT 快照无源码级符号:现象——IDA/Ghidra 直接看 libapp.so 只见 dart:: 内部函数与裸偏移,业务函数名不可见;原因——AOT 快照没有 DWARF 符号,但 Dart 类/函数名实际保留在快照字符串表;对策——blutter 还原(pp.txt / asm/),字符串表 grep 定位类名再对照 asm 分析;不要在 ELF 符号表里找业务符号
- RN Hermes 字节码难还原:现象——hbc-decompiler 出的伪 JS 循环/条件全缺失、变量名成寄存器号,逻辑读不通;原因——HBC 是 VM 指令,反编译到源码级不完整;对策——hbc-disassembler 的 hasm 看调用关系(LoadConstString + CallN 定位字符串使用点),字符串表 / bundle 内明文(URL、错误提示)反查语义,必要时动态抓明文流量对照
- 引擎版本差异:现象——blutter 报不支持该 Dart 版本 / hermes-dec 解析失败或错位;原因——工具只支持特定版本区间,App 用新引擎(Dart 3.x 新快照格式、Hermes 新 bytecode 版本);对策——先取版本(libflutter.so 内版本字符串 /
file输出 HBC version / 构建时间),更新工具(blutter 会按需下载对应 Dart 源码重编)或换支持该版本的专用工具 - JS 与原生桥接边界:现象——JS/bundle 里找不到加解密与密钥逻辑;原因——敏感逻辑在原生模块(RN NativeModules / Flutter MethodChannel 原生端);对策——按步骤 5 找桥接注册(通道名 / 原生模块表)→ 转 [[re-binary-core]] 分析对应 so;别在 JS 层死磕
- 混淆构建:现象——函数名全是 a/b/c 或 hash;原因——Flutter
--obfuscate/ RN Hermes + 混淆选项构建;对策——仍可用的字符串(报错文案 / API 路径)与行为观察驱动,配合动态 [[re-frida]] hook 桥接层定位 - Flutter SSL 校验抓不到包:现象——抓包工具看不到 Flutter 应用流量或报证书错误;原因——Flutter 自带 SSL 校验(不走系统代理/证书信任链),通杀方案在 github 有开源代码;对策——先用通杀 Flutter SSL 校验方案或 frida 绕过抓包;blutter 还原后 libapp.so 符号可见,可直接基于地址 hook 目标函数(如签名函数 generateMD5 的入参/返回值拿盐值与明文)——不需要完整还原算法
- Dart 字符串内存布局(frida 读串关键):现象——hook libapp.so 函数拿到指针却读不出字符串;原因——Dart 字符串不是 C 字符串:指针 +7 偏移处 4 字节 Smi 编码长度(右移 1 位为真实长度),+15 偏移处为 UTF-8 数据;对策——按此布局写 readDartStringExact 工具函数(加长度上限防误读),hook 入参/返回值都能还原明文
- Hermes 函数表头是位域压缩而非结构体:现象——按普通结构体读函数表,函数边界/名字/参数个数错乱,或把溢出函数的大头部偏移当普通字段读;原因——HBC 函数头把 10 个字段压进单个 128 位字(legacy 16 字节,版本 <97)或 96 位字(modern 12 字节,版本 >=97,RN 0.75+ 默认形态),位宽分配按版本不同;对策——先取版本号再选位域图;flags 位可辅助语义推断(bit0 strict、bit3 hasExceptionHandler、bit5 overflowed),overflowed 置位时内联头字段不可信,改按大头部拼装(legacy 为 (info_offset<<16)|offset、modern 为 (function_name<<24)|(offset&0xffffff));函数边界 = 头内偏移 + bytecode_size,全局代码即函数 0(来源:hermes-decomp(SymbioticSec),MIT)
- Hermes 小字符串表:u32 位域 + 哨兵溢出表 + run 编码 + packed 共享存储:现象——字符串长度/内容错位、xref 反查到错函数;原因——每项 1 个 u32:bit0 为 UTF-16 标记、bits1-23 存存储偏移、bits24-31 存长度(最长 255),长度或偏移遇哨兵值(0xff / 0x800000)时真实值在溢出表中按 (offset,length) 对补充;字符串种类表是 run-length 编码(最高位 0=普通 String、1=Identifier,低 31 位为连续同类个数);对策——按位域解析并走溢出表,条目一律按表项偏移定位、绝不子串搜索(多个条目可能共享同一段存储 packed),写回路径同理;按 run 展开可区分代码字符串与标识符(标识符用于属性访问解析与命名提示)(来源:hermes-decomp(SymbioticSec),MIT)
- opcode 定义按版本漂移,builtin 索引只在发布边界变动:现象——同一字节码换反汇编/反编译工具或版本后输出错位、遇未知 opcode 崩溃;原因——每个 HBC 版本一份指令表(40-99):opcode 编号、操作数类型序列(Reg8/Reg32/UInt8/Addr8 等)逐版本不同;对策——per-version 取表(缺失版本用「最近 <= 目标版本」的表降级并报告实际版本),未知 opcode 兜底为 而非越界;反汇编层先把语义变体归一化到抽象定义(Mov 两种编码、GetById 三种变体归并同一抽象操作),IR 层只处理抽象语义;跳转目标按 Addr8/Addr32 相对自身偏移计算(来源:hermes-decomp(SymbioticSec),MIT)
- 变量还原不要上完整 SSA+Phi:现象——反编译输出大量临时变量与边拷贝,可读性差;原因——目标语言 JS 没有 Phi,完整 SSA 的 De-SSA 阶段反而制造噪音;对策——用活区间分裂式还原:到达定义 + 并查集,两定义在同一使用处汇聚(if/else 合并点、循环回边)时并为一类,否则独立成变量,无 Phi;命名先做寄存器角色推断(用属性/方法词汇表反推类型:length/push/map/filter→Array、then/catch/finally→Promise、next/done/value→Iterator、toString/split/trim→String),名称优先级:调试段作用域名 > 解构键名 > 具名函数名 > 参数 > 类型兜底 > 编号,函数名过「有意义性」过滤(拒绝纯数字/生成名/通用核心名)(来源:hermes-decomp(SymbioticSec),MIT)
- Metro 模块工厂参数个数是版本信号:现象——扫描 __d 注册识别不到工厂、工厂形参角色搞错,模块骨架还原失败;原因——classic bundle 工厂是 4 参(require/module/exports/deps),现代 bundle 是 7 参(含 importDefault/importAll);对策——param_count-1(去 this)即角色判定依据;匹配 __d(undefined, factory, moduleId, deps) 模式,参数经寄存器转发时维护「寄存器→函数/整数/数组」流内 map 做值追踪;导出分析分直接赋值/批量赋值/Object.defineProperty 三模式;模块命名走传播链(逆向 require 命名→再导出薄包装继承→单依赖链)比单点推断可靠(来源:hermes-decomp(SymbioticSec),MIT)
- 字节码写回补丁:同长原位、变长搬迁、重叠拒绝:现象——直接改 .hbc 字节后 bundle 运行崩溃、结构表错位;原因——字符串存储允许 packed(多条目共享同一段),偏移被多处引用,变长修改必须联动修正所有后续段;对策——只改字节码不重编 JS(HASM 往返:emit-hasm 出、改、asm 回);字符串补丁按长度分派:同长→原位覆写不动表,变长→重建字符串表+存储并搬迁尾部所有段(偏移联动修正);UTF-16 串、packed 重叠的串一律拒绝补丁;modern(>=97)只支持同长串补丁,变长串/函数 resize/注入 stub 扩容/从零 create 全部显式报错不写坏;补丁后重解析验证结构一致,不一致即拒绝输出(来源:hermes-decomp(SymbioticSec),MIT)
- Hermes 反编译的能力边界:现象——反编译结果缺变量名、控制流失真,或期望直接拿回原源码;原因——反编译器无反混淆能力(混淆后只能得到混淆后的语义);发布包常裁剪调试段(offset=0 或 u32::MAX),变量名/行号全无;反汇编≠反编译——指令流只能给精确控制流与操作数,变量/结构/模块语义依赖 IR + 全程序分析;对策——「无调试信息」本身是分析结论,命名只能靠启发式;优先在字符串表里找编译期嵌入的源码/JSON/映射(如 RN Reanimated 的 Babel 插件把 worklet 原始源码作为普通字符串编进 .hbc 字符串表,按函数名 join 即得真源码),比反编译省力且保真(来源:hermes-decomp(SymbioticSec),MIT)
Signals
- GitHub stars
- 57
- Forks
- 8
- Last commit
- Sep 2026
ahel review
K1binfo
installs-packages
Automated review, not a security audit. Ruleset v1+k2.
Advanced
- Catalog kind
- skill
- Gateway key
re-hybrid-app- Source
- github.com/dslsdzc/rev-skills