tools v4.5

This commit is contained in:
Apprentice Alf 2011-07-14 07:08:06 +01:00
parent 4f34a9a196
commit 297a9ddc66
25 changed files with 1277 additions and 453 deletions

View file

@ -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.' 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 supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
author = 'DiapDealer, SomeUpdates' # The author of this plugin 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 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 on_import = True # Run this plugin during the import
priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm

View file

@ -17,7 +17,7 @@ from __future__ import with_statement
# and many many others # and many many others
__version__ = '3.1' __version__ = '3.5'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):

View file

@ -22,16 +22,16 @@ else:
if inCalibre: if inCalibre:
if sys.platform.startswith('win'): 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'): 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: else:
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
@ -218,14 +218,14 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
print "Keys not found in " + kInfoFile print "Keys not found in " + kInfoFile
return pidlst return pidlst
# Get the HDD serial # Get the ID string used
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1) encodedIDString = encodeHash(GetIDString(),charMap1)
# Get the current user name # Get the current user name
encodedUsername = encodeHash(GetUserName(),charMap1) encodedUsername = encodeHash(GetUserName(),charMap1)
# concat, hash and encode to calculate the DSN # 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). # Compute the device PID (for which I can tell, is used for nothing).
table = generatePidEncryptionTable() table = generatePidEncryptionTable()

View file

@ -8,7 +8,7 @@ This plugin is meant to decrypt Adobe Digital Edition PDFs that are protected wi
Installation: 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. 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.

View file

@ -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. 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: 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: Configuration:

View file

@ -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. 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: 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. 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.

View file

@ -9,7 +9,7 @@ with Adobe's Adept encryption. It is meant to function without having to install
Installation: 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. 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. 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.

View file

@ -8,7 +8,7 @@ This plugin is meant to decrypt Adobe Digital Edition Epubs that are protected w
Installation: 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. 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.

View file

