Mostly Mac fixes. mobidedrm.py now works, and k4mobidedrm for at least some input. kindlekey.py should be working too. But lots more changes and testing to do.

This commit is contained in:
Apprentice Harper 2020-10-04 20:36:12 +01:00
parent 2eb31c8fb5
commit e31752e334
17 changed files with 248 additions and 220 deletions

View File

@ -95,10 +95,8 @@ def unicode_argv():
# this should never happen
return ["kindlekey.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return argv
argvencoding = sys.stdin.encoding or "utf-8"
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
class DrmException(Exception):
pass
@ -336,7 +334,7 @@ def cli_main():
sys.stderr=SafeUnbuffered(sys.stderr)
argv=unicode_argv()
progname = os.path.basename(argv[0])
print("{0} v{1}\nCopyright © 2010-2015 Thom, some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__))
print("{0} v{1}\nCopyright © 2010-2020 Thom, Apprentice Harper et al.".format(progname,__version__))
try:
opts, args = getopt.getopt(argv[1:], "hb:")
@ -386,48 +384,48 @@ def cli_main():
def gui_main():
try:
import Tkinter
import Tkconstants
import tkMessageBox
import tkFileDialog
import tkinter
import tkinter.constants
import tkinter.messagebox
import tkinter.filedialog
except:
print("Tkinter not installed")
print("tkinter not installed")
return cli_main()
class DecryptionDialog(Tkinter.Frame):
class DecryptionDialog(tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text="Select backup.ab file")
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
tkinter.Frame.__init__(self, root, border=5)
self.status = tkinter.Label(self, text="Select backup.ab file")
self.status.pack(fill=tkinter.constants.X, expand=1)
body = tkinter.Frame(self)
body.pack(fill=tkinter.constants.X, expand=1)
sticky = tkinter.constants.E + tkinter.constants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text="Backup file").grid(row=0, column=0)
self.keypath = Tkinter.Entry(body, width=40)
tkinter.Label(body, text="Backup file").grid(row=0, column=0)
self.keypath = tkinter.Entry(body, width=40)
self.keypath.grid(row=0, column=1, sticky=sticky)
self.keypath.insert(2, "backup.ab")
button = Tkinter.Button(body, text="...", command=self.get_keypath)
button = tkinter.Button(body, text="...", command=self.get_keypath)
button.grid(row=0, column=2)
buttons = Tkinter.Frame(self)
buttons = tkinter.Frame(self)
buttons.pack()
button2 = Tkinter.Button(
button2 = tkinter.Button(
buttons, text="Extract", width=10, command=self.generate)
button2.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button3 = Tkinter.Button(
button2.pack(side=tkinter.constants.LEFT)
tkinter.Frame(buttons, width=10).pack(side=tkinter.constants.LEFT)
button3 = tkinter.Button(
buttons, text="Quit", width=10, command=self.quit)
button3.pack(side=Tkconstants.RIGHT)
button3.pack(side=tkinter.constants.RIGHT)
def get_keypath(self):
keypath = tkFileDialog.askopenfilename(
keypath = tkinter.filedialog.askopenfilename(
parent=None, title="Select backup.ab file",
defaultextension=".ab",
filetypes=[('adb backup com.amazon.kindle', '.ab'),
('All Files', '.*')])
if keypath:
keypath = os.path.normpath(keypath)
self.keypath.delete(0, Tkconstants.END)
self.keypath.delete(0, tkinter.constants.END)
self.keypath.insert(0, keypath)
return
@ -447,7 +445,7 @@ def gui_main():
with open(outfile, 'w') as keyfileout:
keyfileout.write(key)
success = True
tkMessageBox.showinfo(progname, "Key successfully retrieved to {0}".format(outfile))
tkinter.messagebox.showinfo(progname, "Key successfully retrieved to {0}".format(outfile))
except Exception as e:
self.status['text'] = "Error: {0}".format(e.args[0])
return
@ -455,11 +453,11 @@ def gui_main():
argv=unicode_argv()
progpath, progname = os.path.split(argv[0])
root = Tkinter.Tk()
root = tkinter.Tk()
root.title("Kindle for Android Key Extraction v.{0}".format(__version__))
root.resizable(True, False)
root.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
DecryptionDialog(root).pack(fill=tkinter.constants.X, expand=1)
root.mainloop()
return 0

View File

@ -5,18 +5,25 @@
# For use with Topaz Scripts Version 2.6
# Python 3, September 2020
class Unbuffered:
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data, str):
data = data.encode(self.encoding,"replace")
self.stream.buffer.write(data)
self.stream.buffer.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
import sys
sys.stdout=Unbuffered(sys.stdout)
import csv
import os
import getopt
@ -834,6 +841,8 @@ def usage():
#
def main(argv):
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
dictFile = ""
pageFile = ""
debug = False

View File

@ -128,10 +128,8 @@ def unicode_argv():
# this should never happen
return ["mobidedrm.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return argv
argvencoding = sys.stdin.encoding or "utf-8"
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
Des = None
if iswindows:
@ -516,7 +514,7 @@ def decryptBook(infile, outpath, make_pmlz, user_key):
# remove temporary directory
shutil.rmtree(outdir, True)
print("Output is {0}".format(pmlzname))
else:
else:
print("Output is in {0}".format(outdir))
print("done")
except ValueError as e:

View File

@ -4,18 +4,25 @@
# Python 3 for calibre 5.0
from __future__ import print_function
class Unbuffered:
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data, str):
data = data.encode(self.encoding,"replace")
self.stream.buffer.write(data)
self.stream.buffer.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
import sys
sys.stdout=Unbuffered(sys.stdout)
import csv
import os
import getopt
@ -687,6 +694,8 @@ def usage():
def main(argv):
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
bookDir = ''
if len(argv) == 0:
argv = sys.argv

View File

@ -95,10 +95,8 @@ def unicode_argv():
range(start, argc.value)]
return ["ineptepub.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return argv
argvencoding = sys.stdin.encoding or "utf-8"
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
class IGNOBLEError(Exception):

View File

@ -83,10 +83,8 @@ def unicode_argv():
# this should never happen
return ["ignoblekey.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return argv
argvencoding = sys.stdin.encoding or "utf-8"
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
class DrmException(Exception):
pass

View File

@ -90,10 +90,8 @@ def unicode_argv():
# this should never happen
return ["ignoblekeyfetch.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return argv
argvencoding = sys.stdin.encoding or "utf-8"
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
class IGNOBLEError(Exception):
@ -109,18 +107,17 @@ def fetch_key(email, password):
import random
random = "%030x" % random.randrange(16**30)
import urllib, urllib2, re
import urllib.parse, urllib.request, re
# try the URL from nook for PC
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
fetch_url += urllib.quote(password,'')+"&devID=PC_BN_2.5.6.9575_"+random+"&emailAddress="
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
fetch_url += urllib.parse.quote(password,'')+"&devID=PC_BN_2.5.6.9575_"+random+"&emailAddress="
fetch_url += urllib.parse.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
#print fetch_url
found = ''
try:
req = urllib2.Request(fetch_url)
response = urllib2.urlopen(req)
response = urllib.request.urlopen(fetch_url)
the_page = response.read()
#print the_page
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
@ -129,14 +126,13 @@ def fetch_key(email, password):
if len(found)!=28:
# try the URL from android devices
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
fetch_url += urllib.quote(password,'')+"&devID=hobbes_9.3.50818_"+random+"&emailAddress="
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
fetch_url += urllib.parse.quote(password,'')+"&devID=hobbes_9.3.50818_"+random+"&emailAddress="
fetch_url += urllib.parse.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
#print fetch_url
found = ''
try:
req = urllib2.Request(fetch_url)
response = urllib2.urlopen(req)
response = urllib.request.urlopen(fetch_url)
the_page = response.read()
#print the_page
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)

View File

@ -42,6 +42,7 @@ __version__ = "3.0"
import sys
import os
import hashlib
import base64
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
@ -99,10 +100,8 @@ def unicode_argv():
# this should never happen
return ["ignoblekeygen.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return argv
argvencoding = sys.stdin.encoding or "utf-8"
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
class IGNOBLEError(Exception):
@ -195,23 +194,24 @@ def normalize_name(name):
def generate_key(name, ccn):
# remove spaces and case from name and CC numbers.
if type(name)==bytes:
name = normalize_name(name)
ccn = normalize_name(ccn)
if type(name)==str:
name = name.encode('utf-8')
if type(ccn)==bytes:
if type(ccn)==str:
ccn = ccn.encode('utf-8')
name = normalize_name(name) + '\x00'
ccn = normalize_name(ccn) + '\x00'
name = name + b'\x00'
ccn = ccn + b'\x00'
name_sha = hashlib.sha1(name).digest()[:16]
ccn_sha = hashlib.sha1(ccn).digest()[:16]
both_sha = hashlib.sha1(name + ccn).digest()
aes = AES(ccn_sha, name_sha)
crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c))
crypt = aes.encrypt(both_sha + (b'\x0c' * 0x0c))
userkey = hashlib.sha1(crypt).digest()
return userkey.encode('base64')
return base64.b64encode(userkey)
def cli_main():

View File

@ -85,10 +85,8 @@ def unicode_argv():
xrange(start, argc.value)]
return ["ignoblepdf.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
argvencoding = sys.stdin.encoding or "utf-8"
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
class IGNOBLEError(Exception):
@ -241,7 +239,10 @@ ARC4, AES = _load_crypto()
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
# Do we generate cross reference streams on output?
@ -546,7 +547,7 @@ class PSBaseParser(object):
except ValueError:
pass
return (self.parse_main, j)
def parse_decimal(self, s, i):
m = END_NUMBER.search(s, i)
if not m:

View File

@ -102,10 +102,8 @@ def unicode_argv():
range(start, argc.value)]
return ["ineptepub.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return argv
argvencoding = sys.stdin.encoding or "utf-8"
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
class ADEPTError(Exception):

View File

@ -146,10 +146,8 @@ def unicode_argv():
# this should never happen
return ["mobidedrm.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return argv
argvencoding = sys.stdin.encoding or "utf-8"
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
# cleanup unicode filenames
# borrowed from calibre from calibre/src/calibre/__init__.py
@ -337,7 +335,7 @@ def cli_main():
if o == "-p":
if a == None :
raise DrmException("Invalid parameter for -p")
pids = a.split(',')
pids = a.encode('utf-8').split(b',')
if o == "-s":
if a == None :
raise DrmException("Invalid parameter for -s")

View File

@ -18,11 +18,6 @@ except ImportError:
except ImportError:
from io import StringIO
try:
from calibre_plugins.dedrm import ion
except ImportError:
import ion
__license__ = 'GPL v3'
__version__ = '2.0'
@ -38,6 +33,10 @@ class KFXZipBook:
return (None, None)
def processBook(self, totalpids):
try:
import ion
except:
from calibre_plugins.dedrm import ion
with zipfile.ZipFile(self.infile, 'r') as zf:
for filename in zf.namelist():
with zf.open(filename) as fh:

View File

@ -205,7 +205,7 @@ def getK4Pids(rec209, token, kindleDatabase):
try:
# Get the kindle account token, if present
kindleAccountToken = bytearray.fromhex((kindleDatabase[1])['kindle.account.tokens']).decode()
kindleAccountToken = bytearray.fromhex((kindleDatabase[1])[b'kindle.account.tokens']).decode()
except KeyError:
kindleAccountToken=""
@ -219,37 +219,37 @@ def getK4Pids(rec209, token, kindleDatabase):
# See if we have the info to generate the DSN
try:
# Get the Mazama Random number
MazamaRandomNumber = bytearray.fromhex((kindleDatabase[1])['MazamaRandomNumber']).decode()
MazamaRandomNumber = bytearray.fromhex((kindleDatabase[1])[b'MazamaRandomNumber']).decode()
#print "Got MazamaRandomNumber from database {0}".format(kindleDatabase[0])
try:
# Get the SerialNumber token, if present
IDString = bytearray.fromhex((kindleDatabase[1])['SerialNumber']).decode()
IDString = bytearray.fromhex((kindleDatabase[1])[b'SerialNumber']).decode()
print("Got SerialNumber from database {0}".format(kindleDatabase[0]))
except KeyError:
# Get the IDString we added
IDString = bytearray.fromhex((kindleDatabase[1])['IDString']).decode()
IDString = bytearray.fromhex((kindleDatabase[1])[b'IDString']).decode()
try:
# Get the UsernameHash token, if present
encodedUsername = bytearray.fromhex((kindleDatabase[1])['UsernameHash']).decode()
encodedUsername = bytearray.fromhex((kindleDatabase[1])[b'UsernameHash']).decode()
print("Got UsernameHash from database {0}".format(kindleDatabase[0]))
except KeyError:
# Get the UserName we added
UserName = bytearray.fromhex((kindleDatabase[1])['UserName']).decode()
UserName = bytearray.fromhex((kindleDatabase[1])[b'UserName']).decode()
# encode it
encodedUsername = encodeHash(UserName.encode(),charMap1)
encodedUsername = encodeHash(UserName,charMap1)
#print "encodedUsername",encodedUsername.encode('hex')
except KeyError:
print("Keys not found in the database {0}.".format(kindleDatabase[0]))
return pids
# Get the ID string used
encodedIDString = encodeHash(IDString.encode(),charMap1)
encodedIDString = encodeHash(IDString,charMap1)
#print "encodedIDString",encodedIDString.encode('hex')
# concat, hash and encode to calculate the DSN
DSN = encode(SHA1((MazamaRandomNumber+encodedIDString+encodedUsername).encode()),charMap1)
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
#print "DSN",DSN.encode('hex')
pass

View File

@ -39,6 +39,7 @@ import sys, os, re
from struct import pack, unpack, unpack_from
import json
import getopt
import traceback
try:
RegError
@ -58,10 +59,11 @@ class SafeUnbuffered:
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
if isinstance(data, str):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
self.stream.buffer.write(data)
self.stream.buffer.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
@ -99,15 +101,13 @@ def unicode_argv():
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return ["kindlekey.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return arg
argvencoding = sys.stdin.encoding or "utf-8"
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
class DrmException(Exception):
pass
@ -155,13 +155,13 @@ def primes(n):
# Encode the bytes in data with the characters in map
def encode(data, map):
result = ''
result = b''
for char in data:
value = ord(char)
value = char
Q = (value ^ 0x80) // len(map)
R = value % len(map)
result += map[Q]
result += map[R]
result += bytes(map[Q])
result += bytes(map[R])
return result
# Hash the bytes in data and then encode the digest with the characters in map
@ -170,7 +170,7 @@ def encodeHash(data,map):
# Decode the string in data with the characters in map. Returns the decoded bytes
def decode(data,map):
result = ''
result = b''
for i in range (0,len(data)-1,2):
high = map.find(data[i])
low = map.find(data[i+1])
@ -833,12 +833,12 @@ if iswindows:
# Various character maps used to decrypt kindle info values.
# Probably supposed to act as obfuscation
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
charMap2 = b"AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
charMap5 = b"AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
# New maps in K4PC 1.9.0
testMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
testMap6 = "9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG"
testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
testMap1 = b"n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
testMap6 = b"9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG"
testMap8 = b"YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
# interface with Windows OS Routines
class DataBlob(Structure):
@ -900,9 +900,9 @@ if iswindows:
# double the buffer size
buffer = create_unicode_buffer(len(buffer) * 2)
size.value = len(buffer)
# replace any non-ASCII values with 0xfffd
for i in xrange(0,len(buffer)):
for i in range(0,len(buffer)):
if buffer[i]>"\u007f":
#print "swapping char "+str(i)+" ("+buffer[i]+")"
buffer[i] = "\ufffd"
@ -985,7 +985,7 @@ if iswindows:
found = True
print('Found K4PC 1.25+ kinf2018 file: ' + kinfopath.encode('ascii','ignore'))
kInfoFiles.append(kinfopath)
# look for (K4PC 1.9.0 and later) .kinf2011 file
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
if os.path.isfile(kinfopath):
@ -1023,28 +1023,28 @@ if iswindows:
# database of keynames and values
def getDBfromFile(kInfoFile):
names = [\
'kindle.account.tokens',\
'kindle.cookie.item',\
'eulaVersionAccepted',\
'login_date',\
'kindle.token.item',\
'login',\
'kindle.key.item',\
'kindle.name.info',\
'kindle.device.info',\
'MazamaRandomNumber',\
'max_date',\
'SIGVERIF',\
'build_version',\
'SerialNumber',\
'UsernameHash',\
'kindle.directedid.info',\
'DSN',\
'kindle.accounttype.info',\
'krx.flashcardsplugin.data.encryption_key',\
'krx.notebookexportplugin.data.encryption_key',\
'proxy.http.password',\
'proxy.http.username'
b'kindle.account.tokens',\
b'kindle.cookie.item',\
b'eulaVersionAccepted',\
b'login_date',\
b'kindle.token.item',\
b'login',\
b'kindle.key.item',\
b'kindle.name.info',\
b'kindle.device.info',\
b'MazamaRandomNumber',\
b'max_date',\
b'SIGVERIF',\
b'build_version',\
b'SerialNumber',\
b'UsernameHash',\
b'kindle.directedid.info',\
b'DSN',\
b'kindle.accounttype.info',\
b'krx.flashcardsplugin.data.encryption_key',\
b'krx.notebookexportplugin.data.encryption_key',\
b'proxy.http.password',\
b'proxy.http.username'
]
DB = {}
with open(kInfoFile, 'rb') as infoReader:
@ -1053,7 +1053,7 @@ if iswindows:
# the .kinf file uses "/" to separate it into records
# so remove the trailing "/" to make it easy to use split
data = data[:-1]
items = data.split('/')
items = data.split(b'/')
# starts with an encoded and encrypted header blob
headerblob = items.pop(0)
@ -1095,7 +1095,7 @@ if iswindows:
# read and store in rcnt records of data
# that make up the contents value
edlst = []
for i in xrange(rcnt):
for i in range(rcnt):
item = items.pop(0)
edlst.append(item)
@ -1276,8 +1276,8 @@ elif isosx:
LibCrypto = _load_crypto()
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
charMap2 = 'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM'
charMap1 = b'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
charMap2 = b'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM'
# For kinf approach of K4Mac 1.6.X or later
# On K4PC charMap5 = 'AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE'
@ -1285,7 +1285,7 @@ elif isosx:
charMap5 = charMap2
# new in K4M 1.9.X
testMap8 = 'YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD'
testMap8 = b'YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD'
# uses a sub process to get the Hard Drive Serial Number using ioreg
# returns serial numbers of all internal hard drive drives
@ -1299,11 +1299,11 @@ elif isosx:
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
out1, out2 = p.communicate()
#print out1
reslst = out1.split('\n')
reslst = out1.split(b'\n')
cnt = len(reslst)
for j in xrange(cnt):
for j in range(cnt):
resline = reslst[j]
pp = resline.find('\"Serial Number\" = \"')
pp = resline.find(b'\"Serial Number\" = \"')
if pp >= 0:
sernum = resline[pp+19:-1]
sernums.append(sernum.strip())
@ -1315,12 +1315,12 @@ elif isosx:
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')
reslst = out1.split(b'\n')
cnt = len(reslst)
for j in xrange(cnt):
for j in range(cnt):
resline = reslst[j]
if resline.startswith('/dev'):
(devpart, mpath) = resline.split(' on ')[:2]
if resline.startswith(b'/dev'):
(devpart, mpath) = resline.split(b' on ')[:2]
dpart = devpart[5:]
names.append(dpart)
return names
@ -1336,11 +1336,11 @@ elif isosx:
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
out1, out2 = p.communicate()
#print out1
reslst = out1.split('\n')
reslst = out1.split(b'\n')
cnt = len(reslst)
for j in xrange(cnt):
for j in range(cnt):
resline = reslst[j]
pp = resline.find('\"UUID\" = \"')
pp = resline.find(b'\"UUID\" = \"')
if pp >= 0:
uuidnum = resline[pp+10:-1]
uuidnum = uuidnum.strip()
@ -1356,16 +1356,16 @@ elif isosx:
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')
reslst = out1.split(b'\n')
cnt = len(reslst)
for j in xrange(cnt):
for j in range(cnt):
resline = reslst[j]
pp = resline.find('Ethernet Address: ')
pp = resline.find(b'Ethernet Address: ')
if pp >= 0:
#print resline
macnum = resline[pp+18:]
macnum = macnum.strip()
maclst = macnum.split(':')
maclst = macnum.split(b':')
n = len(maclst)
if n != 6:
continue
@ -1373,7 +1373,7 @@ elif isosx:
# now munge it up the way Kindle app does
# by xoring it with 0xa5 and swapping elements 3 and 4
for i in range(6):
maclst[i] = int('0x' + maclst[i], 0)
maclst[i] = int(b'0x' + maclst[i], 0)
mlst = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
mlst[5] = maclst[5] ^ 0xa5
mlst[4] = maclst[3] ^ 0xa5
@ -1381,7 +1381,7 @@ elif isosx:
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])
macnum = b'%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x' % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
#print 'munged mac', macnum
macnums.append(macnum)
return macnums
@ -1391,7 +1391,7 @@ elif isosx:
def GetUserName():
username = os.getenv('USER')
#print "Username:",username
return username
return username.encode('utf-8')
def GetIDStrings():
# Return all possible ID Strings
@ -1400,7 +1400,7 @@ elif isosx:
strings.extend(GetVolumesSerialNumbers())
strings.extend(GetDiskPartitionNames())
strings.extend(GetDiskPartitionUUIDs())
strings.append('9999999999')
strings.append(b'9999999999')
#print "ID Strings:\n",strings
return strings
@ -1408,8 +1408,8 @@ elif isosx:
# unprotect the new header blob in .kinf2011
# used in Kindle for Mac Version >= 1.9.0
def UnprotectHeaderData(encryptedData):
passwdData = 'header_key_data'
salt = 'HEADER.2011'
passwdData = b'header_key_data'
salt = b'HEADER.2011'
iter = 0x80
keylen = 0x100
crp = LibCrypto()
@ -1424,7 +1424,7 @@ elif isosx:
# implements an Pseudo Mac Version of Windows built-in Crypto routine
class CryptUnprotectData(object):
def __init__(self, entropy, IDString):
sp = GetUserName() + '+@#$%+' + IDString
sp = GetUserName() + b'+@#$%+' + IDString
passwdData = encode(SHA256(sp),charMap2)
salt = entropy
self.crp = LibCrypto()
@ -1503,59 +1503,79 @@ elif isosx:
# database of keynames and values
def getDBfromFile(kInfoFile):
names = [\
'kindle.account.tokens',\
'kindle.cookie.item',\
'eulaVersionAccepted',\
'login_date',\
'kindle.token.item',\
'login',\
'kindle.key.item',\
'kindle.name.info',\
'kindle.device.info',\
'MazamaRandomNumber',\
'max_date',\
'SIGVERIF',\
'build_version',\
'SerialNumber',\
'UsernameHash',\
'kindle.directedid.info',\
'DSN'
]
b'kindle.account.tokens',\
b'kindle.cookie.item',\
b'eulaVersionAccepted',\
b'login_date',\
b'kindle.token.item',\
b'login',\
b'kindle.key.item',\
b'kindle.name.info',\
b'kindle.device.info',\
b'MazamaRandomNumber',\
b'max_date',\
b'SIGVERIF',\
b'build_version',\
b'SerialNumber',\
b'UsernameHash',\
b'kindle.directedid.info',\
b'DSN'
b'kindle.accounttype.info',\
b'krx.flashcardsplugin.data.encryption_key',\
b'krx.notebookexportplugin.data.encryption_key',\
b'proxy.http.password',\
b'proxy.http.username'
]
with open(kInfoFile, 'rb') as infoReader:
filedata = infoReader.read()
data = filedata[:-1]
items = data.split('/')
items = data.split(b'/')
IDStrings = GetIDStrings()
print ("trying username ", GetUserName())
for IDString in IDStrings:
#print "trying IDString:",IDString
print ("trying IDString:",IDString)
try:
DB = {}
items = data.split('/')
items = data.split(b'/')
# the headerblob is the encrypted information needed to build the entropy string
headerblob = items.pop(0)
#print ("headerblob: ",headerblob)
encryptedValue = decode(headerblob, charMap1)
#print ("encryptedvalue: ",encryptedValue)
cleartext = UnprotectHeaderData(encryptedValue)
print ("cleartext: ",cleartext)
# now extract the pieces in the same way
pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
pattern = re.compile(rb'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
for m in re.finditer(pattern, cleartext):
version = int(m.group(1))
build = m.group(2)
guid = m.group(4)
print ("version",version)
print ("build",build)
print ("guid",guid,"\n")
if version == 5: # .kinf2011: identical to K4PC, except the build number gets multiplied
entropy = str(0x2df * int(build)) + guid
entropy = bytes(0x2df * int(build)) + guid
cud = CryptUnprotectData(entropy,IDString)
print ("entropy",entropy)
print ("cud",cud)
elif version == 6: # .kinf2018: identical to K4PC
salt = str(0x6d8 * int(build)) + guid
sp = GetUserName() + '+@#$%+' + IDString
salt = bytes(0x6d8 * int(build)) + guid
sp = GetUserName() + b'+@#$%+' + IDString
passwd = encode(SHA256(sp), charMap5)
key = LibCrypto().keyivgen(passwd, salt, 10000, 0x400)[:32]
# loop through the item records until all are processed
print ("salt",salt)
print ("sp",sp)
print ("passwd",passwd)
print ("key",key)
# loop through the item records until all are processed
while len(items) > 0:
# get the first item record
@ -1564,7 +1584,7 @@ elif isosx:
# the first 32 chars of the first record of a group
# is the MD5 hash of the key name encoded by charMap5
keyhash = item[0:32]
keyname = 'unknown'
keyname = b'unknown'
# unlike K4PC the keyhash is not used in generating entropy
# entropy = SHA1(keyhash) + added_entropy
@ -1580,16 +1600,16 @@ elif isosx:
# read and store in rcnt records of data
# that make up the contents value
edlst = []
for i in xrange(rcnt):
for i in range(rcnt):
item = items.pop(0)
edlst.append(item)
keyname = 'unknown'
keyname = b'unknown'
for name in names:
if encodeHash(name,testMap8) == keyhash:
keyname = name
break
if keyname == 'unknown':
if keyname == b'unknown':
keyname = keyhash
# the testMap8 encoded contents data has had a length
@ -1603,7 +1623,7 @@ elif isosx:
# (in other words split 'about' 2/3rds of the way through)
# move first offsets chars to end to align for decode by testMap8
encdata = ''.join(edlst)
encdata = b''.join(edlst)
contlen = len(encdata)
# now properly split and recombine
@ -1643,7 +1663,9 @@ elif isosx:
if len(DB)>6:
break
except:
except Exception:
print (traceback.format_exc())
pass
if len(DB)>6:
# store values used in decryption
@ -1709,7 +1731,7 @@ def cli_main():
sys.stderr=SafeUnbuffered(sys.stderr)
argv=unicode_argv()
progname = os.path.basename(argv[0])
print("{0} v{1}\nCopyright © 2010-2016 by some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__))
print("{0} v{1}\nCopyright © 2010-2020 by some_updates, Apprentice Harper et al.".format(progname,__version__))
try:
opts, args = getopt.getopt(argv[1:], "hk:")
@ -1800,6 +1822,7 @@ def gui_main():
return 0
if __name__ == '__main__':
print ("here")
if len(sys.argv) > 1:
sys.exit(cli_main())
sys.exit(gui_main())

View File

@ -137,10 +137,8 @@ def unicode_argv():
# this should never happen
return ["mobidedrm.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = 'utf-8'
return sys.argv
argvencoding = sys.stdin.encoding or "utf-8"
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
class DrmException(Exception):
@ -246,7 +244,7 @@ class MobiBook:
pass
def __init__(self, infile):
print("MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__))
print("MobiDeDrm v{0:s}.\nCopyright © 2008-2020 The Dark Reverser, Apprentice Harper et al.".format(__version__))
try:
from alfcrypto import Pukall_Cipher
@ -522,7 +520,7 @@ def cli_main():
argv=unicode_argv()
progname = os.path.basename(argv[0])
if len(argv)<3 or len(argv)>4:
print("MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__))
print("MobiDeDrm v{0:s}.\nCopyright © 2008-2020 The Dark Reverser, Apprentice Harper et al.".format(__version__))
print("Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks")
print("Usage:")
print(" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname))
@ -531,7 +529,8 @@ def cli_main():
infile = argv[1]
outfile = argv[2]
if len(argv) == 4:
pidlist = argv[3].split(',')
# convert from unicode to bytearray before splitting.
pidlist = argv[3].encode('utf-8').split(b',')
else:
pidlist = []
try:

View File

@ -18,7 +18,10 @@ import zlib, zipfile, tempfile, shutil
import traceback
from struct import pack
from struct import unpack
from calibre_plugins.dedrm.alfcrypto import Topaz_Cipher
try:
from calibre_plugins.dedrm.alfcrypto import Topaz_Cipher
except:
from alfcrypto import Topaz_Cipher
class SafeUnbuffered:
def __init__(self, stream):
@ -70,10 +73,8 @@ def unicode_argv():
# this should never happen
return ["mobidedrm.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = 'utf-8'
return argv
argvencoding = sys.stdin.encoding or "utf-8"
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
#global switch
debug = False

View File

@ -22,7 +22,10 @@ __version__ = "1.1"
import sys
import zlib
import calibre_plugins.dedrm.zipfilerugged as zipfilerugged
try:
import zipfilerugged
except:
import calibre_plugins.dedrm.zipfilerugged as zipfilerugged
import os
import os.path
import getopt