先搞清楚两个参数的分工
闲鱼 App 的 mtop 请求头里有两个关键参数:x-sign 和 x-mini-wua。很多人上来就死磕 x-sign 的算法,方向就偏了——这两个参数是阿里 SecurityGuard 安全 SDK 的一体两面:
x-mini-wua:设备环境摘要。设备型号、系统版本、root 状态、模拟器特征等信息的压缩编码,回答"你是什么设备"x-sign:请求签名。用设备标识 + miniWua + 时间戳 + 业务参数算出,回答"这个请求合不合法"
服务端两个都验,而且交叉验——x-sign 里掺了 x-mini-wua 的内容,所以改设备信息必须重签,两者是咬合的。
Java 层:入口在 SecurityGuard
jadx 打开闲鱼 APK,签名相关调用最终都汇聚到阿里 SecurityGuard 的标准接口:
// com.taobao.wireless.security.adapter.SecurityGuard
SecurityGuard sg = SecurityGuard.getInstance(context);
// 签名入口
SecurityGuardParamContext ctx = new SecurityGuardParamContext();
ctx.appKey = "24895448"; // 闲鱼的 appKey,以抓包为准
ctx.paramMap = paramMap; // 业务参数 map
ctx.requestType = SecurityGuardSignRequestType.MTOP; // 请求类型
String xSign = sg.signRequest(ctx);
// 设备摘要入口
String miniWua = sg.getMiniWua();这两个都是 native 方法,实现在 libsgmain.so、libsgsecuritybody.so 等一组 SO 里。SecurityGuard 是阿里的通用安全组件,淘宝、天猫、闲鱼、1688 都用它,只是 appKey 和采集策略不同。
x-sign 的算法结构
通过 Frida hook signRequest 的输入输出,喂已知参数做对照实验,可以确认签名结构:
x-sign = md5(utdid + "&" + miniWua + "&" + t + "&" + appKey + "&" + data)utdid:阿里设备唯一标识,32 位 hex,App 首次启动生成后持久化,刷机才变t:秒级时间戳(注意和 H5 mtop 的毫秒级不同)data:业务参数 json
公式本身简单,但注意 miniWua 参与签名——这意味着设备环境变化会导致签名链路整体变化,协议模拟时 utdid、miniWua、设备参数必须成套固定。
x-mini-wua:不用读懂,但要会管理
x-mini-wua 是变长编码的设备信息串,完全逆向它的字段定义性价比极低(版本还会变)。工程上的正确姿势:
- 真机采集:在干净真机上 hook
getMiniWua()的返回,连同当时的设备参数(型号、系统版本、分辨率)一起存档 - 成套使用:一套"设备档案"= utdid + miniWua + 型号/系统/分辨率 + 登录 cookie,池化管理,每个账号固定一套
- 别跨设备混用:miniWua 里编码了设备特征,你拿着小米机型的 miniWua 却报华为的设备型号,服务端一致性校验直接命中
工程落地:Frida RPC 直调
对 SecurityGuard 这类成熟安全组件,手搓算法或 unidbg 模拟都不是首选——Frida RPC 直接调 App 自己的签名函数才是性价比之王:
// frida 脚本:把签名能力暴露成 RPC
Java.perform(function() {
var SecurityGuard = Java.use('com.taobao.wireless.security.adapter.SecurityGuard');
var SGParamContext = Java.use('com.taobao.wireless.security.adapter.SecurityGuardParamContext');
var HashMap = Java.use('java.util.HashMap');
var sg = null;
// 等 SDK 初始化完成再拿实例
Java.choose('com.taobao.wireless.security.adapter.SecurityGuard', {
onMatch: function(inst) { sg = inst; },
onComplete: function() {}
});
rpc.exports = {
sign: function(appKey, paramsJson) {
var ctx = SGParamContext.$new();
ctx.appKey.value = appKey;
var map = HashMap.$new();
var obj = JSON.parse(paramsJson);
for (var k in obj) map.put(k, String(obj[k]));
ctx.paramMap.value = map;
ctx.requestType.value = 4; // MTOP 类型,以实际枚举为准
return sg.signRequest(ctx);
},
miniwua: function() {
return sg.getMiniWua();
}
};
});Python 侧通过 frida 的 rpc 调用,签名问题就解决了。一台闲置手机或模拟器挂 frida-server,就是一台签名服务器。
精髓:闲鱼风控的三个真相
- root 标记是硬杀伤。miniWua 里有 root/模拟器特征位,命中后不是签名失败,而是账号行为被全面降权——能登录但搜不到东西、能聊天但发布被审。签名层完全无感知,最坑人。
- utdid 是阿里系全局设备标识。同一个 utdid 在淘宝、闲鱼、1688 之间是打通的,一个端搞脏了,全系受牵连。设备档案要按"阿里系账号组"隔离。
- Frida RPC 方案的天花板是并发。单机单 App 实例签名 QPS 有限,规模化要堆设备或用 unidbg 离线化——但 unidbg 模拟 SecurityGuard 的补环境工作量极大(它大量读取 Android 系统服务),中小规模别碰。
总结
- x-sign 和 x-mini-wua 是 SecurityGuard 的一体两面,交叉校验
- 签名公式简单,但 miniWua 参与签名,设备信息必须成套固定
- 工程首选 Frida RPC 直调,别手搓、慎入 unidbg
- 真正的风控在设备环境(root/模拟器标记),不在签名