@ -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 from __future__ import with_statement
import sys import sys
import os import os
import subprocess import os.path
import subprocess
from struct import pack, unpack, unpack_from from struct import pack, unpack, unpack_from
class DrmException(Exception): class DrmException(Exception):
@ -68,11 +69,9 @@ def _load_crypto_libcrypto():
raise DrmException('AES decryption failed') raise DrmException('AES decryption failed')
return out.raw return out.raw
def keyivgen(self, passwd, salt): def keyivgen(self, passwd, salt, iter, keylen):
saltlen = len(salt) saltlen = len(salt)
passlen = len(passwd) passlen = len(passwd)
iter = 0x3e8
keylen = 80
out = create_string_buffer(keylen) out = create_string_buffer(keylen)
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out) rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
return out.raw return out.raw
@ -114,9 +113,10 @@ def SHA256(message):
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
# For Future Reference from .kinf approach of K4PC # For kinf approach of K4PC/K4Mac
charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" # On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
# For Mac they seem to re-use charMap2 here
charMap5 = charMap2
def encode(data, map): def encode(data, map):
result = "" result = ""
@ -144,7 +144,7 @@ def decode(data,map):
result += pack("B",value) result += pack("B",value)
return result 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 # generate table of prime number less than or equal to int n
def primes(n): def primes(n):
if n==2: return [2] if n==2: return [2]
@ -166,7 +166,6 @@ def primes(n):
return [2]+[x for x in s if x] return [2]+[x for x in s if x]
# uses a sub process to get the Hard Drive Serial Number using ioreg # 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" # returns with the serial number of drive whose BSD Name is "disk0"
def GetVolumeSerialNumber(): def GetVolumeSerialNumber():
@ -196,24 +195,217 @@ def GetVolumeSerialNumber():
foundIt = True foundIt = True
break break
if not foundIt: if not foundIt:
sernum = '9999999999' sernum = ''
return 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 # uses unix env to get username instead of using sysctlbyname
def GetUserName(): def GetUserName():
username = os.getenv('USER') username = os.getenv('USER')
return username return username
# implements an Pseudo Mac Version of Windows built-in Crypto routine # implements an Pseudo Mac Version of Windows built-in Crypto routine
def CryptUnprotectData(encryptedData, salt): # used by Kindle for Mac versions < 1.6.0
sp = GetVolumeSerialNumber() + '!@#' + GetUserName() def CryptUnprotectData(encryptedData):
sernum = GetVolumeSerialNumber()
if sernum == '':
sernum = '9999999999'
sp = sernum + '!@#' + GetUserName()
passwdData = encode(SHA256(sp),charMap1) passwdData = encode(SHA256(sp),charMap1)
salt = '16743'
iter = 0x3e8
keylen = 0x80
crp = LibCrypto() crp = LibCrypto()
key_iv = crp.keyivgen(passwdData, salt) key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
key = key_iv[0:32] key = key_iv[0:32]
iv = key_iv[32:48] iv = key_iv[32:48]
crp.set_decrypt_key(key,iv) crp.set_decrypt_key(key,iv)
cleartext = crp.decrypt(encryptedData) 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 return cleartext
@ -232,18 +424,16 @@ def getKindleInfoFiles(kInfoFiles):
if os.path.isfile(resline): if os.path.isfile(resline):
kInfoFiles.append(resline) kInfoFiles.append(resline)
found = True found = True
# For Future Reference # add any .kinf files
# cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
# # add any .kinf files cmdline = cmdline.encode(sys.getfilesystemencoding())
# cmdline = 'find "' + home + '/Library/Application Support" -name "rainier*.kinf"' p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
# cmdline = cmdline.encode(sys.getfilesystemencoding()) out1, out2 = p1.communicate()
# p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) reslst = out1.split('\n')
# out1, out2 = p1.communicate() for resline in reslst:
# reslst = out1.split('\n') if os.path.isfile(resline):
# for resline in reslst: kInfoFiles.append(resline)
# if os.path.isfile(resline): found = True
# kInfoFiles.append(resline)
# found = True
if not found: if not found:
print('No kindle-info files have been found.') print('No kindle-info files have been found.')
return kInfoFiles return kInfoFiles
@ -251,7 +441,7 @@ def getKindleInfoFiles(kInfoFiles):
# determine type of kindle info provided and return a # determine type of kindle info provided and return a
# database of keynames and values # database of keynames and values
def getDBfromFile(kInfoFile): 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 = {} DB = {}
cnt = 0 cnt = 0
infoReader = open(kInfoFile, 'r') infoReader = open(kInfoFile, 'r')
@ -259,7 +449,6 @@ def getDBfromFile(kInfoFile):
data = infoReader.read() data = infoReader.read()
if data.find('[') != -1 : if data.find('[') != -1 :
# older style kindle-info file # older style kindle-info file
items = data.split('[') items = data.split('[')
for item in items: for item in items:
@ -273,84 +462,89 @@ def getDBfromFile(kInfoFile):
if keyname == "unknown": if keyname == "unknown":
keyname = keyhash keyname = keyhash
encryptedValue = decode(rawdata,charMap2) encryptedValue = decode(rawdata,charMap2)
salt = '16743' cleartext = CryptUnprotectData(encryptedValue)
cleartext = CryptUnprotectData(encryptedValue, salt) DB[keyname] = cleartext
DB[keyname] = decode(cleartext,charMap1)
cnt = cnt + 1 cnt = cnt + 1
if cnt == 0: if cnt == 0:
DB = None DB = None
return DB return DB
# For Future Reference taken from K4PC 1.5.0 .kinf # else newer style .kinf file used by K4Mac >= 1.6.0
# # the .kinf file uses "/" to separate it into records
# # else newer style .kinf file # so remove the trailing "/" to make it easy to use split
# # the .kinf file uses "/" to separate it into records data = data[:-1]
# # so remove the trailing "/" to make it easy to use split items = data.split('/')
# data = data[:-1]
# items = data.split('/') # loop through the item records until all are processed
# while len(items) > 0:
# # loop through the item records until all are processed
# while len(items) > 0: # get the first item record
# item = items.pop(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
# # the first 32 chars of the first record of a group keyhash = item[0:32]
# # is the MD5 hash of the key name encoded by charMap5 keyname = "unknown"
# keyhash = item[0:32]
# # the raw keyhash string is also used to create entropy for the actual
# # the raw keyhash string is also used to create entropy for the actual # CryptProtectData Blob that represents that keys contents
# # CryptProtectData Blob that represents that keys contents # "entropy" not used for K4Mac only K4PC
# entropy = SHA1(keyhash) # entropy = SHA1(keyhash)
#
# # the remainder of the first record when decoded with charMap5 # the remainder of the first record when decoded with charMap5
# # has the ':' split char followed by the string representation # has the ':' split char followed by the string representation
# # of the number of records that follow # of the number of records that follow
# # and make up the contents # and make up the contents
# srcnt = decode(item[34:],charMap5) srcnt = decode(item[34:],charMap5)
# rcnt = int(srcnt) rcnt = int(srcnt)
#
# # read and store in rcnt records of data # read and store in rcnt records of data
# # that make up the contents value # that make up the contents value
# edlst = [] edlst = []
# for i in xrange(rcnt): for i in xrange(rcnt):
# item = items.pop(0) item = items.pop(0)
# edlst.append(item) edlst.append(item)
#
# keyname = "unknown" keyname = "unknown"
# for name in names: for name in names:
# if encodeHash(name,charMap5) == keyhash: if encodeHash(name,charMap5) == keyhash:
# keyname = name keyname = name
# break break
# if keyname == "unknown": if keyname == "unknown":
# keyname = keyhash keyname = keyhash
#
# # the charMap5 encoded contents data has had a length # the charMap5 encoded contents data has had a length
# # of chars (always odd) cut off of the front and moved # of chars (always odd) cut off of the front and moved
# # to the end to prevent decoding using charMap5 from # to the end to prevent decoding using charMap5 from
# # working properly, and thereby preventing the ensuing # working properly, and thereby preventing the ensuing
# # CryptUnprotectData call from succeeding. # CryptUnprotectData call from succeeding.
#
# # The offset into the charMap5 encoded contents seems to be: # The offset into the charMap5 encoded contents seems to be:
# # len(contents) - largest prime number less than or equal to int(len(content)/3) # 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) # (in other words split "about" 2/3rds of the way through)
#
# # move first offsets chars to end to align for decode by charMap5 # move first offsets chars to end to align for decode by charMap5
# encdata = "".join(edlst) encdata = "".join(edlst)
# contlen = len(encdata) contlen = len(encdata)
# noffset = contlen - primes(int(contlen/3))[-1]
# # now properly split and recombine
# # now properly split and recombine # by moving noffset chars from the start of the
# # by moving noffset chars from the start of the # string to the end of the string
# # string to the end of the string noffset = contlen - primes(int(contlen/3))[-1]
# pfx = encdata[0:noffset] pfx = encdata[0:noffset]
# encdata = encdata[noffset:] encdata = encdata[noffset:]
# encdata = encdata + pfx encdata = encdata + pfx
#
# # decode using Map5 to get the CryptProtect Data # decode using charMap5 to get the CryptProtect Data
# encryptedValue = decode(encdata,charMap5) encryptedValue = decode(encdata,charMap5)
# DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) cleartext = CryptUnprotectDataV2(encryptedValue)
# cnt = cnt + 1 # Debugging
# print keyname
# print cleartext
# print cleartext.encode('hex')
# print
DB[keyname] = cleartext
cnt = cnt + 1
if cnt == 0: if cnt == 0:
DB = None DB = None

View file

@ -122,6 +122,9 @@ def GetVolumeSerialNumber():
return GetVolumeSerialNumber return GetVolumeSerialNumber
GetVolumeSerialNumber = GetVolumeSerialNumber() GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetIDString():
return GetVolumeSerialNumber()
def getLastError(): def getLastError():
GetLastError = kernel32.GetLastError GetLastError = kernel32.GetLastError
GetLastError.argtypes = None GetLastError.argtypes = None
@ -181,18 +184,27 @@ def getKindleInfoFiles(kInfoFiles):
kInfoFiles.append(kinfopath) kInfoFiles.append(kinfopath)
# now look for newer (K4PC 1.5.0 and later rainier.2.1.1.kinf file # 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' kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
if not os.path.isfile(kinfopath): 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: else:
kInfoFiles.append(kinfopath) 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 return kInfoFiles
# determine type of kindle info provided and return a # determine type of kindle info provided and return a
# database of keynames and values # database of keynames and values
def getDBfromFile(kInfoFile): 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 = {} DB = {}
cnt = 0 cnt = 0
infoReader = open(kInfoFile, 'r') infoReader = open(kInfoFile, 'r')

