Files
MusicServer/tools/mssdk-sign/README.md
T
snowy-yang 14f7295fd4 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
排除,不随仓库分发。
2026-10-03 10:50:34 +08:00

7.2 KiB

mssdk-sign —— 汽水音乐 metasec 签名 HTTP 服务

把汽水 PC 客户端自带的 metasecml.dll(字节 metasec,官方签名主体 Douyin Vision Co., Ltd.) 关在一个进程里,对外只暴露一个 HTTP /sign。于是:

  • Windows:直接跑。
  • Linux:丢进 Wine 跑,宿主机的服务(MusicServer 等)用 HTTP 调它 —— 专有 DLL 不出 Wine 前缀,宿主机不需要 Windows 运行时。

对应地,MusicServer 侧 sdk/qs/mssdk.ts 里那套「进程内 require bdms.node」可以换成「HTTP 调本服务」, 跨平台部署就不用装官方客户端了(二进制仍需自己从官方客户端里取,不入库、不再分发)。

文件

文件 说明
mssdk-sign.cpp 全部实现(单文件,无第三方依赖:Win32 + Winsock + 自带 MD5/JSON)
build.bat Windows/MSVC 构建
build-mingw.sh Linux 交叉编译成 exe(给 Wine 跑)
client.mjs Node 客户端示例(可直接 node client.mjs --send 验证)
REVERSE.md 逆向记录:DLL 身份/壳/真实调用 ABI/行为规则/升级后如何重新定位偏移

构建

Windows(MSVC,需要 VS BuildTools):

build.bat
:: 生成 mssdk-sign.exe
mssdk-sign.exe --selftest --dll .\metasecml.dll

Linux → Wine(MinGW-w64 交叉编译,静态链接,不依赖 libstdc++/winpthread DLL):

sudo apt install mingw-w64
./build-mingw.sh          # 生成 mssdk-sign.exe

注意:源码含中文注释/字符串,MSVC 必须带 /utf-8(build.bat 已带),否则会报一堆莫名其妙的语法错误。

运行

Windows:

mssdk-sign.exe --port 16889 --dll .\metasecml.dll --token 你的口令 -v

Wine(建议放在自己的 prefix 里,和 DLL 同目录):

mkdir -p /opt/mssdk-sign && cd /opt/mssdk-sign
cp mssdk-sign.exe metasecml.dll .
WINEPREFIX=/opt/wine-mssdk wine mssdk-sign.exe --selftest --dll ./metasecml.dll   # 先自检
WINEPREFIX=/opt/wine-mssdk wine mssdk-sign.exe --port 16889 --bind 0.0.0.0 --token 你的口令 -v

systemd 单元示例:

[Unit]
Description=mssdk-sign (metasec signer in Wine)
After=network.target

[Service]
Environment=WINEPREFIX=/opt/wine-mssdk
Environment=WINEDEBUG=-all
WorkingDirectory=/opt/mssdk-sign
ExecStart=/usr/bin/wine /opt/mssdk-sign/mssdk-sign.exe --port 16889 --bind 127.0.0.1 --token CHANGE_ME
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

参数:

参数 说明
--port <n> 监听端口,默认 16889
--bind <addr> 默认 127.0.0.1;容器/跨机调用才用 0.0.0.0(务必配 token)
--dll <path> metasecml.dll 路径,默认 exe 同目录
--token <t> 启用鉴权(Authorization: Bearer <t> 或 X-Auth-Token: <t>)
--sign-rva <hex> 手动指定签名函数偏移(默认按 DLL 版本表,见下)
--selftest 只做「加载 DLL + 签名自检」后退出(部署前必跑)
-v 打印每个请求的签名耗时

接口

GET /healthz(免鉴权)

{"ok":true,"service":"mssdk-sign","version":"0.1.0",
 "signer":"D:/MusicServer/soda-mssdk/metasecml.dll",
 "dllVersion":"4.6.1.0","signRva":"0x52af0","auth":true}

POST /sign

