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:
parent
2eb31c8fb5
commit
e31752e334
|
@ -95,10 +95,8 @@ def unicode_argv():
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["kindlekey.py"]
|
return ["kindlekey.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding
|
argvencoding = sys.stdin.encoding or "utf-8"
|
||||||
if argvencoding == None:
|
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||||
argvencoding = "utf-8"
|
|
||||||
return argv
|
|
||||||
|
|
||||||
class DrmException(Exception):
|
class DrmException(Exception):
|
||||||
pass
|
pass
|
||||||
|
@ -336,7 +334,7 @@ def cli_main():
|
||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
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:
|
try:
|
||||||
opts, args = getopt.getopt(argv[1:], "hb:")
|
opts, args = getopt.getopt(argv[1:], "hb:")
|
||||||
|
@ -386,48 +384,48 @@ def cli_main():
|
||||||
|
|
||||||
def gui_main():
|
def gui_main():
|
||||||
try:
|
try:
|
||||||
import Tkinter
|
import tkinter
|
||||||
import Tkconstants
|
import tkinter.constants
|
||||||
import tkMessageBox
|
import tkinter.messagebox
|
||||||
import tkFileDialog
|
import tkinter.filedialog
|
||||||
except:
|
except:
|
||||||
print("Tkinter not installed")
|
print("tkinter not installed")
|
||||||
return cli_main()
|
return cli_main()
|
||||||
|
|
||||||
class DecryptionDialog(Tkinter.Frame):
|
class DecryptionDialog(tkinter.Frame):
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
Tkinter.Frame.__init__(self, root, border=5)
|
tkinter.Frame.__init__(self, root, border=5)
|
||||||
self.status = Tkinter.Label(self, text="Select backup.ab file")
|
self.status = tkinter.Label(self, text="Select backup.ab file")
|
||||||
self.status.pack(fill=Tkconstants.X, expand=1)
|
self.status.pack(fill=tkinter.constants.X, expand=1)
|
||||||
body = Tkinter.Frame(self)
|
body = tkinter.Frame(self)
|
||||||
body.pack(fill=Tkconstants.X, expand=1)
|
body.pack(fill=tkinter.constants.X, expand=1)
|
||||||
sticky = Tkconstants.E + Tkconstants.W
|
sticky = tkinter.constants.E + tkinter.constants.W
|
||||||
body.grid_columnconfigure(1, weight=2)
|
body.grid_columnconfigure(1, weight=2)
|
||||||
Tkinter.Label(body, text="Backup file").grid(row=0, column=0)
|
tkinter.Label(body, text="Backup file").grid(row=0, column=0)
|
||||||
self.keypath = Tkinter.Entry(body, width=40)
|
self.keypath = tkinter.Entry(body, width=40)
|
||||||
self.keypath.grid(row=0, column=1, sticky=sticky)
|
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||||
self.keypath.insert(2, "backup.ab")
|
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)
|
button.grid(row=0, column=2)
|
||||||
buttons = Tkinter.Frame(self)
|
buttons = tkinter.Frame(self)
|
||||||
buttons.pack()
|
buttons.pack()
|
||||||
button2 = Tkinter.Button(
|
button2 = tkinter.Button(
|
||||||
buttons, text="Extract", width=10, command=self.generate)
|
buttons, text="Extract", width=10, command=self.generate)
|
||||||
button2.pack(side=Tkconstants.LEFT)
|
button2.pack(side=tkinter.constants.LEFT)
|
||||||
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
tkinter.Frame(buttons, width=10).pack(side=tkinter.constants.LEFT)
|
||||||
button3 = Tkinter.Button(
|
button3 = tkinter.Button(
|
||||||
buttons, text="Quit", width=10, command=self.quit)
|
buttons, text="Quit", width=10, command=self.quit)
|
||||||
button3.pack(side=Tkconstants.RIGHT)
|
button3.pack(side=tkinter.constants.RIGHT)
|
||||||
|
|
||||||
def get_keypath(self):
|
def get_keypath(self):
|
||||||
keypath = tkFileDialog.askopenfilename(
|
keypath = tkinter.filedialog.askopenfilename(
|
||||||
parent=None, title="Select backup.ab file",
|
parent=None, title="Select backup.ab file",
|
||||||
defaultextension=".ab",
|
defaultextension=".ab",
|
||||||
filetypes=[('adb backup com.amazon.kindle', '.ab'),
|
filetypes=[('adb backup com.amazon.kindle', '.ab'),
|
||||||
('All Files', '.*')])
|
('All Files', '.*')])
|
||||||
if keypath:
|
if keypath:
|
||||||
keypath = os.path.normpath(keypath)
|
keypath = os.path.normpath(keypath)
|
||||||
self.keypath.delete(0, Tkconstants.END)
|
self.keypath.delete(0, tkinter.constants.END)
|
||||||
self.keypath.insert(0, keypath)
|
self.keypath.insert(0, keypath)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -447,7 +445,7 @@ def gui_main():
|
||||||
with open(outfile, 'w') as keyfileout:
|
with open(outfile, 'w') as keyfileout:
|
||||||
keyfileout.write(key)
|
keyfileout.write(key)
|
||||||
success = True
|
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:
|
except Exception as e:
|
||||||
self.status['text'] = "Error: {0}".format(e.args[0])
|
self.status['text'] = "Error: {0}".format(e.args[0])
|
||||||
return
|
return
|
||||||
|
@ -455,11 +453,11 @@ def gui_main():
|
||||||
|
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progpath, progname = os.path.split(argv[0])
|
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.title("Kindle for Android Key Extraction v.{0}".format(__version__))
|
||||||
root.resizable(True, False)
|
root.resizable(True, False)
|
||||||
root.minsize(300, 0)
|
root.minsize(300, 0)
|
||||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
DecryptionDialog(root).pack(fill=tkinter.constants.X, expand=1)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
|
@ -5,18 +5,25 @@
|
||||||
# For use with Topaz Scripts Version 2.6
|
# For use with Topaz Scripts Version 2.6
|
||||||
# Python 3, September 2020
|
# 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):
|
def __init__(self, stream):
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
|
self.encoding = stream.encoding
|
||||||
|
if self.encoding == None:
|
||||||
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
|
if isinstance(data, str):
|
||||||
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.buffer.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.buffer.flush()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
sys.stdout=Unbuffered(sys.stdout)
|
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
import os
|
import os
|
||||||
import getopt
|
import getopt
|
||||||
|
@ -834,6 +841,8 @@ def usage():
|
||||||
#
|
#
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
|
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||||
|
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||||
dictFile = ""
|
dictFile = ""
|
||||||
pageFile = ""
|
pageFile = ""
|
||||||
debug = False
|
debug = False
|
||||||
|
|
|
@ -128,10 +128,8 @@ def unicode_argv():
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["mobidedrm.py"]
|
return ["mobidedrm.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding
|
argvencoding = sys.stdin.encoding or "utf-8"
|
||||||
if argvencoding == None:
|
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||||
argvencoding = "utf-8"
|
|
||||||
return argv
|
|
||||||
|
|
||||||
Des = None
|
Des = None
|
||||||
if iswindows:
|
if iswindows:
|
||||||
|
|
|
@ -4,18 +4,25 @@
|
||||||
# Python 3 for calibre 5.0
|
# Python 3 for calibre 5.0
|
||||||
from __future__ import print_function
|
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):
|
def __init__(self, stream):
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
|
self.encoding = stream.encoding
|
||||||
|
if self.encoding == None:
|
||||||
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
|
if isinstance(data, str):
|
||||||
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.buffer.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.buffer.flush()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
sys.stdout=Unbuffered(sys.stdout)
|
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
import os
|
import os
|
||||||
import getopt
|
import getopt
|
||||||
|
@ -687,6 +694,8 @@ def usage():
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
|
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||||
|
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||||
bookDir = ''
|
bookDir = ''
|
||||||
if len(argv) == 0:
|
if len(argv) == 0:
|
||||||
argv = sys.argv
|
argv = sys.argv
|
||||||
|
|
|
@ -95,10 +95,8 @@ def unicode_argv():
|
||||||
range(start, argc.value)]
|
range(start, argc.value)]
|
||||||
return ["ineptepub.py"]
|
return ["ineptepub.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding
|
argvencoding = sys.stdin.encoding or "utf-8"
|
||||||
if argvencoding == None:
|
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||||
argvencoding = "utf-8"
|
|
||||||
return argv
|
|
||||||
|
|
||||||
|
|
||||||
class IGNOBLEError(Exception):
|
class IGNOBLEError(Exception):
|
||||||
|
|
|
@ -83,10 +83,8 @@ def unicode_argv():
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["ignoblekey.py"]
|
return ["ignoblekey.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding
|
argvencoding = sys.stdin.encoding or "utf-8"
|
||||||
if argvencoding == None:
|
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||||
argvencoding = "utf-8"
|
|
||||||
return argv
|
|
||||||
|
|
||||||
class DrmException(Exception):
|
class DrmException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -90,10 +90,8 @@ def unicode_argv():
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["ignoblekeyfetch.py"]
|
return ["ignoblekeyfetch.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding
|
argvencoding = sys.stdin.encoding or "utf-8"
|
||||||
if argvencoding == None:
|
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||||
argvencoding = "utf-8"
|
|
||||||
return argv
|
|
||||||
|
|
||||||
|
|
||||||
class IGNOBLEError(Exception):
|
class IGNOBLEError(Exception):
|
||||||
|
@ -109,18 +107,17 @@ def fetch_key(email, password):
|
||||||
import random
|
import random
|
||||||
random = "%030x" % random.randrange(16**30)
|
random = "%030x" % random.randrange(16**30)
|
||||||
|
|
||||||
import urllib, urllib2, re
|
import urllib.parse, urllib.request, re
|
||||||
|
|
||||||
# try the URL from nook for PC
|
# try the URL from nook for PC
|
||||||
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
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.parse.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(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
||||||
#print fetch_url
|
#print fetch_url
|
||||||
|
|
||||||
found = ''
|
found = ''
|
||||||
try:
|
try:
|
||||||
req = urllib2.Request(fetch_url)
|
response = urllib.request.urlopen(fetch_url)
|
||||||
response = urllib2.urlopen(req)
|
|
||||||
the_page = response.read()
|
the_page = response.read()
|
||||||
#print the_page
|
#print the_page
|
||||||
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
||||||
|
@ -129,14 +126,13 @@ def fetch_key(email, password):
|
||||||
if len(found)!=28:
|
if len(found)!=28:
|
||||||
# try the URL from android devices
|
# try the URL from android devices
|
||||||
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
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.parse.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(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
||||||
#print fetch_url
|
#print fetch_url
|
||||||
|
|
||||||
found = ''
|
found = ''
|
||||||
try:
|
try:
|
||||||
req = urllib2.Request(fetch_url)
|
response = urllib.request.urlopen(fetch_url)
|
||||||
response = urllib2.urlopen(req)
|
|
||||||
the_page = response.read()
|
the_page = response.read()
|
||||||
#print the_page
|
#print the_page
|
||||||
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
||||||
|
|
|
@ -42,6 +42,7 @@ __version__ = "3.0"
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import base64
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
# Wrap a stream so that output gets flushed immediately
|
||||||
# and also make sure that any unicode strings get
|
# and also make sure that any unicode strings get
|
||||||
|
@ -99,10 +100,8 @@ def unicode_argv():
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["ignoblekeygen.py"]
|
return ["ignoblekeygen.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding
|
argvencoding = sys.stdin.encoding or "utf-8"
|
||||||
if argvencoding == None:
|
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||||
argvencoding = "utf-8"
|
|
||||||
return argv
|
|
||||||
|
|
||||||
|
|
||||||
class IGNOBLEError(Exception):
|
class IGNOBLEError(Exception):
|
||||||
|
@ -195,23 +194,24 @@ def normalize_name(name):
|
||||||
|
|
||||||
def generate_key(name, ccn):
|
def generate_key(name, ccn):
|
||||||
# remove spaces and case from name and CC numbers.
|
# 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')
|
name = name.encode('utf-8')
|
||||||
if type(ccn)==bytes:
|
if type(ccn)==str:
|
||||||
ccn = ccn.encode('utf-8')
|
ccn = ccn.encode('utf-8')
|
||||||
|
|
||||||
name = normalize_name(name) + '\x00'
|
name = name + b'\x00'
|
||||||
ccn = normalize_name(ccn) + '\x00'
|
ccn = ccn + b'\x00'
|
||||||
|
|
||||||
name_sha = hashlib.sha1(name).digest()[:16]
|
name_sha = hashlib.sha1(name).digest()[:16]
|
||||||
ccn_sha = hashlib.sha1(ccn).digest()[:16]
|
ccn_sha = hashlib.sha1(ccn).digest()[:16]
|
||||||
both_sha = hashlib.sha1(name + ccn).digest()
|
both_sha = hashlib.sha1(name + ccn).digest()
|
||||||
aes = AES(ccn_sha, name_sha)
|
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()
|
userkey = hashlib.sha1(crypt).digest()
|
||||||
return userkey.encode('base64')
|
return base64.b64encode(userkey)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def cli_main():
|
def cli_main():
|
||||||
|
|
|
@ -85,10 +85,8 @@ def unicode_argv():
|
||||||
xrange(start, argc.value)]
|
xrange(start, argc.value)]
|
||||||
return ["ignoblepdf.py"]
|
return ["ignoblepdf.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding
|
argvencoding = sys.stdin.encoding or "utf-8"
|
||||||
if argvencoding == None:
|
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||||
argvencoding = "utf-8"
|
|
||||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
|
||||||
|
|
||||||
|
|
||||||
class IGNOBLEError(Exception):
|
class IGNOBLEError(Exception):
|
||||||
|
@ -241,7 +239,10 @@ ARC4, AES = _load_crypto()
|
||||||
try:
|
try:
|
||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
try:
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
|
||||||
# Do we generate cross reference streams on output?
|
# Do we generate cross reference streams on output?
|
||||||
|
|
|
@ -102,10 +102,8 @@ def unicode_argv():
|
||||||
range(start, argc.value)]
|
range(start, argc.value)]
|
||||||
return ["ineptepub.py"]
|
return ["ineptepub.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding
|
argvencoding = sys.stdin.encoding or "utf-8"
|
||||||
if argvencoding == None:
|
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||||
argvencoding = "utf-8"
|
|
||||||
return argv
|
|
||||||
|
|
||||||
|
|
||||||
class ADEPTError(Exception):
|
class ADEPTError(Exception):
|
||||||
|
|
|
@ -146,10 +146,8 @@ def unicode_argv():
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["mobidedrm.py"]
|
return ["mobidedrm.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding
|
argvencoding = sys.stdin.encoding or "utf-8"
|
||||||
if argvencoding == None:
|
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||||
argvencoding = "utf-8"
|
|
||||||
return argv
|
|
||||||
|
|
||||||
# cleanup unicode filenames
|
# cleanup unicode filenames
|
||||||
# borrowed from calibre from calibre/src/calibre/__init__.py
|
# borrowed from calibre from calibre/src/calibre/__init__.py
|
||||||
|
@ -337,7 +335,7 @@ def cli_main():
|
||||||
if o == "-p":
|
if o == "-p":
|
||||||
if a == None :
|
if a == None :
|
||||||
raise DrmException("Invalid parameter for -p")
|
raise DrmException("Invalid parameter for -p")
|
||||||
pids = a.split(',')
|
pids = a.encode('utf-8').split(b',')
|
||||||
if o == "-s":
|
if o == "-s":
|
||||||
if a == None :
|
if a == None :
|
||||||
raise DrmException("Invalid parameter for -s")
|
raise DrmException("Invalid parameter for -s")
|
||||||
|
|
|
@ -18,11 +18,6 @@ except ImportError:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
try:
|
|
||||||
from calibre_plugins.dedrm import ion
|
|
||||||
except ImportError:
|
|
||||||
import ion
|
|
||||||
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '2.0'
|
__version__ = '2.0'
|
||||||
|
@ -38,6 +33,10 @@ class KFXZipBook:
|
||||||
return (None, None)
|
return (None, None)
|
||||||
|
|
||||||
def processBook(self, totalpids):
|
def processBook(self, totalpids):
|
||||||
|
try:
|
||||||
|
import ion
|
||||||
|
except:
|
||||||
|
from calibre_plugins.dedrm import ion
|
||||||
with zipfile.ZipFile(self.infile, 'r') as zf:
|
with zipfile.ZipFile(self.infile, 'r') as zf:
|
||||||
for filename in zf.namelist():
|
for filename in zf.namelist():
|
||||||
with zf.open(filename) as fh:
|
with zf.open(filename) as fh:
|
||||||
|
|
|
@ -205,7 +205,7 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get the kindle account token, if present
|
# 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:
|
except KeyError:
|
||||||
kindleAccountToken=""
|
kindleAccountToken=""
|
||||||
|
@ -219,37 +219,37 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||||
# See if we have the info to generate the DSN
|
# See if we have the info to generate the DSN
|
||||||
try:
|
try:
|
||||||
# Get the Mazama Random number
|
# 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])
|
#print "Got MazamaRandomNumber from database {0}".format(kindleDatabase[0])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get the SerialNumber token, if present
|
# 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]))
|
print("Got SerialNumber from database {0}".format(kindleDatabase[0]))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Get the IDString we added
|
# Get the IDString we added
|
||||||
IDString = bytearray.fromhex((kindleDatabase[1])['IDString']).decode()
|
IDString = bytearray.fromhex((kindleDatabase[1])[b'IDString']).decode()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get the UsernameHash token, if present
|
# 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]))
|
print("Got UsernameHash from database {0}".format(kindleDatabase[0]))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Get the UserName we added
|
# Get the UserName we added
|
||||||
UserName = bytearray.fromhex((kindleDatabase[1])['UserName']).decode()
|
UserName = bytearray.fromhex((kindleDatabase[1])[b'UserName']).decode()
|
||||||
# encode it
|
# encode it
|
||||||
encodedUsername = encodeHash(UserName.encode(),charMap1)
|
encodedUsername = encodeHash(UserName,charMap1)
|
||||||
#print "encodedUsername",encodedUsername.encode('hex')
|
#print "encodedUsername",encodedUsername.encode('hex')
|
||||||
except KeyError:
|
except KeyError:
|
||||||
print("Keys not found in the database {0}.".format(kindleDatabase[0]))
|
print("Keys not found in the database {0}.".format(kindleDatabase[0]))
|
||||||
return pids
|
return pids
|
||||||
|
|
||||||
# Get the ID string used
|
# Get the ID string used
|
||||||
encodedIDString = encodeHash(IDString.encode(),charMap1)
|
encodedIDString = encodeHash(IDString,charMap1)
|
||||||
#print "encodedIDString",encodedIDString.encode('hex')
|
#print "encodedIDString",encodedIDString.encode('hex')
|
||||||
|
|
||||||
# concat, hash and encode to calculate the DSN
|
# 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')
|
#print "DSN",DSN.encode('hex')
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ import sys, os, re
|
||||||
from struct import pack, unpack, unpack_from
|
from struct import pack, unpack, unpack_from
|
||||||
import json
|
import json
|
||||||
import getopt
|
import getopt
|
||||||
|
import traceback
|
||||||
|
|
||||||
try:
|
try:
|
||||||
RegError
|
RegError
|
||||||
|
@ -58,10 +59,11 @@ class SafeUnbuffered:
|
||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data,unicode):
|
if isinstance(data, str):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.write(data)
|
self.stream.buffer.write(data)
|
||||||
self.stream.flush()
|
self.stream.buffer.flush()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
|
@ -99,15 +101,13 @@ def unicode_argv():
|
||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
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
|
# if we don't have any arguments at all, just pass back script name
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["kindlekey.py"]
|
return ["kindlekey.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding
|
argvencoding = sys.stdin.encoding or "utf-8"
|
||||||
if argvencoding == None:
|
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||||
argvencoding = "utf-8"
|
|
||||||
return arg
|
|
||||||
|
|
||||||
class DrmException(Exception):
|
class DrmException(Exception):
|
||||||
pass
|
pass
|
||||||
|
@ -155,13 +155,13 @@ def primes(n):
|
||||||
|
|
||||||
# Encode the bytes in data with the characters in map
|
# Encode the bytes in data with the characters in map
|
||||||
def encode(data, map):
|
def encode(data, map):
|
||||||
result = ''
|
result = b''
|
||||||
for char in data:
|
for char in data:
|
||||||
value = ord(char)
|
value = char
|
||||||
Q = (value ^ 0x80) // len(map)
|
Q = (value ^ 0x80) // len(map)
|
||||||
R = value % len(map)
|
R = value % len(map)
|
||||||
result += map[Q]
|
result += bytes(map[Q])
|
||||||
result += map[R]
|
result += bytes(map[R])
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Hash the bytes in data and then encode the digest with the characters in map
|
# 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
|
# Decode the string in data with the characters in map. Returns the decoded bytes
|
||||||
def decode(data,map):
|
def decode(data,map):
|
||||||
result = ''
|
result = b''
|
||||||
for i in range (0,len(data)-1,2):
|
for i in range (0,len(data)-1,2):
|
||||||
high = map.find(data[i])
|
high = map.find(data[i])
|
||||||
low = map.find(data[i+1])
|
low = map.find(data[i+1])
|
||||||
|
@ -833,12 +833,12 @@ if iswindows:
|
||||||
|
|
||||||
# Various character maps used to decrypt kindle info values.
|
# Various character maps used to decrypt kindle info values.
|
||||||
# Probably supposed to act as obfuscation
|
# Probably supposed to act as obfuscation
|
||||||
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
|
charMap2 = b"AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
|
||||||
charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
|
charMap5 = b"AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
|
||||||
# New maps in K4PC 1.9.0
|
# New maps in K4PC 1.9.0
|
||||||
testMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
testMap1 = b"n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||||
testMap6 = "9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG"
|
testMap6 = b"9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG"
|
||||||
testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
|
testMap8 = b"YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
|
||||||
|
|
||||||
# interface with Windows OS Routines
|
# interface with Windows OS Routines
|
||||||
class DataBlob(Structure):
|
class DataBlob(Structure):
|
||||||
|
@ -902,7 +902,7 @@ if iswindows:
|
||||||
size.value = len(buffer)
|
size.value = len(buffer)
|
||||||
|
|
||||||
# replace any non-ASCII values with 0xfffd
|
# 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":
|
if buffer[i]>"\u007f":
|
||||||
#print "swapping char "+str(i)+" ("+buffer[i]+")"
|
#print "swapping char "+str(i)+" ("+buffer[i]+")"
|
||||||
buffer[i] = "\ufffd"
|
buffer[i] = "\ufffd"
|
||||||
|
@ -1023,28 +1023,28 @@ if iswindows:
|
||||||
# database of keynames and values
|
# database of keynames and values
|
||||||
def getDBfromFile(kInfoFile):
|
def getDBfromFile(kInfoFile):
|
||||||
names = [\
|
names = [\
|
||||||
'kindle.account.tokens',\
|
b'kindle.account.tokens',\
|
||||||
'kindle.cookie.item',\
|
b'kindle.cookie.item',\
|
||||||
'eulaVersionAccepted',\
|
b'eulaVersionAccepted',\
|
||||||
'login_date',\
|
b'login_date',\
|
||||||
'kindle.token.item',\
|
b'kindle.token.item',\
|
||||||
'login',\
|
b'login',\
|
||||||
'kindle.key.item',\
|
b'kindle.key.item',\
|
||||||
'kindle.name.info',\
|
b'kindle.name.info',\
|
||||||
'kindle.device.info',\
|
b'kindle.device.info',\
|
||||||
'MazamaRandomNumber',\
|
b'MazamaRandomNumber',\
|
||||||
'max_date',\
|
b'max_date',\
|
||||||
'SIGVERIF',\
|
b'SIGVERIF',\
|
||||||
'build_version',\
|
b'build_version',\
|
||||||
'SerialNumber',\
|
b'SerialNumber',\
|
||||||
'UsernameHash',\
|
b'UsernameHash',\
|
||||||
'kindle.directedid.info',\
|
b'kindle.directedid.info',\
|
||||||
'DSN',\
|
b'DSN',\
|
||||||
'kindle.accounttype.info',\
|
b'kindle.accounttype.info',\
|
||||||
'krx.flashcardsplugin.data.encryption_key',\
|
b'krx.flashcardsplugin.data.encryption_key',\
|
||||||
'krx.notebookexportplugin.data.encryption_key',\
|
b'krx.notebookexportplugin.data.encryption_key',\
|
||||||
'proxy.http.password',\
|
b'proxy.http.password',\
|
||||||
'proxy.http.username'
|
b'proxy.http.username'
|
||||||
]
|
]
|
||||||
DB = {}
|
DB = {}
|
||||||
with open(kInfoFile, 'rb') as infoReader:
|
with open(kInfoFile, 'rb') as infoReader:
|
||||||
|
@ -1053,7 +1053,7 @@ if iswindows:
|
||||||
# the .kinf file uses "/" to separate it into records
|
# the .kinf file uses "/" to separate it into records
|
||||||
# so remove the trailing "/" to make it easy to use split
|
# so remove the trailing "/" to make it easy to use split
|
||||||
data = data[:-1]
|
data = data[:-1]
|
||||||
items = data.split('/')
|
items = data.split(b'/')
|
||||||
|
|
||||||
# starts with an encoded and encrypted header blob
|
# starts with an encoded and encrypted header blob
|
||||||
headerblob = items.pop(0)
|
headerblob = items.pop(0)
|
||||||
|
@ -1095,7 +1095,7 @@ if iswindows:
|
||||||
# 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 range(rcnt):
|
||||||
item = items.pop(0)
|
item = items.pop(0)
|
||||||
edlst.append(item)
|
edlst.append(item)
|
||||||
|
|
||||||
|
@ -1276,8 +1276,8 @@ elif isosx:
|
||||||
LibCrypto = _load_crypto()
|
LibCrypto = _load_crypto()
|
||||||
|
|
||||||
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
|
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
|
||||||
charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
|
charMap1 = b'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
|
||||||
charMap2 = 'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM'
|
charMap2 = b'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM'
|
||||||
|
|
||||||
# For kinf approach of K4Mac 1.6.X or later
|
# For kinf approach of K4Mac 1.6.X or later
|
||||||
# On K4PC charMap5 = 'AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE'
|
# On K4PC charMap5 = 'AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE'
|
||||||
|
@ -1285,7 +1285,7 @@ elif isosx:
|
||||||
charMap5 = charMap2
|
charMap5 = charMap2
|
||||||
|
|
||||||
# new in K4M 1.9.X
|
# 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
|
# uses a sub process to get the Hard Drive Serial Number using ioreg
|
||||||
# returns serial numbers of all internal hard drive drives
|
# 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)
|
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||||
out1, out2 = p.communicate()
|
out1, out2 = p.communicate()
|
||||||
#print out1
|
#print out1
|
||||||
reslst = out1.split('\n')
|
reslst = out1.split(b'\n')
|
||||||
cnt = len(reslst)
|
cnt = len(reslst)
|
||||||
for j in xrange(cnt):
|
for j in range(cnt):
|
||||||
resline = reslst[j]
|
resline = reslst[j]
|
||||||
pp = resline.find('\"Serial Number\" = \"')
|
pp = resline.find(b'\"Serial Number\" = \"')
|
||||||
if pp >= 0:
|
if pp >= 0:
|
||||||
sernum = resline[pp+19:-1]
|
sernum = resline[pp+19:-1]
|
||||||
sernums.append(sernum.strip())
|
sernums.append(sernum.strip())
|
||||||
|
@ -1315,12 +1315,12 @@ elif isosx:
|
||||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||||
out1, out2 = p.communicate()
|
out1, out2 = p.communicate()
|
||||||
reslst = out1.split('\n')
|
reslst = out1.split(b'\n')
|
||||||
cnt = len(reslst)
|
cnt = len(reslst)
|
||||||
for j in xrange(cnt):
|
for j in range(cnt):
|
||||||
resline = reslst[j]
|
resline = reslst[j]
|
||||||
if resline.startswith('/dev'):
|
if resline.startswith(b'/dev'):
|
||||||
(devpart, mpath) = resline.split(' on ')[:2]
|
(devpart, mpath) = resline.split(b' on ')[:2]
|
||||||
dpart = devpart[5:]
|
dpart = devpart[5:]
|
||||||
names.append(dpart)
|
names.append(dpart)
|
||||||
return names
|
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)
|
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||||
out1, out2 = p.communicate()
|
out1, out2 = p.communicate()
|
||||||
#print out1
|
#print out1
|
||||||
reslst = out1.split('\n')
|
reslst = out1.split(b'\n')
|
||||||
cnt = len(reslst)
|
cnt = len(reslst)
|
||||||
for j in xrange(cnt):
|
for j in range(cnt):
|
||||||
resline = reslst[j]
|
resline = reslst[j]
|
||||||
pp = resline.find('\"UUID\" = \"')
|
pp = resline.find(b'\"UUID\" = \"')
|
||||||
if pp >= 0:
|
if pp >= 0:
|
||||||
uuidnum = resline[pp+10:-1]
|
uuidnum = resline[pp+10:-1]
|
||||||
uuidnum = uuidnum.strip()
|
uuidnum = uuidnum.strip()
|
||||||
|
@ -1356,16 +1356,16 @@ elif isosx:
|
||||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||||
out1, out2 = p.communicate()
|
out1, out2 = p.communicate()
|
||||||
reslst = out1.split('\n')
|
reslst = out1.split(b'\n')
|
||||||
cnt = len(reslst)
|
cnt = len(reslst)
|
||||||
for j in xrange(cnt):
|
for j in range(cnt):
|
||||||
resline = reslst[j]
|
resline = reslst[j]
|
||||||
pp = resline.find('Ethernet Address: ')
|
pp = resline.find(b'Ethernet Address: ')
|
||||||
if pp >= 0:
|
if pp >= 0:
|
||||||
#print resline
|
#print resline
|
||||||
macnum = resline[pp+18:]
|
macnum = resline[pp+18:]
|
||||||
macnum = macnum.strip()
|
macnum = macnum.strip()
|
||||||
maclst = macnum.split(':')
|
maclst = macnum.split(b':')
|
||||||
n = len(maclst)
|
n = len(maclst)
|
||||||
if n != 6:
|
if n != 6:
|
||||||
continue
|
continue
|
||||||
|
@ -1373,7 +1373,7 @@ elif isosx:
|
||||||
# now munge it up the way Kindle app does
|
# now munge it up the way Kindle app does
|
||||||
# by xoring it with 0xa5 and swapping elements 3 and 4
|
# by xoring it with 0xa5 and swapping elements 3 and 4
|
||||||
for i in range(6):
|
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 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||||
mlst[5] = maclst[5] ^ 0xa5
|
mlst[5] = maclst[5] ^ 0xa5
|
||||||
mlst[4] = maclst[3] ^ 0xa5
|
mlst[4] = maclst[3] ^ 0xa5
|
||||||
|
@ -1381,7 +1381,7 @@ elif isosx:
|
||||||
mlst[2] = maclst[2] ^ 0xa5
|
mlst[2] = maclst[2] ^ 0xa5
|
||||||
mlst[1] = maclst[1] ^ 0xa5
|
mlst[1] = maclst[1] ^ 0xa5
|
||||||
mlst[0] = maclst[0] ^ 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
|
#print 'munged mac', macnum
|
||||||
macnums.append(macnum)
|
macnums.append(macnum)
|
||||||
return macnums
|
return macnums
|
||||||
|
@ -1391,7 +1391,7 @@ elif isosx:
|
||||||
def GetUserName():
|
def GetUserName():
|
||||||
username = os.getenv('USER')
|
username = os.getenv('USER')
|
||||||
#print "Username:",username
|
#print "Username:",username
|
||||||
return username
|
return username.encode('utf-8')
|
||||||
|
|
||||||
def GetIDStrings():
|
def GetIDStrings():
|
||||||
# Return all possible ID Strings
|
# Return all possible ID Strings
|
||||||
|
@ -1400,7 +1400,7 @@ elif isosx:
|
||||||
strings.extend(GetVolumesSerialNumbers())
|
strings.extend(GetVolumesSerialNumbers())
|
||||||
strings.extend(GetDiskPartitionNames())
|
strings.extend(GetDiskPartitionNames())
|
||||||
strings.extend(GetDiskPartitionUUIDs())
|
strings.extend(GetDiskPartitionUUIDs())
|
||||||
strings.append('9999999999')
|
strings.append(b'9999999999')
|
||||||
#print "ID Strings:\n",strings
|
#print "ID Strings:\n",strings
|
||||||
return strings
|
return strings
|
||||||
|
|
||||||
|
@ -1408,8 +1408,8 @@ elif isosx:
|
||||||
# unprotect the new header blob in .kinf2011
|
# unprotect the new header blob in .kinf2011
|
||||||
# used in Kindle for Mac Version >= 1.9.0
|
# used in Kindle for Mac Version >= 1.9.0
|
||||||
def UnprotectHeaderData(encryptedData):
|
def UnprotectHeaderData(encryptedData):
|
||||||
passwdData = 'header_key_data'
|
passwdData = b'header_key_data'
|
||||||
salt = 'HEADER.2011'
|
salt = b'HEADER.2011'
|
||||||
iter = 0x80
|
iter = 0x80
|
||||||
keylen = 0x100
|
keylen = 0x100
|
||||||
crp = LibCrypto()
|
crp = LibCrypto()
|
||||||
|
@ -1424,7 +1424,7 @@ elif isosx:
|
||||||
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
||||||
class CryptUnprotectData(object):
|
class CryptUnprotectData(object):
|
||||||
def __init__(self, entropy, IDString):
|
def __init__(self, entropy, IDString):
|
||||||
sp = GetUserName() + '+@#$%+' + IDString
|
sp = GetUserName() + b'+@#$%+' + IDString
|
||||||
passwdData = encode(SHA256(sp),charMap2)
|
passwdData = encode(SHA256(sp),charMap2)
|
||||||
salt = entropy
|
salt = entropy
|
||||||
self.crp = LibCrypto()
|
self.crp = LibCrypto()
|
||||||
|
@ -1503,58 +1503,78 @@ elif isosx:
|
||||||
# database of keynames and values
|
# database of keynames and values
|
||||||
def getDBfromFile(kInfoFile):
|
def getDBfromFile(kInfoFile):
|
||||||
names = [\
|
names = [\
|
||||||
'kindle.account.tokens',\
|
b'kindle.account.tokens',\
|
||||||
'kindle.cookie.item',\
|
b'kindle.cookie.item',\
|
||||||
'eulaVersionAccepted',\
|
b'eulaVersionAccepted',\
|
||||||
'login_date',\
|
b'login_date',\
|
||||||
'kindle.token.item',\
|
b'kindle.token.item',\
|
||||||
'login',\
|
b'login',\
|
||||||
'kindle.key.item',\
|
b'kindle.key.item',\
|
||||||
'kindle.name.info',\
|
b'kindle.name.info',\
|
||||||
'kindle.device.info',\
|
b'kindle.device.info',\
|
||||||
'MazamaRandomNumber',\
|
b'MazamaRandomNumber',\
|
||||||
'max_date',\
|
b'max_date',\
|
||||||
'SIGVERIF',\
|
b'SIGVERIF',\
|
||||||
'build_version',\
|
b'build_version',\
|
||||||
'SerialNumber',\
|
b'SerialNumber',\
|
||||||
'UsernameHash',\
|
b'UsernameHash',\
|
||||||
'kindle.directedid.info',\
|
b'kindle.directedid.info',\
|
||||||
'DSN'
|
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:
|
with open(kInfoFile, 'rb') as infoReader:
|
||||||
filedata = infoReader.read()
|
filedata = infoReader.read()
|
||||||
|
|
||||||
data = filedata[:-1]
|
data = filedata[:-1]
|
||||||
items = data.split('/')
|
items = data.split(b'/')
|
||||||
IDStrings = GetIDStrings()
|
IDStrings = GetIDStrings()
|
||||||
|
print ("trying username ", GetUserName())
|
||||||
for IDString in IDStrings:
|
for IDString in IDStrings:
|
||||||
#print "trying IDString:",IDString
|
print ("trying IDString:",IDString)
|
||||||
try:
|
try:
|
||||||
DB = {}
|
DB = {}
|
||||||
items = data.split('/')
|
items = data.split(b'/')
|
||||||
|
|
||||||
# the headerblob is the encrypted information needed to build the entropy string
|
# the headerblob is the encrypted information needed to build the entropy string
|
||||||
headerblob = items.pop(0)
|
headerblob = items.pop(0)
|
||||||
|
#print ("headerblob: ",headerblob)
|
||||||
encryptedValue = decode(headerblob, charMap1)
|
encryptedValue = decode(headerblob, charMap1)
|
||||||
|
#print ("encryptedvalue: ",encryptedValue)
|
||||||
cleartext = UnprotectHeaderData(encryptedValue)
|
cleartext = UnprotectHeaderData(encryptedValue)
|
||||||
|
print ("cleartext: ",cleartext)
|
||||||
|
|
||||||
# now extract the pieces in the same way
|
# 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):
|
for m in re.finditer(pattern, cleartext):
|
||||||
version = int(m.group(1))
|
version = int(m.group(1))
|
||||||
build = m.group(2)
|
build = m.group(2)
|
||||||
guid = m.group(4)
|
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
|
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)
|
cud = CryptUnprotectData(entropy,IDString)
|
||||||
|
print ("entropy",entropy)
|
||||||
|
print ("cud",cud)
|
||||||
|
|
||||||
elif version == 6: # .kinf2018: identical to K4PC
|
elif version == 6: # .kinf2018: identical to K4PC
|
||||||
salt = str(0x6d8 * int(build)) + guid
|
salt = bytes(0x6d8 * int(build)) + guid
|
||||||
sp = GetUserName() + '+@#$%+' + IDString
|
sp = GetUserName() + b'+@#$%+' + IDString
|
||||||
passwd = encode(SHA256(sp), charMap5)
|
passwd = encode(SHA256(sp), charMap5)
|
||||||
key = LibCrypto().keyivgen(passwd, salt, 10000, 0x400)[:32]
|
key = LibCrypto().keyivgen(passwd, salt, 10000, 0x400)[:32]
|
||||||
|
|
||||||
|
print ("salt",salt)
|
||||||
|
print ("sp",sp)
|
||||||
|
print ("passwd",passwd)
|
||||||
|
print ("key",key)
|
||||||
|
|
||||||
# loop through the item records until all are processed
|
# loop through the item records until all are processed
|
||||||
while len(items) > 0:
|
while len(items) > 0:
|
||||||
|
|
||||||
|
@ -1564,7 +1584,7 @@ elif isosx:
|
||||||
# the first 32 chars of the first record of a group
|
# the first 32 chars of the first record of a group
|
||||||
# is the MD5 hash of the key name encoded by charMap5
|
# is the MD5 hash of the key name encoded by charMap5
|
||||||
keyhash = item[0:32]
|
keyhash = item[0:32]
|
||||||
keyname = 'unknown'
|
keyname = b'unknown'
|
||||||
|
|
||||||
# unlike K4PC the keyhash is not used in generating entropy
|
# unlike K4PC the keyhash is not used in generating entropy
|
||||||
# entropy = SHA1(keyhash) + added_entropy
|
# entropy = SHA1(keyhash) + added_entropy
|
||||||
|
@ -1580,16 +1600,16 @@ elif isosx:
|
||||||
# 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 range(rcnt):
|
||||||
item = items.pop(0)
|
item = items.pop(0)
|
||||||
edlst.append(item)
|
edlst.append(item)
|
||||||
|
|
||||||
keyname = 'unknown'
|
keyname = b'unknown'
|
||||||
for name in names:
|
for name in names:
|
||||||
if encodeHash(name,testMap8) == keyhash:
|
if encodeHash(name,testMap8) == keyhash:
|
||||||
keyname = name
|
keyname = name
|
||||||
break
|
break
|
||||||
if keyname == 'unknown':
|
if keyname == b'unknown':
|
||||||
keyname = keyhash
|
keyname = keyhash
|
||||||
|
|
||||||
# the testMap8 encoded contents data has had a length
|
# 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)
|
# (in other words split 'about' 2/3rds of the way through)
|
||||||
|
|
||||||
# move first offsets chars to end to align for decode by testMap8
|
# move first offsets chars to end to align for decode by testMap8
|
||||||
encdata = ''.join(edlst)
|
encdata = b''.join(edlst)
|
||||||
contlen = len(encdata)
|
contlen = len(encdata)
|
||||||
|
|
||||||
# now properly split and recombine
|
# now properly split and recombine
|
||||||
|
@ -1643,7 +1663,9 @@ elif isosx:
|
||||||
|
|
||||||
if len(DB)>6:
|
if len(DB)>6:
|
||||||
break
|
break
|
||||||
except:
|
|
||||||
|
except Exception:
|
||||||
|
print (traceback.format_exc())
|
||||||
pass
|
pass
|
||||||
if len(DB)>6:
|
if len(DB)>6:
|
||||||
# store values used in decryption
|
# store values used in decryption
|
||||||
|
@ -1709,7 +1731,7 @@ def cli_main():
|
||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
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:
|
try:
|
||||||
opts, args = getopt.getopt(argv[1:], "hk:")
|
opts, args = getopt.getopt(argv[1:], "hk:")
|
||||||
|
@ -1800,6 +1822,7 @@ def gui_main():
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
print ("here")
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
sys.exit(cli_main())
|
sys.exit(cli_main())
|
||||||
sys.exit(gui_main())
|
sys.exit(gui_main())
|
||||||
|
|
|
@ -137,10 +137,8 @@ def unicode_argv():
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["mobidedrm.py"]
|
return ["mobidedrm.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding
|
argvencoding = sys.stdin.encoding or "utf-8"
|
||||||
if argvencoding == None:
|
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||||
argvencoding = 'utf-8'
|
|
||||||
return sys.argv
|
|
||||||
|
|
||||||
|
|
||||||
class DrmException(Exception):
|
class DrmException(Exception):
|
||||||
|
@ -246,7 +244,7 @@ class MobiBook:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __init__(self, infile):
|
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:
|
try:
|
||||||
from alfcrypto import Pukall_Cipher
|
from alfcrypto import Pukall_Cipher
|
||||||
|
@ -522,7 +520,7 @@ def cli_main():
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
if len(argv)<3 or len(argv)>4:
|
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("Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks")
|
||||||
print("Usage:")
|
print("Usage:")
|
||||||
print(" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname))
|
print(" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname))
|
||||||
|
@ -531,7 +529,8 @@ def cli_main():
|
||||||
infile = argv[1]
|
infile = argv[1]
|
||||||
outfile = argv[2]
|
outfile = argv[2]
|
||||||
if len(argv) == 4:
|
if len(argv) == 4:
|
||||||
pidlist = argv[3].split(',')
|
# convert from unicode to bytearray before splitting.
|
||||||
|
pidlist = argv[3].encode('utf-8').split(b',')
|
||||||
else:
|
else:
|
||||||
pidlist = []
|
pidlist = []
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -18,7 +18,10 @@ import zlib, zipfile, tempfile, shutil
|
||||||
import traceback
|
import traceback
|
||||||
from struct import pack
|
from struct import pack
|
||||||
from struct import unpack
|
from struct import unpack
|
||||||
|
try:
|
||||||
from calibre_plugins.dedrm.alfcrypto import Topaz_Cipher
|
from calibre_plugins.dedrm.alfcrypto import Topaz_Cipher
|
||||||
|
except:
|
||||||
|
from alfcrypto import Topaz_Cipher
|
||||||
|
|
||||||
class SafeUnbuffered:
|
class SafeUnbuffered:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
|
@ -70,10 +73,8 @@ def unicode_argv():
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["mobidedrm.py"]
|
return ["mobidedrm.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding
|
argvencoding = sys.stdin.encoding or "utf-8"
|
||||||
if argvencoding == None:
|
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||||
argvencoding = 'utf-8'
|
|
||||||
return argv
|
|
||||||
|
|
||||||
#global switch
|
#global switch
|
||||||
debug = False
|
debug = False
|
||||||
|
|
|
@ -22,6 +22,9 @@ __version__ = "1.1"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import zlib
|
import zlib
|
||||||
|
try:
|
||||||
|
import zipfilerugged
|
||||||
|
except:
|
||||||
import calibre_plugins.dedrm.zipfilerugged as zipfilerugged
|
import calibre_plugins.dedrm.zipfilerugged as zipfilerugged
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
|
|
Loading…
Reference in New Issue