第一次看到茅台APP的reservationToken参数时,我就像发现了一个黑匣子——这串32位的十六进制字符串背后到底藏着什么秘密?在实际逆向过程中,这种核心参数往往是破解业务逻辑的关键突破口。对于电商类APP而言,类似token的生成机制直接关系到风控体系和业务安全性。
记得去年帮朋友抢购茅台时,发现每次请求都会携带这个神秘字符串。用Charles抓包看到它的值形如a1b2c3d4e5f6...的MD5样式,但简单尝试用固定值替换就会导致接口报错。这让我意识到:必须逆向分析它的生成逻辑,才能实现自动化操作。
我习惯用JADX作为反编译的起点。把APK拖进去后,直接搜索reservationToken关键词,很快定位到核心代码段:
java复制orderPrepareParameter2.mReservationToken =
MD5Util.encode("ANDROID" + W.e() + X.b());
这个拼接结构透露了三个重要信息:
W.e()获取的动态值X.b()生成的另一组数据静态分析遇到障碍时,Frida就成了救命稻草。比如当代码存在缓存机制时,直接hook可能无法捕获初始生成过程。这时可以写个主动调用脚本:
javascript复制Java.perform(function(){
let result = Java.use("com.maotai.UTDevice").getUtdid();
console.log("UTDevice.getUtdid() =>", result);
});
运行后会输出类似"zT7wY3Xq1R..."的Base64编码字符串,这验证了我们的静态分析结果。
跟踪W.e()方法会发现它最终调用的是UTDevice.getUtdid()。继续深入会发现这个值是通过以下步骤生成的:
TelephonyManager.getDeviceId()m5c()方法中的位运算关键代码段如下:
java复制byte[] rawData = m5c(); // 核心生成逻辑
this.h = Base64.getEncoder().encodeToString(rawData);
其中m5c()内部包含对IMEI的特定处理:
<< 3)^ 0x55)另一个关键参数X.b()的生成更有意思:
java复制String deviceId = telephonyManager.getDeviceId();
UUID uuid = deviceId != null ?
UUID.nameUUIDFromBytes(deviceId.getBytes("utf8")) :
UUID.randomUUID();
return uuid.toString();
这里用到了Java标准库的nameUUIDFromBytes方法,它会根据输入字节生成类型3的UUID(基于MD5哈希)。实测发现:
xxxxxxxx-xxxx-3xxx-yxxx-xxxxxxxxxxxx标准y值固定为8、9、a或b整个生成过程可以总结为:
code复制ANDROID + [Base64(m5c(IMEI))] + [UUIDv3(IMEI)]
│ │ │
│ │ └── 类型3 UUID生成
│ └── 包含位移/异或的IMEI处理
└── 固定前缀
用Python还原时需要注意:
python复制# 模拟Java的字节有符号右移
def right_shift(val, n):
return (val % 0x100000000) >> n
python复制import uuid
uuid.uuid3(uuid.NAMESPACE_DNS, imei)
python复制import base64
base64.b64encode(processed_imei).decode()
在分析过程中,我发现这个机制有几个值得注意的安全设计:
m5c()中的位运算增加逆向难度不过也存在可优化点:
第一次实现算法时遇到了三个典型问题:
byte是有符号类型,而Python默认无符号,导致哈希结果不一致。解决方案是增加& 0xff处理:python复制[b & 0xff for b in imei_bytes]
uuid.uuid4()导致校验失败,实际应该用uuid.uuid3()=,而某些Python库需要手动设置通过WireShark抓包对比发现,自生成的token与官方APP的差异往往出现在:
从这个案例我们可以总结出一些开发经验:
对于逆向工程师来说,这类分析最大的价值在于理解商业级APP的安全设计思路。每次成功的逆向都是一次绝佳的学习机会,你会发现: