米游社salt(LK2 & K2)获取 - MuxiaoWF跳到主要内容
米游社salt(LK2 & K2)获取

米游社salt(LK2 & K2)获取

周五 7月 11 2025jadx
2701 字 · 16 分钟

使用jadx获取内部加密salt

变量名随机,自行对应

打开jadx,直接搜索DSLK2关键词 || 找到com.mihoyo.hyperion.net.aaaaacom.mihoyo.hyperion.net.bbbbb文件里的a2222变量进行搜索(下图告诉你两个方法定位的东西是一样的)

定位到的DS/LK2

定位到的DS/LK2

可以看到上面LK2变量是通过一个叫f57949q的int数组定义的;双击定位

定位f57949q

定位f57949q

复制值备用(注意将变量替代为值),例如这里2.92.0版本的为

{-74, 60, -78, 204, 96, -88, -82, 78, 162, 54, -68, 78, -84, 84, -80, 66, 216, -80, 180, 90, MediaPlayer.MEDIA_PLAYER_OPTION_ABR_AVERAGE_BITRATE, C5432a.f120322i, -74, 66, 114, -98, -116, 54, 84, 204, 204, C5432a.f120320g}

替换为

{-74, 60, -78, 204, 96, -88, -82, 78, 162, 54, -68, 78, -84, 84, -80, 66, 216, -80, 180, 90, 174, -106, -74, 66, 114, -98, -116, 54, 84, 204, 204, -104}

这里很容易让人怀疑f57949q变量上面的f57948p变量是干什么的,搜索一下就可以定位到了,刚好是K2

定位f57948p

定位f57948p

同样操作,复制备用,这里值为

{-98, -66, 192, 120, 60, 198, 168, -68, 216, 66, 168, 126, -108, -76, 126, -82, 114, -88, -88, -122, 162, -112, -66, 180, -122, 72, 108, 180, 78, -108, -106, -116}

至此获取完原始salt

获取加密方法

通过上图,我们发现LK2变量传入了c14930a.m82567b方法,直接定位

定位f57948p

m82567b方法

这非常明显就是一个加密方法,略微重写一下

public static String m61080b(int[] intArr) {
int i12;
StringBuilder sb2 = new StringBuilder();
ArrayList<Integer> arrayList = new ArrayList<>(intArr.length);
for (int i13 : intArr) {
if (i13 < 0) {
i12 = ((double) (-i13)) >= Math.pow(3.0d, 6.0d) ? (int) (((Math.log(-i13) / Math.log(3.0d)) - 6) + 48) : ~i13;
} else {
i12 = (i13 / 3) + 48;
}
arrayList.add(i12);
}
for (Integer integer : arrayList) {
sb2.append((char) integer.intValue());
}
return sb2.toString();
}

现在只需要传入数组就能得到加密后的salt,同理定位获取K2变量所用的加密方法(事实上两个加密方法是一样的)

结束

public static final int[] k2 = {-98, -66, 192, 120, 60, 198, 168, -68, 216, 66, 168, 126, -108, -76, 126, -82, 114, -88, -88, -122, 162, -112, -66, 180, -122, 72, 108, 180, 78, -108, -106, -116};
public static final int[] lk2 = {-74, 60, -78, 204, 96, -88, -82, 78, 162, 54, -68, 78, -84, 84, -80, 66, 216, -80, 180, 90, 174, -106, -74, 66, 114, -98, -116, 54, 84, 204, 204, -104};
public static void main(String[] args) {
System.out.println(m61080b(k2));
System.out.println(m61080b(lk2));
}

运行结果:

aApXDrhCxFhZkKZQVWWyfoAlyHTlJkis
IDMtPWQJfBCJSLOFxOlNjiIFVasBLttg

至此即得


Claude Code Skill:自动化提取

以下是一个用于 Claude Code 的 skill,可以通过 jadx-mcp-server 自动化完成上述定位和解码流程。

使用方法:将下方内容保存为 SKILL.md 放入 .claude/skills/find-miyoushe-ds-salt/ 目录即可。

