Video page:
https://www.maxmeldpunt.nl/uitzendingen/oud-ptters-lopen-pensioenaanvulling-mis/
MPD:
PSSH:Code:https://npo-nl-ams-p16-am3.cdn.streamgate.nl/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MTI1MTA2NDQsInVyaSI6Ilwvdm9kXC9ucG9cL3VzcFwvVEVTVFwvbnBvXC9kYXNoX2NlbmNcL1BPV18wNTgwNjU1OVwvUE9XXzA1ODA2NTU5X3YxNzEyMzQzMTEwLmlzbSIsImNsaWVudF9pcCI6IjgyLjE2My40Ny4yMDQiLCJ2aWV3ZXIiOiJ2aWV3ZXIiLCJyaWQiOiI5MDdlNTNhIn0.uGmQapaw7VCXT1ckdrXrd3aRlUICo-zR7z-FzuDCNrw/vod/npo/usp/TEST/npo/dash_cenc/POW_05806559/POW_05806559_v1712343110.ism/stream.mpd
There are multiple PSSH strings to be found, mutitple DRM methods I guess but whatever, we can brute them all later, right?Code:AAACqnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAoqKAgAAAQABAIACPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgAzAHIATwB3AGoAagAxAG8AcAB1AHcANgBXAEUARABDAEQAdgBSADAASwB3AD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AFMANgB6AFcAYQBUAFAAWQBHAGgAcwA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcAA6AC8ALwBwAGwAYQB5AHIAZQBhAGQAeQAuAHMAdAByAGUAYQBtAGcAYQB0AGUALgBuAGwALwByAGkAZwBoAHQAcwBtAGEAbgBhAGcAZQByAC4AYQBzAG0AeAA8AC8ATABBAF8AVQBSAEwAPgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==
License URL:
So far so good, but not actually the challenge I'm facing. https://cdrm-project.com/ doesn't work for me, maybe it geo-blocks getting the keys or the keys are not known. So I started my own journey after HOURS of reading.Code:https://npo-drm-gateway.samgcloud.nepworldwide.nl/authentication?custom_data=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJucG8iLCJpYXQiOjE3MTI0MzY4NjMsImxpY2Vuc2VfcHJvZmlsZSI6IndlYiIsImRybV90eXBlIjoid2lkZXZpbmUiLCJjbGllbnRfaXAiOiI4Mi4xNjMuNDcuMjA0In0.3GFBAX4y_1Jlds5WiTVUgkla7tIiNMbXtGGvQVyYYmE
This is what I have (using Linux):
- Android Studio
- Android 10 virtual device Pixel 6 API 29
- adb shell with su, running frida-server-16.2.1-android-x86
- python3 dumper/dump_keys.py
Problem:
When I visit the video page url it doesn't dump the private key, it only dumps license_request.bin. The dumper-console looks fine, stuff like:
Same problem while testing with https://bitmovin.com/demos/drmCode:2024-04-06 07:08:52 PM - Helpers.Scanner - 75 - DEBUG - { "from": "android.hardware.drm@1.2-service.widevine", "message": "GetLevel3_LoadDeviceRSAKey", "payload": { "Status": "OEMCrypto_SUCCESS", "Session": 25601, "Length": 1312, "Context": "160471f05b4571b4dbb9cab301a4180a634bf42f8316598e32e7ffc419695a1916cbc9ea1950f2ee372d8aab8cb795fac3a966a0e814b9827f2150a2f02434ecf4e925a9f7ebed3ff49eb0207f29c47e7982df814b11822bb4c6ebefe22a35df93331017b2948a52a3fad5d6219273263e15c32e1d350f716f9a186be356244b7513158dbc0c15cbfd9173b8585f5ef966d479fb0a28162340d4bbf9ae8dcb0adcad9cd7cecfc3c8e5c95f6e1ca10c7bc9f7f7327facf93ebc346d637742a9479257488f1ff9ca17d1ffe9daed5d39191cef0b34dbbea5fa7ce727e854f15b --snip--
I tried fixing this by updating dumper/Helpers/script.js (found that somewhere) and edited to reflect as much as dynamic functions as I could find, but this didn't work (also after examining libwvhidl.so it didn't reveal extra strings to add).
Could it be because this exploit is not possible above OEM Crypto API 13? When I install and run the DRM Info app on Android it says WideVine CDM 15. Also tried doing this using diazole dumper (https://github.com/Diazole/dumper) but this one never gives output except for initialization lines that ends with "INFO - Functions hooked, now open the DRM stream test on Bitmovin from your Android device! https://bitmovin.com/demos/drm" (and then never anything happens)
If the Crypto API version is the problem than there is another problem. The video page I'm visiting only loads on Android 11 android images that I tried... Maybe this website is clever and on purpose didn't support older Android versions to frustrate the key dumping proces? On the other hand I also tried on Android 9 and 10 with the test site https://bitmovin.com/demos/drm and these also didn't dump keys.
+ Reply to Thread
Results 1 to 14 of 14
-
Last edited by cosmicdrm; 6th Apr 2024 at 15:55.
-
Here's the key to your video, all it takes is a basic pywidevine script (cdrm-project like sites should work as well, no headers needed):
Code:--key 8eb0b3de683deca63a5840c20ef4742b:d23409ed222d8f82db5b48f3aa5c0bde
-
-
You have to use the PSSH under widevine :
Code:<!-- Widevine --> <ContentProtection schemeIdUri="urn:uuid:EDEF8BA9-79D6-4ACE-A3C8-27DCD51D21ED"> <cenc:pssh>AAAAXHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADwIARIQjrCz3mg97KY6WEDCDvR0KxoIdXNwLWNlbmMiGGpyQ3ozbWc5N0tZNldFRENEdlIwS3c9PSoAMgA=</cenc:pssh> </ContentProtection>
Code:AAAAXHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADwIARIQjrCz3mg97KY6WEDCDvR0KxoIdXNwLWNlbmMiGGpyQ3ozbWc5N0tZNldFRENEdlIwS3c9PSoAMgA=
-
-
[Attachment 78176 - Click to enlarge]
Btw, CDRM-project.com does not work in this case
AAAAXHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADwIARIQjr Cz3mg97KY6WEDCDvR0KxoIdXNwLWNlbmMiGGpyQ3ozbWc5N0tZ NldFRENEdlIwS3c9PSoAMgA=
https://npo-drm-gateway.samgcloud.nepworldwide.nl/authentication?custom_data=eyJhbGciO...6LKp872pYPzAwk -
CDRM-project.com could work but you need a fresh token and a header.
Here is a code doing all the job :
Code:from pywidevine.cdm import Cdm from pywidevine.device import Device from pywidevine.pssh import PSSH import requests import json import xml.etree.ElementTree as ET import re from dotenv import load_dotenv import os load_dotenv() link = 'https://www.maxmeldpunt.nl/uitzendingen/oud-ptters-lopen-pensioenaanvulling-mis/' link = input("Link for the wepage: ") def getpssh(mpd_url): response = requests.get(mpd_url) root = ET.fromstring(response.content) try: return root[0][1][4][0].text except: return 'pssh not found' headers = {'accept': '*/*', 'accept-language': 'fr-FR,fr;q=0.5', 'content-type': 'application/json', 'origin': 'https://www.maxmeldpunt.nl', 'referer': 'https://www.maxmeldpunt.nl/', 'sec-ch-ua': '"Brave";v="123", "Not:A-Brand";v="8", "Chromium";v="123"','user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36'} r = requests.get(link, headers=headers) match = re.compile(r'let jwtnpoplayer(.*?)\"').search(r.text) if match: word = 'let jwtnpoplayer' + match.group(1) pattern = re.compile(f'{word}"(.*?)"') match2 = pattern.search(r.text) if match2: token = match2.group(1) headers = {'accept': '*/*', 'accept-language': 'fr-FR,fr;q=0.5', 'content-type': 'application/json', 'origin': 'https://www.maxmeldpunt.nl', 'referer': 'https://www.maxmeldpunt.nl/', 'sec-ch-ua': '"Brave";v="123", "Not:A-Brand";v="8", "Chromium";v="123"','user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', 'authorization': f'{token}'} data = '{"profileName":"dash","drmType":"widevine","referrerUrl":"https://www.maxmeldpunt.nl/uitzendingen/oud-ptters-lopen-pensioenaanvulling-mis/"}' r = json.loads(requests.post('https://prod.npoplayer.nl/stream-link', headers=headers, data=data).text) mpd_url = r['stream']['streamURL'] print('mpd_url: ', mpd_url) drm_token = r['stream']['drmToken'] headers = { 'accept': '*/*', 'accept-language': 'fr-FR,fr;q=0.5', 'origin': 'https://www.maxmeldpunt.nl', 'referer': 'https://www.maxmeldpunt.nl/', 'sec-ch-ua': '"Brave";v="123", "Not:A-Brand";v="8", "Chromium";v="123"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"macOS"', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'cross-site', 'sec-gpc': '1', 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',} pssh = getpssh(mpd_url) print('pssh: ', pssh) pssh = PSSH(pssh) lic_url = f'https://npo-drm-gateway.samgcloud.nepworldwide.nl/authentication?custom_data={drm_token}' device = Device.load(os.getenv('DEVICEPATH')) cdm = Cdm.from_device(device) session_id = cdm.open() challenge = cdm.get_license_challenge(session_id, pssh) licence = requests.post(lic_url, headers = headers, data=challenge) licence.raise_for_status() cdm.parse_license(session_id, licence.content) for key in cdm.get_keys(session_id): if key.type=='CONTENT': print(f"\n--key {key.kid.hex}:{key.key.hex()}") cdm.close(session_id)
-
Nice. But do yo manually create a script everytime you want to do something like this? I mean, how deterministic is it? Seeing things like return root[0][1][4][0].text feels that it it tailor made, or are these xml index positions predictable enough in mpd files to have some script templates available or even a generator?
-
-
-
After reading 100 methods, what would be the latest way to convert device_private_key to a usable key?
-
tbh, the stickies are not the best if you want to fathom the general idea behind it all. I do think I understand it. To give back to the community I made a more general description. Please let me know where it can be approved, it's based on the virtual android device approach:
1. Set Up Virtual Android Environment with Frida:
- Create a virtual Android environment (e.g., using an emulator or a rooted device).
- Load Frida, which intercepts internal Android system calls, onto the virtual Android system.
2. Run KeyDive on the Host Machine:
- On your host machine, execute a program like KeyDive. KeyDive interacts with the virtual Android device via ADB (Android Debug Bridge) in the background.
- KeyDive allows you to analyze and manipulate the Android app's behavior during runtime.
3. Access Video Content:
- While the virtual Android environment with Frida and KeyDive is active, visit a video site that presents content.
- Let the site load the MPD file, which contains information about video streams and encryption methods.
4. Extract Client ID and Private Key:
- Because you've opened your target video KeyDive intercepts this and will extract the following files:
- client_id.bin: Represents the client ID within the built-in Android Content Decryption Module (CDM).
- private_key.pem: Contains the private key associated with the CDM.
5. Prepare the .wvd Device File:
- Generate a .wvd device file using pywidevine's create-device command.
- This file simulates a Widevine CDM device and can be used as your own prepped CDM device in a script.
6. Retrieve DRM Keys from the .wvd Device:
- Utilize the pywscript.py script to extract DRM keys from the .wvd device.
- The script requires the following inputs:
- Path to the .wvd file
- Path to the manifest .mpd file (which describes the video streams)
- PSSH (Protection System Specific Header) information
- Authentication server URL
- The script will use the prepped CDM (via the .wvd device) to request and display the DRM keys in cleartext.
7. Download Encrypted Video:
- Finally, use the extracted DRM keys along with a tool like N_m3u8DL-RE to download the DRM-encrypted video via its .mpd URL. -
tbh, the stickies are not the best if you want to fathom the general idea behind it all. I do think I understand it. To give back to the community I made a more general description. Please let me know where it can be improved, it's based on the virtual android device approach:
1. Set Up Virtual Android Environment with Frida:
- Create a virtual Android environment (e.g., using an emulator or a rooted device).
- Load Frida, which intercepts internal Android system calls, onto the virtual Android system.
2. Run KeyDive on the Host Machine:
- On your host machine, execute a program like KeyDive. KeyDive interacts with the virtual Android device via ADB (Android Debug Bridge) in the background.
- KeyDive allows you to analyze and manipulate the Android app's behavior during runtime.
3. Access Video Content:
- While the virtual Android environment with Frida and KeyDive is active, visit a video site that presents content.
- Let the site load the MPD file, which contains information about video streams and encryption methods.
4. Extract Client ID and Private Key:
- Because you've opened your target video KeyDive intercepts this and will extract the following files:
- client_id.bin: Represents the client ID within the built-in Android Content Decryption Module (CDM).
- private_key.pem: Contains the private key associated with the CDM.
5. Prepare the .wvd Device File:
- Generate a .wvd device file using pywidevine's create-device command.
- This file simulates a Widevine CDM device and can be used as your own prepped CDM device in a script.
6. Retrieve DRM Keys from the .wvd Device:
- Utilize the pywscript.py script to extract DRM keys from the .wvd device.
- The script requires the following inputs:
- Path to the .wvd file
- Path to the manifest .mpd file (which describes the video streams)
- PSSH (Protection System Specific Header) information
- Authentication server URL
- The script will use the prepped CDM (via the .wvd device) to request and display the DRM keys in cleartext.The script can be found at the end of this sticky
7. Download Encrypted Video:
- Finally, use the extracted DRM keys along with a tool like N_m3u8DL-RE to download the DRM-encrypted (and decrypt on-the-fly) video via its .mpd URL. Or download the encrypted file using yt-dlp --allow-u and decrypt afterwards with mp4decrypt.Last edited by cosmicdrm; 7th Apr 2024 at 16:56.
Similar Threads
-
Dumping Your own L3 CDM with Android Studio
By cedric8528 in forum Video Streaming DownloadingReplies: 774Last Post: 1st May 2025, 15:43 -
Dumping L3 CDM with Android Studio on AndroidTV - Need Assistance
By elhouari1988 in forum Video Streaming DownloadingReplies: 26Last Post: 15th Mar 2024, 07:42 -
Dumping from Android phone error
By PSXman_uk in forum Video Streaming DownloadingReplies: 13Last Post: 14th Feb 2024, 09:07 -
Can you use a CDM dumped from a virtual android device with tpd-keys?
By adrian_mando1995 in forum Video Streaming DownloadingReplies: 5Last Post: 30th Nov 2023, 19:07 -
Dumping L3 from Android 10, 11 and 12
By Diazole in forum Video Streaming DownloadingReplies: 45Last Post: 9th Jan 2023, 07:13