# 盗版世界杯直播流到底是怎么运作的


我想搞清楚那个明摆在眼前的小把戏到底是怎么运作的。盗版流媒体网站一直都存在，但在世界杯期间，你打开任何一个体育论坛，总能看到有人甩出一条直播比赛的链接——全高清、不卡顿、不用登录。我一直以为底层有什么玄妙的东西。一个朋友在英格兰对克罗地亚那场比赛前给我发了一条链接。"看这个，"他说。于是我打开了 DevTools。

我本以为会看到一个抓取来的 HLS 播放列表，或者一个转推的信号流。结果我看到的却是一个 `<video>` 标签、一个指向合法 Akamai CDN 的 Shaka Player 实例，以及一个 `<script>` 块里那两串十六进制字符串——所有的活儿都是它们干的。那两串十六进制字符串就是 DRM 解密密钥。就在页面源码里。没有藏在 API 后面。没有加密。只是裹在一层看起来吓人、实际上什么都不干的 JavaScript 里。

后来有人给我看了一个安卓应用，它对每一场世界杯比赛都做着同样的事情——但是"很正规地"做的，有加密的配置、Firebase Remote Config、一个授权服务器，应有尽有。一个专门的"MUNDIAL FIFA 2026"板块，每场比赛有 28 个频道，包括多机位、球员视角和教练视角。我花了大约一个小时才意识到，这就是同一个漏洞穿上了一身更体面的西装。

最初只是三十分钟的好奇，结果变成了一整个周末。到最后，我从网页这边绘制出了**横跨 33 个国家、169 个 CDN 的 670 路直播流**，又从**单单一个安卓应用里整理出了另外 525 个频道**——全都由一种被 W3C 自己称为"测试"工具的 DRM 方案保护着。而这正发生在一届世界杯期间——英格兰正把四个球灌进克罗地亚的球门，而每一个进球都同时出现在数百路未经授权的直播流上。

{{< admonition type="warning" title="免责声明" open=true >}}
**这是一篇漏洞分析。**文中不发布任何真实密钥、直播流 URL 或可用工具。应用名称和域名均已隐去。目的是记录各平台部署 DRM 时存在的系统性弱点——而非助长盗版。
{{< /admonition >}}

## 把密码刻在背面的挂锁

每一个播放加密视频的浏览器都会用到 W3C 的 Encrypted Media Extensions（EME）。EME 支持四种密钥系统。其中三种——Widevine、FairPlay、PlayReady——使用授权服务器、加密的密钥交换以及由硬件支撑的解密。第四种是 ClearKey。

ClearKey 以明文形式把解密密钥发给浏览器。

就这么简单。和 Widevine 一样的 AES-128 加密。一样的 MPEG-DASH 分发。但 Widevine 是通过安全通道协商密钥，并在浏览器无法窥探的硬件沙箱里解密视频，而 ClearKey 则是把原始密钥直接交给 JavaScript，说一句"给你"。

```
    WIDEVINE L1                          CLEARKEY
    ───────────────────────              ───────────────────────
    License server (HTTPS)               No license server
    Encrypted key exchange               Key in plaintext JS
    Hardware TEE decryption              Software decryption
    Key never in JS memory               Key IS the JS
    Per-device, per-session policy       No policy at all
```

DASH Industry Forum 表示 ClearKey "仅建议用于测试目的"。但有些平台还是决定在生产环境里使用它。我分析的那些网站和那个应用，都依赖于这个决定。

## 第一部分：网站

我检视了数十个盗版流媒体网站。它们全都遵循同一种套路。

### 架构

```
    ┌──────────────────────────────────────────────────────────────┐
    │                    HOW IT ACTUALLY WORKS                     │
    └──────────────────────────────────────────────────────────────┘

    STEP 1                    STEP 2                    STEP 3
    User visits               Clicks a channel          Page loads an <iframe>
    pirate-site.co            (e.g. "Sky Sports")       from a different domain

    ┌──────────────┐          ┌──────────────┐          ┌──────────────────┐
    │              │          │  ┌────┐┌────┐│          │  player-host.co  │
    │  Channel     │  click   │  │ESPN││DAZN││  iframe  │                  │
    │  Grid Page   │ ───────> │  ├────┤├────┤│ ───────> │  Shaka Player    │
    │              │          │  │Sky ││beIN││          │  + DRM keys      │
    │  (static     │          │  └────┘└────┘│          │  + manifest URL  │
    │   HTML)      │          └──────────────┘          └────────┬─────────┘
    └──────────────┘                                             │
                                                                 │ fetches
                                                                 ▼
                                                    ┌──────────────────────┐
                                                    │  LEGITIMATE CDN      │
                                                    │  (akamaized.net,     │
                                                    │   skycdp.com,        │
                                                    │   indazn.com)        │
                                                    │                      │
                                                    │  Encrypted DASH      │
                                                    │  segments            │
                                                    └──────────────────────┘

    The pirate site serves ZERO video.
    The CDN bill belongs to the legitimate provider.
    The pirate site hosts ~50KB of HTML.
```

