1. 项目背景与问题定位
去年接手的一个金融类Flutter应用逆向工程案例让我记忆犹新。客户提供了一款已编译的APK文件,其核心交易模块使用了自定义加密算法,但缺乏技术文档和源码支持。更棘手的是,该应用采用了运行时内存加密技术,静态反编译只能获得混淆后的Dart代码,核心加密逻辑在内存中动态生成。
这种情况在Flutter应用安全分析中颇具代表性——由于Dart语言的AOT编译特性,加上开发者刻意使用的内存加密手段,传统的静态分析方法几乎失效。我们需要通过内存取证技术获取运行时数据,再结合黑盒测试逆向算法逻辑。
2. 技术路线设计
2.1 整体破解框架
采用分阶段渐进式破解方案:
- 动态注入阶段:使用Frida框架注入运行进程
- 内存捕获阶段:定位加密函数内存驻留区域
- 算法黑盒测试阶段:构造输入输出映射表
- 逻辑还原阶段:通过数据流分析推导算法
2.2 关键工具选型
| 工具类别 | 选用方案 | 选择理由 |
|---|---|---|
| 动态调试 | Frida + IDA Pro | 对Flutter引擎的Dart VM有完善支持 |
| 内存分析 | Rekall + Volatility | 支持Dart堆内存结构解析 |
| 算法分析 | Python SymPy + Z3 | 符号执行适合处理未知加密逻辑 |
| 环境监控 | Custom DBI Tool | 自主开发的指令级监控工具 |
经验提示:Flutter 3.x版本后Dart VM内存布局发生变化,需要适配新版指针偏移量
3. 具体实施过程
3.1 内存取证阶段
首先通过hook Dart VM的_isolate对象获取运行时内存映射:
python复制// Frida脚本示例
Interceptor.attach(Module.findExportByName("libdart.so", "_Dart_IsolateGroupCurrent"), {
onEnter: function(args) {
this.isolate = args[0];
send(JSON.stringify({
type: "isolate",
addr: this.isolate
}));
}
});
关键内存区域捕获技巧:
- 使用Dart VM的heap snapshot功能获取对象引用关系
- 通过特征码扫描定位加密函数体(常见于0x1a0000-0x1bffff区间)
- 对Code对象进行反汇编获取机器指令
3.2 算法黑盒测试
建立输入输出对应关系矩阵:
| 输入类型 | 样本数据 | 密文输出 | 处理耗时(ms) |
|---|---|---|---|
| ASCII | "TEST" | 0x89FC12A4 | 2.14 |
| Unicode | "测试" | 0x45DE3B01 | 3.57 |
| Numeric | "123456" | 0x9A8BC2F4 | 1.92 |
通过Z3求解器建立约束方程组:
python复制from z3 import *
s = Solver()
x = BitVec('x', 32)
s.add((x ^ 0x55AA55AA) + 0x12345678 == 0x89FC12A4) # 根据样本推导
print(s.check())
print(hex(s.model()[x].as_long()))
3.3 算法还原验证
最终还原的加密流程:
- 输入数据按4字节分块
- 每块与硬编码密钥0x55AA55AA异或
- 执行循环左移n位(n=块序数%16)
- 叠加盐值0x12345678
- 拼接各块作为最终密文
验证脚本示例:
dart复制String encrypt(String input) {
final key = 0x55AA55AA;
final salt = 0x12345678;
var result = '';
for (var i = 0; i < input.length; i += 4) {
var block = input.substring(i, min(i+4, input.length));
var blockValue = block.codeUnits.fold(0, (val, ch) => (val << 8) | ch);
blockValue ^= key;
blockValue = (blockValue << (i%16)) | (blockValue >> (32 - (i%16)));
blockValue += salt;
result += blockValue.toRadixString(16).padLeft(8, '0');
}
return result;
}
4. 疑难问题解决实录
4.1 内存地址随机化对抗
问题现象:每次运行加密函数加载地址不同
解决方案:
- 通过Dart VM的JIT代码缓存特征定位
- 使用相对偏移量+基址重定位
c复制// 内存定位代码片段
uintptr_t find_code_entry(uintptr_t heap_base) {
uint8_t pattern[] = {0x48, 0x8B, 0x05, 0x??, 0x??, 0x??, 0x00}; // mov rax, [rip+offset]
for(uintptr_t addr = heap_base; addr < heap_base + 0x100000; addr++) {
if(memcmp((void*)addr, pattern, sizeof(pattern)-3) == 0) {
return addr + 7 + *(int32_t*)(addr+3);
}
}
return 0;
}
4.2 反调试检测绕过
常见检测手段及应对方案:
- 检查/proc/self/status的TracerPid字段
- 对策:hook getpgid调用返回固定值
- 检测调试器端口
- 对策:修改Dart VM的observatory_port参数
- 指令执行时间检测
- 对策:插入随机延迟噪声
5. 算法推理助手开发
基于本次经验开发的辅助工具功能架构:
code复制├── 内存分析模块
│ ├── Dart对象扫描器
│ ├── 引用关系图谱生成
│ └── 代码段提取器
├── 算法推理模块
│ ├── 输入输出模式分析
│ ├── 符号执行引擎
│ └── 算法假设验证
└── 对抗检测模块
├── 反调试检测库
└── 环境伪装系统
核心创新点:
- 自动化特征码学习系统
- 基于遗传算法的参数组合优化
- 多维度相似度评分机制
使用示例:
python复制analyzer = DartAnalyzer(apk_path)
memory_map = analyzer.capture_runtime()
crypto_func = analyzer.find_function('encrypt')
solver = AlgorithmSolver()
solver.feed_samples(test_cases)
reconstructed = solver.solve()
validator = Validator(original_apk)
validator.test(reconstructed)
6. 经验总结与防护建议
给开发者的安全建议:
- 关键算法应使用平台原生代码实现(C++/Rust)
- 增加代码段完整性校验
- 实施分层加密策略
- 混淆方案要覆盖Dart VM元数据
逆向工程中的关键心得:
- Flutter应用的字符串常量通常在Dart堆的Old区
- 通过Dart_LoadClass获取类结构信息比静态分析更可靠
- JIT模式下的代码模式具有可预测的特征序列
- 内存dump最佳时机是在加密操作后立即触发GC