View file

@ -24,7 +24,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>droplet</string> <string>droplet</string>
<key>CFBundleGetInfoString</key> <key>CFBundleGetInfoString</key>
<string>DeDRM 2.7, Written 20102011 by Apprentice Alf and others.</string> <string>DeDRM 2.8, Written 20102011 by Apprentice Alf and others.</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>droplet</string> <string>droplet</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
@ -34,7 +34,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2.7</string> <string>2.8</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>dplt</string> <string>dplt</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>

View file

@ -17,7 +17,7 @@ from __future__ import with_statement
# and many many others # and many many others
__version__ = '3.1' __version__ = '3.5'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):

View file

@ -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 from __future__ import with_statement
import sys import sys
import os import os
import subprocess import os.path
import subprocess
from struct import pack, unpack, unpack_from from struct import pack, unpack, unpack_from
class DrmException(Exception): class DrmException(Exception):
@ -68,11 +69,9 @@ def _load_crypto_libcrypto():
raise DrmException('AES decryption failed') raise DrmException('AES decryption failed')
return out.raw return out.raw
def keyivgen(self, passwd, salt): def keyivgen(self, passwd, salt, iter, keylen):
saltlen = len(salt) saltlen = len(salt)
passlen = len(passwd) passlen = len(passwd)
iter = 0x3e8
keylen = 80
out = create_string_buffer(keylen) out = create_string_buffer(keylen)
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out) rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
return out.raw return out.raw
@ -114,9 +113,10 @@ def SHA256(message):
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
# For Future Reference from .kinf approach of K4PC # For kinf approach of K4PC/K4Mac
charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" # On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
# For Mac they seem to re-use charMap2 here
charMap5 = charMap2
def encode(data, map): def encode(data, map):
result = "" result = ""
@ -144,7 +144,7 @@ def decode(data,map):
result += pack("B",value) result += pack("B",value)
return result 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 # generate table of prime number less than or equal to int n
def primes(n): def primes(n):
if n==2: return [2] if n==2: return [2]
@ -166,7 +166,6 @@ def primes(n):
return [2]+[x for x in s if x] return [2]+[x for x in s if x]
# uses a sub process to get the Hard Drive Serial Number using ioreg # 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" # returns with the serial number of drive whose BSD Name is "disk0"
def GetVolumeSerialNumber(): def GetVolumeSerialNumber():
@ -196,24 +195,217 @@ def GetVolumeSerialNumber():
foundIt = True foundIt = True
break break
if not foundIt: if not foundIt:
sernum = '9999999999' sernum = ''
return 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 # uses unix env to get username instead of using sysctlbyname
def GetUserName(): def GetUserName():
username = os.getenv('USER') username = os.getenv('USER')
return username return username
# implements an Pseudo Mac Version of Windows built-in Crypto routine # implements an Pseudo Mac Version of Windows built-in Crypto routine
def CryptUnprotectData(encryptedData, salt): # used by Kindle for Mac versions < 1.6.0
sp = GetVolumeSerialNumber() + '!@#' + GetUserName() def CryptUnprotectData(encryptedData):
sernum = GetVolumeSerialNumber()
if sernum == '':
sernum = '9999999999'
sp = sernum + '!@#' + GetUserName()
passwdData = encode(SHA256(sp),charMap1) passwdData = encode(SHA256(sp),charMap1)
salt = '16743'
iter = 0x3e8
keylen = 0x80
crp = LibCrypto() crp = LibCrypto()
key_iv = crp.keyivgen(passwdData, salt) key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
key = key_iv[0:32] key = key_iv[0:32]
iv = key_iv[32:48] iv = key_iv[32:48]
crp.set_decrypt_key(key,iv) crp.set_decrypt_key(key,iv)
cleartext = crp.decrypt(encryptedData) 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 return cleartext
@ -232,18 +424,16 @@ def getKindleInfoFiles(kInfoFiles):
if os.path.isfile(resline): if os.path.isfile(resline):
kInfoFiles.append(resline) kInfoFiles.append(resline)
found = True found = True
# For Future Reference # add any .kinf files
# cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
# # add any .kinf files cmdline = cmdline.encode(sys.getfilesystemencoding())
# cmdline = 'find "' + home + '/Library/Application Support" -name "rainier*.kinf"' p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
# cmdline = cmdline.encode(sys.getfilesystemencoding()) out1, out2 = p1.communicate()
# p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) reslst = out1.split('\n')
# out1, out2 = p1.communicate() for resline in reslst:
# reslst = out1.split('\n') if os.path.isfile(resline):
# for resline in reslst: kInfoFiles.append(resline)
# if os.path.isfile(resline): found = True
# kInfoFiles.append(resline)
# found = True
if not found: if not found:
print('No kindle-info files have been found.') print('No kindle-info files have been found.')
return kInfoFiles return kInfoFiles
@ -251,7 +441,7 @@ def getKindleInfoFiles(kInfoFiles):
# determine type of kindle info provided and return a # determine type of kindle info provided and return a
# database of keynames and values # database of keynames and values
def getDBfromFile(kInfoFile): 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 = {} DB = {}
cnt = 0 cnt = 0
infoReader = open(kInfoFile, 'r') infoReader = open(kInfoFile, 'r')
@ -259,7 +449,6 @@ def getDBfromFile(kInfoFile):
data = infoReader.read() data = infoReader.read()
if data.find('[') != -1 : if data.find('[') != -1 :
# older style kindle-info file # older style kindle-info file
items = data.split('[') items = data.split('[')
for item in items: for item in items:
@ -273,84 +462,89 @@ def getDBfromFile(kInfoFile):
if keyname == "unknown": if keyname == "unknown":
keyname = keyhash keyname = keyhash
encryptedValue = decode(rawdata,charMap2) encryptedValue = decode(rawdata,charMap2)
salt = '16743' cleartext = CryptUnprotectData(encryptedValue)
cleartext = CryptUnprotectData(encryptedValue, salt) DB[keyname] = cleartext
DB[keyname] = decode(cleartext,charMap1)
cnt = cnt + 1 cnt = cnt + 1
if cnt == 0: if cnt == 0:
DB = None DB = None
return DB return DB
# For Future Reference taken from K4PC 1.5.0 .kinf # else newer style .kinf file used by K4Mac >= 1.6.0
# # the .kinf file uses "/" to separate it into records
# # else newer style .kinf file # so remove the trailing "/" to make it easy to use split
# # the .kinf file uses "/" to separate it into records data = data[:-1]
# # so remove the trailing "/" to make it easy to use split items = data.split('/')
# data = data[:-1]
# items = data.split('/') # loop through the item records until all are processed
# while len(items) > 0:
# # loop through the item records until all are processed
# while len(items) > 0: # get the first item record
# item = items.pop(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
# # the first 32 chars of the first record of a group keyhash = item[0:32]
# # is the MD5 hash of the key name encoded by charMap5 keyname = "unknown"
# keyhash = item[0:32]
# # the raw keyhash string is also used to create entropy for the actual
# # the raw keyhash string is also used to create entropy for the actual # CryptProtectData Blob that represents that keys contents
# # CryptProtectData Blob that represents that keys contents # "entropy" not used for K4Mac only K4PC
# entropy = SHA1(keyhash) # entropy = SHA1(keyhash)
#
# # the remainder of the first record when decoded with charMap5 # the remainder of the first record when decoded with charMap5
# # has the ':' split char followed by the string representation # has the ':' split char followed by the string representation
# # of the number of records that follow # of the number of records that follow
# # and make up the contents # and make up the contents
# srcnt = decode(item[34:],charMap5) srcnt = decode(item[34:],charMap5)
# rcnt = int(srcnt) rcnt = int(srcnt)
#
# # read and store in rcnt records of data # read and store in rcnt records of data
# # that make up the contents value # that make up the contents value
# edlst = [] edlst = []
# for i in xrange(rcnt): for i in xrange(rcnt):
# item = items.pop(0) item = items.pop(0)
# edlst.append(item) edlst.append(item)
#
# keyname = "unknown" keyname = "unknown"
# for name in names: for name in names:
# if encodeHash(name,charMap5) == keyhash: if encodeHash(name,charMap5) == keyhash:
# keyname = name keyname = name
# break break
# if keyname == "unknown": if keyname == "unknown":
# keyname = keyhash keyname = keyhash
#
# # the charMap5 encoded contents data has had a length # the charMap5 encoded contents data has had a length
# # of chars (always odd) cut off of the front and moved # of chars (always odd) cut off of the front and moved
# # to the end to prevent decoding using charMap5 from # to the end to prevent decoding using charMap5 from
# # working properly, and thereby preventing the ensuing # working properly, and thereby preventing the ensuing
# # CryptUnprotectData call from succeeding. # CryptUnprotectData call from succeeding.
#
# # The offset into the charMap5 encoded contents seems to be: # The offset into the charMap5 encoded contents seems to be:
# # len(contents) - largest prime number less than or equal to int(len(content)/3) # 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) # (in other words split "about" 2/3rds of the way through)
#
# # move first offsets chars to end to align for decode by charMap5 # move first offsets chars to end to align for decode by charMap5
# encdata = "".join(edlst) encdata = "".join(edlst)
# contlen = len(encdata) contlen = len(encdata)
# noffset = contlen - primes(int(contlen/3))[-1]
# # now properly split and recombine
# # now properly split and recombine # by moving noffset chars from the start of the
# # by moving noffset chars from the start of the # string to the end of the string
# # string to the end of the string noffset = contlen - primes(int(contlen/3))[-1]
# pfx = encdata[0:noffset] pfx = encdata[0:noffset]
# encdata = encdata[noffset:] encdata = encdata[noffset:]
# encdata = encdata + pfx encdata = encdata + pfx
#
# # decode using Map5 to get the CryptProtect Data # decode using charMap5 to get the CryptProtect Data
# encryptedValue = decode(encdata,charMap5) encryptedValue = decode(encdata,charMap5)
# DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) cleartext = CryptUnprotectDataV2(encryptedValue)
# cnt = cnt + 1 # Debugging
# print keyname
# print cleartext
# print cleartext.encode('hex')
# print
DB[keyname] = cleartext
cnt = cnt + 1
if cnt == 0: if cnt == 0:
DB = None DB = None

