学习Widevine服务

我不理解,为什么像Netflix、Spotify流媒体服务提供商,包括苹果、谷歌等公司,会使用数字版权管理。他们在我买的设备上,浪费我的硬件算力,加密我买的数字内容,以阻止我下载。君子也防?[doge]

本文尝试欺骗DRM License Server获取秘钥,来解密被DRM加密的多媒体内容。

了解Google Widevine

通过阅读文章大致了解了widevine的工作原理,以及DRM(Digital Rights Management,数字版权管理)系统的执行过程:

获取CDM Device验证文件

CDM(内容解密模块)是嵌入在Chrome浏览器或Android操作系统中用来加解密流媒体的闭源模块。因此,我们可以模拟一个安卓设备。

使用pywidevine创建CDM Device。创建设备需要两个关键文件,client_id.binprivate_key.pem

  1. 按照帖子Dumping Your own L3 CDM with Android Studio的操作,下载并安装Android Studio,再使用frida-server获取关键文件。

  2. 在Android Studio的Device Manager中新建设备,选一台更帖子描述一样的设备,Pixel 6Pie,生成了Pixel 6 API 28设备,启动后显示emulator-5554。(我的Andriod Studio装好时,Device Manager中已经有了一部虚拟手机Pixel_3A_API_34_extension_level_7_x86_64(注意这是一台x86_64的设备),这个设备浏览器解析CDM时貌似有问题)

  3. 在Windows下的conda中新开一个环境安装pip install frida frida-tools,并确定frida版本(我使用了16.2.1),fridafrida-server版本需一致。

  4. 将下载的frida-server-16.2.1-android-x86放到C:\Users\<YourUserName>\AppData\Local\Android\Sdk\platform-tools\目录中,命令行进入该目录,执行:

1
2
3
> .\adb.exe devices
List of devices attached
emulator-5554 device

可以看到虚拟设备已被识别。

  1. frida-server上传到虚拟设备:
1
2
> .\adb.exe push .\frida-server-16.2.1-android-x86 /sdcard
.\frida-server-16.2.1-android-x86: 1 file pushed, 0 skipped. 152.8 MB/s (51807740 bytes in 0.323s)
  1. 连接虚拟设备,将frida-server放在正确的位置,赋予执行权限并启动:
1
2
3
4
5
> .\adb.exe shell
su
mv /sdcard/frida-server-16.2.1-android-x86 /data/local/tmp/
chmod +x /data/local/tmp/frida-server-16.2.1-android-x86
/data/local/tmp/frida-server-16.2.1-android-x86

此时server启动,保持终端运行。

  1. 下载dumper,进入目录并安装依赖pip install -r requirements.txt

