chore(tools): 入库汽水签名工具链(独立签名服务与逆向记录)
- tools/mssdk-sign/:C++ 实现的签名服务,附 mssdk 内部结构记录、MinGW 与 MSVC 两套构建脚本、调用示例 client.mjs 与自检入口 - signer/server.mjs:Node 实现的等价签名服务,面向「主服务不在 Windows 上」 的场景——bdms.node 是 Windows PE 原生模块,只能由 Windows 版 Node 加载, 故把「加载官方模块」关进独立进程,主服务经 HTTP 取 X-Helios / X-Medusa 两者都不被 src/sdk 引用:运行期走 sdk/qs/mssdk.ts 直接加载官方 bdms.node, 它们是组件升级、签名失效时重新定位入口并验证的手段,放在仓库里才能跟着 代码一起维护。 专有二进制(mssdk-sign.exe、metasecml.dll)由 tools/mssdk-sign/.gitignore 排除,不随仓库分发。
This commit is contained in:
1 parent
dd28ba157c
commit
14f7295fd4
8 files changed
+1392
No files matched your search
@@ -0,0 +1,97 @@
|
||||
# metasec 签名组件逆向记录
|
||||
|
||||
> 面向维护者:**汽水客户端升级后签名失效时,先看这里。**
|
||||
> 相关代码:`mssdk-sign.cpp`(签名服务);构建与接口用法见同目录 `README.md`。
|
||||
> 记录时间:2026-10-02,样本:`soda-mssdk/{metasecml.dll, bdms.node}`。
|
||||
|
||||
## 1. 速览
|
||||
|
||||
| 项 | 结论 |
|
||||
|---|---|
|
||||
| 组件身份 | 字节 metasec 官方组件,内部名 `metasecbaseml` **4.6.1.0**(2024-09-04) |
|
||||
| 数字签名 | Authenticode 主体 **Douyin Vision Co., Ltd.**(DigiCert G4 Code Signing) |
|
||||
| 保护 | 自解密壳 + 运行期解密字符串表;`LoadLibrary` 后内存里即为明文镜像 |
|
||||
| 真实签名入口 | `metasecml.dll + 0x52af0`(**本版本**),`char* sign(const char* url, const char* headers)` |
|
||||
| 需要 init 吗 | **不需要**,冷进程直接调即可出有效签名 |
|
||||
| 有效性 | 已端到端验证:签名被 `api.qishui.com` 接受;不签名 / 设备不匹配 → `HTTP 200 + 0 字节` |
|
||||
| 产出能力 | `X-Helios` + `X-Medusa`(部分场景附 `X-Neptune`);**不含** Argus/Ladon/Khronos/Gorgon/a_bogus |
|
||||
|
||||
## 2. 两个文件的关系
|
||||
|
||||
- `metasecml.dll`(8.8MB):算法本体。导出表只有 3 个符号,且**都是幌子**:
|
||||
- `o0o0`(0x191c0):线程局部状态初始化(读 TEB→TLS 槽,条件成立时 memset 一个 0x148 字节结构)。
|
||||
- `MSBridgeML` / `MSBridgeOV`(同为 0x1d010):同一个桩 —— 调一次内部函数后**无条件返回 true**,签名结果不经它返回。
|
||||
- `bdms.node`(164KB):官方客户端的 N-API 胶水。对 JS 暴露 `init` / `generateHttpSignatureHeaders` / `report`;
|
||||
通过**延迟导入** `metasecml.dll` 的 `o0o0`、`MSBridgeML` 来用 DLL。
|
||||
|
||||
## 3. 真实调用 ABI(核心)
|
||||
|
||||
```c
|
||||
// headers: 官方展平形式 —— 每行 "名字\r\n值",行间也用 "\r\n" 连接
|
||||
char* sign(const char* url, const char* headers_lines);
|
||||
```
|
||||
|
||||
- 地址:`image base + 0x52af0`(该处是 `jmp` 跳板,实体在 0x52b00)。
|
||||
- 返回:`"名字\r\n值\r\n…"` 的签名头串,堆分配;释放函数 = `image base + 0x1d8c0`。
|
||||
- 线程:内部有全局/TLS 状态,**串行调用**(服务端已加互斥锁)。
|
||||
- addon 是怎么拿到这个地址的:运行时 DLL 改写了它的 IAT 槽(`bdms.node` 的 `.data` 偏移 `0x25e38`),
|
||||
槽里躺着的就是 `base+0x52af0` —— 这解释了「导出表说是 0x1d010,实际却在 0x52af0」的矛盾。
|
||||
- 壳的形态:磁盘上只有 PE 头 + 单个 8.8MB 加密节 `.xs2`(熵≈8.0),`.text/.rdata/.data/.xs0` 均无 raw 数据;
|
||||
加载时解密进内存 → **内存 dump 即得完整镜像**(重定位已修好)。
|
||||
|
||||
## 4. 行为规则(实测)
|
||||
|
||||
| 场景 | 服务端反应 |
|
||||
|---|---|
|
||||
| 不带签名 | `HTTP 200 + 0 字节` |
|
||||
| 带正确签名 | `HTTP 200` + 正常 JSON(曲名/时长/档位齐全) |
|
||||
| 签名后改 URL 里的 `device_id` | `HTTP 200 + 0 字节` → **签名与 URL 设备参数绑死** |
|
||||
| headers 少一个(尤其 `cookie`、`x-ss-stub`) | `HTTP 200 + 0 字节` → 签名覆盖**全部**请求头 |
|
||||
|
||||
- **`x-ss-stub` = body 的 MD5(大写)**,既是签名输入也必须随请求发出。
|
||||
- **场景决定头集合**:传完整真实头 → `X-Helios` + `X-Medusa`(有时附 `X-Neptune`);
|
||||
只传一个 `user-agent` 这类极简头 → **只给 `X-Neptune`**(网页兜底场景)。
|
||||
所以调用方必须把「真实要发出的那一份头」整套传进来。
|
||||
- 输出含时间戳/nonce,**每次不同**属正常。
|
||||
- 签名有效 ≠ 能拿整曲:匿名仍只有 30 秒试听(实测 236.6s 曲目 `stream_duration=30`),
|
||||
**瓶颈是登录 sessionid,不是签名**。
|
||||
|
||||
## 5. 内部结构(RTTI 符号泄漏)
|
||||
|
||||
| 类 / 符号 | 作用 |
|
||||
|---|---|
|
||||
| `IMSHttpReqSignModule` / `MSHttpReqSignModule` | HTTP 请求签名(本服务的调用目标) |
|
||||
| `IMSWebsocketReqSignModule` | WebSocket 请求签名 |
|
||||
| `MSBlackBox` / `IMSBlackBoxItem` | 黑盒数据采集 |
|
||||
| `IMSSecDeviceIDModule` | 设备指纹(device id) |
|
||||
| `IMSRiskInspectModule` / `IMSRiskInspector` | 风控检测 |
|
||||
| `IMSReportModule` | 数据上报 |
|
||||
| `MSPBSInt32/64`、`MSPBString(List)`(`__ms__`) | protobuf 序列化 |
|
||||
| `MSNetInterceptor::FuncInterceptor` + `Cronet_UrlRequest_Create`、`Cronet_Engine_SetMD5Header` | 注入 Chromium Cronet 网络栈(`x-ss-stub` 的 MD5 由它算) |
|
||||
| `MSConfig` / `MSJSON` / `MSHttpClient` / `MSTaskManager` | 基础设施;日志用 spdlog,源路径 `utils/matrix/os/windows/ms_log.cc` |
|
||||
|
||||
## 6. 保护强度评估
|
||||
|
||||
- 壳是「自解密 + 字符串按需解密」,**没有代码虚拟化**;脱壳后是正常的 MSVC x64 代码,可读可跟。
|
||||
- 敏感字符串(如 `X-Helios`)静态搜不到,运行期才解密;镜像里没找到明文标准 AES S-box
|
||||
(只有 CRC32 表、Base64 表、TEA/Blowfish 类常量)。
|
||||
- 结论:**算法级复刻不划算**——黑盒调用的集成成本≈0,而且能随官方升级同步。
|
||||
|
||||
## 7. 客户端升级后怎么重新定位偏移
|
||||
|
||||
`--selftest` 会**大声失败**,不会静默签出无效头。定位步骤:
|
||||
|
||||
1. 从新版客户端取 `metasecml.dll`(解包方式见 `sdk/qs/mssdk.ts` 头部注释)。
|
||||
2. 跑 `mssdk-sign.exe --selftest --dll <新dll>`:会打印它读到的 DLL 版本;失败即偏移变了。
|
||||
3. 找新偏移(仓库 `tmp/` 下现成脚本):
|
||||
- `tmp/metasec-rebuild.py` —— 内存脱壳 + 重建可分析 PE;
|
||||
- `tmp/bdms-abi.py` —— 读 `bdms.node` 运行时 IAT 槽,直接拿到真实签名函数地址(**最省事**);
|
||||
- `tmp/metasec-call-test.py` —— 用候选偏移直接调用,看返回是否形如 `X-…\r\n…`。
|
||||
4. 用 `--sign-rva 0xXXXXX` 验证,通过后写进 `mssdk-sign.cpp` 的 `KNOWN_VERSIONS`。
|
||||
5. 复验:`node client.mjs --send` 应得到 `HTTP 200` + 非空 body。
|
||||
|
||||
## 8. 边界
|
||||
|
||||
- `metasecml.dll` / `bdms.node` 是**官方专有二进制**,不入库、不随代码分发;各自从自己的官方客户端取,放 exe 同目录。
|
||||
- 用途限于个人自用/互操作性研究;不要对外提供签名服务,也不要用于批量刷接口。
|
||||
- 签名服务本身不联网、不落 cookie;部署仍建议只监听 `127.0.0.1` 并设置 `--token`。
|
||||
Reference in new issue
Block a user