盗版网站不过是一个知道那两串十六进制字符串的中间人。它把观众的浏览器指向一个真实的 CDN，把解密密钥递过去，剩下的事浏览器自己做。CDN 从头到尾都不知道这个观众并不是付费订阅用户。

### 密钥就在源码里

播放器宿主页面包含一个带有密钥的 `<script>` 块，外面裹着一层混淆，而一个 30 行的 Node 脚本不到一秒就能把它解开：

```javascript
// Before: 40 lines of _0x4a2f, IIFEs, string-array lookups
// After: this is what it actually does

var drmKeyId = 'a1b2c3d4e5f6a7b80000000000000000';
var drmKey   = '0123456789abcdef0123456789abcdef';

player.configure({
    drm: { clearKeys: { [drmKeyId]: drmKey } }
});
player.load('https://cdn.legitimate-provider.com/manifest.mpd');
```

两个变量。两串十六进制字符串。这就是全部的"DRM 保护"。那层混淆——变量重命名、字符串数组、控制流扁平化——会被 `eval()` 还原，因为浏览器本身也得运行它。只要浏览器能执行，脚本就能执行。

一旦被提取出来，密钥就会通过 GitHub 和 Telegram 上的 M3U 播放列表扩散开去。我找到过一个 M3U 文件，里面有**横跨 92 个 CDN 的 545 条 ClearKey 条目**。公开、可搜索、被 Google 收录。从提取到全球分发的时间：几分钟。密钥被吊销的时间：通常永远不会。

## 第二部分：安卓应用

并非每个盗版团伙都只搞一个静态网页。有些会发布一个完整的安卓应用——带登录界面、Firebase 分析、加密配置和专业的 UI。我在世界杯期间分析的一个应用就采用了这种做法。525 个频道。36 个分类。一个专门的"MUNDIAL FIFA 2026"板块，每场比赛都有多机位视角。

{{< image src="images/app-config.png" caption="该应用加密的频道配置——横跨 36 个分类的 525 个频道，带有 AES 加密的 URL 和 DRM 凭据。[URL 已隐去]" >}}

### 加密（AES-128-ECB）

该应用从远程服务器获取其频道列表，形式是一段经过 AES 加密的 JSON。每一个字段——直播流 URL、DRM 授权 URI、HTTP 头——都是经过 base64 编码的 AES 密文。解密密钥是一个 16 字符的字符串，存放在 Firebase Remote Config 里，在启动时获取。

听起来挺正经。然后你看一眼密文：

```
    236 URLs share the same first 35 encrypted blocks.

    Block 0: f91b7f273abccc6e...  ← identical across all 236
    Block 1: 932b23560f1d43ba...  ← identical
    Block 2: a7d788a399cea962...  ← identical
    ...

    ECB mode. Same plaintext block = same ciphertext block.
    The URLs all start with the same CDN domain prefix.
```

