Posted by Amir Mazzarella on July 04, 2018
I'm taking a break from the Widevine series of blog posts for a while; in the meantime, I wanted to discuss the recent advancements in reverse engineering the web streaming DRM known as PlayReady. PlayReady is a DRM created by Microsoft for browsers like Internet Explorer and Edge. You may recognize its predecessor, WMDRM or Windows Media DRM. Following WMDRM, was Silverlight, a browser plugin to enable playing PlayReady protected media. After Silverlight, Microsoft adopted the HTML5 standard EME and integrated the PlayReady CDM into its browsers. Since it uses the EME standard, it's very similar to Widevine — that is it handles interactions with the CDM through JavaScript.
So, how do we break it? Well, let's take a look at PlayReady initialization data and a respective PlayReady license request. For simplicity, I'll be using the sample PSSH from the "Utilities" page of my website, and a license request generated using the PlayReady license request generator on that same page.
The PlayReady initialization data:
AAADfHBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAA1xcAwAAAQABAFIDPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgA0AFIAcABsAGIAKwBUAGIATgBFAFMAOAB0AEcAawBOAEYAVwBUAEUASABBAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEsATABqADMAUQB6AFEAUAAvAE4AQQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcABzADoALwAvAHAAcgBvAGYAZgBpAGMAaQBhAGwAcwBpAHQAZQAuAGsAZQB5AGQAZQBsAGkAdgBlAHIAeQAuAG0AZQBkAGkAYQBzAGUAcgB2AGkAYwBlAHMALgB3AGkAbgBkAG8AdwBzAC4AbgBlAHQALwBQAGwAYQB5AFIAZQBhAGQAeQAvADwALwBMAEEAXwBVAFIATAA+ADwAQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwASQBJAFMAXwBEAFIATQBfAFYARQBSAFMASQBPAE4APgA4AC4AMAAuADEAOAAwADIALgAyADUAPAAvAEkASQBTAF8ARABSAE0AXwBWAEUAUgBTAEkATwBOAD4APAAvAEMAVQBTAFQATwBNAEEAVABUAFIASQBCAFUAVABFAFMAPgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==
The initialization data base64 encoded, so let's run it through a decoder:
>>> import base64
>>> print(base64.b64decode(init_data))
b'\x00\x00\x03|pssh\x00\x00\x00\x00\x9a\x04\xf0y\x98@B\x86\xab\x92\xe6[\xe0\x88_\x95\x00\x00\x03\\\\\x03\x00\x00\x01\x00\x01\x00R\x03<\x00W\x00R\x00M\x00H\x00E\x00A\x00D\x00E\x00R\x00 \x00x\x00m\x00l\x00n\x00s\x00=\x00"\x00h\x00t\x00t\x00p\x00:\x00/\x00/\x00s\x00c\x00h\x00e\x00m\x00a\x00s\x00.\x00m\x00i\x00c\x00r\x00o\x00s\x00o\x00f\x00t\x00.\x00c\x00o\x00m\x00/\x00D\x00R\x00M\x00/\x002\x000\x000\x007\x00/\x000\x003\x00/\x00P\x00l\x00a\x00y\x00R\x00e\x00a\x00d\x00y\x00H\x00e\x00a\x00d\x00e\x00r\x00"\x00 \x00v\x00e\x00r\x00s\x00i\x00o\x00n\x00=\x00"\x004\x00.\x000\x00.\x000\x00.\x000\x00"\x00>\x00<\x00D\x00A\x00T\x00A\x00>\x00<\x00P\x00R\x00O\x00T\x00E\x00C\x00T\x00I\x00N\x00F\x00O\x00>\x00<\x00K\x00E\x00Y\x00L\x00E\x00N\x00>\x001\x006\x00<\x00/\x00K\x00E\x00Y\x00L\x00E\x00N\x00>\x00<\x00A\x00L\x00G\x00I\x00D\x00>\x00A\x00E\x00S\x00C\x00T\x00R\x00<\x00/\x00A\x00L\x00G\x00I\x00D\x00>\x00<\x00/\x00P\x00R\x00O\x00T\x00E\x00C\x00T\x00I\x00N\x00F\x00O\x00>\x00<\x00K\x00I\x00D\x00>\x004\x00R\x00p\x00l\x00b\x00+\x00T\x00b\x00N\x00E\x00S\x008\x00t\x00G\x00k\x00N\x00F\x00W\x00T\x00E\x00H\x00A\x00=\x00=\x00<\x00/\x00K\x00I\x00D\x00>\x00<\x00C\x00H\x00E\x00C\x00K\x00S\x00U\x00M\x00>\x00K\x00L\x00j\x003\x00Q\x00z\x00Q\x00P\x00/\x00N\x00A\x00=\x00<\x00/\x00C\x00H\x00E\x00C\x00K\x00S\x00U\x00M\x00>\x00<\x00L\x00A\x00_\x00U\x00R\x00L\x00>\x00h\x00t\x00t\x00p\x00s\x00:\x00/\x00/\x00p\x00r\x00o\x00f\x00f\x00i\x00c\x00i\x00a\x00l\x00s\x00i\x00t\x00e\x00.\x00k\x00e\x00y\x00d\x00e\x00l\x00i\x00v\x00e\x00r\x00y\x00.\x00m\x00e\x00d\x00i\x00a\x00s\x00e\x00r\x00v\x00i\x00c\x00e\x00s\x00.\x00w\x00i\x00n\x00d\x00o\x00w\x00s\x00.\x00n\x00e\x00t\x00/\x00P\x00l\x00a\x00y\x00R\x00e\x00a\x00d\x00y\x00/\x00<\x00/\x00L\x00A\x00_\x00U\x00R\x00L\x00>\x00<\x00C\x00U\x00S\x00T\x00O\x00M\x00A\x00T\x00T\x00R\x00I\x00B\x00U\x00T\x00E\x00S\x00>\x00<\x00I\x00I\x00S\x00_\x00D\x00R\x00M\x00_\x00V\x00E\x00R\x00S\x00I\x00O\x00N\x00>\x008\x00.\x000\x00.\x001\x008\x000\x002\x00.\x002\x005\x00<\x00/\x00I\x00I\x00S\x00_\x00D\x00R\x00M\x00_\x00V\x00E\x00R\x00S\x00I\x00O\x00N\x00>\x00<\x00/\x00C\x00U\x00S\x00T\x00O\x00M\x00A\x00T\x00T\x00R\x00I\x00B\x00U\x00T\x00E\x00S\x00>\x00<\x00/\x00D\x00A\x00T\x00A\x00>\x00<\x00/\x00W\x00R\x00M\x00H\x00E\x00A\x00D\x00E\x00R\x00>\x00'
Wow, that's a lot of bytes. What I can see here though, is that this initialization data is a PSSH MP4 box. Let's parse it as such and get the actual initialization data.
>>> from pymp4.parser import Box
>>> print(Box.parse(init_data_bytes))
Container:
offset = 0
type = b'pssh'
version = 0
flags = 0
system_ID = 9a04f079-9840-4286-ab92-e65be0885f95
key_IDs = None
init_data = b'\\\x03\x00\x00\x01\x00\x01\x00R\x03<\x00W\x00R\x00M\x00H\x00E\x00A\x00D\x00E\x00R\x00 \x00x\x00m\x00l\x00n\x00s\x00=\x00"\x00h\x00t\x00t\x00p\x00:\x00/\x00/\x00s\x00c\x00h\x00e\x00m\x00a\x00s\x00.\x00m\x00i\x00c\x00r\x00o\x00s\x00o\x00f\x00t\x00.\x00c\x00o\x00m\x00/\x00D\x00R\x00M\x00/\x002\x000\x000\x007\x00/\x000\x003\x00/\x00P\x00l\x00a\x00y\x00R\x00e\x00a\x00d\x00y\x00H\x00e\x00a\x00d\x00e\x00r\x00"\x00 \x00v\x00e\x00r\x00s\x00i\x00o\x00n\x00=\x00"\x004\x00.\x000\x00.\x000\x00.\x000\x00"\x00>\x00<\x00D\x00A\x00T\x00A\x00>\x00<\x00P\x00R\x00O\x00T\x00E\x00C\x00T\x00I\x00N\x00F\x00O\x00>\x00<\x00K\x00E\x00Y\x00L\x00E\x00N\x00>\x001\x006\x00<\x00/\x00K\x00E\x00Y\x00L\x00E\x00N\x00>\x00<\x00A\x00L\x00G\x00I\x00D\x00>\x00A\x00E\x00S\x00C\x00T\x00R\x00<\x00/\x00A\x00L\x00G\x00I\x00D\x00>\x00<\x00/\x00P\x00R\x00O\x00T\x00E\x00C\x00T\x00I\x00N\x00F\x00O\x00>\x00<\x00K\x00I\x00D\x00>\x004\x00R\x00p\x00l\x00b\x00+\x00T\x00b\x00N\x00E\x00S\x008\x00t\x00G\x00k\x00N\x00F\x00W\x00T\x00E\x00H\x00A\x00=\x00=\x00<\x00/\x00K\x00I\x00D\x00>\x00<\x00C\x00H\x00E\x00C\x00K\x00S\x00U\x00M\x00>\x00K\x00L\x00j\x003\x00Q\x00z\x00Q\x00P\x00/\x00N\x00A\x00=\x00<\x00/\x00C\x00H\x00E\x00C\x00K\x00S\x00U\x00M\x00>\x00<\x00L\x00A\x00_\x00U\x00R\x00L\x00>\x00h\x00t\x00t\x00p\x00s\x00:\x00/\x00/\x00p\x00r\x00o\x00f\x00f\x00i\x00c\x00i\x00a\x00l\x00s\x00i\x00t\x00e\x00.\x00k\x00e\x00y\x00d\x00e\x00l\x00i\x00v\x00e\x00r\x00y\x00.\x00m\x00e\x00d\x00i\x00a\x00s\x00e\x00r\x00v\x00i\x00c\x00e\x00s\x00.\x00w\x00i\x00n\x00d\x00o\x00w\x00s\x00.\x00n\x00e\x00t\x00/\x00P\x00l\x00a\x00y\x00R\x00e\x00a\x00d\x00y\x00/\x00<\x00/\x00L\x00A\x00_\x00U\x00R\x00L\x00>\x00<\x00C\x00U\x00S\x00T\x00O\x00M\x00A\x00T\x00T\x00R\x00I\x00B\x00U\x00T\x00E\x00S\x00>\x00<\x00I\x00I\x00S\x00_\x00D\x00R\x00M\x00_\x00V\x00E\x00R\x00S\x00I\x00O\x00N\x00>\x008\x00.\x000\x00.\x001\x008\x000\x002\x00.\x002\x005\x00<\x00/\x00I\x00I\x00S\x00_\x00D\x00R\x00M\x00_\x00V\x00E\x00R\x00S\x00I\x00O\x00N\x00>\x00<\x00/\x00C\x00U\x00S\x00T\x00O\x00M\x00A\x00T\x00T\x00R\x00I\x00B\x00U\x00T\x00E\x00S\x00>\x00<\x00/\x00D\x00A\x00T\x00A\x00>\x00<\x00/\x00W\x00R\x00M\x00H\x00E\x00A\x00D\x00E\x00R\x00>\x00'
end = 892
Yup, I was right. The system ID for PlayReady is 9a04f079-9840-4286-ab92-e65be0885f95
, so it checks out. However, the initialization data looks like a string with every character being null terminated. Let's read up on the PlayReady WRMHEADER documentation.
Reading through it, I see that the initialization data is an object containing fields that detail how many records there are, the length, and the type. The actual initialization data is encoded UTF-16, which explains the null-terminated characters. So, I wrote a Python function to parse the header object and return the real initialization data:
def __parse_playready_header(init_data):
try:
header = Box.parse(init_data).init_data
except Exception:
header = init_data
header_length, record_count = struct.unpack('<LH', header[:6])
records = header[6:]
record_values = []
for record in range(record_count):
record_type, record_length = struct.unpack('<HH', records[:4])
record_value = records[4:][:record_length].decode('utf16')
records = records[4:][record_length:]
record_values.append({
'type': record_type,
'value': record_value
})
pssh = [x for x in record_values if x['type'] == 1]
if len(pssh) != 1:
raise RuntimeError
return pssh[0]['value']
What this does is it first tries to see if the PSSH provided is a PSSH box from an MP4 or just a straight header object. If it is a PSSH box, it will parse it accordingly. Then it follows the header documentation on reading the number of records and loops based on that number, parsing for record length, value, and type. The record type for initialization data is 1, so the function will return the record with that type. After doing that process, let's take a proper look at what the real PlayReady initialization data is:
<WRMHEADER xmlns="http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader" version="4.0.0.0">
<DATA>
<PROTECTINFO>
<KEYLEN>16</KEYLEN>
<ALGID>AESCTR</ALGID>
</PROTECTINFO>
<KID>4Rplb+TbNES8tGkNFWTEHA==</KID>
<CHECKSUM>KLj3QzQP/NA=</CHECKSUM>
<LA_URL>https://profficialsite.keydelivery.mediaservices.windows.net/PlayReady/</LA_URL>
<CUSTOMATTRIBUTES>
<IIS_DRM_VERSION>8.0.1802.25</IIS_DRM_VERSION>
</CUSTOMATTRIBUTES>
</DATA>
</WRMHEADER>
All it is is an XML string containing Key IDs, MP4 encryption algorithms, and key checksums. Seems pretty simple, right? Now let's take a look at a PlayReady license request generated by my generator (after being base64 decoded and beautified):
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<AcquireLicense xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols">
<challenge>
<Challenge xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols/messages">
<LA xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols" Id="SignedData" xml:space="preserve">
<Version>1</Version>
<ContentHeader>
<WRMHEADER xmlns="http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader" version="4.0.0.0">
<DATA>
<PROTECTINFO>
<KEYLEN>16</KEYLEN>
<ALGID>AESCTR</ALGID>
</PROTECTINFO>
<KID>4Rplb+TbNES8tGkNFWTEHA==</KID>
<CHECKSUM>KLj3QzQP/NA=</CHECKSUM>
<LA_URL>https://profficialsite.keydelivery.mediaservices.windows.net/PlayReady/</LA_URL>
<CUSTOMATTRIBUTES>
<IIS_DRM_VERSION>8.0.1802.25</IIS_DRM_VERSION>
</CUSTOMATTRIBUTES>
</DATA>
</WRMHEADER>
</ContentHeader>
<CLIENTINFO>
<CLIENTVERSION>10.0.16384.10011</CLIENTVERSION>
</CLIENTINFO>
<RevocationLists>
<RevListInfo>
<ListID>ioydTlK2p0WXkWklprR5Hw==</ListID>
<Version>0</Version>
</RevListInfo>
<RevListInfo>
<ListID>gC4IKKPHsUCCVhnlttibJw==</ListID>
<Version>0</Version>
</RevListInfo>
<RevListInfo>
<ListID>Ef/RUojT3U6Ct2jqTCChbA==</ListID>
<Version>0</Version>
</RevListInfo>
<RevListInfo>
<ListID>BOZ1zT1UnEqfCf5tJOi/kA==</ListID>
<Version>0</Version>
</RevListInfo>
</RevocationLists>
<LicenseNonce>JfFq9xbNizYSEcMQSzoqAA==</LicenseNonce>
<ClientTime>1530668901</ClientTime>
<EncryptedData xmlns="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"></EncryptionMethod>
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">
<EncryptionMethod Algorithm="http://schemas.microsoft.com/DRM/2007/03/protocols#ecc256"></EncryptionMethod>
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<KeyName>WMRMServer</KeyName>
</KeyInfo>
<CipherData>
<CipherValue>P87nuZ04k/gbrOvihVUZJvJ0DFj/5hRDMaChYE7G8IgbQXAKVBdnPiZocmBqc0q5y2W6pskEQiWkm0UdkiPUVuSTrze49BEdPBSzjDeU3zZDM9+T0rUcYIhdQ9u/Dpi7AJYuse1Os3NaC3Bpo7QPBySUoB7m4WIN0kviG2NS0UU=</CipherValue>
</CipherData>
</EncryptedKey>
</KeyInfo>
<CipherData>
<CipherValue>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</CipherValue>
</CipherData>
</EncryptedData>
</LA>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod>
<SignatureMethod Algorithm="http://schemas.microsoft.com/DRM/2007/03/protocols#ecdsa-sha256"></SignatureMethod>
<Reference URI="#SignedData">
<DigestMethod Algorithm="http://schemas.microsoft.com/DRM/2007/03/protocols#sha256"></DigestMethod>
<DigestValue>ZaGsIyjaM/21e0mLBxcyz1aCJ5XMtZ/QeTP2lPzSs90=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>qpU0mJCvSjq6IKn16MUQiN6SGA92K6qNqQkgGwvbYoIMaYJxiIPj6Y3aZAofW+PcYIGKf6zxbF+eMzXNOL6EMg==</SignatureValue>
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<KeyValue>
<ECCKeyValue>
<PublicKey>l3oD88wdIxUUQYKWZrH+xhxzHf7T3W/00xJjrZ7qAVgMS39Vwp6/wVpbTnaZ5KBGC6yplDSkqBdx/DbASzmW0Q==</PublicKey>
</ECCKeyValue>
</KeyValue>
</KeyInfo>
</Signature>
</Challenge>
</challenge>
</AcquireLicense>
</soap:Body>
</soap:Envelope>
So what I can infer from the license request is that it contains the initialization data in full, has basic client info (like client version, revocation lists, etc.), includes an ECC public key for license encryption, and is signed by the client device key. What's odd is that there's a giant block of ciphertext. What is it encrypted against, what does it contain? That's something I'll touch on in the next post. What's interesting is that PlayReady uses ECC for its asymmetric encryption. Most DRM libraries, like Widevine and Adobe Flash Access, use RSA, so it's exciting to see Microsoft go against the current and use the underdog of cryptosystems. However, since it is an underdog, there's barely any good implementations of ECC encryption/decryption and ECDSA signing, so we'll have to make our own, which I'll cover in a later blog post. This blog post serves as an intro to the world of PlayReady, and next time we'll go over PlayReady's custom message system called XMR and what lies in the encrypted block of a license request. Thanks for reading!