View file

@ -122,6 +122,9 @@ def GetVolumeSerialNumber():
return GetVolumeSerialNumber return GetVolumeSerialNumber
GetVolumeSerialNumber = GetVolumeSerialNumber() GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetIDString():
return GetVolumeSerialNumber()
def getLastError(): def getLastError():
GetLastError = kernel32.GetLastError GetLastError = kernel32.GetLastError
GetLastError.argtypes = None GetLastError.argtypes = None
@ -181,18 +184,27 @@ def getKindleInfoFiles(kInfoFiles):
kInfoFiles.append(kinfopath) kInfoFiles.append(kinfopath)
# now look for newer (K4PC 1.5.0 and later rainier.2.1.1.kinf file # 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' kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
if not os.path.isfile(kinfopath): 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: else:
kInfoFiles.append(kinfopath) 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 return kInfoFiles
# determine type of kindle info provided and return a # determine type of kindle info provided and return a
# database of keynames and values # database of keynames and values
def getDBfromFile(kInfoFile): 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 = {} DB = {}
cnt = 0 cnt = 0
infoReader = open(kInfoFile, 'r') infoReader = open(kInfoFile, 'r')

View file

@ -22,16 +22,16 @@ else:
if inCalibre: if inCalibre:
if sys.platform.startswith('win'): 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'): 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: else:
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
@ -218,14 +218,14 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
print "Keys not found in " + kInfoFile print "Keys not found in " + kInfoFile
return pidlst return pidlst
# Get the HDD serial # Get the ID string used
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1) encodedIDString = encodeHash(GetIDString(),charMap1)
# Get the current user name # Get the current user name
encodedUsername = encodeHash(GetUserName(),charMap1) encodedUsername = encodeHash(GetUserName(),charMap1)
# concat, hash and encode to calculate the DSN # 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). # Compute the device PID (for which I can tell, is used for nothing).
table = generatePidEncryptionTable() table = generatePidEncryptionTable()

