mirror of
https://github.com/apprenticeharper/DeDRM_tools
synced 2024-12-28 09:58:56 +01:00
tools v4.5
This commit is contained in:
parent
4f34a9a196
commit
297a9ddc66
25 changed files with 1277 additions and 453 deletions
|
@ -19,7 +19,7 @@ class K4DeDRM(FileTypePlugin):
|
|||
description = 'Removes DRM from K4PC and Mac, Kindle Mobi and Topaz files. Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
|
||||
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
|
||||
author = 'DiapDealer, SomeUpdates' # The author of this plugin
|
||||
version = (0, 3, 1) # The version number of this plugin
|
||||
version = (0, 3, 5) # The version number of this plugin
|
||||
file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to
|
||||
on_import = True # Run this plugin during the import
|
||||
priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
|
||||
|
|
|
@ -17,7 +17,7 @@ from __future__ import with_statement
|
|||
# and many many others
|
||||
|
||||
|
||||
__version__ = '3.1'
|
||||
__version__ = '3.5'
|
||||
|
||||
class Unbuffered:
|
||||
def __init__(self, stream):
|
||||
|
|
|
@ -22,16 +22,16 @@ else:
|
|||
|
||||
if inCalibre:
|
||||
if sys.platform.startswith('win'):
|
||||
from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber
|
||||
from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
|
||||
|
||||
if sys.platform.startswith('darwin'):
|
||||
from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber
|
||||
from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
|
||||
else:
|
||||
if sys.platform.startswith('win'):
|
||||
from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber
|
||||
from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
|
||||
|
||||
if sys.platform.startswith('darwin'):
|
||||
from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber
|
||||
from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
|
||||
|
||||
|
||||
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||
|
@ -218,14 +218,14 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
|
|||
print "Keys not found in " + kInfoFile
|
||||
return pidlst
|
||||
|
||||
# Get the HDD serial
|
||||
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
|
||||
# Get the ID string used
|
||||
encodedIDString = encodeHash(GetIDString(),charMap1)
|
||||
|
||||
# Get the current user name
|
||||
encodedUsername = encodeHash(GetUserName(),charMap1)
|
||||
|
||||
# concat, hash and encode to calculate the DSN
|
||||
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
|
||||
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
|
||||
|
||||
# Compute the device PID (for which I can tell, is used for nothing).
|
||||
table = generatePidEncryptionTable()
|
||||
|
|
|
@ -8,7 +8,7 @@ This plugin is meant to decrypt Adobe Digital Edition PDFs that are protected wi
|
|||
|
||||
Installation:
|
||||
|
||||
Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file (ineptpdf_vXX_plugin.zip) and click the 'Add' button. you're done.
|
||||
Go to Calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ineptpdf_vXX_plugin.zip) and click the 'Add' button. you're done.
|
||||
|
||||
Please note: Calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added.
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ This plugin supersedes MobiDeDRM, K4DeDRM, and K4PCDeDRM and K4X plugins. If yo
|
|||
This plugin is meant to remove the DRM from .prc, .azw, .azw1, and .tpz ebooks. Calibre can then convert them to whatever format you desire. It is meant to function without having to install any dependencies except for Calibre being on your same machine and in the same account as your "Kindle for PC" or "Kindle for Mac" application if you are going to remove the DRM from those types of books.
|
||||
|
||||
Installation:
|
||||
Go to Calibre's Preferences page... click on the Plugins button. Click on the "Add a new plugin" button at the bottom of the screen. Use the file dialog button to select the plugin's zip file (K4MobiDeDRM_vXX_plugin.zip) and click the "Add" (or it may say "Open" button. Then click on the "Yes" button in the warning dialog that appears. A Confirmation dialog appears that says the plugin has been installed.
|
||||
Go to Calibre's Preferences page. Do **NOT** select "Get Plugins to enhance calibre" as this is reserved for official calibre plugins", instead select "Change calibre behavior". Under "Advanced" click on the on the Plugins button. Click on the "Load plugin from file" button at the bottom of the screen. Use the file dialog button to select the plugin's zip file (K4MobiDeDRM_vXX_plugin.zip) and click the "Add" (or it may say "Open" button. Then click on the "Yes" button in the warning dialog that appears. A Confirmation dialog appears that says the plugin has been installed.
|
||||
|
||||
|
||||
Configuration:
|
||||
|
|
|
@ -5,7 +5,7 @@ All credit given to The Dark Reverser for the original standalone script. I had
|
|||
This plugin is meant to convert secure Ereader files (PDB) to unsecured PMLZ files. Calibre can then convert it to whatever format you desire. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. I've included the psyco libraries (compiled for each platform) for speed. If your system can use them, great! Otherwise, they won't be used and things will just work slower.
|
||||
|
||||
Installation:
|
||||
Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file (eReaderPDB2PML_vXX_plugin.zip) and click the 'Add' button. You're done.
|
||||
Go to Calibre's Preferences page. Do **NOT** select "Get Plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (eReaderPDB2PML_vXX_plugin.zip) and click the 'Add' button. You're done.
|
||||
|
||||
Please note: Calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added.
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ with Adobe's Adept encryption. It is meant to function without having to install
|
|||
|
||||
Installation:
|
||||
|
||||
Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file (ignobleepub_vXX_plugin.zip) and
|
||||
Go to Calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ignobleepub_vXX_plugin.zip) and
|
||||
click the 'Add' button. you're done.
|
||||
|
||||
Please note: Calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added.
|
||||
|
|
|
@ -8,7 +8,7 @@ This plugin is meant to decrypt Adobe Digital Edition Epubs that are protected w
|
|||
|
||||
Installation:
|
||||
|
||||
Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file (ineptepub_vXX_plugin.zip) and click the 'Add' button. you're done.
|
||||
Go to Calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Cahnge calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ineptepub_vXX_plugin.zip) and click the 'Add' button. you're done.
|
||||
|
||||
Please note: Calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added.
|
||||
|
||||
|
|
Binary file not shown.
|
@ -1,11 +1,12 @@
|
|||
# standlone set of Mac OSX specific routines needed for K4DeDRM
|
||||
# standlone set of Mac OSX specific routines needed for KindleBooks
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import os.path
|
||||
|
||||
import subprocess
|
||||
from struct import pack, unpack, unpack_from
|
||||
|
||||
class DrmException(Exception):
|
||||
|
@ -68,11 +69,9 @@ def _load_crypto_libcrypto():
|
|||
raise DrmException('AES decryption failed')
|
||||
return out.raw
|
||||
|
||||
def keyivgen(self, passwd, salt):
|
||||
def keyivgen(self, passwd, salt, iter, keylen):
|
||||
saltlen = len(salt)
|
||||
passlen = len(passwd)
|
||||
iter = 0x3e8
|
||||
keylen = 80
|
||||
out = create_string_buffer(keylen)
|
||||
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
|
||||
return out.raw
|
||||
|
@ -114,9 +113,10 @@ def SHA256(message):
|
|||
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
|
||||
|
||||
# For Future Reference from .kinf approach of K4PC
|
||||
charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
|
||||
|
||||
# For kinf approach of K4PC/K4Mac
|
||||
# On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
|
||||
# For Mac they seem to re-use charMap2 here
|
||||
charMap5 = charMap2
|
||||
|
||||
def encode(data, map):
|
||||
result = ""
|
||||
|
@ -144,7 +144,7 @@ def decode(data,map):
|
|||
result += pack("B",value)
|
||||
return result
|
||||
|
||||
# For Future Reference from .kinf approach of K4PC
|
||||
# For .kinf approach of K4PC and now K4Mac
|
||||
# generate table of prime number less than or equal to int n
|
||||
def primes(n):
|
||||
if n==2: return [2]
|
||||
|
@ -166,7 +166,6 @@ def primes(n):
|
|||
return [2]+[x for x in s if x]
|
||||
|
||||
|
||||
|
||||
# uses a sub process to get the Hard Drive Serial Number using ioreg
|
||||
# returns with the serial number of drive whose BSD Name is "disk0"
|
||||
def GetVolumeSerialNumber():
|
||||
|
@ -196,24 +195,217 @@ def GetVolumeSerialNumber():
|
|||
foundIt = True
|
||||
break
|
||||
if not foundIt:
|
||||
sernum = '9999999999'
|
||||
sernum = ''
|
||||
return sernum
|
||||
|
||||
def GetUserHomeAppSupKindleDirParitionName():
|
||||
home = os.getenv('HOME')
|
||||
dpath = home + '/Library/Application Support/Kindle'
|
||||
cmdline = '/sbin/mount'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p.communicate()
|
||||
reslst = out1.split('\n')
|
||||
cnt = len(reslst)
|
||||
disk = ''
|
||||
foundIt = False
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
if resline.startswith('/dev'):
|
||||
(devpart, mpath) = resline.split(' on ')
|
||||
dpart = devpart[5:]
|
||||
pp = mpath.find('(')
|
||||
if pp >= 0:
|
||||
mpath = mpath[:pp-1]
|
||||
if dpath.startswith(mpath):
|
||||
disk = dpart
|
||||
return disk
|
||||
|
||||
# uses a sub process to get the UUID of the specified disk partition using ioreg
|
||||
def GetDiskPartitionUUID(diskpart):
|
||||
uuidnum = os.getenv('MYUUIDNUMBER')
|
||||
if uuidnum != None:
|
||||
return uuidnum
|
||||
cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p.communicate()
|
||||
reslst = out1.split('\n')
|
||||
cnt = len(reslst)
|
||||
bsdname = None
|
||||
uuidnum = None
|
||||
foundIt = False
|
||||
nest = 0
|
||||
uuidnest = -1
|
||||
partnest = -2
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
if resline.find('{') >= 0:
|
||||
nest += 1
|
||||
if resline.find('}') >= 0:
|
||||
nest -= 1
|
||||
pp = resline.find('"UUID" = "')
|
||||
if pp >= 0:
|
||||
uuidnum = resline[pp+10:-1]
|
||||
uuidnum = uuidnum.strip()
|
||||
uuidnest = nest
|
||||
if partnest == uuidnest and uuidnest > 0:
|
||||
foundIt = True
|
||||
break
|
||||
bb = resline.find('"BSD Name" = "')
|
||||
if bb >= 0:
|
||||
bsdname = resline[bb+14:-1]
|
||||
bsdname = bsdname.strip()
|
||||
if (bsdname == diskpart):
|
||||
partnest = nest
|
||||
else :
|
||||
partnest = -2
|
||||
if partnest == uuidnest and partnest > 0:
|
||||
foundIt = True
|
||||
break
|
||||
if nest == 0:
|
||||
partnest = -2
|
||||
uuidnest = -1
|
||||
uuidnum = None
|
||||
bsdname = None
|
||||
if not foundIt:
|
||||
uuidnum = ''
|
||||
return uuidnum
|
||||
|
||||
def GetMACAddressMunged():
|
||||
macnum = os.getenv('MYMACNUM')
|
||||
if macnum != None:
|
||||
return macnum
|
||||
cmdline = '/sbin/ifconfig en0'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p.communicate()
|
||||
reslst = out1.split('\n')
|
||||
cnt = len(reslst)
|
||||
macnum = None
|
||||
foundIt = False
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
pp = resline.find('ether ')
|
||||
if pp >= 0:
|
||||
macnum = resline[pp+6:-1]
|
||||
macnum = macnum.strip()
|
||||
# print "original mac", macnum
|
||||
# now munge it up the way Kindle app does
|
||||
# by xoring it with 0xa5 and swapping elements 3 and 4
|
||||
maclst = macnum.split(':')
|
||||
n = len(maclst)
|
||||
if n != 6:
|
||||
fountIt = False
|
||||
break
|
||||
for i in range(6):
|
||||
maclst[i] = int('0x' + maclst[i], 0)
|
||||
mlst = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
mlst[5] = maclst[5] ^ 0xa5
|
||||
mlst[4] = maclst[3] ^ 0xa5
|
||||
mlst[3] = maclst[4] ^ 0xa5
|
||||
mlst[2] = maclst[2] ^ 0xa5
|
||||
mlst[1] = maclst[1] ^ 0xa5
|
||||
mlst[0] = maclst[0] ^ 0xa5
|
||||
macnum = "%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x" % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
|
||||
foundIt = True
|
||||
break
|
||||
if not foundIt:
|
||||
macnum = ''
|
||||
return macnum
|
||||
|
||||
|
||||
# uses unix env to get username instead of using sysctlbyname
|
||||
def GetUserName():
|
||||
username = os.getenv('USER')
|
||||
return username
|
||||
|
||||
|
||||
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
||||
def CryptUnprotectData(encryptedData, salt):
|
||||
sp = GetVolumeSerialNumber() + '!@#' + GetUserName()
|
||||
# used by Kindle for Mac versions < 1.6.0
|
||||
def CryptUnprotectData(encryptedData):
|
||||
sernum = GetVolumeSerialNumber()
|
||||
if sernum == '':
|
||||
sernum = '9999999999'
|
||||
sp = sernum + '!@#' + GetUserName()
|
||||
passwdData = encode(SHA256(sp),charMap1)
|
||||
salt = '16743'
|
||||
iter = 0x3e8
|
||||
keylen = 0x80
|
||||
crp = LibCrypto()
|
||||
key_iv = crp.keyivgen(passwdData, salt)
|
||||
key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
|
||||
key = key_iv[0:32]
|
||||
iv = key_iv[32:48]
|
||||
crp.set_decrypt_key(key,iv)
|
||||
cleartext = crp.decrypt(encryptedData)
|
||||
cleartext = decode(cleartext,charMap1)
|
||||
return cleartext
|
||||
|
||||
|
||||
def isNewInstall():
|
||||
home = os.getenv('HOME')
|
||||
# soccer game fan anyone
|
||||
dpath = home + '/Library/Application Support/Kindle/storage/.pes2011'
|
||||
# print dpath, os.path.exists(dpath)
|
||||
if os.path.exists(dpath):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def GetIDString():
|
||||
# K4Mac now has an extensive set of ids strings it uses
|
||||
# in encoding pids and in creating unique passwords
|
||||
# for use in its own version of CryptUnprotectDataV2
|
||||
|
||||
# BUT Amazon has now become nasty enough to detect when its app
|
||||
# is being run under a debugger and actually changes code paths
|
||||
# including which one of these strings is chosen, all to try
|
||||
# to prevent reverse engineering
|
||||
|
||||
# Sad really ... they will only hurt their own sales ...
|
||||
# true book lovers really want to keep their books forever
|
||||
# and move them to their devices and DRM prevents that so they
|
||||
# will just buy from someplace else that they can remove
|
||||
# the DRM from
|
||||
|
||||
# Amazon should know by now that true book lover's are not like
|
||||
# penniless kids that pirate music, we do not pirate books
|
||||
|
||||
if isNewInstall():
|
||||
mungedmac = GetMACAddressMunged()
|
||||
if len(mungedmac) > 7:
|
||||
return mungedmac
|
||||
sernum = GetVolumeSerialNumber()
|
||||
if len(sernum) > 7:
|
||||
return sernum
|
||||
diskpart = GetUserHomeAppSupKindleDirParitionName()
|
||||
uuidnum = GetDiskPartitionUUID(diskpart)
|
||||
if len(uuidnum) > 7:
|
||||
return uuidnum
|
||||
mungedmac = GetMACAddressMunged()
|
||||
if len(mungedmac) > 7:
|
||||
return mungedmac
|
||||
return '9999999999'
|
||||
|
||||
|
||||
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
||||
# used for Kindle for Mac Versions >= 1.6.0
|
||||
def CryptUnprotectDataV2(encryptedData):
|
||||
sp = GetUserName() + ':&%:' + GetIDString()
|
||||
passwdData = encode(SHA256(sp),charMap5)
|
||||
# salt generation as per the code
|
||||
salt = 0x0512981d * 2 * 1 * 1
|
||||
salt = str(salt) + GetUserName()
|
||||
salt = encode(salt,charMap5)
|
||||
crp = LibCrypto()
|
||||
iter = 0x800
|
||||
keylen = 0x400
|
||||
key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
|
||||
key = key_iv[0:32]
|
||||
iv = key_iv[32:48]
|
||||
crp.set_decrypt_key(key,iv)
|
||||
cleartext = crp.decrypt(encryptedData)
|
||||
cleartext = decode(cleartext, charMap5)
|
||||
return cleartext
|
||||
|
||||
|
||||
|
@ -232,18 +424,16 @@ def getKindleInfoFiles(kInfoFiles):
|
|||
if os.path.isfile(resline):
|
||||
kInfoFiles.append(resline)
|
||||
found = True
|
||||
# For Future Reference
|
||||
#
|
||||
# # add any .kinf files
|
||||
# cmdline = 'find "' + home + '/Library/Application Support" -name "rainier*.kinf"'
|
||||
# cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
# p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
# out1, out2 = p1.communicate()
|
||||
# reslst = out1.split('\n')
|
||||
# for resline in reslst:
|
||||
# if os.path.isfile(resline):
|
||||
# kInfoFiles.append(resline)
|
||||
# found = True
|
||||
# add any .kinf files
|
||||
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p1.communicate()
|
||||
reslst = out1.split('\n')
|
||||
for resline in reslst:
|
||||
if os.path.isfile(resline):
|
||||
kInfoFiles.append(resline)
|
||||
found = True
|
||||
if not found:
|
||||
print('No kindle-info files have been found.')
|
||||
return kInfoFiles
|
||||
|
@ -251,7 +441,7 @@ def getKindleInfoFiles(kInfoFiles):
|
|||
# determine type of kindle info provided and return a
|
||||
# database of keynames and values
|
||||
def getDBfromFile(kInfoFile):
|
||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
|
||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
|
||||
DB = {}
|
||||
cnt = 0
|
||||
infoReader = open(kInfoFile, 'r')
|
||||
|
@ -259,7 +449,6 @@ def getDBfromFile(kInfoFile):
|
|||
data = infoReader.read()
|
||||
|
||||
if data.find('[') != -1 :
|
||||
|
||||
# older style kindle-info file
|
||||
items = data.split('[')
|
||||
for item in items:
|
||||
|
@ -273,84 +462,89 @@ def getDBfromFile(kInfoFile):
|
|||
if keyname == "unknown":
|
||||
keyname = keyhash
|
||||
encryptedValue = decode(rawdata,charMap2)
|
||||
salt = '16743'
|
||||
cleartext = CryptUnprotectData(encryptedValue, salt)
|
||||
DB[keyname] = decode(cleartext,charMap1)
|
||||
cleartext = CryptUnprotectData(encryptedValue)
|
||||
DB[keyname] = cleartext
|
||||
cnt = cnt + 1
|
||||
if cnt == 0:
|
||||
DB = None
|
||||
return DB
|
||||
|
||||
# For Future Reference taken from K4PC 1.5.0 .kinf
|
||||
#
|
||||
# # else newer 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]
|
||||
# items = data.split('/')
|
||||
#
|
||||
# # loop through the item records until all are processed
|
||||
# while len(items) > 0:
|
||||
#
|
||||
# # get the first item record
|
||||
# item = items.pop(0)
|
||||
#
|
||||
# # the first 32 chars of the first record of a group
|
||||
# # is the MD5 hash of the key name encoded by charMap5
|
||||
# keyhash = item[0:32]
|
||||
#
|
||||
# # the raw keyhash string is also used to create entropy for the actual
|
||||
# # CryptProtectData Blob that represents that keys contents
|
||||
# entropy = SHA1(keyhash)
|
||||
#
|
||||
# # 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
|
||||
# # and make up the contents
|
||||
# srcnt = decode(item[34:],charMap5)
|
||||
# rcnt = int(srcnt)
|
||||
#
|
||||
# # read and store in rcnt records of data
|
||||
# # that make up the contents value
|
||||
# edlst = []
|
||||
# for i in xrange(rcnt):
|
||||
# item = items.pop(0)
|
||||
# edlst.append(item)
|
||||
#
|
||||
# keyname = "unknown"
|
||||
# for name in names:
|
||||
# if encodeHash(name,charMap5) == keyhash:
|
||||
# keyname = name
|
||||
# break
|
||||
# if keyname == "unknown":
|
||||
# keyname = keyhash
|
||||
#
|
||||
# # the charMap5 encoded contents data has had a length
|
||||
# # of chars (always odd) cut off of the front and moved
|
||||
# # to the end to prevent decoding using charMap5 from
|
||||
# # working properly, and thereby preventing the ensuing
|
||||
# # CryptUnprotectData call from succeeding.
|
||||
#
|
||||
# # The offset into the charMap5 encoded contents seems to be:
|
||||
# # len(contents) - largest prime number less than or equal to int(len(content)/3)
|
||||
# # (in other words split "about" 2/3rds of the way through)
|
||||
#
|
||||
# # move first offsets chars to end to align for decode by charMap5
|
||||
# encdata = "".join(edlst)
|
||||
# contlen = len(encdata)
|
||||
# noffset = contlen - primes(int(contlen/3))[-1]
|
||||
#
|
||||
# # now properly split and recombine
|
||||
# # by moving noffset chars from the start of the
|
||||
# # string to the end of the string
|
||||
# pfx = encdata[0:noffset]
|
||||
# encdata = encdata[noffset:]
|
||||
# encdata = encdata + pfx
|
||||
#
|
||||
# # decode using Map5 to get the CryptProtect Data
|
||||
# encryptedValue = decode(encdata,charMap5)
|
||||
# DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1)
|
||||
# cnt = cnt + 1
|
||||
# else newer style .kinf file used by K4Mac >= 1.6.0
|
||||
# the .kinf file uses "/" to separate it into records
|
||||
# so remove the trailing "/" to make it easy to use split
|
||||
data = data[:-1]
|
||||
items = data.split('/')
|
||||
|
||||
# loop through the item records until all are processed
|
||||
while len(items) > 0:
|
||||
|
||||
# get the first item record
|
||||
item = items.pop(0)
|
||||
|
||||
# the first 32 chars of the first record of a group
|
||||
# is the MD5 hash of the key name encoded by charMap5
|
||||
keyhash = item[0:32]
|
||||
keyname = "unknown"
|
||||
|
||||
# the raw keyhash string is also used to create entropy for the actual
|
||||
# CryptProtectData Blob that represents that keys contents
|
||||
# "entropy" not used for K4Mac only K4PC
|
||||
# entropy = SHA1(keyhash)
|
||||
|
||||
# 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
|
||||
# and make up the contents
|
||||
srcnt = decode(item[34:],charMap5)
|
||||
rcnt = int(srcnt)
|
||||
|
||||
# read and store in rcnt records of data
|
||||
# that make up the contents value
|
||||
edlst = []
|
||||
for i in xrange(rcnt):
|
||||
item = items.pop(0)
|
||||
edlst.append(item)
|
||||
|
||||
keyname = "unknown"
|
||||
for name in names:
|
||||
if encodeHash(name,charMap5) == keyhash:
|
||||
keyname = name
|
||||
break
|
||||
if keyname == "unknown":
|
||||
keyname = keyhash
|
||||
|
||||
# the charMap5 encoded contents data has had a length
|
||||
# of chars (always odd) cut off of the front and moved
|
||||
# to the end to prevent decoding using charMap5 from
|
||||
# working properly, and thereby preventing the ensuing
|
||||
# CryptUnprotectData call from succeeding.
|
||||
|
||||
# The offset into the charMap5 encoded contents seems to be:
|
||||
# len(contents) - largest prime number less than or equal to int(len(content)/3)
|
||||
# (in other words split "about" 2/3rds of the way through)
|
||||
|
||||
# move first offsets chars to end to align for decode by charMap5
|
||||
encdata = "".join(edlst)
|
||||
contlen = len(encdata)
|
||||
|
||||
# now properly split and recombine
|
||||
# by moving noffset chars from the start of the
|
||||
# string to the end of the string
|
||||
noffset = contlen - primes(int(contlen/3))[-1]
|
||||
pfx = encdata[0:noffset]
|
||||
encdata = encdata[noffset:]
|
||||
encdata = encdata + pfx
|
||||
|
||||
# decode using charMap5 to get the CryptProtect Data
|
||||
encryptedValue = decode(encdata,charMap5)
|
||||
cleartext = CryptUnprotectDataV2(encryptedValue)
|
||||
# Debugging
|
||||
# print keyname
|
||||
# print cleartext
|
||||
# print cleartext.encode('hex')
|
||||
# print
|
||||
DB[keyname] = cleartext
|
||||
cnt = cnt + 1
|
||||
|
||||
if cnt == 0:
|
||||
DB = None
|
||||
|
|
|
@ -122,6 +122,9 @@ def GetVolumeSerialNumber():
|
|||
return GetVolumeSerialNumber
|
||||
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
||||
|
||||
def GetIDString():
|
||||
return GetVolumeSerialNumber()
|
||||
|
||||
def getLastError():
|
||||
GetLastError = kernel32.GetLastError
|
||||
GetLastError.argtypes = None
|
||||
|
@ -181,18 +184,27 @@ def getKindleInfoFiles(kInfoFiles):
|
|||
kInfoFiles.append(kinfopath)
|
||||
|
||||
# now look for newer (K4PC 1.5.0 and later rainier.2.1.1.kinf file
|
||||
|
||||
kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
|
||||
if not os.path.isfile(kinfopath):
|
||||
print('No .kinf files have not been found.')
|
||||
print('No K4PC 1.5.X .kinf files have not been found.')
|
||||
else:
|
||||
kInfoFiles.append(kinfopath)
|
||||
|
||||
# now look for even newer (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
|
||||
kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf'
|
||||
if not os.path.isfile(kinfopath):
|
||||
print('No K4PC 1.6.X .kinf files have not been found.')
|
||||
else:
|
||||
kInfoFiles.append(kinfopath)
|
||||
|
||||
return kInfoFiles
|
||||
|
||||
|
||||
# determine type of kindle info provided and return a
|
||||
# database of keynames and values
|
||||
def getDBfromFile(kInfoFile):
|
||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
|
||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
|
||||
DB = {}
|
||||
cnt = 0
|
||||
infoReader = open(kInfoFile, 'r')
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<key>CFBundleExecutable</key>
|
||||
<string>droplet</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>DeDRM 2.7, Written 2010–2011 by Apprentice Alf and others.</string>
|
||||
<string>DeDRM 2.8, Written 2010–2011 by Apprentice Alf and others.</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>droplet</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
|
@ -34,7 +34,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.7</string>
|
||||
<string>2.8</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>dplt</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
|
|
@ -17,7 +17,7 @@ from __future__ import with_statement
|
|||
# and many many others
|
||||
|
||||
|
||||
__version__ = '3.1'
|
||||
__version__ = '3.5'
|
||||
|
||||
class Unbuffered:
|
||||
def __init__(self, stream):
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
# standlone set of Mac OSX specific routines needed for K4DeDRM
|
||||
# standlone set of Mac OSX specific routines needed for KindleBooks
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import os.path
|
||||
|
||||
import subprocess
|
||||
from struct import pack, unpack, unpack_from
|
||||
|
||||
class DrmException(Exception):
|
||||
|
@ -68,11 +69,9 @@ def _load_crypto_libcrypto():
|
|||
raise DrmException('AES decryption failed')
|
||||
return out.raw
|
||||
|
||||
def keyivgen(self, passwd, salt):
|
||||
def keyivgen(self, passwd, salt, iter, keylen):
|
||||
saltlen = len(salt)
|
||||
passlen = len(passwd)
|
||||
iter = 0x3e8
|
||||
keylen = 80
|
||||
out = create_string_buffer(keylen)
|
||||
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
|
||||
return out.raw
|
||||
|
@ -114,9 +113,10 @@ def SHA256(message):
|
|||
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
|
||||
|
||||
# For Future Reference from .kinf approach of K4PC
|
||||
charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
|
||||
|
||||
# For kinf approach of K4PC/K4Mac
|
||||
# On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
|
||||
# For Mac they seem to re-use charMap2 here
|
||||
charMap5 = charMap2
|
||||
|
||||
def encode(data, map):
|
||||
result = ""
|
||||
|
@ -144,7 +144,7 @@ def decode(data,map):
|
|||
result += pack("B",value)
|
||||
return result
|
||||
|
||||
# For Future Reference from .kinf approach of K4PC
|
||||
# For .kinf approach of K4PC and now K4Mac
|
||||
# generate table of prime number less than or equal to int n
|
||||
def primes(n):
|
||||
if n==2: return [2]
|
||||
|
@ -166,7 +166,6 @@ def primes(n):
|
|||
return [2]+[x for x in s if x]
|
||||
|
||||
|
||||
|
||||
# uses a sub process to get the Hard Drive Serial Number using ioreg
|
||||
# returns with the serial number of drive whose BSD Name is "disk0"
|
||||
def GetVolumeSerialNumber():
|
||||
|
@ -196,24 +195,217 @@ def GetVolumeSerialNumber():
|
|||
foundIt = True
|
||||
break
|
||||
if not foundIt:
|
||||
sernum = '9999999999'
|
||||
sernum = ''
|
||||
return sernum
|
||||
|
||||
def GetUserHomeAppSupKindleDirParitionName():
|
||||
home = os.getenv('HOME')
|
||||
dpath = home + '/Library/Application Support/Kindle'
|
||||
cmdline = '/sbin/mount'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p.communicate()
|
||||
reslst = out1.split('\n')
|
||||
cnt = len(reslst)
|
||||
disk = ''
|
||||
foundIt = False
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
if resline.startswith('/dev'):
|
||||
(devpart, mpath) = resline.split(' on ')
|
||||
dpart = devpart[5:]
|
||||
pp = mpath.find('(')
|
||||
if pp >= 0:
|
||||
mpath = mpath[:pp-1]
|
||||
if dpath.startswith(mpath):
|
||||
disk = dpart
|
||||
return disk
|
||||
|
||||
# uses a sub process to get the UUID of the specified disk partition using ioreg
|
||||
def GetDiskPartitionUUID(diskpart):
|
||||
uuidnum = os.getenv('MYUUIDNUMBER')
|
||||
if uuidnum != None:
|
||||
return uuidnum
|
||||
cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p.communicate()
|
||||
reslst = out1.split('\n')
|
||||
cnt = len(reslst)
|
||||
bsdname = None
|
||||
uuidnum = None
|
||||
foundIt = False
|
||||
nest = 0
|
||||
uuidnest = -1
|
||||
partnest = -2
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
if resline.find('{') >= 0:
|
||||
nest += 1
|
||||
if resline.find('}') >= 0:
|
||||
nest -= 1
|
||||
pp = resline.find('"UUID" = "')
|
||||
if pp >= 0:
|
||||
uuidnum = resline[pp+10:-1]
|
||||
uuidnum = uuidnum.strip()
|
||||
uuidnest = nest
|
||||
if partnest == uuidnest and uuidnest > 0:
|
||||
foundIt = True
|
||||
break
|
||||
bb = resline.find('"BSD Name" = "')
|
||||
if bb >= 0:
|
||||
bsdname = resline[bb+14:-1]
|
||||
bsdname = bsdname.strip()
|
||||
if (bsdname == diskpart):
|
||||
partnest = nest
|
||||
else :
|
||||
partnest = -2
|
||||
if partnest == uuidnest and partnest > 0:
|
||||
foundIt = True
|
||||
break
|
||||
if nest == 0:
|
||||
partnest = -2
|
||||
uuidnest = -1
|
||||
uuidnum = None
|
||||
bsdname = None
|
||||
if not foundIt:
|
||||
uuidnum = ''
|
||||
return uuidnum
|
||||
|
||||
def GetMACAddressMunged():
|
||||
macnum = os.getenv('MYMACNUM')
|
||||
if macnum != None:
|
||||
return macnum
|
||||
cmdline = '/sbin/ifconfig en0'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p.communicate()
|
||||
reslst = out1.split('\n')
|
||||
cnt = len(reslst)
|
||||
macnum = None
|
||||
foundIt = False
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
pp = resline.find('ether ')
|
||||
if pp >= 0:
|
||||
macnum = resline[pp+6:-1]
|
||||
macnum = macnum.strip()
|
||||
# print "original mac", macnum
|
||||
# now munge it up the way Kindle app does
|
||||
# by xoring it with 0xa5 and swapping elements 3 and 4
|
||||
maclst = macnum.split(':')
|
||||
n = len(maclst)
|
||||
if n != 6:
|
||||
fountIt = False
|
||||
break
|
||||
for i in range(6):
|
||||
maclst[i] = int('0x' + maclst[i], 0)
|
||||
mlst = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
mlst[5] = maclst[5] ^ 0xa5
|
||||
mlst[4] = maclst[3] ^ 0xa5
|
||||
mlst[3] = maclst[4] ^ 0xa5
|
||||
mlst[2] = maclst[2] ^ 0xa5
|
||||
mlst[1] = maclst[1] ^ 0xa5
|
||||
mlst[0] = maclst[0] ^ 0xa5
|
||||
macnum = "%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x" % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
|
||||
foundIt = True
|
||||
break
|
||||
if not foundIt:
|
||||
macnum = ''
|
||||
return macnum
|
||||
|
||||
|
||||
# uses unix env to get username instead of using sysctlbyname
|
||||
def GetUserName():
|
||||
username = os.getenv('USER')
|
||||
return username
|
||||
|
||||
|
||||
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
||||
def CryptUnprotectData(encryptedData, salt):
|
||||
sp = GetVolumeSerialNumber() + '!@#' + GetUserName()
|
||||
# used by Kindle for Mac versions < 1.6.0
|
||||
def CryptUnprotectData(encryptedData):
|
||||
sernum = GetVolumeSerialNumber()
|
||||
if sernum == '':
|
||||
sernum = '9999999999'
|
||||
sp = sernum + '!@#' + GetUserName()
|
||||
passwdData = encode(SHA256(sp),charMap1)
|
||||
salt = '16743'
|
||||
iter = 0x3e8
|
||||
keylen = 0x80
|
||||
crp = LibCrypto()
|
||||
key_iv = crp.keyivgen(passwdData, salt)
|
||||
key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
|
||||
key = key_iv[0:32]
|
||||
iv = key_iv[32:48]
|
||||
crp.set_decrypt_key(key,iv)
|
||||
cleartext = crp.decrypt(encryptedData)
|
||||
cleartext = decode(cleartext,charMap1)
|
||||
return cleartext
|
||||
|
||||
|
||||
def isNewInstall():
|
||||
home = os.getenv('HOME')
|
||||
# soccer game fan anyone
|
||||
dpath = home + '/Library/Application Support/Kindle/storage/.pes2011'
|
||||
# print dpath, os.path.exists(dpath)
|
||||
if os.path.exists(dpath):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def GetIDString():
|
||||
# K4Mac now has an extensive set of ids strings it uses
|
||||
# in encoding pids and in creating unique passwords
|
||||
# for use in its own version of CryptUnprotectDataV2
|
||||
|
||||
# BUT Amazon has now become nasty enough to detect when its app
|
||||
# is being run under a debugger and actually changes code paths
|
||||
# including which one of these strings is chosen, all to try
|
||||
# to prevent reverse engineering
|
||||
|
||||
# Sad really ... they will only hurt their own sales ...
|
||||
# true book lovers really want to keep their books forever
|
||||
# and move them to their devices and DRM prevents that so they
|
||||
# will just buy from someplace else that they can remove
|
||||
# the DRM from
|
||||
|
||||
# Amazon should know by now that true book lover's are not like
|
||||
# penniless kids that pirate music, we do not pirate books
|
||||
|
||||
if isNewInstall():
|
||||
mungedmac = GetMACAddressMunged()
|
||||
if len(mungedmac) > 7:
|
||||
return mungedmac
|
||||
sernum = GetVolumeSerialNumber()
|
||||
if len(sernum) > 7:
|
||||
return sernum
|
||||
diskpart = GetUserHomeAppSupKindleDirParitionName()
|
||||
uuidnum = GetDiskPartitionUUID(diskpart)
|
||||
if len(uuidnum) > 7:
|
||||
return uuidnum
|
||||
mungedmac = GetMACAddressMunged()
|
||||
if len(mungedmac) > 7:
|
||||
return mungedmac
|
||||
return '9999999999'
|
||||
|
||||
|
||||
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
||||
# used for Kindle for Mac Versions >= 1.6.0
|
||||
def CryptUnprotectDataV2(encryptedData):
|
||||
sp = GetUserName() + ':&%:' + GetIDString()
|
||||
passwdData = encode(SHA256(sp),charMap5)
|
||||
# salt generation as per the code
|
||||
salt = 0x0512981d * 2 * 1 * 1
|
||||
salt = str(salt) + GetUserName()
|
||||
salt = encode(salt,charMap5)
|
||||
crp = LibCrypto()
|
||||
iter = 0x800
|
||||
keylen = 0x400
|
||||
key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
|
||||
key = key_iv[0:32]
|
||||
iv = key_iv[32:48]
|
||||
crp.set_decrypt_key(key,iv)
|
||||
cleartext = crp.decrypt(encryptedData)
|
||||
cleartext = decode(cleartext, charMap5)
|
||||
return cleartext
|
||||
|
||||
|
||||
|
@ -232,18 +424,16 @@ def getKindleInfoFiles(kInfoFiles):
|
|||
if os.path.isfile(resline):
|
||||
kInfoFiles.append(resline)
|
||||
found = True
|
||||
# For Future Reference
|
||||
#
|
||||
# # add any .kinf files
|
||||
# cmdline = 'find "' + home + '/Library/Application Support" -name "rainier*.kinf"'
|
||||
# cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
# p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
# out1, out2 = p1.communicate()
|
||||
# reslst = out1.split('\n')
|
||||
# for resline in reslst:
|
||||
# if os.path.isfile(resline):
|
||||
# kInfoFiles.append(resline)
|
||||
# found = True
|
||||
# add any .kinf files
|
||||
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p1.communicate()
|
||||
reslst = out1.split('\n')
|
||||
for resline in reslst:
|
||||
if os.path.isfile(resline):
|
||||
kInfoFiles.append(resline)
|
||||
found = True
|
||||
if not found:
|
||||
print('No kindle-info files have been found.')
|
||||
return kInfoFiles
|
||||
|
@ -251,7 +441,7 @@ def getKindleInfoFiles(kInfoFiles):
|
|||
# determine type of kindle info provided and return a
|
||||
# database of keynames and values
|
||||
def getDBfromFile(kInfoFile):
|
||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
|
||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
|
||||
DB = {}
|
||||
cnt = 0
|
||||
infoReader = open(kInfoFile, 'r')
|
||||
|
@ -259,7 +449,6 @@ def getDBfromFile(kInfoFile):
|
|||
data = infoReader.read()
|
||||
|
||||
if data.find('[') != -1 :
|
||||
|
||||
# older style kindle-info file
|
||||
items = data.split('[')
|
||||
for item in items:
|
||||
|
@ -273,84 +462,89 @@ def getDBfromFile(kInfoFile):
|
|||
if keyname == "unknown":
|
||||
keyname = keyhash
|
||||
encryptedValue = decode(rawdata,charMap2)
|
||||
salt = '16743'
|
||||
cleartext = CryptUnprotectData(encryptedValue, salt)
|
||||
DB[keyname] = decode(cleartext,charMap1)
|
||||
cleartext = CryptUnprotectData(encryptedValue)
|
||||
DB[keyname] = cleartext
|
||||
cnt = cnt + 1
|
||||
if cnt == 0:
|
||||
DB = None
|
||||
return DB
|
||||
|
||||
# For Future Reference taken from K4PC 1.5.0 .kinf
|
||||
#
|
||||
# # else newer 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]
|
||||
# items = data.split('/')
|
||||
#
|
||||
# # loop through the item records until all are processed
|
||||
# while len(items) > 0:
|
||||
#
|
||||
# # get the first item record
|
||||
# item = items.pop(0)
|
||||
#
|
||||
# # the first 32 chars of the first record of a group
|
||||
# # is the MD5 hash of the key name encoded by charMap5
|
||||
# keyhash = item[0:32]
|
||||
#
|
||||
# # the raw keyhash string is also used to create entropy for the actual
|
||||
# # CryptProtectData Blob that represents that keys contents
|
||||
# entropy = SHA1(keyhash)
|
||||
#
|
||||
# # 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
|
||||
# # and make up the contents
|
||||
# srcnt = decode(item[34:],charMap5)
|
||||
# rcnt = int(srcnt)
|
||||
#
|
||||
# # read and store in rcnt records of data
|
||||
# # that make up the contents value
|
||||
# edlst = []
|
||||
# for i in xrange(rcnt):
|
||||
# item = items.pop(0)
|
||||
# edlst.append(item)
|
||||
#
|
||||
# keyname = "unknown"
|
||||
# for name in names:
|
||||
# if encodeHash(name,charMap5) == keyhash:
|
||||
# keyname = name
|
||||
# break
|
||||
# if keyname == "unknown":
|
||||
# keyname = keyhash
|
||||
#
|
||||
# # the charMap5 encoded contents data has had a length
|
||||
# # of chars (always odd) cut off of the front and moved
|
||||
# # to the end to prevent decoding using charMap5 from
|
||||
# # working properly, and thereby preventing the ensuing
|
||||
# # CryptUnprotectData call from succeeding.
|
||||
#
|
||||
# # The offset into the charMap5 encoded contents seems to be:
|
||||
# # len(contents) - largest prime number less than or equal to int(len(content)/3)
|
||||
# # (in other words split "about" 2/3rds of the way through)
|
||||
#
|
||||
# # move first offsets chars to end to align for decode by charMap5
|
||||
# encdata = "".join(edlst)
|
||||
# contlen = len(encdata)
|
||||
# noffset = contlen - primes(int(contlen/3))[-1]
|
||||
#
|
||||
# # now properly split and recombine
|
||||
# # by moving noffset chars from the start of the
|
||||
# # string to the end of the string
|
||||
# pfx = encdata[0:noffset]
|
||||
# encdata = encdata[noffset:]
|
||||
# encdata = encdata + pfx
|
||||
#
|
||||
# # decode using Map5 to get the CryptProtect Data
|
||||
# encryptedValue = decode(encdata,charMap5)
|
||||
# DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1)
|
||||
# cnt = cnt + 1
|
||||
# else newer style .kinf file used by K4Mac >= 1.6.0
|
||||
# the .kinf file uses "/" to separate it into records
|
||||
# so remove the trailing "/" to make it easy to use split
|
||||
data = data[:-1]
|
||||
items = data.split('/')
|
||||
|
||||
# loop through the item records until all are processed
|
||||
while len(items) > 0:
|
||||
|
||||
# get the first item record
|
||||
item = items.pop(0)
|
||||
|
||||
# the first 32 chars of the first record of a group
|
||||
# is the MD5 hash of the key name encoded by charMap5
|
||||
keyhash = item[0:32]
|
||||
keyname = "unknown"
|
||||
|
||||
# the raw keyhash string is also used to create entropy for the actual
|
||||
# CryptProtectData Blob that represents that keys contents
|
||||
# "entropy" not used for K4Mac only K4PC
|
||||
# entropy = SHA1(keyhash)
|
||||
|
||||
# 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
|
||||
# and make up the contents
|
||||
srcnt = decode(item[34:],charMap5)
|
||||
rcnt = int(srcnt)
|
||||
|
||||
# read and store in rcnt records of data
|
||||
# that make up the contents value
|
||||
edlst = []
|
||||
for i in xrange(rcnt):
|
||||
item = items.pop(0)
|
||||
edlst.append(item)
|
||||
|
||||
keyname = "unknown"
|
||||
for name in names:
|
||||
if encodeHash(name,charMap5) == keyhash:
|
||||
keyname = name
|
||||
break
|
||||
if keyname == "unknown":
|
||||
keyname = keyhash
|
||||
|
||||
# the charMap5 encoded contents data has had a length
|
||||
# of chars (always odd) cut off of the front and moved
|
||||
# to the end to prevent decoding using charMap5 from
|
||||
# working properly, and thereby preventing the ensuing
|
||||
# CryptUnprotectData call from succeeding.
|
||||
|
||||
# The offset into the charMap5 encoded contents seems to be:
|
||||
# len(contents) - largest prime number less than or equal to int(len(content)/3)
|
||||
# (in other words split "about" 2/3rds of the way through)
|
||||
|
||||
# move first offsets chars to end to align for decode by charMap5
|
||||
encdata = "".join(edlst)
|
||||
contlen = len(encdata)
|
||||
|
||||
# now properly split and recombine
|
||||
# by moving noffset chars from the start of the
|
||||
# string to the end of the string
|
||||
noffset = contlen - primes(int(contlen/3))[-1]
|
||||
pfx = encdata[0:noffset]
|
||||
encdata = encdata[noffset:]
|
||||
encdata = encdata + pfx
|
||||
|
||||
# decode using charMap5 to get the CryptProtect Data
|
||||
encryptedValue = decode(encdata,charMap5)
|
||||
cleartext = CryptUnprotectDataV2(encryptedValue)
|
||||
# Debugging
|
||||
# print keyname
|
||||
# print cleartext
|
||||
# print cleartext.encode('hex')
|
||||
# print
|
||||
DB[keyname] = cleartext
|
||||
cnt = cnt + 1
|
||||
|
||||
if cnt == 0:
|
||||
DB = None
|
||||
|
|
|
@ -122,6 +122,9 @@ def GetVolumeSerialNumber():
|
|||
return GetVolumeSerialNumber
|
||||
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
||||
|
||||
def GetIDString():
|
||||
return GetVolumeSerialNumber()
|
||||
|
||||
def getLastError():
|
||||
GetLastError = kernel32.GetLastError
|
||||
GetLastError.argtypes = None
|
||||
|
@ -181,18 +184,27 @@ def getKindleInfoFiles(kInfoFiles):
|
|||
kInfoFiles.append(kinfopath)
|
||||
|
||||
# now look for newer (K4PC 1.5.0 and later rainier.2.1.1.kinf file
|
||||
|
||||
kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
|
||||
if not os.path.isfile(kinfopath):
|
||||
print('No .kinf files have not been found.')
|
||||
print('No K4PC 1.5.X .kinf files have not been found.')
|
||||
else:
|
||||
kInfoFiles.append(kinfopath)
|
||||
|
||||
# now look for even newer (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
|
||||
kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf'
|
||||
if not os.path.isfile(kinfopath):
|
||||
print('No K4PC 1.6.X .kinf files have not been found.')
|
||||
else:
|
||||
kInfoFiles.append(kinfopath)
|
||||
|
||||
return kInfoFiles
|
||||
|
||||
|
||||
# determine type of kindle info provided and return a
|
||||
# database of keynames and values
|
||||
def getDBfromFile(kInfoFile):
|
||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
|
||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
|
||||
DB = {}
|
||||
cnt = 0
|
||||
infoReader = open(kInfoFile, 'r')
|
||||
|
|
|
@ -22,16 +22,16 @@ else:
|
|||
|
||||
if inCalibre:
|
||||
if sys.platform.startswith('win'):
|
||||
from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber
|
||||
from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
|
||||
|
||||
if sys.platform.startswith('darwin'):
|
||||
from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber
|
||||
from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
|
||||
else:
|
||||
if sys.platform.startswith('win'):
|
||||
from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber
|
||||
from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
|
||||
|
||||
if sys.platform.startswith('darwin'):
|
||||
from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber
|
||||
from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
|
||||
|
||||
|
||||
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||
|
@ -218,14 +218,14 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
|
|||
print "Keys not found in " + kInfoFile
|
||||
return pidlst
|
||||
|
||||
# Get the HDD serial
|
||||
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
|
||||
# Get the ID string used
|
||||
encodedIDString = encodeHash(GetIDString(),charMap1)
|
||||
|
||||
# Get the current user name
|
||||
encodedUsername = encodeHash(GetUserName(),charMap1)
|
||||
|
||||
# concat, hash and encode to calculate the DSN
|
||||
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
|
||||
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
|
||||
|
||||
# Compute the device PID (for which I can tell, is used for nothing).
|
||||
table = generatePidEncryptionTable()
|
||||
|
|
|
@ -17,7 +17,7 @@ from __future__ import with_statement
|
|||
# and many many others
|
||||
|
||||
|
||||
__version__ = '3.1'
|
||||
__version__ = '3.5'
|
||||
|
||||
class Unbuffered:
|
||||
def __init__(self, stream):
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
# standlone set of Mac OSX specific routines needed for K4DeDRM
|
||||
# standlone set of Mac OSX specific routines needed for KindleBooks
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import os.path
|
||||
|
||||
import subprocess
|
||||
from struct import pack, unpack, unpack_from
|
||||
|
||||
class DrmException(Exception):
|
||||
|
@ -68,11 +69,9 @@ def _load_crypto_libcrypto():
|
|||
raise DrmException('AES decryption failed')
|
||||
return out.raw
|
||||
|
||||
def keyivgen(self, passwd, salt):
|
||||
def keyivgen(self, passwd, salt, iter, keylen):
|
||||
saltlen = len(salt)
|
||||
passlen = len(passwd)
|
||||
iter = 0x3e8
|
||||
keylen = 80
|
||||
out = create_string_buffer(keylen)
|
||||
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
|
||||
return out.raw
|
||||
|
@ -114,9 +113,10 @@ def SHA256(message):
|
|||
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
|
||||
|
||||
# For Future Reference from .kinf approach of K4PC
|
||||
charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
|
||||
|
||||
# For kinf approach of K4PC/K4Mac
|
||||
# On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
|
||||
# For Mac they seem to re-use charMap2 here
|
||||
charMap5 = charMap2
|
||||
|
||||
def encode(data, map):
|
||||
result = ""
|
||||
|
@ -144,7 +144,7 @@ def decode(data,map):
|
|||
result += pack("B",value)
|
||||
return result
|
||||
|
||||
# For Future Reference from .kinf approach of K4PC
|
||||
# For .kinf approach of K4PC and now K4Mac
|
||||
# generate table of prime number less than or equal to int n
|
||||
def primes(n):
|
||||
if n==2: return [2]
|
||||
|
@ -166,7 +166,6 @@ def primes(n):
|
|||
return [2]+[x for x in s if x]
|
||||
|
||||
|
||||
|
||||
# uses a sub process to get the Hard Drive Serial Number using ioreg
|
||||
# returns with the serial number of drive whose BSD Name is "disk0"
|
||||
def GetVolumeSerialNumber():
|
||||
|
@ -196,24 +195,217 @@ def GetVolumeSerialNumber():
|
|||
foundIt = True
|
||||
break
|
||||
if not foundIt:
|
||||
sernum = '9999999999'
|
||||
sernum = ''
|
||||
return sernum
|
||||
|
||||
def GetUserHomeAppSupKindleDirParitionName():
|
||||
home = os.getenv('HOME')
|
||||
dpath = home + '/Library/Application Support/Kindle'
|
||||
cmdline = '/sbin/mount'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p.communicate()
|
||||
reslst = out1.split('\n')
|
||||
cnt = len(reslst)
|
||||
disk = ''
|
||||
foundIt = False
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
if resline.startswith('/dev'):
|
||||
(devpart, mpath) = resline.split(' on ')
|
||||
dpart = devpart[5:]
|
||||
pp = mpath.find('(')
|
||||
if pp >= 0:
|
||||
mpath = mpath[:pp-1]
|
||||
if dpath.startswith(mpath):
|
||||
disk = dpart
|
||||
return disk
|
||||
|
||||
# uses a sub process to get the UUID of the specified disk partition using ioreg
|
||||
def GetDiskPartitionUUID(diskpart):
|
||||
uuidnum = os.getenv('MYUUIDNUMBER')
|
||||
if uuidnum != None:
|
||||
return uuidnum
|
||||
cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p.communicate()
|
||||
reslst = out1.split('\n')
|
||||
cnt = len(reslst)
|
||||
bsdname = None
|
||||
uuidnum = None
|
||||
foundIt = False
|
||||
nest = 0
|
||||
uuidnest = -1
|
||||
partnest = -2
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
if resline.find('{') >= 0:
|
||||
nest += 1
|
||||
if resline.find('}') >= 0:
|
||||
nest -= 1
|
||||
pp = resline.find('"UUID" = "')
|
||||
if pp >= 0:
|
||||
uuidnum = resline[pp+10:-1]
|
||||
uuidnum = uuidnum.strip()
|
||||
uuidnest = nest
|
||||
if partnest == uuidnest and uuidnest > 0:
|
||||
foundIt = True
|
||||
break
|
||||
bb = resline.find('"BSD Name" = "')
|
||||
if bb >= 0:
|
||||
bsdname = resline[bb+14:-1]
|
||||
bsdname = bsdname.strip()
|
||||
if (bsdname == diskpart):
|
||||
partnest = nest
|
||||
else :
|
||||
partnest = -2
|
||||
if partnest == uuidnest and partnest > 0:
|
||||
foundIt = True
|
||||
break
|
||||
if nest == 0:
|
||||
partnest = -2
|
||||
uuidnest = -1
|
||||
uuidnum = None
|
||||
bsdname = None
|
||||
if not foundIt:
|
||||
uuidnum = ''
|
||||
return uuidnum
|
||||
|
||||
def GetMACAddressMunged():
|
||||
macnum = os.getenv('MYMACNUM')
|
||||
if macnum != None:
|
||||
return macnum
|
||||
cmdline = '/sbin/ifconfig en0'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p.communicate()
|
||||
reslst = out1.split('\n')
|
||||
cnt = len(reslst)
|
||||
macnum = None
|
||||
foundIt = False
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
pp = resline.find('ether ')
|
||||
if pp >= 0:
|
||||
macnum = resline[pp+6:-1]
|
||||
macnum = macnum.strip()
|
||||
# print "original mac", macnum
|
||||
# now munge it up the way Kindle app does
|
||||
# by xoring it with 0xa5 and swapping elements 3 and 4
|
||||
maclst = macnum.split(':')
|
||||
n = len(maclst)
|
||||
if n != 6:
|
||||
fountIt = False
|
||||
break
|
||||
for i in range(6):
|
||||
maclst[i] = int('0x' + maclst[i], 0)
|
||||
mlst = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
mlst[5] = maclst[5] ^ 0xa5
|
||||
mlst[4] = maclst[3] ^ 0xa5
|
||||
mlst[3] = maclst[4] ^ 0xa5
|
||||
mlst[2] = maclst[2] ^ 0xa5
|
||||
mlst[1] = maclst[1] ^ 0xa5
|
||||
mlst[0] = maclst[0] ^ 0xa5
|
||||
macnum = "%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x" % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
|
||||
foundIt = True
|
||||
break
|
||||
if not foundIt:
|
||||
macnum = ''
|
||||
return macnum
|
||||
|
||||
|
||||
# uses unix env to get username instead of using sysctlbyname
|
||||
def GetUserName():
|
||||
username = os.getenv('USER')
|
||||
return username
|
||||
|
||||
|
||||
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
||||
def CryptUnprotectData(encryptedData, salt):
|
||||
sp = GetVolumeSerialNumber() + '!@#' + GetUserName()
|
||||
# used by Kindle for Mac versions < 1.6.0
|
||||
def CryptUnprotectData(encryptedData):
|
||||
sernum = GetVolumeSerialNumber()
|
||||
if sernum == '':
|
||||
sernum = '9999999999'
|
||||
sp = sernum + '!@#' + GetUserName()
|
||||
passwdData = encode(SHA256(sp),charMap1)
|
||||
salt = '16743'
|
||||
iter = 0x3e8
|
||||
keylen = 0x80
|
||||
crp = LibCrypto()
|
||||
key_iv = crp.keyivgen(passwdData, salt)
|
||||
key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
|
||||
key = key_iv[0:32]
|
||||
iv = key_iv[32:48]
|
||||
crp.set_decrypt_key(key,iv)
|
||||
cleartext = crp.decrypt(encryptedData)
|
||||
cleartext = decode(cleartext,charMap1)
|
||||
return cleartext
|
||||
|
||||
|
||||
def isNewInstall():
|
||||
home = os.getenv('HOME')
|
||||
# soccer game fan anyone
|
||||
dpath = home + '/Library/Application Support/Kindle/storage/.pes2011'
|
||||
# print dpath, os.path.exists(dpath)
|
||||
if os.path.exists(dpath):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def GetIDString():
|
||||
# K4Mac now has an extensive set of ids strings it uses
|
||||
# in encoding pids and in creating unique passwords
|
||||
# for use in its own version of CryptUnprotectDataV2
|
||||
|
||||
# BUT Amazon has now become nasty enough to detect when its app
|
||||
# is being run under a debugger and actually changes code paths
|
||||
# including which one of these strings is chosen, all to try
|
||||
# to prevent reverse engineering
|
||||
|
||||
# Sad really ... they will only hurt their own sales ...
|
||||
# true book lovers really want to keep their books forever
|
||||
# and move them to their devices and DRM prevents that so they
|
||||
# will just buy from someplace else that they can remove
|
||||
# the DRM from
|
||||
|
||||
# Amazon should know by now that true book lover's are not like
|
||||
# penniless kids that pirate music, we do not pirate books
|
||||
|
||||
if isNewInstall():
|
||||
mungedmac = GetMACAddressMunged()
|
||||
if len(mungedmac) > 7:
|
||||
return mungedmac
|
||||
sernum = GetVolumeSerialNumber()
|
||||
if len(sernum) > 7:
|
||||
return sernum
|
||||
diskpart = GetUserHomeAppSupKindleDirParitionName()
|
||||
uuidnum = GetDiskPartitionUUID(diskpart)
|
||||
if len(uuidnum) > 7:
|
||||
return uuidnum
|
||||
mungedmac = GetMACAddressMunged()
|
||||
if len(mungedmac) > 7:
|
||||
return mungedmac
|
||||
return '9999999999'
|
||||
|
||||
|
||||
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
||||
# used for Kindle for Mac Versions >= 1.6.0
|
||||
def CryptUnprotectDataV2(encryptedData):
|
||||
sp = GetUserName() + ':&%:' + GetIDString()
|
||||
passwdData = encode(SHA256(sp),charMap5)
|
||||
# salt generation as per the code
|
||||
salt = 0x0512981d * 2 * 1 * 1
|
||||
salt = str(salt) + GetUserName()
|
||||
salt = encode(salt,charMap5)
|
||||
crp = LibCrypto()
|
||||
iter = 0x800
|
||||
keylen = 0x400
|
||||
key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
|
||||
key = key_iv[0:32]
|
||||
iv = key_iv[32:48]
|
||||
crp.set_decrypt_key(key,iv)
|
||||
cleartext = crp.decrypt(encryptedData)
|
||||
cleartext = decode(cleartext, charMap5)
|
||||
return cleartext
|
||||
|
||||
|
||||
|
@ -232,18 +424,16 @@ def getKindleInfoFiles(kInfoFiles):
|
|||
if os.path.isfile(resline):
|
||||
kInfoFiles.append(resline)
|
||||
found = True
|
||||
# For Future Reference
|
||||
#
|
||||
# # add any .kinf files
|
||||
# cmdline = 'find "' + home + '/Library/Application Support" -name "rainier*.kinf"'
|
||||
# cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
# p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
# out1, out2 = p1.communicate()
|
||||
# reslst = out1.split('\n')
|
||||
# for resline in reslst:
|
||||
# if os.path.isfile(resline):
|
||||
# kInfoFiles.append(resline)
|
||||
# found = True
|
||||
# add any .kinf files
|
||||
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p1.communicate()
|
||||
reslst = out1.split('\n')
|
||||
for resline in reslst:
|
||||
if os.path.isfile(resline):
|
||||
kInfoFiles.append(resline)
|
||||
found = True
|
||||
if not found:
|
||||
print('No kindle-info files have been found.')
|
||||
return kInfoFiles
|
||||
|
@ -251,7 +441,7 @@ def getKindleInfoFiles(kInfoFiles):
|
|||
# determine type of kindle info provided and return a
|
||||
# database of keynames and values
|
||||
def getDBfromFile(kInfoFile):
|
||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
|
||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
|
||||
DB = {}
|
||||
cnt = 0
|
||||
infoReader = open(kInfoFile, 'r')
|
||||
|
@ -259,7 +449,6 @@ def getDBfromFile(kInfoFile):
|
|||
data = infoReader.read()
|
||||
|
||||
if data.find('[') != -1 :
|
||||
|
||||
# older style kindle-info file
|
||||
items = data.split('[')
|
||||
for item in items:
|
||||
|
@ -273,84 +462,89 @@ def getDBfromFile(kInfoFile):
|
|||
if keyname == "unknown":
|
||||
keyname = keyhash
|
||||
encryptedValue = decode(rawdata,charMap2)
|
||||
salt = '16743'
|
||||
cleartext = CryptUnprotectData(encryptedValue, salt)
|
||||
DB[keyname] = decode(cleartext,charMap1)
|
||||
cleartext = CryptUnprotectData(encryptedValue)
|
||||
DB[keyname] = cleartext
|
||||
cnt = cnt + 1
|
||||
if cnt == 0:
|
||||
DB = None
|
||||
return DB
|
||||
|
||||
# For Future Reference taken from K4PC 1.5.0 .kinf
|
||||
#
|
||||
# # else newer 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]
|
||||
# items = data.split('/')
|
||||
#
|
||||
# # loop through the item records until all are processed
|
||||
# while len(items) > 0:
|
||||
#
|
||||
# # get the first item record
|
||||
# item = items.pop(0)
|
||||
#
|
||||
# # the first 32 chars of the first record of a group
|
||||
# # is the MD5 hash of the key name encoded by charMap5
|
||||
# keyhash = item[0:32]
|
||||
#
|
||||
# # the raw keyhash string is also used to create entropy for the actual
|
||||
# # CryptProtectData Blob that represents that keys contents
|
||||
# entropy = SHA1(keyhash)
|
||||
#
|
||||
# # 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
|
||||
# # and make up the contents
|
||||
# srcnt = decode(item[34:],charMap5)
|
||||
# rcnt = int(srcnt)
|
||||
#
|
||||
# # read and store in rcnt records of data
|
||||
# # that make up the contents value
|
||||
# edlst = []
|
||||
# for i in xrange(rcnt):
|
||||
# item = items.pop(0)
|
||||
# edlst.append(item)
|
||||
#
|
||||
# keyname = "unknown"
|
||||
# for name in names:
|
||||
# if encodeHash(name,charMap5) == keyhash:
|
||||
# keyname = name
|
||||
# break
|
||||
# if keyname == "unknown":
|
||||
# keyname = keyhash
|
||||
#
|
||||
# # the charMap5 encoded contents data has had a length
|
||||
# # of chars (always odd) cut off of the front and moved
|
||||
# # to the end to prevent decoding using charMap5 from
|
||||
# # working properly, and thereby preventing the ensuing
|
||||
# # CryptUnprotectData call from succeeding.
|
||||
#
|
||||
# # The offset into the charMap5 encoded contents seems to be:
|
||||
# # len(contents) - largest prime number less than or equal to int(len(content)/3)
|
||||
# # (in other words split "about" 2/3rds of the way through)
|
||||
#
|
||||
# # move first offsets chars to end to align for decode by charMap5
|
||||
# encdata = "".join(edlst)
|
||||
# contlen = len(encdata)
|
||||
# noffset = contlen - primes(int(contlen/3))[-1]
|
||||
#
|
||||
# # now properly split and recombine
|
||||
# # by moving noffset chars from the start of the
|
||||
# # string to the end of the string
|
||||
# pfx = encdata[0:noffset]
|
||||
# encdata = encdata[noffset:]
|
||||
# encdata = encdata + pfx
|
||||
#
|
||||
# # decode using Map5 to get the CryptProtect Data
|
||||
# encryptedValue = decode(encdata,charMap5)
|
||||
# DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1)
|
||||
# cnt = cnt + 1
|
||||
# else newer style .kinf file used by K4Mac >= 1.6.0
|
||||
# the .kinf file uses "/" to separate it into records
|
||||
# so remove the trailing "/" to make it easy to use split
|
||||
data = data[:-1]
|
||||
items = data.split('/')
|
||||
|
||||
# loop through the item records until all are processed
|
||||
while len(items) > 0:
|
||||
|
||||
# get the first item record
|
||||
item = items.pop(0)
|
||||
|
||||
# the first 32 chars of the first record of a group
|
||||
# is the MD5 hash of the key name encoded by charMap5
|
||||
keyhash = item[0:32]
|
||||
keyname = "unknown"
|
||||
|
||||
# the raw keyhash string is also used to create entropy for the actual
|
||||
# CryptProtectData Blob that represents that keys contents
|
||||
# "entropy" not used for K4Mac only K4PC
|
||||
# entropy = SHA1(keyhash)
|
||||
|
||||
# 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
|
||||
# and make up the contents
|
||||
srcnt = decode(item[34:],charMap5)
|
||||
rcnt = int(srcnt)
|
||||
|
||||
# read and store in rcnt records of data
|
||||
# that make up the contents value
|
||||
edlst = []
|
||||
for i in xrange(rcnt):
|
||||
item = items.pop(0)
|
||||
edlst.append(item)
|
||||
|
||||
keyname = "unknown"
|
||||
for name in names:
|
||||
if encodeHash(name,charMap5) == keyhash:
|
||||
keyname = name
|
||||
break
|
||||
if keyname == "unknown":
|
||||
keyname = keyhash
|
||||
|
||||
# the charMap5 encoded contents data has had a length
|
||||
# of chars (always odd) cut off of the front and moved
|
||||
# to the end to prevent decoding using charMap5 from
|
||||
# working properly, and thereby preventing the ensuing
|
||||
# CryptUnprotectData call from succeeding.
|
||||
|
||||
# The offset into the charMap5 encoded contents seems to be:
|
||||
# len(contents) - largest prime number less than or equal to int(len(content)/3)
|
||||
# (in other words split "about" 2/3rds of the way through)
|
||||
|
||||
# move first offsets chars to end to align for decode by charMap5
|
||||
encdata = "".join(edlst)
|
||||
contlen = len(encdata)
|
||||
|
||||
# now properly split and recombine
|
||||
# by moving noffset chars from the start of the
|
||||
# string to the end of the string
|
||||
noffset = contlen - primes(int(contlen/3))[-1]
|
||||
pfx = encdata[0:noffset]
|
||||
encdata = encdata[noffset:]
|
||||
encdata = encdata + pfx
|
||||
|
||||
# decode using charMap5 to get the CryptProtect Data
|
||||
encryptedValue = decode(encdata,charMap5)
|
||||
cleartext = CryptUnprotectDataV2(encryptedValue)
|
||||
# Debugging
|
||||
# print keyname
|
||||
# print cleartext
|
||||
# print cleartext.encode('hex')
|
||||
# print
|
||||
DB[keyname] = cleartext
|
||||
cnt = cnt + 1
|
||||
|
||||
if cnt == 0:
|
||||
DB = None
|
||||
|
|
|
@ -122,6 +122,9 @@ def GetVolumeSerialNumber():
|
|||
return GetVolumeSerialNumber
|
||||
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
||||
|
||||
def GetIDString():
|
||||
return GetVolumeSerialNumber()
|
||||
|
||||
def getLastError():
|
||||
GetLastError = kernel32.GetLastError
|
||||
GetLastError.argtypes = None
|
||||
|
@ -181,18 +184,27 @@ def getKindleInfoFiles(kInfoFiles):
|
|||
kInfoFiles.append(kinfopath)
|
||||
|
||||
# now look for newer (K4PC 1.5.0 and later rainier.2.1.1.kinf file
|
||||
|
||||
kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
|
||||
if not os.path.isfile(kinfopath):
|
||||
print('No .kinf files have not been found.')
|
||||
print('No K4PC 1.5.X .kinf files have not been found.')
|
||||
else:
|
||||
kInfoFiles.append(kinfopath)
|
||||
|
||||
# now look for even newer (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
|
||||
kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf'
|
||||
if not os.path.isfile(kinfopath):
|
||||
print('No K4PC 1.6.X .kinf files have not been found.')
|
||||
else:
|
||||
kInfoFiles.append(kinfopath)
|
||||
|
||||
return kInfoFiles
|
||||
|
||||
|
||||
# determine type of kindle info provided and return a
|
||||
# database of keynames and values
|
||||
def getDBfromFile(kInfoFile):
|
||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
|
||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
|
||||
DB = {}
|
||||
cnt = 0
|
||||
infoReader = open(kInfoFile, 'r')
|
||||
|
|
|
@ -22,16 +22,16 @@ else:
|
|||
|
||||
if inCalibre:
|
||||
if sys.platform.startswith('win'):
|
||||
from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber
|
||||
from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
|
||||
|
||||
if sys.platform.startswith('darwin'):
|
||||
from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber
|
||||
from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
|
||||
else:
|
||||
if sys.platform.startswith('win'):
|
||||
from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber
|
||||
from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
|
||||
|
||||
if sys.platform.startswith('darwin'):
|
||||
from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber
|
||||
from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
|
||||
|
||||
|
||||
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||
|
@ -218,14 +218,14 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
|
|||
print "Keys not found in " + kInfoFile
|
||||
return pidlst
|
||||
|
||||
# Get the HDD serial
|
||||
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
|
||||
# Get the ID string used
|
||||
encodedIDString = encodeHash(GetIDString(),charMap1)
|
||||
|
||||
# Get the current user name
|
||||
encodedUsername = encodeHash(GetUserName(),charMap1)
|
||||
|
||||
# concat, hash and encode to calculate the DSN
|
||||
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
|
||||
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
|
||||
|
||||
# Compute the device PID (for which I can tell, is used for nothing).
|
||||
table = generatePidEncryptionTable()
|
||||
|
|
|
@ -17,7 +17,7 @@ from __future__ import with_statement
|
|||
# and many many others
|
||||
|
||||
|
||||
__version__ = '3.1'
|
||||
__version__ = '3.5'
|
||||
|
||||
class Unbuffered:
|
||||
def __init__(self, stream):
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
# standlone set of Mac OSX specific routines needed for K4DeDRM
|
||||
# standlone set of Mac OSX specific routines needed for KindleBooks
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import os.path
|
||||
|
||||
import subprocess
|
||||
from struct import pack, unpack, unpack_from
|
||||
|
||||
class DrmException(Exception):
|
||||
|
@ -68,11 +69,9 @@ def _load_crypto_libcrypto():
|
|||
raise DrmException('AES decryption failed')
|
||||
return out.raw
|
||||
|
||||
def keyivgen(self, passwd, salt):
|
||||
def keyivgen(self, passwd, salt, iter, keylen):
|
||||
saltlen = len(salt)
|
||||
passlen = len(passwd)
|
||||
iter = 0x3e8
|
||||
keylen = 80
|
||||
out = create_string_buffer(keylen)
|
||||
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
|
||||
return out.raw
|
||||
|
@ -114,9 +113,10 @@ def SHA256(message):
|
|||
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
|
||||
|
||||
# For Future Reference from .kinf approach of K4PC
|
||||
charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
|
||||
|
||||
# For kinf approach of K4PC/K4Mac
|
||||
# On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
|
||||
# For Mac they seem to re-use charMap2 here
|
||||
charMap5 = charMap2
|
||||
|
||||
def encode(data, map):
|
||||
result = ""
|
||||
|
@ -144,7 +144,7 @@ def decode(data,map):
|
|||
result += pack("B",value)
|
||||
return result
|
||||
|
||||
# For Future Reference from .kinf approach of K4PC
|
||||
# For .kinf approach of K4PC and now K4Mac
|
||||
# generate table of prime number less than or equal to int n
|
||||
def primes(n):
|
||||
if n==2: return [2]
|
||||
|
@ -166,7 +166,6 @@ def primes(n):
|
|||
return [2]+[x for x in s if x]
|
||||
|
||||
|
||||
|
||||
# uses a sub process to get the Hard Drive Serial Number using ioreg
|
||||
# returns with the serial number of drive whose BSD Name is "disk0"
|
||||
def GetVolumeSerialNumber():
|
||||
|
@ -196,24 +195,217 @@ def GetVolumeSerialNumber():
|
|||
foundIt = True
|
||||
break
|
||||
if not foundIt:
|
||||
sernum = '9999999999'
|
||||
sernum = ''
|
||||
return sernum
|
||||
|
||||
def GetUserHomeAppSupKindleDirParitionName():
|
||||
home = os.getenv('HOME')
|
||||
dpath = home + '/Library/Application Support/Kindle'
|
||||
cmdline = '/sbin/mount'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p.communicate()
|
||||
reslst = out1.split('\n')
|
||||
cnt = len(reslst)
|
||||
disk = ''
|
||||
foundIt = False
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
if resline.startswith('/dev'):
|
||||
(devpart, mpath) = resline.split(' on ')
|
||||
dpart = devpart[5:]
|
||||
pp = mpath.find('(')
|
||||
if pp >= 0:
|
||||
mpath = mpath[:pp-1]
|
||||
if dpath.startswith(mpath):
|
||||
disk = dpart
|
||||
return disk
|
||||
|
||||
# uses a sub process to get the UUID of the specified disk partition using ioreg
|
||||
def GetDiskPartitionUUID(diskpart):
|
||||
uuidnum = os.getenv('MYUUIDNUMBER')
|
||||
if uuidnum != None:
|
||||
return uuidnum
|
||||
cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p.communicate()
|
||||
reslst = out1.split('\n')
|
||||
cnt = len(reslst)
|
||||
bsdname = None
|
||||
uuidnum = None
|
||||
foundIt = False
|
||||
nest = 0
|
||||
uuidnest = -1
|
||||
partnest = -2
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
if resline.find('{') >= 0:
|
||||
nest += 1
|
||||
if resline.find('}') >= 0:
|
||||
nest -= 1
|
||||
pp = resline.find('"UUID" = "')
|
||||
if pp >= 0:
|
||||
uuidnum = resline[pp+10:-1]
|
||||
uuidnum = uuidnum.strip()
|
||||
uuidnest = nest
|
||||
if partnest == uuidnest and uuidnest > 0:
|
||||
foundIt = True
|
||||
break
|
||||
bb = resline.find('"BSD Name" = "')
|
||||
if bb >= 0:
|
||||
bsdname = resline[bb+14:-1]
|
||||
bsdname = bsdname.strip()
|
||||
if (bsdname == diskpart):
|
||||
partnest = nest
|
||||
else :
|
||||
partnest = -2
|
||||
if partnest == uuidnest and partnest > 0:
|
||||
foundIt = True
|
||||
break
|
||||
if nest == 0:
|
||||
partnest = -2
|
||||
uuidnest = -1
|
||||
uuidnum = None
|
||||
bsdname = None
|
||||
if not foundIt:
|
||||
uuidnum = ''
|
||||
return uuidnum
|
||||
|
||||
def GetMACAddressMunged():
|
||||
macnum = os.getenv('MYMACNUM')
|
||||
if macnum != None:
|
||||
return macnum
|
||||
cmdline = '/sbin/ifconfig en0'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p.communicate()
|
||||
reslst = out1.split('\n')
|
||||
cnt = len(reslst)
|
||||
macnum = None
|
||||
foundIt = False
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
pp = resline.find('ether ')
|
||||
if pp >= 0:
|
||||
macnum = resline[pp+6:-1]
|
||||
macnum = macnum.strip()
|
||||
# print "original mac", macnum
|
||||
# now munge it up the way Kindle app does
|
||||
# by xoring it with 0xa5 and swapping elements 3 and 4
|
||||
maclst = macnum.split(':')
|
||||
n = len(maclst)
|
||||
if n != 6:
|
||||
fountIt = False
|
||||
break
|
||||
for i in range(6):
|
||||
maclst[i] = int('0x' + maclst[i], 0)
|
||||
mlst = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
mlst[5] = maclst[5] ^ 0xa5
|
||||
mlst[4] = maclst[3] ^ 0xa5
|
||||
mlst[3] = maclst[4] ^ 0xa5
|
||||
mlst[2] = maclst[2] ^ 0xa5
|
||||
mlst[1] = maclst[1] ^ 0xa5
|
||||
mlst[0] = maclst[0] ^ 0xa5
|
||||
macnum = "%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x" % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
|
||||
foundIt = True
|
||||
break
|
||||
if not foundIt:
|
||||
macnum = ''
|
||||
return macnum
|
||||
|
||||
|
||||
# uses unix env to get username instead of using sysctlbyname
|
||||
def GetUserName():
|
||||
username = os.getenv('USER')
|
||||
return username
|
||||
|
||||
|
||||
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
||||
def CryptUnprotectData(encryptedData, salt):
|
||||
sp = GetVolumeSerialNumber() + '!@#' + GetUserName()
|
||||
# used by Kindle for Mac versions < 1.6.0
|
||||
def CryptUnprotectData(encryptedData):
|
||||
sernum = GetVolumeSerialNumber()
|
||||
if sernum == '':
|
||||
sernum = '9999999999'
|
||||
sp = sernum + '!@#' + GetUserName()
|
||||
passwdData = encode(SHA256(sp),charMap1)
|
||||
salt = '16743'
|
||||
iter = 0x3e8
|
||||
keylen = 0x80
|
||||
crp = LibCrypto()
|
||||
key_iv = crp.keyivgen(passwdData, salt)
|
||||
key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
|
||||
key = key_iv[0:32]
|
||||
iv = key_iv[32:48]
|
||||
crp.set_decrypt_key(key,iv)
|
||||
cleartext = crp.decrypt(encryptedData)
|
||||
cleartext = decode(cleartext,charMap1)
|
||||
return cleartext
|
||||
|
||||
|
||||
def isNewInstall():
|
||||
home = os.getenv('HOME')
|
||||
# soccer game fan anyone
|
||||
dpath = home + '/Library/Application Support/Kindle/storage/.pes2011'
|
||||
# print dpath, os.path.exists(dpath)
|
||||
if os.path.exists(dpath):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def GetIDString():
|
||||
# K4Mac now has an extensive set of ids strings it uses
|
||||
# in encoding pids and in creating unique passwords
|
||||
# for use in its own version of CryptUnprotectDataV2
|
||||
|
||||
# BUT Amazon has now become nasty enough to detect when its app
|
||||
# is being run under a debugger and actually changes code paths
|
||||
# including which one of these strings is chosen, all to try
|
||||
# to prevent reverse engineering
|
||||
|
||||
# Sad really ... they will only hurt their own sales ...
|
||||
# true book lovers really want to keep their books forever
|
||||
# and move them to their devices and DRM prevents that so they
|
||||
# will just buy from someplace else that they can remove
|
||||
# the DRM from
|
||||
|
||||
# Amazon should know by now that true book lover's are not like
|
||||
# penniless kids that pirate music, we do not pirate books
|
||||
|
||||
if isNewInstall():
|
||||
mungedmac = GetMACAddressMunged()
|
||||
if len(mungedmac) > 7:
|
||||
return mungedmac
|
||||
sernum = GetVolumeSerialNumber()
|
||||
if len(sernum) > 7:
|
||||
return sernum
|
||||
diskpart = GetUserHomeAppSupKindleDirParitionName()
|
||||
uuidnum = GetDiskPartitionUUID(diskpart)
|
||||
if len(uuidnum) > 7:
|
||||
return uuidnum
|
||||
mungedmac = GetMACAddressMunged()
|
||||
if len(mungedmac) > 7:
|
||||
return mungedmac
|
||||
return '9999999999'
|
||||
|
||||
|
||||
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
||||
# used for Kindle for Mac Versions >= 1.6.0
|
||||
def CryptUnprotectDataV2(encryptedData):
|
||||
sp = GetUserName() + ':&%:' + GetIDString()
|
||||
passwdData = encode(SHA256(sp),charMap5)
|
||||
# salt generation as per the code
|
||||
salt = 0x0512981d * 2 * 1 * 1
|
||||
salt = str(salt) + GetUserName()
|
||||
salt = encode(salt,charMap5)
|
||||
crp = LibCrypto()
|
||||
iter = 0x800
|
||||
keylen = 0x400
|
||||
key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
|
||||
key = key_iv[0:32]
|
||||
iv = key_iv[32:48]
|
||||
crp.set_decrypt_key(key,iv)
|
||||
cleartext = crp.decrypt(encryptedData)
|
||||
cleartext = decode(cleartext, charMap5)
|
||||
return cleartext
|
||||
|
||||
|
||||
|
@ -232,18 +424,16 @@ def getKindleInfoFiles(kInfoFiles):
|
|||
if os.path.isfile(resline):
|
||||
kInfoFiles.append(resline)
|
||||
found = True
|
||||
# For Future Reference
|
||||
#
|
||||
# # add any .kinf files
|
||||
# cmdline = 'find "' + home + '/Library/Application Support" -name "rainier*.kinf"'
|
||||
# cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
# p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
# out1, out2 = p1.communicate()
|
||||
# reslst = out1.split('\n')
|
||||
# for resline in reslst:
|
||||
# if os.path.isfile(resline):
|
||||
# kInfoFiles.append(resline)
|
||||
# found = True
|
||||
# add any .kinf files
|
||||
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p1.communicate()
|
||||
reslst = out1.split('\n')
|
||||
for resline in reslst:
|
||||
if os.path.isfile(resline):
|
||||
kInfoFiles.append(resline)
|
||||
found = True
|
||||
if not found:
|
||||
print('No kindle-info files have been found.')
|
||||
return kInfoFiles
|
||||
|
@ -251,7 +441,7 @@ def getKindleInfoFiles(kInfoFiles):
|
|||
# determine type of kindle info provided and return a
|
||||
# database of keynames and values
|
||||
def getDBfromFile(kInfoFile):
|
||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
|
||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
|
||||
DB = {}
|
||||
cnt = 0
|
||||
infoReader = open(kInfoFile, 'r')
|
||||
|
@ -259,7 +449,6 @@ def getDBfromFile(kInfoFile):
|
|||
data = infoReader.read()
|
||||
|
||||
if data.find('[') != -1 :
|
||||
|
||||
# older style kindle-info file
|
||||
items = data.split('[')
|
||||
for item in items:
|
||||
|
@ -273,84 +462,89 @@ def getDBfromFile(kInfoFile):
|
|||
if keyname == "unknown":
|
||||
keyname = keyhash
|
||||
encryptedValue = decode(rawdata,charMap2)
|
||||
salt = '16743'
|
||||
cleartext = CryptUnprotectData(encryptedValue, salt)
|
||||
DB[keyname] = decode(cleartext,charMap1)
|
||||
cleartext = CryptUnprotectData(encryptedValue)
|
||||
DB[keyname] = cleartext
|
||||
cnt = cnt + 1
|
||||
if cnt == 0:
|
||||
DB = None
|
||||
return DB
|
||||
|
||||
# For Future Reference taken from K4PC 1.5.0 .kinf
|
||||
#
|
||||
# # else newer 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]
|
||||
# items = data.split('/')
|
||||
#
|
||||
# # loop through the item records until all are processed
|
||||
# while len(items) > 0:
|
||||
#
|
||||
# # get the first item record
|
||||
# item = items.pop(0)
|
||||
#
|
||||
# # the first 32 chars of the first record of a group
|
||||
# # is the MD5 hash of the key name encoded by charMap5
|
||||
# keyhash = item[0:32]
|
||||
#
|
||||
# # the raw keyhash string is also used to create entropy for the actual
|
||||
# # CryptProtectData Blob that represents that keys contents
|
||||
# entropy = SHA1(keyhash)
|
||||
#
|
||||
# # 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
|
||||
# # and make up the contents
|
||||
# srcnt = decode(item[34:],charMap5)
|
||||
# rcnt = int(srcnt)
|
||||
#
|
||||
# # read and store in rcnt records of data
|
||||
# # that make up the contents value
|
||||
# edlst = []
|
||||
# for i in xrange(rcnt):
|
||||
# item = items.pop(0)
|
||||
# edlst.append(item)
|
||||
#
|
||||
# keyname = "unknown"
|
||||
# for name in names:
|
||||
# if encodeHash(name,charMap5) == keyhash:
|
||||
# keyname = name
|
||||
# break
|
||||
# if keyname == "unknown":
|
||||
# keyname = keyhash
|
||||
#
|
||||
# # the charMap5 encoded contents data has had a length
|
||||
# # of chars (always odd) cut off of the front and moved
|
||||
# # to the end to prevent decoding using charMap5 from
|
||||
# # working properly, and thereby preventing the ensuing
|
||||
# # CryptUnprotectData call from succeeding.
|
||||
#
|
||||
# # The offset into the charMap5 encoded contents seems to be:
|
||||
# # len(contents) - largest prime number less than or equal to int(len(content)/3)
|
||||
# # (in other words split "about" 2/3rds of the way through)
|
||||
#
|
||||
# # move first offsets chars to end to align for decode by charMap5
|
||||
# encdata = "".join(edlst)
|
||||
# contlen = len(encdata)
|
||||
# noffset = contlen - primes(int(contlen/3))[-1]
|
||||
#
|
||||
# # now properly split and recombine
|
||||
# # by moving noffset chars from the start of the
|
||||
# # string to the end of the string
|
||||
# pfx = encdata[0:noffset]
|
||||
# encdata = encdata[noffset:]
|
||||
# encdata = encdata + pfx
|
||||
#
|
||||
# # decode using Map5 to get the CryptProtect Data
|
||||
# encryptedValue = decode(encdata,charMap5)
|
||||
# DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1)
|
||||
# cnt = cnt + 1
|
||||
# else newer style .kinf file used by K4Mac >= 1.6.0
|
||||
# the .kinf file uses "/" to separate it into records
|
||||
# so remove the trailing "/" to make it easy to use split
|
||||
data = data[:-1]
|
||||
items = data.split('/')
|
||||
|
||||
# loop through the item records until all are processed
|
||||
while len(items) > 0:
|
||||
|
||||
# get the first item record
|
||||
item = items.pop(0)
|
||||
|
||||
# the first 32 chars of the first record of a group
|
||||
# is the MD5 hash of the key name encoded by charMap5
|
||||
keyhash = item[0:32]
|
||||
keyname = "unknown"
|
||||
|
||||
# the raw keyhash string is also used to create entropy for the actual
|
||||
# CryptProtectData Blob that represents that keys contents
|
||||
# "entropy" not used for K4Mac only K4PC
|
||||
# entropy = SHA1(keyhash)
|
||||
|
||||
# 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
|
||||
# and make up the contents
|
||||
srcnt = decode(item[34:],charMap5)
|
||||
rcnt = int(srcnt)
|
||||
|
||||
# read and store in rcnt records of data
|
||||
# that make up the contents value
|
||||
edlst = []
|
||||
for i in xrange(rcnt):
|
||||
item = items.pop(0)
|
||||
edlst.append(item)
|
||||
|
||||
keyname = "unknown"
|
||||
for name in names:
|
||||
if encodeHash(name,charMap5) == keyhash:
|
||||
keyname = name
|
||||
break
|
||||
if keyname == "unknown":
|
||||
keyname = keyhash
|
||||
|
||||
# the charMap5 encoded contents data has had a length
|
||||
# of chars (always odd) cut off of the front and moved
|
||||
# to the end to prevent decoding using charMap5 from
|
||||
# working properly, and thereby preventing the ensuing
|
||||
# CryptUnprotectData call from succeeding.
|
||||
|
||||
# The offset into the charMap5 encoded contents seems to be:
|
||||
# len(contents) - largest prime number less than or equal to int(len(content)/3)
|
||||
# (in other words split "about" 2/3rds of the way through)
|
||||
|
||||
# move first offsets chars to end to align for decode by charMap5
|
||||
encdata = "".join(edlst)
|
||||
contlen = len(encdata)
|
||||
|
||||
# now properly split and recombine
|
||||
# by moving noffset chars from the start of the
|
||||
# string to the end of the string
|
||||
noffset = contlen - primes(int(contlen/3))[-1]
|
||||
pfx = encdata[0:noffset]
|
||||
encdata = encdata[noffset:]
|
||||
encdata = encdata + pfx
|
||||
|
||||
# decode using charMap5 to get the CryptProtect Data
|
||||
encryptedValue = decode(encdata,charMap5)
|
||||
cleartext = CryptUnprotectDataV2(encryptedValue)
|
||||
# Debugging
|
||||
# print keyname
|
||||
# print cleartext
|
||||
# print cleartext.encode('hex')
|
||||
# print
|
||||
DB[keyname] = cleartext
|
||||
cnt = cnt + 1
|
||||
|
||||
if cnt == 0:
|
||||
DB = None
|
||||
|
|
|
@ -122,6 +122,9 @@ def GetVolumeSerialNumber():
|
|||
return GetVolumeSerialNumber
|
||||
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
||||
|
||||
def GetIDString():
|
||||
return GetVolumeSerialNumber()
|
||||
|
||||
def getLastError():
|
||||
GetLastError = kernel32.GetLastError
|
||||
GetLastError.argtypes = None
|
||||
|
@ -181,18 +184,27 @@ def getKindleInfoFiles(kInfoFiles):
|
|||
kInfoFiles.append(kinfopath)
|
||||
|
||||
# now look for newer (K4PC 1.5.0 and later rainier.2.1.1.kinf file
|
||||
|
||||
kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
|
||||
if not os.path.isfile(kinfopath):
|
||||
print('No .kinf files have not been found.')
|
||||
print('No K4PC 1.5.X .kinf files have not been found.')
|
||||
else:
|
||||
kInfoFiles.append(kinfopath)
|
||||
|
||||
# now look for even newer (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
|
||||
kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf'
|
||||
if not os.path.isfile(kinfopath):
|
||||
print('No K4PC 1.6.X .kinf files have not been found.')
|
||||
else:
|
||||
kInfoFiles.append(kinfopath)
|
||||
|
||||
return kInfoFiles
|
||||
|
||||
|
||||
# determine type of kindle info provided and return a
|
||||
# database of keynames and values
|
||||
def getDBfromFile(kInfoFile):
|
||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
|
||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
|
||||
DB = {}
|
||||
cnt = 0
|
||||
infoReader = open(kInfoFile, 'r')
|
||||
|
|
|
@ -22,16 +22,16 @@ else:
|
|||
|
||||
if inCalibre:
|
||||
if sys.platform.startswith('win'):
|
||||
from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber
|
||||
from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
|
||||
|
||||
if sys.platform.startswith('darwin'):
|
||||
from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber
|
||||
from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
|
||||
else:
|
||||
if sys.platform.startswith('win'):
|
||||
from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber
|
||||
from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
|
||||
|
||||
if sys.platform.startswith('darwin'):
|
||||
from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber
|
||||
from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
|
||||
|
||||
|
||||
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||
|
@ -218,14 +218,14 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
|
|||
print "Keys not found in " + kInfoFile
|
||||
return pidlst
|
||||
|
||||
# Get the HDD serial
|
||||
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
|
||||
# Get the ID string used
|
||||
encodedIDString = encodeHash(GetIDString(),charMap1)
|
||||
|
||||
# Get the current user name
|
||||
encodedUsername = encodeHash(GetUserName(),charMap1)
|
||||
|
||||
# concat, hash and encode to calculate the DSN
|
||||
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
|
||||
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
|
||||
|
||||
# Compute the device PID (for which I can tell, is used for nothing).
|
||||
table = generatePidEncryptionTable()
|
||||
|
|
|
@ -25,7 +25,7 @@ adb pull /data/app/com.amazon.kindle-1.apk kindle3.apk
|
|||
adb uninstall com.amazon.kindle
|
||||
apktool d kindle3.apk kindle3
|
||||
cd kindle3
|
||||
patch -p1 < ..\kindle3.patch
|
||||
patch -p1 < ../kindle3.patch
|
||||
cd ..
|
||||
apktool b kindle3 kindle3_patched.apk
|
||||
jarsigner -verbose -keystore kindle.keystore kindle3_patched.apk kindle
|
||||
|
|
Loading…
Reference in a new issue