携程APP中user-dun算法的逆向工程与实战解析
1. 初识user-dun算法从抓包到定位核心so文件第一次接触携程APP的user-dun算法时我和大多数逆向新手一样走了不少弯路。这个藏在libduncode.so里的算法表面看起来就是个普通的设备指纹生成逻辑但实际逆向时才发现水有多深。记得最早通过抓包工具看到请求头里那个长长的user-dun字段时我还天真地以为就是个Base64编码的简单字符串。定位核心so文件的过程就给我上了第一课。当时犯了个典型错误——看到APP里有个大写的User-Dun就直接扑上去分析结果花了两天时间才发现这其实是H5页面的JS实现和native层的算法完全不是一回事。后来通过JADX反编译发现真正的入口在com.ctrip.android.nativeapi.security模块里的NativeSign类关键方法是个native的signature生成函数。用Frida hook验证时更发现玄机这个so加载时机非常晚通常在APP启动后10-15秒才会初始化。更麻烦的是libduncode.so用了OLLVM做了指令级混淆IDA打开后全是这种画风loc_12345: mov x0, x1 b loc_67890 ; 中间夹杂着大量无意义跳转2. 逆向工程的三重难关混淆、多线程与JNI陷阱2.1 对抗代码混淆的实战技巧面对OLLVM混淆的so文件传统的静态分析基本失效。我试过用d810插件做反混淆效果有限。后来发现结合动态调试才是王道——先用Frida hook住JNI_OnLoad然后在关键内存地址下硬件断点。这里有个小技巧在libduncode.so的.init_array段设置断点往往能捕捉到早期的初始化逻辑。实际调试中发现算法核心逻辑被拆分成多个线程执行。主线程负责设备信息采集子线程做加密运算还有个隐藏线程在监控调试行为。这种架构导致直接用unidbg模拟时经常卡死必须手动补全线程调度逻辑。2.2 JNI调用的那些坑分析JNI方法调用链时踩过最深的坑是so里那些看似简单的FindClass操作。比如下面这段伪代码jclass clazz env-FindClass(com/ctrip/non/exist/Class);在unidbg模拟时会直接返回NULL导致后续流程崩溃。解决方法是在VM的JNI函数表里预先注册这些类或者更暴力点——直接hook FindClass返回固定值。更棘手的是so里用到的某些JNI方法在低版本Android上不存在比如GetStringUTFChars的某些变种。这时就需要在unidbg里手动实现这些方法否则连初始化都过不去。3. 算法还原的关键突破点3.1 设备信息加密机制解析通过动态调试发现libduncode.so在初始化阶段就会采集20项设备参数包括常规的IMEI、MAC地址传感器列表及其精度内存页大小等底层特征甚至包括触摸屏采样率这些信息会被拼接成特定格式的字符串经过多层加密后作为算法种子。有意思的是加密过程会用到CPU特性检测——如果在模拟器环境运行生成的密文格式会完全不同。3.2 自定义Base64的玄机算法中最容易让人栽跟头的是那个自定义Base64编码。标准Base64的编码表是ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/而携程用的却是custom_table KLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/ABCDEFGHIJ这个细节在trace日志里很容易被忽略。我当初就是卡在这里三天直到发现解码后的二进制末尾总有两个字节异常才意识到编码表被魔改过。4. unidbg模拟的实战经验4.1 环境搭建的注意事项用unidbg模拟时遇到最头疼的问题是so对apk文件的校验。原so会尝试读取base.apk的某些特征在模拟环境里这会导致无限等待。我的解决方案是准备一个最小化的空apk文件hook所有文件操作相关系统调用对关键路径的访问请求做重定向具体实现代码类似这样// 在unidbg的AndroidEmulator里添加hook emulator.getSyscallHandler().addIOResolver(new IOResolver() { Override public FileResult resolve(Emulator emulator, String pathname, int oflags) { if (pathname.contains(base.apk)) { return FileResult.success(new SimpleFileIO(oflags, new File(fake.apk), pathname)); } return null; } });4.2 算法分支的处理策略实际测试发现user-dun算法至少有三种分支路径标准设备模式模拟器环境模式异常设备回退模式每种模式的加密逻辑差异在20%左右主要体现在随机数生成方式不同时间戳的参与计算比例关键参数的哈希轮数在unidbg里可以通过hook getauxval等系统函数来强制指定运行路径。比如下面这段代码就能让so认为运行在真机环境emulator.getSyscallHandler().addHook(new BaseHook() { Override public long hook(SyscallHandler syscallHandler, Number... args) { if (args[0].longValue() 0x10) { // AT_HWCAP return 0x7FF; // 模拟特定CPU特性 } return 0; } });5. 逆向工程中的调试技巧5.1 高效trace日志分析面对海量的trace日志我总结出一套过滤技巧先用正则过滤JNI调用cat trace.log | grep -E JNIEnv-Call|JNIEnv-Get重点监控内存读写操作cat trace.log | grep -A5 -B5 memcpy\|memmove对加密函数定位特别有效的是搜索常量特征cat trace.log | grep -E 0x[0-9a-f]{8}5.2 IDA静态分析配合动态调试虽然直观但遇到复杂算法时还是需要IDA辅助。我的工作流通常是用Frida dump出关键内存段在IDA里创建相同基址的segment通过交叉引用定位算法核心对于混淆严重的代码可以尝试在IDA里用以下脚本简化控制流import ida_bytes import ida_segment for seg in ida_segment.Segments(): if text in ida_segment.get_segm_name(seg): for addr in range(seg, ida_segment.get_segm_end(seg)): if ida_bytes.is_code(ida_bytes.get_flags(addr)): if ida_bytes.get_byte(addr) 0xE1: # 典型跳转指令 ida_bytes.patch_byte(addr, 0xE0) # 改为NOP6. 算法还原后的验证策略费尽周折还原出算法后最崩溃的莫过于发现生成的user-dun和抓包结果对不上。这时候需要系统性地排查时间戳同步问题服务器时间可能有5分钟容差设备指纹变化某些参数如Android ID在模拟环境无法复现网络环境因素IP地理信息也会影响最终结果我的验证方案是构造差分测试固定所有参数只变化单个变量对比相同输入下算法输出差异用二分法逐步缩小问题范围最终发现影响最大的三个参数是构建指纹时的内存对齐方式传感器列表的哈希算法系统属性读取的字节序7. 从逆向到风控绕过的思考完整还原user-dun算法只是第一步真正的挑战在于理解背后的风控策略。通过长期观察发现相同设备生成的user-dun每天会有细微变化关键参数加密权重每周调整异常行为检测响应时间在200ms以内这些特征说明携程的风控系统具备动态权重调整机制基于时间的热更新能力实时计算集群支持在实际业务场景中单纯模拟user-dun已经不够还需要配合设备指纹的自然演变模式用户行为的时空连续性请求参数的合理分布这就像下棋算法还原只是学会规则真正要赢还需要理解对手的思考方式。每次逆向工程到最后都会变成对系统设计者思路的揣摩和博弈。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2510291.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!