View file

@ -17,7 +17,7 @@ from __future__ import with_statement
# and many many others # and many many others
__version__ = '3.1' __version__ = '3.5'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):

View file

@ -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 from __future__ import with_statement
import sys import sys
import os import os
import subprocess import os.path
import subprocess
from struct import pack, unpack, unpack_from from struct import pack, unpack, unpack_from
class DrmException(Exception): class DrmException(Exception):
@ -68,11 +69,9 @@ def _load_crypto_libcrypto():
raise DrmException('AES decryption failed') raise DrmException('AES decryption failed')
return out.raw return out.raw
def keyivgen(self, passwd, salt): def keyivgen(self, passwd, salt, iter, keylen):
saltlen = len(salt) saltlen = len(salt)
passlen = len(passwd) passlen = len(passwd)
iter = 0x3e8
keylen = 80
out = create_string_buffer(keylen) out = create_string_buffer(keylen)
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out) rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
return out.raw return out.raw
@ -114,9 +113,10 @@ def SHA256(message):
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
# For Future Reference from .kinf approach of K4PC # For kinf approach of K4PC/K4Mac
charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" # On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
# For Mac they seem to re-use charMap2 here
charMap5 = charMap2
def encode(data, map): def encode(data, map):
result = "" result = ""
@ -144,7 +144,7 @@ def decode(data,map):
result += pack("B",value) result += pack("B",value)
return result 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 # generate table of prime number less than or equal to int n
def primes(n): def primes(n):
if n==2: return [2] if n==2: return [2]
@ -166,7 +166,6 @@ def primes(n):
return [2]+[x for x in s if x] return [2]+[x for x in s if x]
# uses a sub process to get the Hard Drive Serial Number using ioreg # 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" # returns with the serial number of drive whose BSD Name is "disk0"
def GetVolumeSerialNumber(): def GetVolumeSerialNumber():
@ -196,24 +195,217 @@ def GetVolumeSerialNumber():
foundIt = True foundIt = True
break break
if not foundIt: if not foundIt:
sernum = '9999999999' sernum = ''
return 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 # uses unix env to get username instead of using sysctlbyname
def GetUserName(): def GetUserName():
username = os.getenv('USER') username = os.getenv('USER')
return username return username
# implements an Pseudo Mac Version of Windows built-in Crypto routine # implements an Pseudo Mac Version of Windows built-in Crypto routine
def CryptUnprotectData(encryptedData, salt): # used by Kindle for Mac versions < 1.6.0
sp = GetVolumeSerialNumber() + '!@#' + GetUserName() def CryptUnprotectData(encryptedData):
sernum = GetVolumeSerialNumber()
if sernum == '':
sernum = '9999999999'
sp = sernum + '!@#' + GetUserName()
passwdData = encode(SHA256(sp),charMap1) passwdData = encode(SHA256(sp),charMap1)
salt = '16743'
iter = 0x3e8
keylen = 0x80
crp = LibCrypto() crp = LibCrypto()
key_iv = crp.keyivgen(passwdData, salt) key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
key = key_iv[0:32] key = key_iv[0:32]
iv = key_iv[32:48] iv = key_iv[32:48]
crp.set_decrypt_key(key,iv) crp.set_decrypt_key(key,iv)
cleartext = crp.decrypt(encryptedData) 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 return cleartext
@ -232,18 +424,16 @@ def getKindleInfoFiles(kInfoFiles):
if os.path.isfile(resline): if os.path.isfile(resline):
kInfoFiles.append(resline) kInfoFiles.append(resline)
found = True found = True
# For Future Reference # add any .kinf files
# cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
# # add any .kinf files cmdline = cmdline.encode(sys.getfilesystemencoding())
# cmdline = 'find "' + home + '/Library/Application Support" -name "rainier*.kinf"' p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
# cmdline = cmdline.encode(sys.getfilesystemencoding()) out1, out2 = p1.communicate()
# p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) reslst = out1.split('\n')
# out1, out2 = p1.communicate() for resline in reslst:
# reslst = out1.split('\n') if os.path.isfile(resline):
# for resline in reslst: kInfoFiles.append(resline)
# if os.path.isfile(resline): found = True
# kInfoFiles.append(resline)
# found = True
if not found: if not found:
print('No kindle-info files have been found.') print('No kindle-info files have been found.')
return kInfoFiles return kInfoFiles
@ -251,7 +441,7 @@ def getKindleInfoFiles(kInfoFiles):
# determine type of kindle info provided and return a # determine type of kindle info provided and return a
# database of keynames and values # database of keynames and values
def getDBfromFile(kInfoFile): 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 = {} DB = {}
cnt = 0 cnt = 0
infoReader = open(kInfoFile, 'r') infoReader = open(kInfoFile, 'r')
@ -259,7 +449,6 @@ def getDBfromFile(kInfoFile):
data = infoReader.read() data = infoReader.read()
if data.find('[') != -1 : if data.find('[') != -1 :
# older style kindle-info file # older style kindle-info file
items = data.split('[') items = data.split('[')
for item in items: for item in items:
@ -273,84 +462,89 @@ def getDBfromFile(kInfoFile):
if keyname == "unknown": if keyname == "unknown":
keyname = keyhash keyname = keyhash
encryptedValue = decode(rawdata,charMap2) encryptedValue = decode(rawdata,charMap2)
salt = '16743' cleartext = CryptUnprotectData(encryptedValue)
cleartext = CryptUnprotectData(encryptedValue, salt) DB[keyname] = cleartext
DB[keyname] = decode(cleartext,charMap1)
cnt = cnt + 1 cnt = cnt + 1
if cnt == 0: if cnt == 0:
DB = None DB = None
return DB return DB
# For Future Reference taken from K4PC 1.5.0 .kinf # else newer style .kinf file used by K4Mac >= 1.6.0
# # the .kinf file uses "/" to separate it into records
# # else newer style .kinf file # so remove the trailing "/" to make it easy to use split
# # the .kinf file uses "/" to separate it into records data = data[:-1]
# # so remove the trailing "/" to make it easy to use split items = data.split('/')
# data = data[:-1]
# items = data.split('/') # loop through the item records until all are processed
# while len(items) > 0:
# # loop through the item records until all are processed
# while len(items) > 0: # get the first item record
# item = items.pop(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
# # the first 32 chars of the first record of a group keyhash = item[0:32]
# # is the MD5 hash of the key name encoded by charMap5 keyname = "unknown"
# keyhash = item[0:32]
# # the raw keyhash string is also used to create entropy for the actual
# # the raw keyhash string is also used to create entropy for the actual # CryptProtectData Blob that represents that keys contents
# # CryptProtectData Blob that represents that keys contents # "entropy" not used for K4Mac only K4PC
# entropy = SHA1(keyhash) # entropy = SHA1(keyhash)
#
# # the remainder of the first record when decoded with charMap5 # the remainder of the first record when decoded with charMap5
# # has the ':' split char followed by the string representation # has the ':' split char followed by the string representation
# # of the number of records that follow # of the number of records that follow
# # and make up the contents # and make up the contents
# srcnt = decode(item[34:],charMap5) srcnt = decode(item[34:],charMap5)
# rcnt = int(srcnt) rcnt = int(srcnt)
#
# # read and store in rcnt records of data # read and store in rcnt records of data
# # that make up the contents value # that make up the contents value
# edlst = [] edlst = []
# for i in xrange(rcnt): for i in xrange(rcnt):
# item = items.pop(0) item = items.pop(0)
# edlst.append(item) edlst.append(item)
#
# keyname = "unknown" keyname = "unknown"
# for name in names: for name in names:
# if encodeHash(name,charMap5) == keyhash: if encodeHash(name,charMap5) == keyhash:
# keyname = name keyname = name
# break break
# if keyname == "unknown": if keyname == "unknown":
# keyname = keyhash keyname = keyhash
#
# # the charMap5 encoded contents data has had a length # the charMap5 encoded contents data has had a length
# # of chars (always odd) cut off of the front and moved # of chars (always odd) cut off of the front and moved
# # to the end to prevent decoding using charMap5 from # to the end to prevent decoding using charMap5 from
# # working properly, and thereby preventing the ensuing # working properly, and thereby preventing the ensuing
# # CryptUnprotectData call from succeeding. # CryptUnprotectData call from succeeding.
#
# # The offset into the charMap5 encoded contents seems to be: # The offset into the charMap5 encoded contents seems to be:
# # len(contents) - largest prime number less than or equal to int(len(content)/3) # 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) # (in other words split "about" 2/3rds of the way through)
#
# # move first offsets chars to end to align for decode by charMap5 # move first offsets chars to end to align for decode by charMap5
# encdata = "".join(edlst) encdata = "".join(edlst)
# contlen = len(encdata) contlen = len(encdata)
# noffset = contlen - primes(int(contlen/3))[-1]
# # now properly split and recombine
# # now properly split and recombine # by moving noffset chars from the start of the
# # by moving noffset chars from the start of the # string to the end of the string
# # string to the end of the string noffset = contlen - primes(int(contlen/3))[-1]
# pfx = encdata[0:noffset] pfx = encdata[0:noffset]
# encdata = encdata[noffset:] encdata = encdata[noffset:]
# encdata = encdata + pfx encdata = encdata + pfx
#
# # decode using Map5 to get the CryptProtect Data # decode using charMap5 to get the CryptProtect Data
# encryptedValue = decode(encdata,charMap5) encryptedValue = decode(encdata,charMap5)
# DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) cleartext = CryptUnprotectDataV2(encryptedValue)
# cnt = cnt + 1 # Debugging
# print keyname
# print cleartext
# print cleartext.encode('hex')
# print
DB[keyname] = cleartext
cnt = cnt + 1
if cnt == 0: if cnt == 0:
DB = None DB = None

