1. 项目背景与问题定义
去年接手了一个企业级Flutter应用的逆向分析需求,客户提供的信息只有APK文件和一句"登录环节有加密逻辑"。这种典型的黑盒测试场景下,常规的抓包+反编译组合拳完全失效——所有请求参数都被某种动态算法处理过,而Flutter引擎的特殊性又让传统Android逆向工具集体哑火。
经过72小时的高强度技术攻关,最终通过内存取证+算法推理的组合拳,不仅完整还原了加密流程,还开发出一套通用的Flutter黑盒分析方案。整个过程涉及三个技术深水区:
- Flutter引擎的Dart虚拟机内存特性
- 非标准加密算法的动态追踪
- 基于输入输出反推算法逻辑
2. 逆向环境搭建与工具链选型
2.1 为什么选择内存取证路线
面对Flutter应用的逆向困境,我们测试了三种技术路线:
- 传统反编译:idaPro对libapp.so的分析只能看到Skia图形库的调用,关键业务逻辑完全隐藏
- Dart代码导出:flutter_blutter等工具对Obfuscation过的代码收效甚微
- 运行时分析:通过Hook Dart虚拟机内存空间直接提取运行时对象
实测发现当Flutter开启混淆编译时,只有内存取证能保留完整的符号信息。这源于Dart VM的两个特性:
- JIT模式下的内存中存在未压缩的代码对象
- 即便是AOT编译也会保留Dart对象的类型信息
2.2 关键工具配置清单
搭建分析环境需要特别注意工具版本匹配:
bash复制# 必须使用特定版本的Flutter引擎才能触发JIT
export FLUTTER_VERSION=1.22.6
# 内存dump工具组合
frida -v14.2.2
objection==1.11.0
reflutter==0.1.4
# 逆向分析平台
Ghidra 10.3 with Dart插件
JADX 1.4.1
踩坑提示:Flutter 3.x后的版本会启用Pointer Compression,导致传统内存扫描失效,建议强制降级到1.x版本进行分析
3. 内存取证关键技术实现
3.1 Dart对象内存结构解析
通过frida注入获取的原始内存数据需要按Dart VM规范解码,关键数据结构如下:
| 偏移量 | 长度 | 含义 |
|---|---|---|
| 0x00 | 8 | 类对象指针 |
| 0x08 | 4 | 对象哈希值 |
| 0x0C | 4 | 对象大小(字节数) |
| 0x10 | N | 实际数据区 |
实战中发现被分析应用使用了自定义的_SecurePayload类,其内存布局需要通过动态构造对象来验证:
dart复制// 构造探测对象
class _SecurePayload {
final String salt;
final List<int> cipher;
final DateTime timestamp;
// 内存对齐方式验证
@pragma('vm:never-inline')
void debugLayout() {...}
}
3.2 加密算法的动态追踪
通过内存断点定位到加密逻辑发生在isolate 3的0x7f8c1a2e区域,关键发现:
- 采用非标准的XXTEA变种算法
- 每次调用会动态生成32字节的salt
- 时间戳参与最终hash计算
使用reflutter的memory watch功能捕获到算法核心流程:
code复制[0x7f8c1a2e] MOV R0, [R4+0x10] ; 加载salt指针
[0x7f8c1a32] BLX _XXTEA_Encrypt ; 调用加密例程
[0x7f8c1a36] ADD R1, R0, #0x20 ; 结果缓冲区扩展
4. 算法逆向与推理助手开发
4.1 从内存快照重建算法
基于捕获的300+组输入输出样本,总结出加密规律:
- 原始数据按32字节分块
- 每块与前一区块的HMAC值做异或
- 追加8字节的CRC64校验码
使用Python还原算法核心:
python复制def custom_encrypt(data: bytes, salt: bytes) -> bytes:
blocks = [data[i:i+32] for i in range(0, len(data), 32)]
hmac_key = pbkdf2(salt, iterations=1024)
result = bytearray()
prev_hmac = bytes(32)
for block in blocks:
masked = xor_bytes(block, prev_hmac)
encrypted = xxtea_encrypt(masked, hmac_key)
prev_hmac = hmac_sha256(encrypted, hmac_key)
result.extend(encrypted)
result.extend(crc64(data).to_bytes(8, 'big'))
return bytes(result)
4.2 算法推理助手设计
为提升类似场景的分析效率,开发了具有以下功能的交互式工具:
- 内存模式识别:自动检测Dart对象的内存特征
- 输入输出关联:基于统计学的参数相关性分析
- 算法假设验证:自动生成测试用例验证猜想
工具架构关键点:
mermaid复制graph TD
A[内存快照] --> B[对象扫描]
B --> C[类型重建]
C --> D[行为监控]
D --> E[模式提取]
E --> F[算法生成]
5. 对抗优化与防御方案
在完成分析后,我们为客户提供了三重防御建议:
5.1 代码混淆增强
dart复制// 原始代码
String encrypt(String input) {...}
// 改造后
typedef _F1 = Function(String);
_F1 get _encryptImpl => () {
final _key = _deriveKey(DateTime.now().microsecond);
return (s) => _realEncrypt(s, _key);
}();
5.2 内存防护措施
- 关键对象分配后立即锁定内存页
- 定期重写敏感数据结构
- 使用Dart FFI将核心逻辑转移到native层
5.3 算法动态化
设计每天自动更新的算法组件:
code复制周一:XXTEA + SHA256
周二:ChaCha20 + BLAKE3
周三:自定义Feistel网络
...
6. 实战经验总结
- JIT模式是突破口:强制Flutter运行在debug模式可以保留更多符号信息
- 内存时序很重要:在登录操作后立即dump内存能提高有效数据密度
- 多维度验证:静态分析+动态追踪+数学推导必须相互印证
某次关键突破的现场记录:
code复制07:32 发现疑似加密函数入口
08:15 构造出第一个有效内存断点
09:40 捕获到完整的算法调用链
11:23 成功复现加密结果
这套方法目前已应用于三个大型Flutter应用的安全审计,平均分析效率提升4倍。核心价值在于建立了从内存取证到算法推理的完整技术闭环,为Flutter逆向工程提供了新的方法论参考。