Hey ya'll!
I'm current trying to load libwidevinecdm.so in cpp code on GNU/Linux and like to share the current progress.
Background
After modifying Firefox source code so it connects to a TCP server whenever a CDM module is loaded and sending raw frames to the server for recording, I was not satisfied with the webrip disadvantages (large files, re-encoding), otherwise it worked like a charm.
So what about webdl? The Android emulator thing for dumping private key could work for me, but that would be too easy :')
After fiddling around with gdb and scanmem searching for known KID and CKEY (from bitmovin) I found the KID's, but not the content keys.
I need a better understanding of how the module works before attempting to reverse engineering it at least so far that the content keys could be extracted. What I'm trying to do now is using the shared lib (libwidevinecdm.so) from Firefox in own code, much is copied and modified from Firefox sauce, like headers and such.
Current issue
This is WIP and I'm currently stuck with the license challenge, either I get a corrupt challenge from the CDM or the challenge is send wrongly to the license server.
The suspected issue is that the returned challenge by the CDM is corrupt. The size of the challenge returned in Firefox is 1743 bytes and the one we get is 1748 bytes in size. The difference in pattern I discovered is that our challenge always has the character 0x80 repeating 9 times, the challenge returned by the Firefox CDM does not have this.
Then there is this thing that I don't get, when in Firefox I can replay a license challenge without a problem, the I copy the cURL cmd from Firefox and apply it in the terminal and it fails. Only difference I'm seeing is that Firefox is doing a HTTP/3 request and cURL is doing a HTTP/2 request. Have tried the --http3 arg with cURL but that did not work out either.
Or the issue could be the license server certificate, which I'm not giving the CDM because Firefox isn't either showed the debug prints.
All tests are done on https://bitmovin.com/demos/drm
Response of the license server after sending the 1748 byte challenge:
Response of the license server after sending the challenge but truncating the 1748 byte challenge to 1743 bytes:Code:Status: 400 response size: 845 response: 'License Request FailedInvalid License Request Traceback (most recent call last): File "/base/data/home/apps/s~cwip-shaka-proxy/only.453931069905091665/proxy_external.py", line 86, in post status_ok, response = self._ProcessLicenseResponse(response) File "/base/data/home/apps/s~cwip-shaka-proxy/only.453931069905091665/proxy_external.py", line 149, in _ProcessLicenseResponse license_response = json.loads(response) File "/base/alloc/tmpfs/dynamic_runtimes/python27g/cc0d8fc936fa7ac/python27/python27_dist/lib/python2.7/json/__init__.py", line 339, in loads return _default_decoder.decode(s) File "/base/alloc/tmpfs/dynamic_runtimes/python27g/cc0d8fc936fa7ac/python27/python27_dist/lib/python2.7/json/decoder.py", line 364, in decode obj, end = self.raw_decode(s, idx=_w(s, 0).end()) TypeError: expected string or buffer '
ProgressCode:Status: 500 response size: 25 response: 'INVALID_LICENSE_CHALLENGE'
- [x] Load libwidevinecdm.so (dlopen)
- [x] Find symbols (dlsym)
Code:GetCdmVersion VerifyCdmHost_0 CreateCdmInstance InitializeCdmModule_4 DeinitializeCdmModule- [x] Call VerifyCdmHost_0 (it returns true)
!! Possible that the problem is here, that it returns true even when problems are detected.
Debug prints from Firefox:
What the code current does:Code:ChromiumCDMAdapter::GMPInit binpath:'/home/user/profiles/profile1/gmp-widevinecdm/4.10.2830.0/libwidevinecdm.so' binfp:21 sigfp:-1 binpath:'/home/user/code/firefox/firefox' binfp:22 sigfp:-1 binpath:'/home/user/code/firefox/firefox' binfp:23 sigfp:-1 binpath:'/home/user/code/firefox/libxul.so' binfp:24 sigfp:-1
With only the binary fd opened, sig fds are -1 just like Firefox does.Code:binpath:'/home/user/profiles/profile1/gmp-widevinecdm/4.10.2830.0/libwidevinecdm.so' binfp:23 sigfp:-1 binpath:'/home/user/code/myproj/myproj' binfp:24 sigfp:-1
Also tried giving exact same as Firefox and combinations of those with my bin inside it, and giving just nullptr, but it always returns true.- [x] Call InitializeCdmModule_4
- [x] Call CreateCdmInstance
This will set our API instance for the CDM (Host_10) and we will receive a pointer to the CDM it's API (ContentDecryptionModule_10).- [x] Call ContentDecryptionModule_10::Initialize(false, false, false)
- The CMD will start calling Host_10::GetCurrentWallTime to get the current time in microseconds.
- The CDM will call our Host_10::QueryOutputProtectionStatus, we respond by calling ContentDecryptionModule_10::OnQueryOutputProtectio nStatus.
- The CDM does call our defined Host_10::OnInitialized(bool success) with success as true.- [!] Call ContentDecryptionModule_10::CreateSessionAndGenera teRequest
The following arguments are given:- PromiseId 2 (should not matter that much)These match what Firefox gives as arguments, confirmed by debug prints.
- SessionType is kTemporary
- InitDataType is kCenc
- Pointer to the init data and the init data size
The CDM will call Host_10::OnResolveNewSessionPromise and gives us a sessionId that we store, for now doing nothing with it.
Then the CDM will call HostApi::OnResolveNewSessionPromise with the license challenge we need to send to the license server.
We send the challenge to the license server with a HTTP/2 POST request with matching headers from Firefox, and yes that did not work out.
To be continued..
Code
Willing to share once it isn't such a mess with hardcoded stuff etc anyore.
Support our site by donate $5 directly to us Thanks!!!
Try StreamFab Downloader and download streaming video from Netflix, Amazon!
Try StreamFab Downloader and download streaming video from Netflix, Amazon!
+ Reply to Thread
Results 1 to 5 of 5
-
-
If you're trying to reverse engineer the Widevine Cdm then you're in luck because this has already been done years ago: https://github.com/devine-dl/pywidevine. If you really want to figure this out you can post the challenge here in base64 encoded form so I can take a look, but the only point would be to extract the device private key and client id itself, because that's all you really need to just use pywidevine.
But be warned, these files are INCREDIBLY well obfuscated and if you do somehow are able to extract the private key do NOT leak them. It'll get revoked instantly by Google and we'll get a new ChromeCDM. If you want to know how hard it is, look at the reverse engineering process of the old Widevine CDM: https://github.com/tomer8007/widevine-l3-decryptor/wiki/Reversing-the-old-Widevine-Con...ryption-Module
With that being said, it's still possible, because the chinese downloader "StreamFab" has an API for internal usage (paid, ~200$).Bypass HMACs, One-time-tokens and Lic.Wrapping: https://github.com/DevLARLEY/WidevineProxy2 -
For me this project is about gaining more knowlage about how the CDM module works and have a clean project to debug the so and lets see where it ends, every unknown function/buffer/mechanism uncovered is a win for me. Yes I've already encounterd anti debugger stuff that needs to be adressed too, but first lets make the license challenge work.
You're the author of WidevineProxy2, thats cool and thanks for the project! I'm currently using it with remote CDM. And yes pywidevine is also awesome and good resource.
Example challenge from the CDM:
Code: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
-
At least the challenge can be decoded by pywidevine:
Code:import base64, sys from pywidevine import ( Device, Cdm, PSSH ) from pywidevine.license_protocol_pb2 import SignedMessage, LicenseRequest data = b'CAES...' # your challenge signed_message = SignedMessage() # signed_message.ParseFromString(data) signed_message.ParseFromString(base64.b64decode(data)) # print(signed_message.msg) license_request = LicenseRequest() license_request.ParseFromString(signed_message.msg) print(license_request)
-
@Obo thank you that helped!
First I had to use this branch for pymp4 and then update some stuff in pywidevine to be compatible with Python construct 2.10, but that works now. So I've tried your code and the one thing I noticed was that "request_time" value was negative and large, it looked like there was something overflowing.
The return value (double) of my "Host_10::GetCurrentWallTime()" had no delimiter, it needed to be devided by 1000000000.
It seems to work now, I got valid reponse.
Similar Threads
-
download Odrive-shared files with yt-dlp
By blanc in forum Video Streaming DownloadingReplies: 5Last Post: 3rd Jan 2025, 08:49 -
Run MPV on files found with GNU Findutils with these Bash scripts
By o770 in forum LinuxReplies: 0Last Post: 5th Oct 2024, 21:10 -
AVI TOO BIG capturing Hi8 tapes using VLC, GNU/Linux and a USB stick...
By asbesto in forum LinuxReplies: 13Last Post: 26th Dec 2023, 10:28 -
DScaler: Providers.cpp line: 488 Message: Can't load Hardware Driver
By phelissimo_ in forum Capturing and VCRReplies: 1Last Post: 25th Jul 2022, 02:33 -
[LINUX] can ffmpeg (or any othe linux command) genere an ANIMATED waveform
By Jintor_Universe in forum ProgrammingReplies: 5Last Post: 28th Oct 2021, 20:29