
介绍

花销

过程
调用链
OkHttp 框架 │ 发起请求前触发拦截器 ▼OkHttpSignInterceptor.intercept() ← Java native 方法声明 │ JVM 通过 JNI 分发 ▼libkmsec.so: sub_170F0(JNIEnv* env, jobject, jstring canonical, jstring accessKeyId, jstring timestamp) │ ↑ │ native 持有 JNIEnv*,可以随时"反向"调用 Java │ │ │ ├─► env->FindClass("com/ke/securitylib/SecManager") ─┘ ├─► env->GetStaticMethodID("getCertificateMD5Digest") ├─► env->CallStaticObjectMethod(...) ← 调回 Java 执行 getCertificateMD5Digest() │ │ │ └─► libkmsec.so 里另一个 native 函数(getCertificateMD5Digest 的实现) │ └─► 返回 APK 证书 MD5 字符串 │ ├─► sprintf("%s%s%s%s", accessKeyId, salt, certMD5, timestamp) ├─► env->FindClass("com/.../MD5Utils") ├─► env->CallStaticObjectMethod("getMd5", ...) ← 再次调回 Java │ └─► Java MD5Utils.getMd5() 执行,返回 HMAC key │ ├─► 用 key 做 HMAC-SHA256(canonical) ← 纯 C,不回 Java │ └─► 构造 String[] 返回给 Java 层,拦截器填入请求头人柱力
接下俩总结下作为人柱力的我,提供了哪些信息
java层入口
com.ke.infrastructure.app.signature.addon.OkHttpSignInterceptor └── intercept() → native (libkmsec.so)com.ke.securitylib.SecManager └── sign(String canonical, String accessKeyId, String timestamp) → String[] native └── getCertificateMD5Digest() → String native所有的so文件

这让 AI 能做静态字符串扫描,找到:
uioefhli98476843(关键 salt,后来证明是 key 推导的核心)getCertificateMD5Digest、%s%s%s%s、MD5Utils(提示了 key 的构造方式)libzxprotect.so里的HmacSHA256、SecretKeySpec、signatureNonce
运行Ai给的各种frida-hook
[sign] hex=F3E37E060A4F89197B4E2D0A055AECDDCF6473D3F91696D9A13740A3E1954DB5[sign] canonical=accessKeyId=sjoe98HI099dhdD7&appinfo-s=...(完整字符串)[WLL-KGSA] LJAPPVA ...; signature=8+N+BgpPiRl7Ti0KBVrs3c9kc9P5FpbZoTdAo+GVTbU=这三条数据让 AI 能够:
确认 base64(bytes.fromhex(hex)) == signature(签名格式)确认 canonical 字段构成和排序规则 有了可以验证 key 的"答案"
关键性突破
在ai穷举各种key都验证失败后,从ai输出日志getCertificateMD5Digest找到可以位置sprintf((char *)v85, "%s%s%s%s", s1, "uioefhli98476843", v80, v71);AI 立刻要求你提供该字符串的引用函数的完整反编译代码。

第五步:提供 IDA 完整反编译函数
贴出了 sub_170F0 的完整 IDA 伪代码(数百行,LLVM 控制流平坦化混淆的状态机)。
AI 从中解读出:
// 核心行sprintf((char *)v85, "%s%s%s%s", s1, // GetStringUTFChars(a4) = accessKeyId"uioefhli98476843", // 硬编码 salt v80, // getCertificateMD5Digest() 返回值 v71); // GetStringUTFChars(a5) = timestampv76 = CallStaticMethod(MD5Utils, "getMd5", v85); // HMAC keyptr = GetStringUTFChars(v76);通过 JNI 虚表偏移量解码(offset 48 = FindClass,offset 904 = GetStaticMethodID 等),AI 完整还原了 key 推导公式:
HMAC_key = MD5(accessKeyId + "uioefhli98476843" + getCertificateMD5Digest() + timestamp).upper()具体流程
Canonical String 构造
将以下所有字段按 key 字母序升序 排列,以 key=value 形式用 & 连接:
accessKeyId | sjoe98HI099dhdD7 |
host | apps.api.ke.com |
method | GET / POST |
nonce | |
path | /api/proxy/... |
query | |
signedHeaders | Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s |
timestamp | |
device-id-s | |
appinfo-s | |
user-agent | |
hardware-s | |
channel-s | |
systeminfo-s |
Nonce 字符集:
ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678(排除了易混淆字符:I、L、O、0、1、g、l、o、u、v、9)
示例 Canonical:
accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;3.03.31;3033100&channel-s=Android_ke_wandoujia&device-id-s=...&hardware-s=OnePlus;ONEPLUS A6000&host=apps.api.ke.com&method=GET&nonce=1oGlsi5W...&path=/api/proxy/...&query=cityId=310000&page=1&signedHeaders=Device-id-s,...&systeminfo-s=android;11×tamp=1775716377&user-agent=Beike3.03.31;...HMAC Key 推导
step 1: key_input = accessKeyId + "uioefhli98476843" + getCertificateMD5Digest() + timestampstep 2: hmac_key = MD5(key_input).upper() // 32 字符大写 Hex 字符串关键常量:
uioefhli98476843 | |
getCertificateMD5Digest() | 28B22DAC5AAD6BFBDBFD2DBB4F40E878 |
getCertificateMD5Digest() 是 SecManager 的 native 方法,返回 APK 签名证书的 MD5 摘要(32 位大写 Hex)。换包或重签后此值会变。
IDA 关键代码(混淆后伪代码片段):
// sub_170F0 = SecManager.sign() native 实现sprintf(v85, "%s%s%s%s", s1, // accessKeyId (a4 参数)"uioefhli98476843", // 硬编码 salt v80, // getCertificateMD5Digest() 返回值 v71); // timestamp (a5 参数)// v74 = FindClass("com/ke/infrastructure/app/signature/util/MD5Utils")// v57 = GetStaticMethodID("getMd5", "(Ljava/lang/String;)Ljava/lang/String;")v76 = CallStaticMethod(v74, v57, v85); // MD5Utils.getMd5(v85)ptr = GetStringUTFChars(v76); // ptr = HMAC key (32字符大写)签名计算
signature_bytes = HMAC-SHA256(key=hmac_key.encode('utf-8'), msg=canonical.encode('utf-8'))signature = Base64(signature_bytes)sign() native 方法返回 String[],result[0] 为 HMAC 结果的大写 Hex,拦截器再将其转为 Base64 填入 WLL-KGSA。
验证
使用 Frida 捕获的真实请求数据验证:
1775716377 | |
1oGlsi5WaJv3ZlwhRammzb8pw8xmsuS0 | |
12F6CC24101CE761A5F0D64CB3F32A4E | |
8+N+BgpPiRl7Ti0KBVrs3c9kc9P5FpbZoTdAo+GVTbU= | |
8+N+BgpPiRl7Ti0KBVrs3c9kc9P5FpbZoTdAo+GVTbU= | |
| 结果 | 完全一致 |
广子
❝有需要用于爬虫、逆向、抓包的测试机又不想花时间折腾Root、刷机、环境配置可以来看下下面联系方式,或闲鱼链接


夜雨聆风