View file

@ -122,6 +122,9 @@ def GetVolumeSerialNumber():
return GetVolumeSerialNumber return GetVolumeSerialNumber
GetVolumeSerialNumber = GetVolumeSerialNumber() GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetIDString():
return GetVolumeSerialNumber()
def getLastError(): def getLastError():
GetLastError = kernel32.GetLastError GetLastError = kernel32.GetLastError
GetLastError.argtypes = None GetLastError.argtypes = None
@ -181,18 +184,27 @@ def getKindleInfoFiles(kInfoFiles):
kInfoFiles.append(kinfopath) kInfoFiles.append(kinfopath)
# now look for newer (K4PC 1.5.0 and later rainier.2.1.1.kinf file # 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' kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
if not os.path.isfile(kinfopath): 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: else:
kInfoFiles.append(kinfopath) 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 return kInfoFiles
# determine type of kindle info provided and return a # determine type of kindle info provided and return a
# database of keynames and values # database of keynames and values
def getDBfromFile(kInfoFile): 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 = {} DB = {}
cnt = 0 cnt = 0
infoReader = open(kInfoFile, 'r') infoReader = open(kInfoFile, 'r')

View file

@ -22,16 +22,16 @@ else:
if inCalibre: if inCalibre:
if sys.platform.startswith('win'): 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'): 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: else:
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
@ -218,14 +218,14 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
print "Keys not found in " + kInfoFile print "Keys not found in " + kInfoFile
return pidlst return pidlst
# Get the HDD serial # Get the ID string used
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1) encodedIDString = encodeHash(GetIDString(),charMap1)
# Get the current user name # Get the current user name
encodedUsername = encodeHash(GetUserName(),charMap1) encodedUsername = encodeHash(GetUserName(),charMap1)
# concat, hash and encode to calculate the DSN # 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). # Compute the device PID (for which I can tell, is used for nothing).
table = generatePidEncryptionTable() table = generatePidEncryptionTable()

View file

@ -17,7 +17,7 @@ from __future__ import with_statement
# and many many others # and many many others
__version__ = '3.1' __version__ = '3.5'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):