---
name: find-miyoushe-ds-salt
description: >
从米游社(miyoushe/hyperion) APK 中提取 DS 签名参数(K2/LK2 salt)。
当用户要求分析米游社的 DS 签名机制、查找 K2/LK2 salt、逆向米游社网络请求签名、
或通过 jadx-mcp-server 分析 com.mihoyo.hyperion 包时使用此 skill。
适用于任何版本的米游社 APK,变量名会因混淆而不同,但逻辑结构不变。
---
# 米游社 DS 签名参数提取指南
## 背景
米游社 APK 使用 DS (Dynamic Signature) 机制对网络请求进行签名。DS 由 native (JNI) 方法生成,
Java 层负责组装参数并注入到 HTTP 请求头中。核心参数 K2 和 LK2 是 `int[]` 数组,
通过 `AManager` 类解码为 salt 字符串后传给 native 方法。
## 前置条件
- jadx-mcp-server 已启动并连接到 JADX-GUI
- JADX-GUI 已打开目标 APK 且反编译完成
- 通过 `curl http://127.0.0.1:8650/health` 验证连接正常
## 定位流程
以下步骤的类名和字段名会因 APK 版本不同而变化(混淆重命名),但逻辑结构始终一致。
每一步都给出了识别特征,不要硬编码类名。
### 第一步:找到 native JNI 包装类
DS 签名的核心实现在 native 层。先找到 `com.mihoyo.hyperion.net` 包下的 native 包装类。
> ⚠️ **搜索 API 注意事项(必读)**`search-classes-by-keyword``search_in=class`
> 模式会按 `.` 把类名切成 token,再对**单个 token** 做前缀/整词匹配。因此**任何带 `.` 的限定包名**
> (如 `com.mihoyo.hyperion.net``com.mihoyo``hyperion.net`)都会返回 0 条。
> 正确做法是:用**单 token**(如 `mihoyo`/`hyperion`/`net`),或改用 `search_in=code` 搜代码级特征。
**推荐查询(最精准,直接命中 native 库的加载点):**
```bash
# 搜代码里的 System.loadLibrary,再从结果里挑 com.mihoyo.hyperion.net.* 的类
curl -s "http://127.0.0.1:8650/search-classes-by-keyword?search_term=loadLibrary&search_in=code&offset=0&count=20"
```
**备选查询(单 token 包名片段,需自己用 `com.mihoyo.hyperion.net` 前缀过滤):**
```bash
curl -s "http://127.0.0.1:8650/search-classes-by-keyword?search_term=net&search_in=class&offset=0&count=40"
```
识别特征:该包下通常有 2 个类,满足以下条件:
- `static { System.loadLibrary("xxx"); }` 加载 native 库
- 包含 `native` 方法,参数和返回值都是 `String`
- 类名极短(被混淆),如 `aaaaa``bbbbb`
记录这两个类,分别记为 **类A****类B**。它们各自有一个接受 1-2 个 String 参数、返回 String 的 native 方法(通常是同名的,如 `a2222`)。
### 第二步:找到 DS 拦截器(OkHttp Interceptor)
使用交叉引用找到谁调用了这些 native 方法。
```bash
# 对类A和类B的 native 签名方法分别做 xrefs
curl -s "http://127.0.0.1:8650/xrefs-to-method?class_name=<类A全名>&method_name=<方法名>&offset=0&count=20"
curl -s "http://127.0.0.1:8650/xrefs-to-method?class_name=<类B全名>&method_name=<方法名>&offset=0&count=20"
```
识别特征:xrefs 中会有一个类实现了 `Interceptor`(OkHttp 的 `InterfaceC18926w`),
且其 `intercept` 方法中包含 `chain.proceed(...)``"DS"` 字符串。
这个类就是 **DS 拦截器**。但实测中**拦截器多为内层类**(如 `hn.C33707m`),而 MCP 的 `class-source`
`xrefs-to-class` 对带内层类尾部标记(如 `C33707m`)的类都会返回 "Class not found";只有
`xrefs-to-method`(按方法)能解析。另外**不存在 `/method-source` 端点**,不要尝试。
>**推荐程序化走法(无需手动开 GUI)**:对 native 方法做 `xrefs-to-method` 找到**调用方**
> (通常是 `xxx.app.xxxApplicationHelper` 这类 Application 初始化类),再去读**调用方的**
> `class-source`(能正常取到),从中搜索 `new <拦截器类>(K22)` / `int[] K22 = ...` 即可反推出盐值数组。
> 这绕开了取不到拦截器源码的问题。
兜底(调用方也取不到时):在 JADX-GUI 中手动打开拦截器类,再用 `current-class` API 读取:
```bash
curl -s "http://127.0.0.1:8650/current-class"
```
### 第三步:分析 DS 拦截器逻辑
从拦截器的 `intercept` 方法中可以看到:
1. **DS 类型判断**:通过请求 tag 或路径决定使用 DS1 还是 DS2
2. **DS1 路径**:调用某个 native 方法,传入一个 salt 字符串
3. **DS2 路径**:调用另一个 native 方法,传入请求体和查询串
4. **salt 来源**:salt 由 `int[]` 数组通过某个管理器类解码得到
关键代码模式:
```java
// DS2 路径(特殊请求)
if (isDS2) {
ds = nativeMethod2(requestBody, queryString);
} else {
// DS1 路径(普通请求)
ds = nativeMethod1(manager.decode(intArray));
}
chain.proceed(request.addHeader("DS", ds).build());
```
### 第四步:找到 salt 数组的定义位置
优先用第二步的"程序化走法":读拦截器的**调用方** `class-source`,搜索 `new <拦截器>(K22)` 即可定位
盐值数组。若坚持用 xrefs,注意 `xrefs-to-class` 对拦截器内层类同样可能返回 not found:
```bash
curl -s "http://127.0.0.1:8650/xrefs-to-class?class_name=<DS拦截器类名>&offset=0&count=20"
```
> ⚠️ **K2/LK2 未必在 BuildConfig 里**`com.mihoyo.hyperion.utils.BuildConfig`
> 只是标准 Gradle 产物(只有 `BUILD_TYPE` 等),两个 `int[]` 实际在另一个类(如 `p232Df.C1989a`)。
> 不要硬编码"找 BuildConfig",应找**含两个相邻 `public static final int[]` 静态字段的类**
> 字段名被混淆(如 `f37869p`/`f37870q`),顺序固定:上 **K2**、下 **LK2**
识别特征:实例化通常在 Application 初始化方法中(如 `initAppEnv` 或类似名称)。找到类似代码:
```java
int[] K22 = SomeClass.K2_FIELD; // 上方字段 = K2
new DSInterceptor(K22);
// 同类下方还有 LK2_FIELD = LK2
```
记录该类的两个 `int[]` 字段(名字被混淆)。通常两个数组字段紧挨着定义,
上面一个是 **K2**,下面一个是 **LK2**(顺序固定)。可用第五步的 JS Bridge 类交叉确认。
如果该类可通过 API 访问,直接获取:
```bash
curl -s "http://127.0.0.1:8650/class-source?class_name=<盐值数组所在类名>"
```
### 第五步:确认 K2 和 LK2 的对应关系
通过 xrefs 或搜索找到另一个使用这些数组的地方。`GetDSMethodImpl`(JS Bridge 实现)
会使用 LK2,可以通过搜索其中的数组引用来确认:
```bash
# 搜索包含两个 native 方法名的类(通常是 JS Bridge)
curl -s "http://127.0.0.1:8650/search-classes-by-keyword?search_term=getDS&search_in=code&offset=0&count=10"
```
该类中会有类似代码来明确标识 LK2:
```java
int[] LK2 = SomeClass.LK2_FIELD; // 与第四步同一个类
data.put("DS", nativeMethod(manager.decode(LK2)));
```
### 第六步:解码 salt
找到 `AManager` 类(负责将 `int[]` 解码为 String 的工具类)。
识别特征:
- 包名通常在 `bn` 包下
- 包含两个参数和返回值相同的方法(一个给 K2 用,一个给 LK2 用)
- 方法逻辑:遍历 int 数组,对每个元素做数学运算转为 char
解码算法(版本间不变):
```javascript
// Node.js 解码脚本
function decodeSalt(intArr) {
return intArr.map(x => {
if (x < 0) {
return Math.abs(x) >= 729
? String.fromCharCode(Math.floor(Math.log(-x) / Math.log(3) - 6 + 48))
: String.fromCharCode(~x);
}
return String.fromCharCode(Math.floor(x / 3) + 48);
}).join('');
}
```
直接执行:
```bash
node -e "
const k2 = [<K2数组值>];
const lk2 = [<LK2数组值>];
function d(a){return a.map(x=>x<0?Math.abs(x)>=729?String.fromCharCode(Math.floor(Math.log(-x)/Math.log(3)-6+48)):String.fromCharCode(~x):String.fromCharCode(Math.floor(x/3)+48)).join('');}
console.log('K2 salt:', d(k2));
console.log('LK2 salt:', d(lk2));
"
```
## 版本差异说明
| 元素 | 是否变化 | 说明 |
|---------------|--------|----------------------------------------------------------------------------|
| 包结构 | 不变 | `com.mihoyo.hyperion.net` 始终存在 |
| native 包装类名 | 变化 | 混淆为短名,如 `aaaaa`/`bbbbb` |
| native 方法名 | 变化 | 混淆为短名,如 `a2222`/`b5555` |
| native 库名 | 变化 | 混淆为短名,如 `libdddd.so` |
| DS 拦截器类名 | 变化 | 混淆,但一定实现 Interceptor 接口 |
| 盐值数组所在类名 | 变化 | 包名/类名会被混淆,且**不一定是 BuildConfig**(本版本实测在 `p232Df.C1989a`);一定含两个相邻 `int[]` 字段 |
| K2/LK2 数组值 | **变化** | 每个版本的 salt 不同 |
| AManager 解码算法 | 不变 | 数学逻辑固定 |
| DS header 名称 | 不变 | 始终为 `"DS"` |
| DS1/DS2 判断逻辑 | 不变 | 通过 tag 或路径判断 |
## 快速参考
DS 生成的完整调用链:
```
OkHttp 请求 → DS 拦截器.intercept()
├─ DS2: nativeMethod(body, query) → 加密签名
└─ DS1: nativeMethod(AManager.decode(K2/LK2)) → 加密签名
└─ int[] → 解码算法 → salt 字符串
最终: request.addHeader("DS", 签名值)
```
K2 用于主 API 拦截器,LK2 用于 WebView JS Bridge 的 `getDS` 方法。
两者解码方式完全相同,只是应用场景不同。

感谢您的阅读!如果可以,给俺点些关注吧~

米游社salt(LK2 & K2)获取

周五 7月 11 2025jadx
2701 · 16 分钟
封面
示例歌曲
示例艺术家
封面
示例歌曲
示例艺术家
0:00 / 0:00