4
0
0

teamspeak6 客户端服务端授权攻击分析

2026-10-03
2026-10-03

服务端

授权类型

目前ts6的服务端还在开发状态,所以teamspeak6每个版本内置了一套证书链,内容写死,有效期也是固定的,大概两三个月,到期了就得继续去拉新版服务端继续使用

除开内置授权,授权类型有以下几种

证书值

名称

protobuf

名称

是否接受

0

NONE

0

NOLICENSE

否

1

OFFLINE

2

OFFLINE

是

2

SDK

4

SDK

否

3

SDK_OFFLINE

5

SDK_OFFLINE

否

4

NPL

3

NONPROFIT

是

5

ATHP

11

HOSTER_ATHP

是

6

AAL

12

HOSTER_AAL

是

7

DEFAULT

13

DEFAULT

是

8

GAMER

14

GAMER

是

9

SPONSORSHIP

15

SPONSORSHIP

是

10

COMMERCIAL

16

COMMERCIAL

是

11

EDUCATIONAL

17

EDUCATIONAL

是

12

EVENT

18

EVENT

是

SDK是定制版本所以普通服务端会拒绝
OFFLINE / SDK_OFFLINE授权版本

除了SDK_OFFLINE与OFFLINE授权不向 accounting2.teamspeak.com:443 汇报。其它类型会按 doc/server/accounting.txt 做开机汇报和每小时汇报;过期、被封、超槽、超虚机或汇报失败会停服。

授权文件使用

同目录下licensekey.dat,或 --license-path / TSSERVER_LICENSE_PATH。

授权文件格式

Signed_License_Info
├── 1 license_info (嵌套消息 License_Info) ← 被签名的内容
├── 2 sign_certificate (bytes,签名证书链)
└── 3 signature (bytes,必须正好 64 字节)

License_Info
├── 1 certificate (bytes,服务器证书链)
├── 2 private_key (bytes,必须正好 32 字节)
├── 3 max_slots (uint32) ← 文件路径真正生效的槽数
├── 4 max_virtual_servers (uint32) ← 文件路径真正生效的虚机数
├── 5 description (string)
└── 6 server_version_major(int32)

验证方式为签名而不是加密

根公钥

cd0de2aed46345509a7e3cfd8f68b3dc7555b29dccec73cd18750f993812408a

只有能对累积公钥做 Ed25519 签名的人,才能让服务端接受这份 protobuf

h_i = SHA-512( P_i || cert_i[33:] )     # 跳过开头那个 0x00
h_i[0]  &= 0xF8                         # 与 Ed25519 私钥相同的 clamp
h_i[31] &= 0x3F
h_i[31] |= 0x40
h_i     = int_le(h_i[:32]) mod L
acc_0 = ROOT
acc_{i+1} = acc_i + h_i · P_i           # Edwards 点加

cert[33:] 就是 type + 日期 + payload,所以改日期、改类型、改位图、改发行者都会改 h_i,累积点跟着变。

对应私钥

sk_acc = sk_root + Σ h_i · a_i   (mod L)
A_acc  = sk_acc · B              = acc

官方 sk_root 未知,所以未改根的服务端无法验证自制授权文件。

此外内置的DEFAULT授权不会走授权类型和Ed25519验证,加载成功后会POST https://reporting.teamspeak.com/report

licensekey.dat

key2 + Base64 + key2 ← 可打印封装,可逆
│
▼
Signed_License_Info (protobuf 明文)
┌───────────────────┐
│ License_Info │ ← 签名消息 M
│ certificate = 服务器链 │
│ private_key = 32B 标量 │ ← licensesignmessage
│ slots / vs / desc / major │
│ sign_certificate = 签名链 │ ← 用来累加出 A
│ signature = 64B Ed25519 │
└───────────────────┘
│
▼

acc = ROOT + Σ clamp(SHA512(P||rest)) · P

Ed25519_Verify(acc, M, signature)
│
▼

类型白名单 + 日期 + 叶子类型 2/8
│
▼

授权生效(或失败回落 DEFAULT)

官方签发流程

官方签发时拥有 sk_root,流程是:

  1. 选授权类型、槽数、虚机、有效期、持有人名称。

  2. 生成中间证、服务器叶子、LICENSE_SIGN 三把 Ed25519 钥。

  3. 组服务器链(INTERMEDIATE + 类型 8 或类型 2)。

  4. 组签名链(INTERMEDIATE + LICENSE_SIGN)。

  5. 填 License_Info,按字段号 1..6 做 protobuf 序列化。

  6. 对签名链算 sk_acc,对序列化字节做 Ed25519 签名。

  7. 包成 Signed_License_Info,Base64,两边加 key2。

小结

由于生成授权的私钥未知,所以即使知道授权文件的生成机制也无法在不修改服务端的情况下让其接受自生成的授权文件

客户端

对服务端进行修改,成功使用OFFLINE类型的授权

注意到TS6客户端无法正常连接,但是TS6的客户端则出现了无法连接的情况,原因invalid serverID

使用TS3客户端,可以正常连接

怀疑可能是TS6客户端还加了中间验证,继续进行分析

