VideoHelp Forum



Support our site by donate $5 directly to us Thanks!!!

Try StreamFab Downloader and download streaming video from Netflix, Amazon!



+ Reply to Thread
Results 1 to 5 of 5
  1. 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:

    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
    '
    Response of the license server after sending the challenge but truncating the 1748 byte challenge to 1743 bytes:

    Code:
    Status: 500
    response size: 25
    response: 'INVALID_LICENSE_CHALLENGE'
    Progress
    • [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:

      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
      What the code current 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
      With only the binary fd opened, sig fds are -1 just like Firefox does.

      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)
      - SessionType is kTemporary
      - InitDataType is kCenc
      - Pointer to the init data and the init data size
      These match what Firefox gives as arguments, confirmed by debug prints.

      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.
    Quote Quote  
  2. 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
    Quote Quote  
  3. 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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
    Quote Quote  
  4. 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)
    Quote Quote  
  5. @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.
    Quote Quote  



Similar Threads

Visit our sponsor! Try DVDFab and backup Blu-rays!