]> xmof Git - DeDRM.git/commitdiff
Finish .kinf2018 support
authorapprenticesakuya <62770541+apprenticesakuya@users.noreply.github.com>
Fri, 27 Mar 2020 20:01:09 +0000 (13:01 -0700)
committerGitHub <noreply@github.com>
Fri, 27 Mar 2020 20:01:09 +0000 (13:01 -0700)
DeDRM_plugin/kindlekey.py

index 7b994ed4c662ceb193b000a6a1b247de3f0a5e58..9a00b1fb5f1385e867e66cc371e71e26fd90811d 100644 (file)
@@ -4,7 +4,7 @@
 from __future__ import with_statement
 
 # kindlekey.py
-# Copyright © 2008-2017 Apprentice Harper et al.
+# Copyright © 2008-2020 Apprentice Harper et al.
 
 __license__ = 'GPL v3'
 __version__ = '2.6'
@@ -29,6 +29,7 @@ __version__ = '2.6'
 #  2.4   - Fix for complex Mac disk setups, thanks to Tibs
 #  2.5   - Final Fix for Windows user names with non-ascii characters, thanks to oneofusoneofus
 #  2.6   - Start adding support for Kindle 1.25+ .kinf2018 file
+#  2.7   - Finish .kinf2018 support
 
 
 """
@@ -36,7 +37,7 @@ Retrieve Kindle for PC/Mac user key.
 """
 
 import sys, os, re
-from struct import pack, unpack, unpack_from
+from struct import pack, unpack
 import json
 import getopt
 
@@ -207,7 +208,7 @@ if iswindows:
             Original Version
             Copyright (c) 2002 by Paul A. Lambert
             Under:
-            CryptoPy Artisitic License Version 1.0
+            CryptoPy Artistic License Version 1.0
             See the wonderful pure python package cryptopy-1.2.5
             and read its LICENSE.txt for complete license details.
         """
@@ -1050,7 +1051,7 @@ if iswindows:
         DB = {}
         with open(kInfoFile, 'rb') as infoReader:
             data = infoReader.read()
-        # assume newest .kinf2011 style .kinf file
+        # assume .kinf2011 or .kinf2018 style .kinf file
         # the .kinf file uses "/" to separate it into records
         # so remove the trailing "/" to make it easy to use split
         data = data[:-1]
@@ -1064,8 +1065,17 @@ if iswindows:
         # now extract the pieces that form the added entropy
         pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
         for m in re.finditer(pattern, cleartext):
-            added_entropy = m.group(2) + m.group(4)
-
+            version = int(m.group(1))
+            build = m.group(2)
+            guid = m.group(4)
+
+        if version == 5:  # .kinf2011
+            added_entropy = build + guid
+        elif version == 6:  # .kinf2018
+            salt = str(0x6d8 * int(build)) + guid
+            sp = GetUserName() + '+@#$%+' + GetIDString()
+            passwd = encode(SHA256(sp), charMap5)
+            key = KeyIVGen().pbkdf2(passwd, salt, 10000, 0x400)[:32]  # this is very slow
 
         # loop through the item records until all are processed
         while len(items) > 0:
@@ -1077,10 +1087,6 @@ if iswindows:
             # is the MD5 hash of the key name encoded by charMap5
             keyhash = item[0:32]
 
-            # the sha1 of raw keyhash string is used to create entropy along
-            # with the added entropy provided above from the headerblob
-            entropy = SHA1(keyhash) + added_entropy
-
             # the remainder of the first record when decoded with charMap5
             # has the ':' split char followed by the string representation
             # of the number of records that follow
@@ -1128,11 +1134,29 @@ if iswindows:
             encdata = encdata + pfx
             #print "rearranged data:",encdata
 
+            if version == 5:
+                # decode using new testMap8 to get the original CryptProtect Data
+                encryptedValue = decode(encdata,testMap8)
+                #print "decoded data:",encryptedValue.encode('hex')
+                entropy = SHA1(keyhash) + added_entropy
+                cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
+            elif version == 6:
+                from Crypto.Cipher import AES
+                from Crypto.Util import Counter
+                # decode using new testMap8 to get IV + ciphertext
+                iv_ciphertext = decode(encdata, testMap8)
+                # pad IV so that we can substitute AES-CTR for GCM
+                iv = iv_ciphertext[:12] + b'\x00\x00\x00\x02'
+                ciphertext = iv_ciphertext[12:]
+                # convert IV to int for use with pycrypto
+                iv_ints = unpack('>QQ', iv)
+                iv = iv_ints[0] << 64 | iv_ints[1]
+                # set up AES-CTR
+                ctr = Counter.new(128, initial_value=iv)
+                cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
+                # decrypt and decode
+                cleartext = decode(cipher.decrypt(ciphertext), charMap5)
 
-            # decode using new testMap8 to get the original CryptProtect Data
-            encryptedValue = decode(encdata,testMap8)
-            #print "decoded data:",encryptedValue.encode('hex')
-            cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
             if len(cleartext)>0:
                 #print "cleartext data:",cleartext,":end data"
                 DB[keyname] = cleartext