拓冰建站拓冰建站
首页 / 资讯中心 / 正文

某站 App 的 Sign 加密方式逆向分析

一、前言在分析某视频平台的 App 通信协议时发现其 API 请求头中携带了一个动态的sign参数。该参数随着请求参数和时间戳变化如果无法生成有效的签名服务端将拒绝访问。抓包发现该签名由 Native 层libbili.so生成因此我们需要对该 SO 进行逆向还原其签名算法。二、静态分析IDA2.1 定位关键函数通过 JNI 调用链追踪最终锁定核心签名函数为sub_2F88在 IDA 中显示为sub_2F88实际入口偏移0x2F89。该函数原型为cint __fastcall sub_2F88(JNIEnv *env, jobject obj, int a3, int a4)参数说明envJNI 环境指针objJava 层传入的SignedQuery对象包含appkey字段a3、a4两个整型标志用于选择不同的加密常量表2.2 发现 MD5 算法痕迹在函数中我们发现了熟悉的初始化常量c_DWORD *__fastcall sub_227C(_DWORD *result) { *result 1732584193; // 0x67452301 result[1] -271733879; // 0xEFCDAB89 result[2] -1732584194; // 0x98BADCFE result[3] 271733878; // 0x10325476 result[4] 0; result[5] 0; return result; }这完全符合MD5 初始化向量IV标准证实底层采用 MD5 哈希算法而非自定义加密。随后调用了sub_22B0类似MD5_Update用于追加数据sub_2AE0类似MD5_Final输出 16 字节摘要2.3 数据拼接逻辑分析sub_2F88的伪代码理清了数据流入 MD5 的顺序首先从obj中取出appkey字段经sub_34B8计算得到一个整数值v19用于索引查表。调用静态方法dword_B104获取一个 Java 字符串转为 C 字符串v32即原始请求数据。根据a3、a4从三个硬编码表unk_96AC、unk_971C、unk_978C中选取一个再以v19为偏移取出4 个 32 位整数存入v20。拼接顺序先向 MD5 上下文追加v32原始数据然后将 4 个整数分别通过sprintf(%08x)格式化为8 位小写十六进制字符串依次追加每个固定 8 字节。最后执行 MD5 最终计算并将 16 字节摘要转为 32 位十六进制字符串。至此算法逻辑已经清晰但硬编码表中的具体值以及v19的计算方式仍需要动态验证。三、动态验证Frida Hook为了快速获取运行时实际的拼接数据我们直接Hooksub_22B0即 MD5_Update这样就能打印出每一次追加的明文内容。3.1 注意指令集模式Thumb该 SO 为 32 位 ARM编译时使用了 Thumb 指令集。IDA 中函数地址为0x22B0但实际入口需要10x22B1才能正确 Hook。这是 ARM 体系中 LSB 位表示 Thumb 状态的关键细节。3.2 Frida Hook 脚本javascriptJava.perform(function () { var libbili Module.findBaseAddress(libbili.so); // 因为 Thumb 模式偏移加 1 var md5_update libbili.add(0x22B0 1); console.log([*] Hook address: md5_update); Interceptor.attach(md5_update, { onEnter: function (args) { // args[1] 为数据指针args[2] 为数据长度 var len args[2].toInt32(); console.log([MD5_Update] 长度: len); if (len 0) { // 读取数据内容假定为 UTF-8 字符串 console.log(数据: args[1].readUtf8String(len)); // 如果数据可能含有二进制可以使用 hexdump // console.log(hexdump(args[1], { length: len })); } } }); });3.3 Hook 输出示例运行该脚本后当 App 执行签名时控制台输出如下text[*] Hook address: 0x7a8c22b1 [MD5_Update] 长度: 28 数据: useradmintime1617181920 [MD5_Update] 长度: 8 数据: 12345678 [MD5_Update] 长度: 8 数据: 9abcdef0 [MD5_Update] 长度: 8 数据: 11223344 [MD5_Update] 长度: 8 数据: 55667788可以看到第一次 Update原始请求参数useradmintime1617181920。后续 4 次 Update均为长度为 8 的十六进制字符串这就是动态盐值。通过 Hook 我们直接获取了盐值明文无需再分析查表逻辑和v19的计算方式极大地简化了逆向难度。四、签名算法总结根据以上分析该 App 的 Sign 生成规则如下textSign MD5( 原始参数字符串 盐值1 盐值2 盐值3 盐值4 )其中原始参数字符串从 Java 静态方法获取通常为 URL 参数拼接或 JSON 字符串。盐值1~4由appkey和标志位动态查表得到的 4 个 32 位整数分别转化为8 位小写十六进制不足补 0。最终输出为32 位小写十六进制字符串。五、签名生成复现代码Python基于上述规则我们可以用 Python 轻松复现pythonimport hashlib def generate_sign(data, salt_ints): data: 原始参数字符串 (str) salt_ints: 4 个整数的列表 (list of int) 返回: 32位小写十六进制签名 md5 hashlib.md5() md5.update(data.encode(utf-8)) for v in salt_ints: # 格式化为 8 位小写十六进制 salt_str format(v, 08x) md5.update(salt_str.encode(utf-8)) return md5.hexdigest() # 测试使用 Hook 获取到的盐值 test_data useradmintime1617181920 salts [0x12345678, 0x9ABCDEF0, 0x11223344, 0x55667788] sign generate_sign(test_data, salts) print(Sign:, sign)运行结果应与 App 实际生成的签名完全一致。六、总结与注意事项6.1 逆向关键点识别标准算法通过 MD5 初始化常量快速定位加密类型避免在混淆代码中迷失。动态 Hook 降维打击直接 HookMD5_Update打印所有输入省去了分析复杂查表逻辑的麻烦。注意指令集模式ARM Thumb 模式下函数地址需加 1这是 Hook 成功的前提。6.2 安全启示对于 App 开发者而言仅依靠标准 MD5 加硬编码盐值并不安全因为盐值最终会在内存中以明文形式暴露。更安全的做法包括使用白盒加密或动态密钥。增加代码混淆和反调试机制。将关键逻辑移至服务端。6.3 免责声明本文仅供技术研究和学习交流请勿用于非法用途。逆向分析时应遵守相关法律法规及平台服务条款。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门