当前位置: 首页 > news >正文

逆向解析京东sign加密算法的实战过程

1. 逆向分析前的准备工作

在开始逆向分析京东sign加密算法之前,我们需要做好充分的准备工作。首先需要准备一台root过的安卓手机或者模拟器,我推荐使用Genymotion模拟器,因为它对xposed框架的支持比较好。其次需要安装以下工具:

  • jadx:用于反编译apk文件,查看java层代码
  • IDA Pro:用于分析so文件中的native代码
  • Frida:动态hook工具,可以实时修改和监控函数调用
  • Charles:抓包工具,用于捕获网络请求

我建议先使用Charles抓取几个京东APP的请求,观察sign参数的特征。从抓包结果来看,sign通常是一个32位的字符串,看起来像是MD5或者SHA1之类的哈希值。但是直接对参数进行哈希计算并不能得到相同的结果,说明京东在哈希之前还做了其他处理。

2. 定位sign生成的关键代码

2.1 使用jadx分析apk

用jadx打开京东apk后,我们可以通过搜索关键词来定位sign生成的代码。根据经验,sign通常会在网络请求的拦截器或者工具类中生成。我们可以搜索以下关键词:

  • "sign"
  • "signature"
  • "encrypt"
  • "getSign"

在京东apk中,我们发现了一个名为BitmapkitUtils的类,里面有一个getSignFromJni方法,这个方法看起来很有嫌疑。它的参数包括context、业务类型字符串、json数据和一些设备信息,返回值就是我们要找的sign。

public class BitmapkitUtils { public static native String getSignFromJni(Context context, String str, String str2, String str3, String str4, String str5); }

2.2 追踪native方法实现

getSignFromJni是一个native方法,说明实际的加密逻辑是在so文件中实现的。我们需要找到对应的so文件。通过分析apk的lib目录,发现libjdbitmapkit.so这个文件最有可能是我们要找的。

用IDA Pro打开这个so文件,我们需要找到getSignFromJni对应的native函数。可以通过查看JNI_OnLoad函数或者直接搜索"getSignFromJni"字符串来定位。找到函数后,我们可以开始分析它的实现逻辑。

3. 动态分析加密过程

3.1 使用Frida进行hook

静态分析只能看到代码的结构,要真正理解加密过程,我们需要进行动态分析。这里我们使用Frida来hook关键函数。首先hook Java层的getSignFromJni方法:

Java.perform(function() { var BitmapkitUtils = Java.use("com.jingdong.common.utils.BitmapkitUtils"); BitmapkitUtils.getSignFromJni.implementation = function(context, str1, str2, str3, str4, str5) { console.log("getSignFromJni called with params:"); console.log("str1: " + str1); console.log("str2: " + str2); console.log("str3: " + str3); console.log("str4: " + str4); console.log("str5: " + str5); var result = this.getSignFromJni(context, str1, str2, str3, str4, str5); console.log("getSignFromJni result: " + result); return result; }; });

通过hook我们发现,str2参数是一个json字符串,包含了请求的各种参数。str3看起来像是一个固定值,可能是某种密钥或者盐值。

3.2 hook native层函数

接下来我们需要hook so文件中的native函数。首先找到getSignFromJni对应的native函数,然后hook它的调用:

Interceptor.attach(Module.findExportByName("libjdbitmapkit.so", "Java_com_jingdong_common_utils_BitmapkitUtils_getSignFromJni"), { onEnter: function(args) { console.log("native getSignFromJni called"); // 打印参数 var env = Java.vm.getEnv(); var str1 = env.getStringUtfChars(args[2], null).readCString(); var str2 = env.getStringUtfChars(args[3], null).readCString(); var str3 = env.getStringUtfChars(args[4], null).readCString(); console.log("str1: " + str1); console.log("str2: " + str2); console.log("str3: " + str3); }, onLeave: function(retval) { console.log("native getSignFromJni return:"); console.log(Java.vm.getEnv().getStringUtfChars(retval, null).readCString()); } });

通过hook我们发现,native函数内部还会调用其他函数进行实际的计算。我们需要继续深入分析这些函数。

4. 分析加密算法实现

4.1 跟踪加密流程

通过IDA Pro的交叉引用功能,我们可以跟踪getSignFromJni函数的调用流程。发现它最终会调用一个sub_xxxx的函数进行实际的加密计算。这个函数内部又调用了MD5相关的函数。

有趣的是,在调用MD5之前,数据会经过一个自定义的加密函数处理。这个函数会对数据进行异或和加减操作,使用了一个固定的密钥表和密钥值。这可能是京东为了防止直接MD5被破解而增加的混淆层。

4.2 复现加密算法

通过分析,我们可以将加密过程总结为以下几个步骤:

  1. 将输入参数按照特定格式拼接
  2. 使用自定义算法对拼接后的字符串进行加密
  3. 对加密结果进行MD5计算
  4. 对MD5结果进行二次处理

以下是加密算法的C语言实现:

#include <stdio.h> #include <string.h> #include <openssl/md5.h> void jd_encrypt(char* input, int length) { const char* key = "80306f4370b39fd5630ad0529f77adb6"; unsigned char table[16] = {0x37, 0x92, 0x44, 0x68, 0xA5, 0x3D, 0xCC, 0x7F, 0xBB, 0xF, 0xD9, 0x88, 0xEE, 0x9A, 0xE9, 0x5A}; for(int i = 0; i < length; i++) { int key_index = i & 7; int table_index = i & 0xF; unsigned char tmp = (table[table_index] + (input[i] ^ key[key_index] ^ table[table_index])) ^ table[table_index]; input[i] = key[key_index] ^ tmp; } } void calculate_jd_sign(char* input, char* output) { // 第一步:自定义加密 int length = strlen(input); jd_encrypt(input, length); // 第二步:MD5计算 unsigned char md5_result[MD5_DIGEST_LENGTH]; MD5((unsigned char*)input, length, md5_result); // 第三步:格式化输出 for(int i = 0; i < MD5_DIGEST_LENGTH; i++) { sprintf(output + i*2, "%02x", md5_result[i]); } output[MD5_DIGEST_LENGTH*2] = '\0'; } int main() { char input[] = "wareBusiness{\"skuId\":\"100000000001\"}"; char output[33]; calculate_jd_sign(input, output); printf("JD Sign: %s\n", output); return 0; }

5. 验证和优化

5.1 验证算法正确性

为了验证我们的算法是否正确,我们可以使用Frida在APP运行时捕获输入参数和输出结果,然后用我们的算法计算同样的输入,比较结果是否一致。我在测试中发现,对于某些特殊字符,算法结果会有差异,这说明京东可能在处理特殊字符时有额外的规则。

5.2 处理边界情况

经过多次测试,我发现以下边界情况需要特别注意:

  1. 当参数值为null时,京东会将其转换为空字符串
  2. 布尔值true/false会转换为字符串"true"/"false"
  3. 浮点数会保留一定位数的小数
  4. 参数名会按照字母顺序排序

修正后的参数处理代码如下:

def prepare_params(params): prepared = {} for key in sorted(params.keys()): value = params[key] if value is None: prepared[key] = "" elif isinstance(value, bool): prepared[key] = "true" if value else "false" elif isinstance(value, float): prepared[key] = "{:.6f}".format(value) else: prepared[key] = str(value) return prepared

6. 完整实现方案

6.1 Python实现

基于以上分析,我们可以用Python实现完整的sign生成算法:

import hashlib import json def jd_encrypt(data): key = "80306f4370b39fd5630ad0529f77adb6" table = [0x37, 0x92, 0x44, 0x68, 0xA5, 0x3D, 0xCC, 0x7F, 0xBB, 0x0F, 0xD9, 0x88, 0xEE, 0x9A, 0xE9, 0x5A] data_bytes = bytearray(data.encode('utf-8')) for i in range(len(data_bytes)): key_index = i & 7 table_index = i & 0xF tmp = (table[table_index] + (data_bytes[i] ^ ord(key[key_index]) ^ table[table_index])) ^ table[table_index] data_bytes[i] = ord(key[key_index]) ^ tmp return bytes(data_bytes) def generate_jd_sign(params): # 准备参数 prepared_params = prepare_params(params) param_str = json.dumps(prepared_params, separators=(',', ':'), ensure_ascii=False) # 加密 encrypted = jd_encrypt(param_str) # 计算MD5 md5 = hashlib.md5() md5.update(encrypted) return md5.hexdigest()

6.2 使用示例

params = { "skuId": "100000000001", "cityId": 72, "latitude": 39.90469, "longitude": 116.40717, "isFromOpenApp": True } sign = generate_jd_sign(params) print("Generated Sign:", sign)

7. 实际应用中的注意事项

在实际使用中,我发现京东的sign算法会不定期更新,所以这个方案可能需要根据情况进行调整。以下是一些使用建议:

  1. 定期验证:每隔一段时间验证算法是否仍然有效
  2. 异常处理:当sign无效时,要有备用方案获取新的算法
  3. 性能优化:对于高频请求,可以缓存计算结果
  4. 参数更新:注意京东APP更新后参数可能发生变化

我在实际项目中实现了一个自动更新机制,当检测到sign失效时,会自动触发重新分析流程。这个机制大大提高了系统的稳定性。

http://www.jsqmd.com/news/516037/

相关文章:

  • Pixel Dimension Fissioner代码实例:自定义裂变模板与输出格式控制
  • 嵌入式系统中七大底层数据结构实战解析
  • 无人机视角智慧农业水稻生长周期水稻生长状态检测数据集VOC+YOLO格式5413张3类别
  • 保姆级教程:用DISM++和WePE在5分钟内搞定Win10 22H2 Oct版系统安装
  • Stata进阶可视化技巧:从基础绘图到专业图表优化
  • 嵌入式工程师的破局跃迁:从信息不对称到系统可靠性
  • KeePassXC浏览器扩展完全指南:本地密码管理的安全实践
  • 计算机组成原理视角:分析Ostrakon-VL-8B模型推理的GPU计算与存储瓶颈
  • Nextion字符串通信库:ESP32轻量级HMI交互方案
  • RK3568开发板实战:手把手教你编译RTL8723DU驱动(附常见错误解决方案)
  • 漫画脸描述生成惊艳效果:古风角色+发簪纹样+衣料质感+诗词气质生成
  • 嵌入式传感器抽象库AD_Sensors设计与实践
  • msvcr110_clr0400.dll文件免费下载方法分享
  • 计算机毕业设计:Python图书个性化推荐与可视化分析平台 Django框架 协同过滤推荐算法 可视化 书籍 数据分析 大数据 大模型(建议收藏)✅
  • Python遗传规划实战:用gplearn和DEAP解决符号回归问题(附完整代码)
  • AC/DC/DC模拟EV充电仿真。 前级采用两相交错PFC boost,后级采用移相全桥隔离变换器
  • 编译器未告诉你的真实功耗代价,裸机C代码每行能耗实测数据曝光,立即停用这3个“节能假象”写法
  • Wan2.2-T2V-A5B优化技巧:如何让RTX 3060显卡发挥最大效能?
  • 实测LFM2.5-1.2B-Thinking:职场文案、创意写作、逻辑校验全搞定
  • 别再只用YOLOv8了!手把手教你用PaddleOCR实现高精度车牌识别(附完整代码)
  • Wan2.1-UMT5企业级集成实战:与.NET后端服务通信的完整方案
  • 让Mac鼠标滚动丝滑如触控板:Mos终极配置指南
  • MySQL数据库存储方案:管理万象熔炉·丹青幻境的海量生成记录
  • UG NX 12.0安装全流程:从下载到配置的保姆级教程(含许可证设置)
  • 使用Nano-Banana Studio构建服装设计知识图谱
  • STM32F746NG LCD驱动:LTDC+DMA2D双缓冲显示实现
  • Pixel Dimension Fissioner企业应用:审计日志+操作留痕+权限分级管理模块
  • PyCharm与Anaconda环境配置全攻略:从零搭建Python开发环境
  • 5分钟部署腾讯混元翻译模型:HY-MT1.5-1.8B Docker一键搭建教程
  • VectorNav-PIO:嵌入式平台高精度惯性传感器C++驱动库