]> xmof Git - DeDRM.git/commitdiff
Support KFX VoucherEnvelope versions 2 and 3
authorapprenticesakuya <62770541+apprenticesakuya@users.noreply.github.com>
Tue, 16 Jun 2020 01:19:15 +0000 (01:19 +0000)
committerGitHub <noreply@github.com>
Tue, 16 Jun 2020 01:19:15 +0000 (01:19 +0000)
DeDRM_plugin/ion.py

index c361d208b50d13f95ba270d39e1d1e9189eb1b9d..56450567ca5052f0cdc60fe3abd2eba056e95a25 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# Pascal implementation by lulzkabulz. Python translation by apprenticenaomi. DeDRM integration by anon.
+# Pascal implementation by lulzkabulz. Python translation by apprenticenaomi. DeDRM integration by anon. VoucherEnvelope v2/v3 support by apprenticesakuya.
 # BinaryIon.pas + DrmIon.pas + IonSymbols.pas
 
 from __future__ import with_statement
@@ -719,7 +719,8 @@ SYM_NAMES = [ 'com.amazon.drm.Envelope@1.0',
               'com.amazon.drm.EnvelopeMetadata@2.0',
               'com.amazon.drm.EncryptedPage@2.0',
               'com.amazon.drm.PlainText@2.0', 'compression_algorithm',
-              'com.amazon.drm.Compressed@1.0', 'priority', 'refines']
+              'com.amazon.drm.Compressed@1.0', 'page_index_table',
+              'com.amazon.drm.VoucherEnvelope@2.0', 'com.amazon.drm.VoucherEnvelope@3.0' ]
 
 def addprottable(ion):
     ion.addtocatalog("ProtectedData", 1, SYM_NAMES)
@@ -741,8 +742,42 @@ def pkcs7unpad(msg, blocklen):
     return msg[:-paddinglen]
 
 
+# every VoucherEnvelope version has a corresponding "word" and magic number, used in obfuscating the shared secret
+VOUCHER_VERSION_INFOS = {
+    2: [b'Antidisestablishmentarianism', 5],
+    3: [b'Floccinaucinihilipilification', 8]
+}
+
+
+# obfuscate shared secret according to the VoucherEnvelope version
+def obfuscate(secret, version):
+    if version == 1:  # v1 does not use obfuscation
+        return secret
+
+    params = VOUCHER_VERSION_INFOS[version]
+    word = params[0]
+    magic = params[1]
+
+    # extend secret so that its length is divisible by the magic number
+    if len(secret) % magic != 0:
+        secret = secret + b'\x00' * (magic - len(secret) % magic)
+
+    secret = bytearray(secret)
+
+    obfuscated = bytearray(len(secret))
+    wordhash = bytearray(hashlib.sha256(word).digest())
+
+    # shuffle secret and xor it with the first half of the word hash
+    for i in range(0, len(secret)):
+        index = i // (len(secret) // magic) + magic * (i % (len(secret) // magic))
+        obfuscated[index] = secret[i] ^ wordhash[index % 16]
+
+    return obfuscated
+
+
 class DrmIonVoucher(object):
     envelope = None
+    version = None
     voucher = None
     drmkey = None
     license_type = "Unknown"
@@ -777,9 +812,9 @@ class DrmIonVoucher(object):
             else:
                 _assert(False, "Unknown lock parameter: %s" % param)
 
-        sharedsecret = shared.encode("UTF-8")
+        sharedsecret = obfuscate(shared.encode('ASCII'), self.version)
 
-        key = hmac.new(sharedsecret, sharedsecret[:5], digestmod=hashlib.sha256).digest()
+        key = hmac.new(sharedsecret, "PIDv3", digestmod=hashlib.sha256).digest()
         aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
         b = aes.decrypt(self.ciphertext)
         b = pkcs7unpad(b, 16)
@@ -814,8 +849,9 @@ class DrmIonVoucher(object):
     def parse(self):
         self.envelope.reset()
         _assert(self.envelope.hasnext(), "Envelope is empty")
-        _assert(self.envelope.next() == TID_STRUCT and self.envelope.gettypename() == "com.amazon.drm.VoucherEnvelope@1.0",
+        _assert(self.envelope.next() == TID_STRUCT and str.startswith(self.envelope.gettypename(), "com.amazon.drm.VoucherEnvelope@"),
                 "Unknown type encountered in envelope, expected VoucherEnvelope")
+        self.version = int(self.envelope.gettypename().split('@')[1][:-2])
 
         self.envelope.stepin()
         while self.envelope.hasnext():