AES-ECB 是教科书级别的"如何*不*正确使用 AES"的反面例子。每一门密码学课程都会用[ECB 企鹅](https://blog.filippo.io/the-ecb-penguin/)来讲解这一点——用 ECB 加密一张位图，图像依然能被辨认出来，因为相同的明文块会产生相同的密文块。这个应用加密了 236 个以同一个 CDN 域名开头的 URL，产生了 236 段拥有**相同 560 字节前缀**的密文。光是做模式分析，你还没找到密钥，就已经能看出结构了。

### 名不副实的"授权服务器"

一旦解密，ClearKey 凭据并不存在于某个单独的密钥交换里。它们就在**授权 URI 本身**之中，作为明文查询参数：

```
    drm_license_uri (after decryption):

    https://[redacted]/?keyid=49eb924b...&key=6e131b04...
                              ^^^^^^^^        ^^^^^^^^
                              ClearKey ID     ClearKey Key
                              (in the URL)    (in the URL)
```

这个应用的"授权服务器"就是一个*包含着密钥*的 URL。没有挑战-应答。没有会话绑定。应用去获取这个 URL，而这个 URL *就是*密钥，密钥再去解密直播流。

三层间接——Firebase Remote Config、AES 加密、一个"授权服务器"端点——全都坍缩成同一个失败：解密密钥以明文形式落到了客户端手里，因为 ClearKey 要求如此。

### 应用的完整技术栈

```
    ┌──────────────────────────────────────────────────────────────┐
    │           ANDROID APP ARCHITECTURE                           │
    └──────────────────────────────────────────────────────────────┘

    1. App launches
       └──> Firebase Remote Config
            └──> Fetches AES key ("claveapp") + config URLs

    2. App fetches encrypted JSON (525 channels)
       └──> Base64 decode ──> AES-128-ECB decrypt
            └──> Stream URLs, DRM license URIs, headers (plaintext)

    3. For CLEARKEY channels (348 of 525):
       └──> "License URI" = https://[redacted]/?keyid=XXX&key=YYY
            └──> Key ID and Key are IN THE URL

    4. App configures ExoPlayer with ClearKey
       └──> Fetches DASH manifest from legitimate CDN
            └──> Decrypts video with the key from step 3

    Package name: com.example.myapplication
    Encryption:   AES/ECB/PKCS5Padding (textbook insecure)
    Key storage:  Firebase Remote Config (single API call to extract)
    Key length:   16 characters, static, never rotated
```

包名 `com.example.myapplication` 把这场操作背后的开发严谨程度交代得明明白白。Android Studio 的默认模板。连名字都没改。

### 实时频道列表就放在一个公开仓库里

最不动声色却又最致命的一点——也是最能体现这场操作*真正*高明之处的一点（其高明之处主要在于后勤调度）——是那份加密配置的托管位置。该应用从一个公开的 GitHub 仓库获取它的频道 JSON。任何人都能浏览它。任何人都能 `git clone` 它。任何人都能查看提交历史。

而提交历史才是有意思的地方：

{{< image src="images/commits.png" caption="托管着该应用加密频道列表的那个公开 GitHub 仓库。每 4–5 分钟就会落下一个提交——这是对频道 JSON 的自动化编辑，随着上游直播流被轮换、被掐断或被替换而更新。用户名和仓库所有者已隐去。" >}}

每 4–5 分钟就会落下一个提交。差异始终是针对 `tv (10).json` 或 `bearer.json` 的——前者是加密的频道列表，后者是用来从上游获取新直播流的鉴权凭据。提交信息全都是 `"Actualizar tv (10).json desde tv.json"` 或 `"Actualizar bearer.json desde panel - <timestamp>"`。翻译过来就是：某处有一个自动化任务，每隔几分钟就重写一遍频道配置并推送更新，好让运行该应用的手机在一个轮询周期之内拿到新的直播流 URL 和轮换后的 DRM 凭据。

这才是这场操作真正的护城河。不是 AES-ECB（已破解）。不是 Firebase Remote Config（一次 API 调用）。不是那个"授权服务器"（一个把密钥写在里头的 URL）。这条护城河是**运营层面的**：一段自动化程序，时刻让频道 JSON 与那一刻还活着的上游信号源保持对齐，并且明目张胆地托管在一个他们根本不用自己搭建的免费 CDN 上。当一个合法提供商轮换密钥时，机器人会察觉到，去获取新密钥，重新加密配置，然后提交。当一个 CDN 封掉源 IP 时，机器人就再找一个。客户的手机轮询、看到新提交、获取新配置，然后继续看比赛。用户甚至都看不到轮换发生。

人人都会想到的防御手段是"吊销密钥"。但你吊销一个密钥的速度，永远快不过一个脚本把新密钥推到 GitHub 仓库的速度。这种节奏上的不匹配，就是整盘棋的关键。

## 数字

| | 网页审计 | 安卓应用 |
|---|---|---|
| **直播流** | 670 | 525（348 路 ClearKey） |
| **CDN** | 169 | 34 |
| **国家** | 33 | ~8（以拉美为主） |
| **密钥存储** | JavaScript `<script>` 块 | Firebase Remote Config |
| **混淆** | JS 变量重命名 + IIFE | AES-128-ECB（已破解） |
| **客户端明文密钥** | 是 | 是 |
| **世界杯覆盖** | 数十个体育频道 | 28 个专属频道 + 多机位 |

两种做法——静态网站和完整的安卓应用——最终都落到同一处：一对 ClearKey 密钥躺在客户端内存里，没有任何机制能阻止它被提取，没有会话绑定，没有吊销，也没有密钥轮换。

## 钱，以及为什么它停不下来

经济账解释了一切。盗版网站托管着约 50KB 的静态 HTML，把你的浏览器指向别人的 CDN。那个应用不过是裹在别人基础设施外面的一层薄薄的 ExoPlayer。两者都不提供视频。两者都靠广告网络变现。

StreamEast 在 2025 年 9 月被查封前，已经服务了 16 亿次访问。"Operation Takendown"行动中的那个 IPTV 团伙拥有 2200 万用户，每月进账 2.5 亿欧元。一个中等规模的网站在世界杯期间——比赛日一百万访客，每人六次广告展示，CPM 1.50 美元——在几乎为零的托管成本下，**每天净赚 9000 美元**。

执法力度不断升级——西班牙的监禁判决、得州的 1875 万美元判赔、世界杯前一周下架的 27000 个信号源——而新网站还是层出不穷。因为这个漏洞是架构性的。面对一个把解密密钥放进浏览器的规范，你抓人是抓不完的。你也加密不出去，正如那个安卓应用所示范的：三层加密，密钥最后还是以明文形式落在客户端，因为 ClearKey 就是这么要求的。

真正的修复办法，是彻底迁移出 ClearKey——转向 Widevine、FairPlay 或 PlayReady。在那之前，密码就刻在挂锁的背面，而世界杯正确保所有人都知道该往哪儿看。

