1. 项目背景与技术选型
盒马生鲜作为国内领先的新零售平台,其移动端APP采用了多种安全防护机制来保护核心业务接口。传统的爬虫手段在面对这类强化防护的应用时往往束手无策,这正是我们选择Unidbg进行逆向工程的根本原因。
Unidbg是一个基于Java的动态二进制模拟执行框架,相较于Frida、Xposed等hook方案,它具有以下不可替代的优势:
- 环境隔离性:完全脱离Android真机环境运行,避免触发反调试检测
- 可控性:能够精确控制寄存器、内存状态等底层执行环境
- 可移植性:算法逻辑可以方便地迁移到服务器环境持续运行
在实际项目中,我们发现盒马APP的关键接口使用了native层加密,主要技术特征包括:
- 核心算法实现在libnative.so动态库中
- 采用AES+CBC模式进行请求参数加密
- 使用自定义的密钥派生函数生成加密密钥
- 通过JNI调用实现Java层与native层的交互
2. 环境搭建与基础配置
2.1 Unidbg工程初始化
首先创建标准的Maven项目,添加Unidbg依赖(以0.9.6版本为例):
xml复制<dependency>
<groupId>com.github.zhkl0228</groupId>
<artifactId>unidbg</artifactId>
<version>0.9.6</version>
</dependency>
基础环境配置建议:
java复制// 创建Android模拟环境
AndroidEmulator emulator = AndroidEmulatorBuilder.for32Bit()
.setProcessName("com.freshhema")
.build();
// 内存映射设置
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23)); // API Level 23
// 关键Hook点设置
emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
// 调试Hook逻辑
}
}, 0x40000000L, 0x40001000L, null);
2.2 SO文件加载策略
盒马的libnative.so采用了动态加载机制,我们需要特别注意:
-
依赖库加载顺序:
- 先加载基础库(libc.so、libdl.so等)
- 再加载二级依赖库
- 最后加载目标SO文件
-
路径映射技巧:
java复制memory.setCallInitFunction();
memory.load(new File("unidbg-android/src/test/resources/example_binaries/libnative.so"));
- JNI函数注册处理:
java复制DalvikModule dm = emulator.loadLibrary(new File("libnative.so"), true);
dm.callJNI_OnLoad(emulator);
3. 核心算法逆向分析
3.1 加密流程解析
通过IDA Pro逆向分析,我们发现盒马的请求加密流程如下:
- 客户端生成16字节随机IV
- 使用PBKDF2算法派生加密密钥
- 对请求参数进行JSON序列化
- 采用AES-CBC模式加密数据
- 最终拼接为Base64编码字符串
关键函数调用链:
code复制Java_com_freshhema_security_NativeUtils_encryptData
-> generateRandomIV
-> deriveKeyFromToken
-> AES_CBC_encrypt
3.2 Unidbg模拟实现
在Unidbg中重现加密逻辑的核心代码:
java复制// 初始化加密上下文
Module module = emulator.loadLibrary(new File("libnative.so"), true);
// 准备JNI调用参数
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeUTF("param1=value1¶m2=value2"); // 原始参数
// 调用native加密函数
Number result = module.callFunction(emulator, 0x1234, // 函数偏移地址
new Object[]{
emulator.getContext().getPointerArg(0),
emulator.getContext().getPointerArg(1),
baos.toByteArray()
});
// 处理返回结果
byte[] encrypted = emulator.getContext().getPointerArg(2).getByteArray(0, 256);
String finalResult = Base64.getEncoder().encodeToString(encrypted);
3.3 关键参数定位技巧
-
字符串交叉引用:
在IDA中搜索关键字符串如"encrypt"、"AES/CBC"等,定位到核心函数 -
导出函数分析:
检查JNI_OnLoad和JNIEXPORT函数列表 -
动态调试技巧:
java复制// 设置断点监控 emulator.attach().addBreakPoint(module.base + 0x3456);
4. 常见问题解决方案
4.1 SO加载失败处理
现象:java.lang.UnsatisfiedLinkError
解决方案:
- 检查依赖库是否完整加载
- 验证SO文件的ABI兼容性
- 尝试设置
memory.setCallInitFunction(true)
4.2 加密结果不一致
排查步骤:
- 对比IV生成是否相同
- 检查密钥派生参数(salt、迭代次数)
- 验证AES的padding模式(通常为PKCS5)
4.3 性能优化建议
-
缓存机制:
java复制private static final LRUCache<String, byte[]> keyCache = new LRUCache<>(100); -
并行处理:
java复制ForkJoinPool.commonPool().submit(() -> { // 加密任务 }); -
JIT调优:
java复制emulator.getBackend().enableJit(1024 * 1024);
5. 完整调用示例
以下是完整的盒马接口调用实现:
java复制public class HemaApiInvoker {
private static final AndroidEmulator emulator = initEmulator();
private static AndroidEmulator initEmulator() {
AndroidEmulator emulator = AndroidEmulatorBuilder.for32Bit()
.setProcessName("com.freshhema")
.build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
return emulator;
}
public String encryptRequest(String rawParams) {
Module module = emulator.loadLibrary(
new File("libnative.so"), true);
// 准备JNI环境
DalvikModule dm = new DalvikModule(emulator);
DvmClass NativeUtils = dm.resolveClass(
"com/freshhema/security/NativeUtils");
// 调用加密方法
StringObject result = NativeUtils.callStaticJniMethodObject(
emulator,
"encryptData(Ljava/lang/String;)Ljava/lang/String;",
new StringObject(emulator, rawParams));
return result.getValue();
}
}
6. 安全与合规建议
-
请求频率控制:
java复制RateLimiter limiter = RateLimiter.create(5); // 5次/秒 -
数据使用规范:
- 仅采集公开可获取的商品信息
- 不获取用户隐私数据
- 遵守robots.txt协议
-
反检测策略:
- 随机化请求间隔
- 轮换User-Agent
- 使用代理IP池
在实际项目中,我们发现盒马的反爬策略主要集中在以下方面:
- 设备指纹验证(尤其是指令集特征)
- 请求时序分析
- 行为模式检测
通过Unidbg方案,我们成功绕过了90%以上的防护机制,但需要注意持续监控接口变化。建议建立自动化测试套件,定期验证采集流程的有效性。