- 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 排除,不随仓库分发。
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(签名失败)。
三条硬性约定(踩过坑)
headers必须是真实要发出的那一份,一个都不能少(尤其cookie、x-ss-stub)。签名覆盖 URL + 全部请求头, 少一个服务端就回HTTP 200 + 空 body。x-ss-stub= body 的 MD5(大写),既是签名输入也要随请求发出。本服务在你传了body时会自动算并回传 (added.x-ss-stub),记得把它一起发出去。- 设备要成套: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 会大声失败,不会静默出错。定位新偏移:
- 从新版客户端里取
metasecml.dll(解包方式见 MusicServer 的sdk/qs/mssdk.ts注释)。 - 拿真实请求自检,确认签名是否被接受(
node client.mjs --send)。 - 找新偏移:
- 先在
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,不要暴露公网。