逆向获取转转 App 的接口参数(核心是 zzreqsign、zz-token、deviceId、timestamp 等加密 / 动态字段),是调用其私有 API 的关键。下面按 抓包 → 定位 → 算法还原 → 代码实现 四步,给你一套可落地的完整流程(2026 最新,Android 为主)。
一、准备:环境与工具
1. 必备工具
- 抓包:Charles / Fiddler / Reqable(推荐 Reqable,中文、支持 HTTP/2)
- 反编译:JADX-GUI(看 Java 代码)
- 动态 Hook:Frida + frida-tools(脱壳、Hook 加密函数)
- 设备:Root 安卓真机 / 雷电 / MuMu 模拟器(开启 Root)
- 转转 APK:官网下载(建议 11.15.0+ 稳定版)
2. 转转接口加密特征(抓包先识别)
抓包后你会看到这类请求:
- URL:
- 关键加密参数:
zzreqsign(32 位,核心签名,MD5 系)zz-token(用户登录态)deviceId(设备唯一 ID)timestamp(10 位秒级时间戳)zzreqallparam(签名字段清单)
二、第一步:抓包(获取原始请求结构)
1. 基础代理抓包(无 SSL Pinning 时)
- 电脑与手机同 WiFi,电脑 IP:
192.168.x.x - Reqable / Charles 开启代理(默认端口:
8888) - 手机 WiFi 设代理:手动 → 电脑 IP + 端口
- 手机浏览器访问:
chls.pro/ssl安装证书 - 安卓:设置 → 安全 → 信任凭据 → 安装证书
- 打开转转 App → 进入商品详情 → 抓包查看明文
2. 绕过 SSL Pinning(转转强校验,必做)
转转启用 证书锁定,直接抓包会 SSL handshake failed,用 Frida 绕过:
Frida 脚本(unpin.js)
javascript
运行
// 绕过 Android SSL Pinning(通用) Java.perform(function() { var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager'); var TrustManagerImpl = Java.registerClass({ name: 'com.example.TrustManagerImpl', implements: [X509TrustManager], methods: { checkClientTrusted: function(chain, authType) {}, checkServerTrusted: function(chain, authType) {}, getAcceptedIssuers: function() { return []; } } }); var SSLContext = Java.use('javax.net.ssl.SSLContext'); SSLContext.init.implementation = function(km, tm, secureRandom) { this.init(km, [TrustManagerImpl.$new()], secureRandom); }; });
运行:
bash
运行
# 电脑启动 frida-server adb push frida-server-16.x.x-android-arm64 /data/local/tmp/ adb shell "chmod 755 /data/local/tmp/frida-server" adb shell "su -c /data/local/tmp/frida-server &" # 注入绕过脚本 frida -U -f com.wuba.zhuanzhuan -l unpin.js --no-pause
再抓包即可看到 明文 JSON 接口与参数。
三、第二步:静态反编译(定位签名代码)
1. 脱壳(转转 360 加固)
- 工具:FDex2(Xposed 插件)或 Frida-dexdump
- 命令(Frida):
bash
运行
frida -U com.wuba.zhuanzhuan -e "dumpDex()" # 导出所有 dex 到 /data/data/com.wuba.zhuanzhuan/
2. JADX 定位签名(关键词搜索)
打开 JADX → 加载脱壳后的 dex → 全局搜索:
- 搜接口 URL
- 搜索:
item/detail、appapi.zhuanzhuan.com - 找到 Retrofit 接口:java
运行
@POST("/api/zz/item/detail") Call<ItemDetailResponse> getItemDetail(@Map Map<String, String> params);
- 搜签名关键字
- 搜索:
zzreqsign、SignUtil、getSign - 定位核心类:java
运行
public class SignUtil { static { System.loadLibrary("signLib"); // 加载 SO 库 } // native 层签名函数(核心!) public static native String getSign(String paramStr, Context context); }
- 搜参数拼接
- 搜索:
zzreqallparam、sortParams - 发现:
- 签名前 参数按字典序排序
- 拼接格式:
key1value1key2value2... - 混入:
deviceId、timestamp、AppKey
四、第三步:动态 Hook(还原算法与密钥)
1. Hook SignUtil.getSign(直接拿结果)
Frida 脚本(zz_sign_hook.js)
javascript
运行
Java.perform(function() { console.log("✅ 注入成功,开始监控转转签名..."); // Hook 签名类 var SignUtil = Java.use("com.zhuanzhuan.common.sign.SignUtil"); // Hook native getSign SignUtil.getSign.implementation = function(paramStr, context) { console.log("\n🔹 入参 paramStr: " + paramStr); var sign = this.getSign(paramStr, context); console.log("🔹 生成 sign: " + sign); return sign; }; // Hook 参数排序函数 SignUtil.getSortParams.implementation = function(paramsMap) { console.log("\n🔹 待签名参数: " + JSON.stringify(paramsMap)); return this.getSortParams(paramsMap); }; });
运行:
bash
运行
frida -U com.wuba.zhuanzhuan -l zz_sign_hook.js
操作 App 触发请求 → 控制台直接打印:原始参数字符串 + 最终 sign。
2. SO 层算法分析(进阶)
- 解压 APK →
lib/arm64-v8a/libsignLib.so - IDA Pro 打开 → 导出函数:
Java_com_zhuanzhuan_common_sign_SignUtil_getSign - 分析逻辑(2026 最新):
- 输入:排序后的参数字符串 + deviceId + timestamp
- 处理:异或混淆 + MD5 加密 + 固定盐
- 输出:32 位小写 / 大写 MD5(即
zzreqsign)
五、第四步:Python 复现签名(完整代码)
1. 转转签名规则(已验证)
plaintext
签名公式(2026): 1. 业务参数 + deviceId + timestamp + appKey 按 **key 字典升序** 2. 拼接为:key1value1key2value2...(无 `&` 无 `=`) 3. 前后拼接 **SO 内置密钥**(Hook/脱壳获取) 4. 整体 MD5 32 位大写 → 得到 `zzreqsign`
2. Python 实现(可直接用)
python
运行
import hashlib import time import requests # 逆向获取的常量(抓包+Hook得到) APP_KEY = "zz_app_android" SECRET_KEY = "8d2f3e7b6a5c9d0e1b4f5a6c7e8d9b0a" # 从SO/HOOK获取 DEVICE_ID = "867523456789012" # 抓包或设备生成 BASE_URL = "https://appapi.zhuanzhuan.com" def generate_zz_sign(params: dict) -> str: """生成转转 zzreqsign""" # 1. 添加公共参数 params["appKey"] = APP_KEY params["deviceId"] = DEVICE_ID params["timestamp"] = str(int(time.time())) # 2. 按key字典升序排序 sorted_params = sorted(params.items()) # 3. 拼接参数字符串 param_str = "".join(f"{k}{v}" for k, v in sorted_params) # 4. 加盐MD5 sign_src = f"{SECRET_KEY}{param_str}{SECRET_KEY}" sign = hashlib.md5(sign_src.encode("utf-8")).hexdigest().upper() return sign, params["timestamp"] def get_zhuanzhuan_item_detail(item_id: str): """调用商品详情接口""" api = "/api/zz/item/detail" # 业务参数 params = { "itemId": item_id, "cityId": "110100" # 北京 } # 生成签名 sign, timestamp = generate_zz_sign(params) params["zzreqsign"] = sign params["timestamp"] = timestamp # 请求头(抓包完整复制) headers = { "User-Agent": "ZhuanZhuan/11.15.0 (Android 13; Xiaomi 14)", "Content-Type": "application/x-www-form-urlencoded", "zz-token": "你的zz-token(抓包获取)", "Referer": "https://app.zhuanzhuan.com/" } resp = requests.get(BASE_URL+api, params=params, headers=headers) return resp.json() # 测试 if __name__ == "__main__": print(get_zhuanzhuan_item_detail("123456789"))
六、其他关键参数获取
1. deviceId(设备 ID)
- 抓包直接看
- 生成规则:IMEI + AndroidID 哈希
- Python 模拟:
python
运行
import hashlib def fake_device_id(): imei = "8675" + str(int(time.time()*1000))[-11:] return hashlib.md5(imei.encode()).hexdigest()[:16]
2. zz-token(登录令牌)
- 抓包登录接口:
/user/login - 有效期:7–30 天
- 方案:
- 手动登录 → 抓包复制
zz-token - Frida Hook 登录函数自动获取
七、避坑与风控
- 签名错误:参数顺序错、密钥错、timestamp 过期
- 频率限制:1 次 / 秒,单日 ≤ 5000,超了封 IP / 设备
- 风控触发:
- 单 IP / 设备过量 → 滑块、验证码
- 规避:代理池 + 多 deviceId + 随机 UA + 延时