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.

pull/1296/head
Apprentice Harper 4 years ago
parent 2eb31c8fb5
commit e31752e334

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

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

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

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

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

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

@ -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)

@ -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():

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

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

@ -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")

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

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

@ -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())

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

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

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

Loading…
Cancel
Save