最终发现会有三层验证

第一层验证-本地验证

官方客户端硬编码与 TS3 accounting 相同的根公钥(TeamSpeak.dll 内嵌 32 字节):

步骤:

  1. 验 proof
    把 omega 当 P-256(prime256v1)公钥,对 l 的原始字节做 ECDSA-SHA256 验签。通过只说明“持有 omega 私钥的那一端发出了这条链”,不说明这条链挂在官方根下。打补丁的服务端也能签出合法 proof,因为它自己有 License_Info.private_key。

  2. 解析链
    version 必须为 1;2–8 张证书;日期嵌套合法;倒数第二张为 Server(类型 2 或 8);最后一张为 ephemeral(32)。license_type 要落在客户端/服务端白名单(服务端 sub_140941E10:{1,4,5,6,7,8,9,10,11,12})。结构不对 → illegal_server_license。

  3. 从官方根做曲线累积,得到会话公钥
    与服务端 sub_14094AE60 同一公式(Ed25519 / Curve25519 点,clamp 与私钥相同):

    h_i  = clamp(SHA-512(P_i || cert_i[33:])[:32])
    acc  = ROOT + Σ h_i * P_i          # ROOT = cd0de2ae…,含 ephemeral

    官方客户端的 ROOT 写死为上面那 32 字节。服务端导出链、加 ephemeral 之后用自己加载许可证时的根做同一累积。两边 ROOT 一致,acc 才一致。

  4. ECDH → SharedIV / SharedMac
    客户端再生成临时 Curve25519 密钥对,令

    sharedData = acc * client_ek_sk
    SharedIV   = SHA-512(sharedData)
    SharedIV[0..10)  ^= alpha
    SharedIV[10..64) ^= beta
    SharedMac        = SHA-1(SharedIV)[0..8]

    之后所有 Command 包按协议纸 §1.6 用 AES-128-EAX。acc 对不上,MAC 全错,表现为握手失败。

  5. clientek
    ek = 临时公钥;proof = 用客户端身份(另一把 P-256)对 ek[0..32) ‖ beta[0..54) 签名。这是绑身份,不是验服务器许可证。

“官方性”完全取决于第 3 步的 ROOT。proof 只防中间人改 l 而不持有 omega 私钥。

第二层验证-官方的黑灰名单验证

客户端里关于黑灰名单检查的请求接口如下

https://blacklist2.teamspeak.com/check
Content-Type: application/x-ts3blacklist

protobuf com.teamspeak.blacklist.BlacklistInfoRequest / BlacklistInfoResponse:

请求里带出去的标识(都来自本机已有信息,不是官方回传授权):

字段

来源

ip_address

当前连上的服务器 IP

domain_name

用户输入 / 书签里的主机名

port

语音端口

virtual_server_id

握手后的虚机 UID

public_license_id

由公钥链 l(去掉本次 ephemeral 的稳定部分)在客户端算出的许可证指纹

timestamp

客户端时间

valid_token_present

是否带了特权密钥

slots_ok

客户端认为槽位信息是否正常

updated_virtual_server_id

虚机 UID 变更时的新值

响应按对象分别给出 status_ip / status_domain / status_virtual_server_id / status_public_license_id,枚举:

INVALID | BLACKLISTED | GREYLISTED | NOT_LISTED

映射到 UI:

结果

客户端

BLACKLISTED

error.server.server_blacklisted — server blacklisted

GREYLISTED

server.error.license.greylisted — This is a Greylisted Server!(文案写“许可证看起来无效”)

NOT_LISTED

继续 clientinit

接口不可达

不构成“官方授权通过”;历史上客户端对超时的处理与版本有关,不能当成放行条件

第三层验证-服务端拒绝

ClientInitRequest / ClientProperties 里服务端会看到:昵称、client_version + client_version_sign、平台、密码(base64(sha1(...)))、client_key_offset(身份安全等级)、client_myteamspeak_id、徽章、hardware_id、默认频道等。

常见拒绝(UDP 已通、第一层验证过了):

  • 版本过旧 / 签名不是官方客户端三元组

  • 安全等级低于虚机设置

  • 服务器密码错误

  • 封禁、克隆数、槽位满

  • myTSID:证书链、过期、吊销列表、签名失败(服务端字符串 rejecting myteamspeakid: ...)

myTSID 吊销列表由服务端去拉;失败日志为 failed to download mytsid revocation list,相关特性关掉,不带 myTSID 的普通身份仍可进。

总结

目前在不对客户端或服务端进行修改的情况下,无法实现对授权的伪装,但如果只修改服务端的根公钥,可以实现不上报官方服务器的情况下的扩容,但是只能TS3进行连接,TS6客户端由于会对服务器的授权的根公钥进行验证则会被拒绝连接。如果也修改TS6客户端内置的公钥则会导致客户端只能连接同样修改过的服务端,除非进行更多修改。

笔者水平有限,绝大多数分析工作都是由AI完成的,我主要是根据AI的分析结果了解他们产品背后的工作逻辑,授权逻辑,受益匪浅。

Support

If this article helped you, feel free to share it or give support!