运行时报错提示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
python .\dump_keys.py
Traceback (most recent call last):
File "C:\Users\qinzi\pyws\wdevof\dumper\dump_keys.py", line 6, in <module>
from Helpers.Scanner import Scan
File "C:\Users\qinzi\pyws\wdevof\dumper\Helpers\Scanner.py", line 7, in <module>
from Helpers.wv_proto2_pb2 import SignedLicenseRequest
File "C:\Users\qinzi\pyws\wdevof\dumper\Helpers\wv_proto2_pb2.py", line 33, in <module>
_descriptor.EnumValueDescriptor(
File "C:\Users\qinzi\anaconda3\envs\wdev\Lib\site-packages\google\protobuf\descriptor.py", line 789, in __new__
_message.Message._CheckCalledFromGeneratedFile()
TypeError: Descriptors cannot be created directly.
If this call came from a _pb2.py file, your generated code is out of date and must be regenerated with protoc >= 3.19.0.
If you cannot immediately regenerate your protos, some other possible workarounds are:
1. Downgrade the protobuf package to 3.20.x or lower.
2. Set PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python (but this will use pure-Python parsing and will be much slower).

因此安装建议为PowerShell添加环境变量,再次运行python .\dump_keys.py即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> $env:PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION = 'python'
> $env:PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION
python

> python .\dump_keys.py
2024-03-02 12:22:36 PM - root - 16 - INFO - Connected to Android Emulator 5554
2024-03-02 12:22:36 PM - root - 17 - INFO - scanning all processes for the following libraries
...
2024-03-02 02:17:06 PM - Helpers.Scanner - 82 - INFO - Running libwvhidl.so at 0xec717000
2024-03-02 02:17:06 PM - Helpers.Scanner - 75 - DEBUG - {
"from": "Dynamic Function",
"message": "L3 RSA Key export function found: cwkfcplc"
}
...
2024-03-02 12:22:37 PM - root - 25 - INFO - Hooks completed

此时dumper启动,保持终端运行。

创建CDM设备

此时可以使用client_id.binprivate_key.pem,通过pywidevine命令行工具来生成wvd设备文件了:

1
pywidevine create-device -k ./private_key.pem -c ./client_id.bin -t "ANDROID" -l 3

得到一个名为google_aosp_on_ia_emulator_14.0.0_67016d30_4464_l3.wvd的设备文件。

获取秘钥

使用pywidevine的例程即可获取秘钥:

  • pssh在希望下载的视频描述文件中,可能是浏览器请求的一个https://a.com/b/c.mpdxml文件或是一个https://a.com/b/c.m3u8list文件。
  • device的路径即为刚才创建的设备文件路径。
  • licence请求的地址,是浏览器加载视频时发出的一次或两次(aduio和video各一次)秘钥请求,URL特征应当非常明显,通常为POST方式,带有二进制负载。

另外需要注意的是,CDM秘钥的请求原理上是独立于媒体提供平台的,或是与媒体服务API不同的一套API,因此要使得licence请求成功,可能还需要从浏览器中复制秘钥请求headers一并加入requests.post(如使用浏览器**复制为cURL (bash)**)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from pywidevine.cdm import Cdm
from pywidevine.device import Device
from pywidevine.pssh import PSSH

import requests

# prepare pssh
pssh = PSSH("AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa"
"7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA==")

# load device
device = Device.load("C:/Path/To/A/Provision.wvd")

# load cdm
cdm = Cdm.from_device(device)

# open cdm session
session_id = cdm.open()

# get license challenge
challenge = cdm.get_license_challenge(session_id, pssh)

# send license challenge (assuming a generic license server SDK with no API front)
licence = requests.post("https://...", data=challenge)
licence.raise_for_status()

# parse license challenge
cdm.parse_license(session_id, licence.content)

# print keys
for key in cdm.get_keys(session_id):
print(f"[{key.type}] {key.kid.hex}:{key.key.hex()}")

# close session, disposes of session data
cdm.close(session_id)

请求成功后会打印SIGNINGCONTENT,这个CONTENT就是我们需要用来解密音视频的秘钥对kid:key,我的请求输出大致格式为:

1
2
[SIGNING] <32bit-hex>:<128bit-hex>
[CONTENT] <32bit-hex-kid>:<32bit-hex-key>

解密多媒体文件

解密工具有现成的,比如mp4decryptshaka,我用的是mp4decrypt,使用刚才拿到的`kid:key。

1
mp4decrypt --key <kid>:<key> my_encrypted_file.mp4 my_decrypted_file.mp4

也可以直接使用支持DRM解密的下载工具,如使用rust编写的dash-mpd-cli,只需要在参数中指定解密工具和秘钥即可:

1
2
3
4
5
6
dash-mpd-cli https://url_of_.mpd_file.mpd --output my_decrypted_file.mp4
-H cURL_format_headers
-H ...
--decryption-application mp4decrypt \
--mp4decrypt-location /home/wsl/devof/Bento4/cmakebuild/mp4decrypt \
--key d2afe67c226cf2e932ac9b4c0c13432d:c5cf130ddf1087ca2f134725e82f1c1e

评论

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×