View file

@ -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 from __future__ import with_statement
import sys import sys
import os import os
import subprocess import os.path
import subprocess
from struct import pack, unpack, unpack_from from struct import pack, unpack, unpack_from
class DrmException(Exception): class DrmException(Exception):
@ -68,11 +69,9 @@ def _load_crypto_libcrypto():
raise DrmException('AES decryption failed') raise DrmException('AES decryption failed')
return out.raw return out.raw
def keyivgen(self, passwd, salt): def keyivgen(self, passwd, salt, iter, keylen):
saltlen = len(salt) saltlen = len(salt)
passlen = len(passwd) passlen = len(passwd)
iter = 0x3e8
keylen = 80
out = create_string_buffer(keylen) out = create_string_buffer(keylen)
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out) rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
return out.raw return out.raw
@ -114,9 +113,10 @@ def SHA256(message):
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
# For Future Reference from .kinf approach of K4PC # For kinf approach of K4PC/K4Mac
charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" # On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
# For Mac they seem to re-use charMap2 here
charMap5 = charMap2
def encode(data, map): def encode(data, map):
result = "" result = ""
@ -144,7 +144,7 @@ def decode(data,map):
result += pack("B",value) result += pack("B",value)
return result 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 # generate table of prime number less than or equal to int n
def primes(n): def primes(n):
if n==2: return [2] if n==2: return [2]
@ -166,7 +166,6 @@ def primes(n):
return [2]+[x for x in s if x] return [2]+[x for x in s if x]
# uses a sub process to get the Hard Drive Serial Number using ioreg # 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" # returns with the serial number of drive whose BSD Name is "disk0"
def GetVolumeSerialNumber(): def GetVolumeSerialNumber():
@ -196,24 +195,217 @@ def GetVolumeSerialNumber():
foundIt = True foundIt = True
break break
if not foundIt: if not foundIt:
sernum = '9999999999' sernum = ''
return 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 # uses unix env to get username instead of using sysctlbyname
def GetUserName(): def GetUserName():
username = os.getenv('USER') username = os.getenv('USER')
return username return username
# implements an Pseudo Mac Version of Windows built-in Crypto routine # implements an Pseudo Mac Version of Windows built-in Crypto routine
def CryptUnprotectData(encryptedData, salt): # used by Kindle for Mac versions < 1.6.0
sp = GetVolumeSerialNumber() + '!@#' + GetUserName() def CryptUnprotectData(encryptedData):
sernum = GetVolumeSerialNumber()
if sernum == '':
sernum = '9999999999'
sp = sernum + '!@#' + GetUserName()
passwdData = encode(SHA256(sp),charMap1) passwdData = encode(SHA256(sp),charMap1)
salt = '16743'
iter = 0x3e8
keylen = 0x80
crp = LibCrypto() crp = LibCrypto()
key_iv = crp.keyivgen(passwdData, salt) key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
key = key_iv[0:32] key = key_iv[0:32]
iv = key_iv[32:48] iv = key_iv[32:48]
crp.set_decrypt_key(key,iv) crp.set_decrypt_key(key,iv)
cleartext = crp.decrypt(encryptedData) 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 return cleartext
@ -232,18 +424,16 @@ def getKindleInfoFiles(kInfoFiles):
if os.path.isfile(resline): if os.path.isfile(resline):
kInfoFiles.append(resline) kInfoFiles.append(resline)
found = True found = True
# For Future Reference # add any .kinf files
# cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
# # add any .kinf files cmdline = cmdline.encode(sys.getfilesystemencoding())
# cmdline = 'find "' + home + '/Library/Application Support" -name "rainier*.kinf"' p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
# cmdline = cmdline.encode(sys.getfilesystemencoding()) out1, out2 = p1.communicate()
# p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) reslst = out1.split('\n')
# out1, out2 = p1.communicate() for resline in reslst:
# reslst = out1.split('\n') if os.path.isfile(resline):
# for resline in reslst: kInfoFiles.append(resline)
# if os.path.isfile(resline): found = True
# kInfoFiles.append(resline)
# found = True
if not found: if not found:
print('No kindle-info files have been found.') print('No kindle-info files have been found.')
return kInfoFiles return kInfoFiles
@ -251,7 +441,7 @@ def getKindleInfoFiles(kInfoFiles):
# determine type of kindle info provided and return a # determine type of kindle info provided and return a
# database of keynames and values # database of keynames and values
def getDBfromFile(kInfoFile): 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 = {} DB = {}
cnt = 0 cnt = 0
infoReader = open(kInfoFile, 'r') infoReader = open(kInfoFile, 'r')
@ -259,7 +449,6 @@ def getDBfromFile(kInfoFile):
data = infoReader.read() data = infoReader.read()
if data.find('[') != -1 : if data.find('[') != -1 :
# older style kindle-info file # older style kindle-info file
items = data.split('[') items = data.split('[')
for item in items: for item in items:
@ -273,84 +462,89 @@ def getDBfromFile(kInfoFile):
if keyname == "unknown": if keyname == "unknown":
keyname = keyhash keyname = keyhash
encryptedValue = decode(rawdata,charMap2) encryptedValue = decode(rawdata,charMap2)
salt = '16743' cleartext = CryptUnprotectData(encryptedValue)
cleartext = CryptUnprotectData(encryptedValue, salt) DB[keyname] = cleartext
DB[keyname] = decode(cleartext,charMap1)
cnt = cnt + 1 cnt = cnt + 1
if cnt == 0: if cnt == 0:
DB = None DB = None
return DB return DB
# For Future Reference taken from K4PC 1.5.0 .kinf # else newer style .kinf file used by K4Mac >= 1.6.0
# # the .kinf file uses "/" to separate it into records
# # else newer style .kinf file # so remove the trailing "/" to make it easy to use split
# # the .kinf file uses "/" to separate it into records data = data[:-1]
# # so remove the trailing "/" to make it easy to use split items = data.split('/')
# data = data[:-1]
# items = data.split('/') # loop through the item records until all are processed
# while len(items) > 0:
# # loop through the item records until all are processed
# while len(items) > 0: # get the first item record
# item = items.pop(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
# # the first 32 chars of the first record of a group keyhash = item[0:32]
# # is the MD5 hash of the key name encoded by charMap5 keyname = "unknown"
# keyhash = item[0:32]
# # the raw keyhash string is also used to create entropy for the actual
# # the raw keyhash string is also used to create entropy for the actual # CryptProtectData Blob that represents that keys contents
# # CryptProtectData Blob that represents that keys contents # "entropy" not used for K4Mac only K4PC
# entropy = SHA1(keyhash) # entropy = SHA1(keyhash)
#
# # the remainder of the first record when decoded with charMap5 # the remainder of the first record when decoded with charMap5
# # has the ':' split char followed by the string representation # has the ':' split char followed by the string representation
# # of the number of records that follow # of the number of records that follow
# # and make up the contents # and make up the contents
# srcnt = decode(item[34:],charMap5) srcnt = decode(item[34:],charMap5)
# rcnt = int(srcnt) rcnt = int(srcnt)
#
# # read and store in rcnt records of data # read and store in rcnt records of data
# # that make up the contents value # that make up the contents value
# edlst = [] edlst = []
# for i in xrange(rcnt): for i in xrange(rcnt):
# item = items.pop(0) item = items.pop(0)
# edlst.append(item) edlst.append(item)
#
# keyname = "unknown" keyname = "unknown"
# for name in names: for name in names:
# if encodeHash(name,charMap5) == keyhash: if encodeHash(name,charMap5) == keyhash:
# keyname = name keyname = name
# break break
# if keyname == "unknown": if keyname == "unknown":
# keyname = keyhash keyname = keyhash
#
# # the charMap5 encoded contents data has had a length # the charMap5 encoded contents data has had a length
# # of chars (always odd) cut off of the front and moved # of chars (always odd) cut off of the front and moved
# # to the end to prevent decoding using charMap5 from # to the end to prevent decoding using charMap5 from
# # working properly, and thereby preventing the ensuing # working properly, and thereby preventing the ensuing
# # CryptUnprotectData call from succeeding. # CryptUnprotectData call from succeeding.
#
# # The offset into the charMap5 encoded contents seems to be: # The offset into the charMap5 encoded contents seems to be:
# # len(contents) - largest prime number less than or equal to int(len(content)/3) # 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) # (in other words split "about" 2/3rds of the way through)
#
# # move first offsets chars to end to align for decode by charMap5 # move first offsets chars to end to align for decode by charMap5
# encdata = "".join(edlst) encdata = "".join(edlst)
# contlen = len(encdata) contlen = len(encdata)
# noffset = contlen - primes(int(contlen/3))[-1]
# # now properly split and recombine
# # now properly split and recombine # by moving noffset chars from the start of the
# # by moving noffset chars from the start of the # string to the end of the string
# # string to the end of the string noffset = contlen - primes(int(contlen/3))[-1]
# pfx = encdata[0:noffset] pfx = encdata[0:noffset]
# encdata = encdata[noffset:] encdata = encdata[noffset:]
# encdata = encdata + pfx encdata = encdata + pfx
#
# # decode using Map5 to get the CryptProtect Data # decode using charMap5 to get the CryptProtect Data
# encryptedValue = decode(encdata,charMap5) encryptedValue = decode(encdata,charMap5)
# DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) cleartext = CryptUnprotectDataV2(encryptedValue)
# cnt = cnt + 1 # Debugging
# print keyname
# print cleartext
# print cleartext.encode('hex')
# print
DB[keyname] = cleartext
cnt = cnt + 1
if cnt == 0: if cnt == 0:
DB = None DB = None

