实战复盘:用Unidbg搞定某APP的7个加密头(附完整Java代码与避坑点)

张开发
2026/4/7 17:51:22 15 分钟阅读

分享文章

实战复盘:用Unidbg搞定某APP的7个加密头(附完整Java代码与避坑点)
逆向工程实战Unidbg模拟APP加密头的全流程解析第一次看到那些神秘的X-Argus、X-Gorgon请求头时我意识到这可能是APP逆向中最具挑战性的任务之一。这些看似随机的字符串背后往往隐藏着复杂的Native层加密算法。经过两周的摸索和调试我终于用Unidbg成功复现了全部7个加密头的生成逻辑。本文将完整还原这次技术攻坚的每个关键步骤从抓包分析到Unidbg环境搭建再到最终的算法复现特别会分享那些官方文档里找不到的实战技巧和避坑指南。1. 逆向工程前的准备工作逆向工程从来不是单打独斗的游戏合适的工具组合能让效率提升数倍。我的工作台上常年备着以下几件兵器抓包三件套CharlesPostmanWireshark的组合可以应对90%的HTTP/HTTPS流量分析逆向分析工具Jadx-GUI、IDA Pro和Frida构成了静态分析与动态调试的黄金三角Unidbg环境基于Maven构建的Java项目配合Android SDK和NDK工具链在开始之前建议先创建一个干净的Python虚拟环境避免库版本冲突python -m venv unidbg_env source unidbg_env/bin/activate pip install frida-tools objection提示遇到SSL Pinning时可以考虑使用JustTrustMe模块或者手动注入Xposed模块绕过证书校验第一次抓包时我注意到这个APP的请求头中包含了7个特殊的加密字段。通过对比多个请求发现X-Khronos看起来像是时间戳而其他字段则明显是某种加密算法的输出。这成为了整个逆向工程的突破口。2. 加密算法的定位与提取2.1 动态Hook关键函数使用Frida进行动态注入是定位加密逻辑最高效的方式。下面这个脚本帮我快速锁定了加密函数的入口Interceptor.attach(Module.findExportByName(libencrypt.so, Java_com_xxx_EncryptUtils_getArgus), { onEnter: function(args) { console.log(Argus input:); console.log(hexdump(args[1], { length: Memory.readInt(args[2]), header: false })); }, onLeave: function(retval) { console.log(Argus output:); console.log(hexdump(retval, { length: Memory.readInt(ptr(retval).add(4)), header: false })); } });通过反复测试我整理出了7个加密头的调用关系表加密头名称依赖的SO库调用频率输入参数类型X-Arguslibencrypt.so每次请求设备信息URLX-Gorgonlibsecurity.so首次启动时间戳随机数X-Helioslibcrypto.so特定API用户TokenX-Khronos无每次请求系统时间X-Ladonlibauth.so登录时用户名密码哈希X-Medusalibnet.so视频请求视频ID分辨率X-Soterlibverify.so支付时订单金额2.2 SO文件的反编译技巧在IDA中分析SO文件时有几个关键点需要注意识别JNI函数命名的真实含义跟踪关键字符串的交叉引用注意.init_array和.fini_array段中的初始化代码特别要留意那些使用了ARMv7-A NEON指令集的函数它们往往是性能敏感的加密算法。下面是一个典型的AES加密函数特征vld1.64 {d16-d17}, [r1] aese.8 q8, q9 aesmc.8 q8, q8 vst1.64 {d16-d17}, [r0]3. Unidbg环境搭建与配置3.1 基础环境搭建创建一个标准的Maven项目添加Unidbg依赖dependency groupIdcom.github.zhkl0228/groupId artifactIdunidbg-api/artifactId version0.9.4/version /dependency初始化Unidbg虚拟机时内存设置很关键。这是我的推荐配置AndroidEmulator emulator AndroidEmulatorBuilder.for32Bit() .setProcessName(com.target.app) .addBackendFactory(new Unicorn2Factory(true)) .setMemorySize(0x10000000) // 256MB .build();3.2 JNI函数补全实战遇到未实现的JNI函数时需要手动补全。以下是处理GetStringUTFChars的典型示例private void patchJNI() { emulator.getMemory().setLibraryResolver(new AndroidResolver(23)); emulator.getMemory().addHookListener(new HookListener() { Override public long hook(Emulator? emulator, String libraryName, String symbolName, long old) { if (GetStringUTFChars.equals(symbolName)) { return new JniFunction(emulator) { Override public long call(long... args) { String str getObject(args[0]).getValue().toString(); return emulator.getMemory().writeStackString(str).peer; } }.getPointer().peer; } return old; } }); }4. 加密算法的模拟与复现4.1 上下文初始化技巧许多加密算法需要先初始化上下文。通过Hook发现libencrypt.so的初始化函数会读取以下设备信息设备型号如SM-G955FAndroid版本如8.0.0屏幕分辨率CPU信息网络类型在Unidbg中模拟这些信息需要特别注意字节对齐问题。这是我的设备信息模拟代码MemoryBlock deviceInfo emulator.getMemory().malloc(0x200, true); deviceInfo.getPointer().write((modelSM-G955F\n android8.0.0\n resolution1080x1920\n cpuarm64-v8a\n).getBytes());4.2 多线程问题处理当加密算法涉及多线程时Unidbg需要特殊处理。我发现X-Gorgon的生成会在后台线程中进行这导致直接调用会失败。解决方案是emulator.getThreadDispatcher().setWorkaroundSIGSEGV(true); emulator.getThreadDispatcher().setMaxThreads(4);对于特别复杂的线程同步问题可以尝试以下调试技巧使用emulator.traceCode()记录指令执行流在关键内存地址设置断点监控pthread_create和pthread_join调用4.3 完整加密流程示例以下是生成X-Argus头的完整Java代码public String generateArgus(Context context, String url) { NativeLibrary lib emulator.loadLibrary(libencrypt.so); long funcAddr lib.findSymbol(generate_argus); ByteArrayOutputStream baos new ByteArrayOutputStream(); DataOutputStream dos new DataOutputStream(baos); dos.writeUTF(context.deviceId); dos.writeUTF(url); MemoryBlock input emulator.getMemory().malloc(baos.size(), true); input.getPointer().write(baos.toByteArray()); Number[] ret emulator.eFunc(funcAddr, new Number[]{input.peer, baos.size()}); MemoryBlock output emulator.getMemory().malloc(0x100, true); return output.getPointer().getString(0); }5. 实战中的典型问题与解决方案5.1 内存泄漏排查在长时间运行后Unidbg可能会出现内存泄漏。通过以下命令可以监控内存状态jmap -histo:live pid | grep unidbg常见的内存泄漏点包括未释放的MemoryBlock缓存过度的JNI引用未关闭的Trace记录5.2 性能优化技巧当处理大量请求时可以应用这些优化手段预加载SO库在启动时加载所有需要的SO文件缓存加密上下文复用已经初始化的加密句柄并行处理对不同的加密头使用独立线程我的测试数据显示经过优化后7个加密头的生成时间从1200ms降到了400ms左右。5.3 跨版本兼容性处理不同版本的APP可能使用不同的加密算法。我建立了一个版本映射表APP版本libencrypt.so哈希关键变化点3.2.1a1b2c3d4使用AES-1283.5.0e5f6g7h8升级到AES-2564.0.0i9j0k1l2增加RSA签名处理多版本时可以采用动态SO加载策略public void loadVersionSpecificLib(String version) { String libPath /path/to/libs/ version /libencrypt.so; emulator.loadLibrary(new File(libPath)); }6. 安全防护与对抗策略现代APP会采用各种反调试技术常见的有ptrace检测通过/proc/self/status检查Frida检测扫描内存中的frida-agent字符串环境异常检测检查Xposed、Magisk等痕迹在Unidbg中绕过这些检测的方法包括// 伪装/proc/self/status内容 emulator.getMemory().addHook(new ReadHook() { Override public void hook(Emulator? emulator, long address, int size, Object user) { if (address procStatusAddr) { emulator.getMemory().pointer(address).writeString(TracerPid:\t0\n); } } });7. 进阶技巧与扩展应用掌握了基础加密头的复现后可以进一步探索算法白盒化将Native算法转换为纯Java实现协议逆向分析整个通信协议的安全机制自动化测试构建加密头的自动化测试框架一个实用的技巧是将Unidbg封装为HTTP服务RestController public class EncryptController { PostMapping(/encrypt) public MapString, String encrypt(RequestBody RequestData data) { MapString, String result new HashMap(); result.put(X-Argus, argusGenerator.generate(data)); // 其他加密头... return result; } }在项目后期我发现这套方案不仅可以用于逆向分析还能应用于接口自动化测试竞品协议分析安全审计兼容性测试

更多文章