{
  "url": "https://api.qishui.com/luna/pc/track_v2?aid=386088&…",   // 必填,带完整 query
  "body": "{\"track_id\":\"…\"}",                                  // 可选:给了且没带 x-ss-stub 时,服务端自动补
  "headers": {                                                     // 必填:真实要发出去的那一份
    "content-type": "application/json; charset=utf-8",
    "user-agent": "LunaPC/3.7.0(452316191)",
    "x-luna-background-type": "foreground",
    "x-tt-trace-id": "00-…-…-01"
  }
}
{
  "ok": true,
  "headers": {"X-Helios":"…","X-Medusa":"…","X-Neptune":"…"},
  "added": {"x-ss-stub":"DBA20AB94302CB03AC25A216DD953258"},
  "elapsed_ms": 43
}

失败时:{"ok":false,"error":"…"} + 400(入参错)/ 401(token)/ 500(签名失败)。

三条硬性约定(踩过坑)

  1. headers 必须是真实要发出的那一份,一个都不能少(尤其 cookie、x-ss-stub)。签名覆盖 URL + 全部请求头, 少一个服务端就回 HTTP 200 + 空 body。
  2. x-ss-stub = body 的 MD5(大写),既是签名输入也要随请求发出。本服务在你传了 body 时会自动算并回传 (added.x-ss-stub),记得把它一起发出去。
  3. 设备要成套:URL 里的 device_id / iid / fp 必须是你自己的设备;签名和 URL 里的 device_id 绑死, 改了 URL 设备号 → 服务端 200 + 空 body(实测)。

客户端(Node)

import { signRequest } from './client.mjs'

const { headers, added } = await signRequest(url, headersToSend, body)
await fetch(url, { method: 'POST', headers: { ...headersToSend, ...headers, ...added }, body })

集成到 MusicServer 时,把 sdk/qs/mssdk.ts 的 signQishuiRequest() 换成上面这次 HTTP 调用即可 (签名覆盖的 headers 与请求实际发出的 headers 保持同一份、同一顺序)。

自检:

node client.mjs          # healthz + 签一次(不发真实请求)
node client.mjs --send   # 顺带发给 api.qishui.com 验证签名被接受

它是怎么调 DLL 的(逆向结论,2026-10-02)

  • 导出表里的 MSBridgeML / MSBridgeOV / o0o0 都是幌子(桩 / 线程状态初始化), 真正的签名函数在 image base + 0x52af0,原型:

    char* sign(const char* url, const char* headers_lines);   // headers: "名\r\n值\r\n…"
    
  • 返回 "名\r\n值\r\n…" 的签名头串,由 DLL 自己的释放函数(base + 0x1d8c0)回收。

  • 不需要 init;DLL 版本(本机样本)= metasecbaseml 4.6.1.0。

  • 调用是串行的(内部有全局/TLS 状态),服务端用互斥锁包住,实测 20 并发全部成功。

客户端升级了怎么办(DLL 换了,偏移会变)

--selftest 会大声失败,不会静默出错。定位新偏移:

  1. 从新版客户端里取 metasecml.dll(解包方式见 MusicServer 的 sdk/qs/mssdk.ts 注释)。
  2. 拿真实请求自检,确认签名是否被接受(node client.mjs --send)。
  3. 找新偏移:
    • 先在 KNOWN_VERSIONS 里加版本,用旧偏移试;不行就:
    • 用 MusicServer 仓库里的脚本脱壳 + 定位:tmp/metasec-rebuild.py(内存脱壳 + 重建 PE)、 tmp/metasec-call-test.py(直接调用试偏移)、tmp/bdms-abi.py(读 bdms.node 运行时 IAT 槽, 那里躺着真实地址)。找到后用 --sign-rva 0xXXXXX 验证,再写进 KNOWN_VERSIONS。

已知限制 / 未验证项

  • Wine 下未实测(本机没有 Wine):DLL 依赖的都是 Wine 常规实现的 Win32 API(kernel32/advapi32/ole32/ winhttp/ws2_32/imm32/…),但 metasec 有设备指纹采集,行为可能和原生不同。部署前请先在目标机跑 wine mssdk-sign.exe --selftest 和 node client.mjs --send。若加载失败,先看 WINEDEBUG=+loaddll 的缺失依赖,必要时 winetricks corefonts vcrun2019、winecfg 里把 Windows 版本设为 Win10。
  • metasecml.dll 是官方专有二进制(签名主体抖音视界),不入库、不再分发;本目录只放源码与构建脚本, DLL 自己从官方客户端取,放 exe 同目录即可。
  • 签名服务本身不联网、不存 cookie;但仍建议只监听 127.0.0.1 并设置 --token,不要暴露公网。