View file

@ -122,6 +122,9 @@ def GetVolumeSerialNumber():
return GetVolumeSerialNumber return GetVolumeSerialNumber
GetVolumeSerialNumber = GetVolumeSerialNumber() GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetIDString():
return GetVolumeSerialNumber()
def getLastError(): def getLastError():
GetLastError = kernel32.GetLastError GetLastError = kernel32.GetLastError
GetLastError.argtypes = None GetLastError.argtypes = None
@ -181,18 +184,27 @@ def getKindleInfoFiles(kInfoFiles):
kInfoFiles.append(kinfopath) kInfoFiles.append(kinfopath)
# now look for newer (K4PC 1.5.0 and later rainier.2.1.1.kinf file # 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' kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
if not os.path.isfile(kinfopath): 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: else:
kInfoFiles.append(kinfopath) 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 return kInfoFiles
# determine type of kindle info provided and return a # determine type of kindle info provided and return a
# database of keynames and values # database of keynames and values
def getDBfromFile(kInfoFile): 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 = {} DB = {}
cnt = 0 cnt = 0
infoReader = open(kInfoFile, 'r') infoReader = open(kInfoFile, 'r')

View file

@ -22,16 +22,16 @@ else:
if inCalibre: if inCalibre:
if sys.platform.startswith('win'): 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'): 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: else:
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
@ -218,14 +218,14 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
print "Keys not found in " + kInfoFile print "Keys not found in " + kInfoFile
return pidlst return pidlst
# Get the HDD serial # Get the ID string used
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1) encodedIDString = encodeHash(GetIDString(),charMap1)
# Get the current user name # Get the current user name
encodedUsername = encodeHash(GetUserName(),charMap1) encodedUsername = encodeHash(GetUserName(),charMap1)
# concat, hash and encode to calculate the DSN # 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). # Compute the device PID (for which I can tell, is used for nothing).
table = generatePidEncryptionTable() table = generatePidEncryptionTable()

View file

@ -25,7 +25,7 @@ adb pull /data/app/com.amazon.kindle-1.apk kindle3.apk
adb uninstall com.amazon.kindle adb uninstall com.amazon.kindle
apktool d kindle3.apk kindle3 apktool d kindle3.apk kindle3
cd kindle3 cd kindle3
patch -p1 < ..\kindle3.patch patch -p1 < ../kindle3.patch
cd .. cd ..
apktool b kindle3 kindle3_patched.apk apktool b kindle3 kindle3_patched.apk
jarsigner -verbose -keystore kindle.keystore kindle3_patched.apk kindle jarsigner -verbose -keystore kindle.keystore kindle3_patched.apk kindle