summaryrefslogtreecommitdiffstats
path: root/Other_Tools
diff options
context:
space:
mode:
authorApprentice Harper <[email protected]>2015-03-09 07:38:31 +0000
committerApprentice Alf <[email protected]>2015-03-09 07:41:07 +0000
commit9d9c879413e8ea2bc805399c15f2e94b02f48e1d (patch)
treead223063f4bf8e3de5abdb68c28277ab41cc4c19 /Other_Tools
parentc4fc10395b1a21ae9b0f76f4bbbf553e52129bba (diff)
tools v6.2.0
Updated for B&N new scheme, added obok plugin, and many minor fixes,
Diffstat (limited to 'Other_Tools')
-rw-r--r--Other_Tools/DRM_Key_Scripts/Barnes_and_Noble_ePubs/ignoblekey.pyw335
-rw-r--r--Other_Tools/Kindle_for_Android_Patches/kindle_version_4.8.1.10/Notes on the Patch.txt11
-rw-r--r--Other_Tools/Kindle_for_Android_Patches/kindle_version_4.8.1.10/kindle4.8.1.10.patch157
-rw-r--r--Other_Tools/Kobo/obok.py490
-rw-r--r--Other_Tools/Kobo/obok_2.01.py231
5 files changed, 993 insertions, 231 deletions
diff --git a/Other_Tools/DRM_Key_Scripts/Barnes_and_Noble_ePubs/ignoblekey.pyw b/Other_Tools/DRM_Key_Scripts/Barnes_and_Noble_ePubs/ignoblekey.pyw
new file mode 100644
index 0000000..4e9eead
--- /dev/null
+++ b/Other_Tools/DRM_Key_Scripts/Barnes_and_Noble_ePubs/ignoblekey.pyw
@@ -0,0 +1,335 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+# ignoblekey.py
+# Copyright © 2015 Apprentice Alf
+
+# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
+
+# Released under the terms of the GNU General Public Licence, version 3
+# <http://www.gnu.org/licenses/>
+
+# Revision history:
+# 1.0 - Initial release
+
+"""
+Get Barnes & Noble EPUB user key from nook Studio log file
+"""
+
+__license__ = 'GPL v3'
+__version__ = "1.0"
+
+import sys
+import os
+import hashlib
+import getopt
+import re
+
+# 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,unicode):
+ data = data.encode(self.encoding,"replace")
+ self.stream.write(data)
+ self.stream.flush()
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+
+try:
+ from calibre.constants import iswindows, isosx
+except:
+ iswindows = sys.platform.startswith('win')
+ isosx = sys.platform.startswith('darwin')
+
+def unicode_argv():
+ if iswindows:
+ # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
+ # strings.
+
+ # Versions 2.x of Python don't support Unicode in sys.argv on
+ # Windows, with the underlying Windows API instead replacing multi-byte
+ # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
+ # as a list of Unicode strings and encode them as utf-8
+
+ from ctypes import POINTER, byref, cdll, c_int, windll
+ from ctypes.wintypes import LPCWSTR, LPWSTR
+
+ GetCommandLineW = cdll.kernel32.GetCommandLineW
+ GetCommandLineW.argtypes = []
+ GetCommandLineW.restype = LPCWSTR
+
+ CommandLineToArgvW = windll.shell32.CommandLineToArgvW
+ CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
+ CommandLineToArgvW.restype = POINTER(LPWSTR)
+
+ cmd = GetCommandLineW()
+ argc = c_int(0)
+ argv = CommandLineToArgvW(cmd, byref(argc))
+ if argc.value > 0:
+ # Remove Python executable and commands if present
+ start = argc.value - len(sys.argv)
+ return [argv[i] for i in
+ xrange(start, argc.value)]
+ # if we don't have any arguments at all, just pass back script name
+ # this should never happen
+ return [u"ignoblekey.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]
+
+class DrmException(Exception):
+ pass
+
+# Locate all of the nookStudy/nook for PC/Mac log file and return as list
+def getNookLogFiles():
+ logFiles = []
+ found = False
+ if iswindows:
+ import _winreg as winreg
+
+ # some 64 bit machines do not have the proper registry key for some reason
+ # or the python interface to the 32 vs 64 bit registry is broken
+ paths = set()
+ if 'LOCALAPPDATA' in os.environ.keys():
+ # Python 2.x does not return unicode env. Use Python 3.x
+ path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
+ if os.path.isdir(path):
+ paths.add(path)
+ if 'USERPROFILE' in os.environ.keys():
+ # Python 2.x does not return unicode env. Use Python 3.x
+ path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Local"
+ if os.path.isdir(path):
+ paths.add(path)
+ path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Roaming"
+ if os.path.isdir(path):
+ paths.add(path)
+ # User Shell Folders show take precedent over Shell Folders if present
+ try:
+ regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
+ path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
+ if os.path.isdir(path):
+ paths.add(path)
+ except WindowsError:
+ pass
+ try:
+ regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
+ path = winreg.QueryValueEx(regkey, 'AppData')[0]
+ if os.path.isdir(path):
+ paths.add(path)
+ except WindowsError:
+ pass
+ try:
+ regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
+ path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
+ if os.path.isdir(path):
+ paths.add(path)
+ except WindowsError:
+ pass
+ try:
+ regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
+ path = winreg.QueryValueEx(regkey, 'AppData')[0]
+ if os.path.isdir(path):
+ paths.add(path)
+ except WindowsError:
+ pass
+
+ for path in paths:
+ # look for nookStudy log file
+ logpath = path +'\\Barnes & Noble\\NOOKstudy\\logs\\BNClientLog.txt'
+ if os.path.isfile(logpath):
+ found = True
+ print('Found nookStudy log file: ' + logpath.encode('ascii','ignore'))
+ logFiles.append(logpath)
+ else:
+ home = os.getenv('HOME')
+ # check for BNClientLog.txt in various locations
+ testpath = home + '/Library/Application Support/Barnes & Noble/DesktopReader/logs/BNClientLog.txt'
+ if os.path.isfile(testpath):
+ logFiles.append(testpath)
+ print('Found nookStudy log file: ' + testpath)
+ found = True
+ testpath = home + '/Library/Application Support/Barnes & Noble/DesktopReader/indices/BNClientLog.txt'
+ if os.path.isfile(testpath):
+ logFiles.append(testpath)
+ print('Found nookStudy log file: ' + testpath)
+ found = True
+ testpath = home + '/Library/Application Support/Barnes & Noble/BNDesktopReader/logs/BNClientLog.txt'
+ if os.path.isfile(testpath):
+ logFiles.append(testpath)
+ print('Found nookStudy log file: ' + testpath)
+ found = True
+ testpath = home + '/Library/Application Support/Barnes & Noble/BNDesktopReader/indices/BNClientLog.txt'
+ if os.path.isfile(testpath):
+ logFiles.append(testpath)
+ print('Found nookStudy log file: ' + testpath)
+ found = True
+
+ if not found:
+ print('No nook Study log files have been found.')
+ return logFiles
+
+
+# Extract CCHash key(s) from log file
+def getKeysFromLog(kLogFile):
+ keys = []
+ regex = re.compile("ccHash: \"(.{28})\"");
+ for line in open(kLogFile):
+ for m in regex.findall(line):
+ keys.append(m)
+ return keys
+
+# interface for calibre plugin
+def nookkeys(files = []):
+ keys = []
+ if files == []:
+ files = getNookLogFiles()
+ for file in files:
+ fileKeys = getKeysFromLog(file)
+ if fileKeys:
+ print u"Found {0} keys in the Nook Study log files".format(len(fileKeys))
+ keys.extend(fileKeys)
+ return keys
+
+# interface for Python DeDRM
+# returns single key or multiple keys, depending on path or file passed in
+def getkey(outpath, files=[]):
+ keys = nookkeys(files)
+ if len(keys) > 0:
+ if not os.path.isdir(outpath):
+ outfile = outpath
+ with file(outfile, 'w') as keyfileout:
+ keyfileout.write(keys[0])
+ print u"Saved a key to {0}".format(outfile)
+ else:
+ keycount = 0
+ for key in keys:
+ while True:
+ keycount += 1
+ outfile = os.path.join(outpath,u"nookkey{0:d}.b64".format(keycount))
+ if not os.path.exists(outfile):
+ break
+ with file(outfile, 'w') as keyfileout:
+ keyfileout.write(key)
+ print u"Saved a key to {0}".format(outfile)
+ return True
+ return False
+
+def usage(progname):
+ print u"Finds the nook Study encryption keys."
+ print u"Keys are saved to the current directory, or a specified output directory."
+ print u"If a file name is passed instead of a directory, only the first key is saved, in that file."
+ print u"Usage:"
+ print u" {0:s} [-h] [-k <logFile>] [<outpath>]".format(progname)
+
+
+def cli_main():
+ sys.stdout=SafeUnbuffered(sys.stdout)
+ sys.stderr=SafeUnbuffered(sys.stderr)
+ argv=unicode_argv()
+ progname = os.path.basename(argv[0])
+ print u"{0} v{1}\nCopyright © 2015 Apprentice Alf".format(progname,__version__)
+
+ try:
+ opts, args = getopt.getopt(argv[1:], "hk:")
+ except getopt.GetoptError, err:
+ print u"Error in options or arguments: {0}".format(err.args[0])
+ usage(progname)
+ sys.exit(2)
+
+ files = []
+ for o, a in opts:
+ if o == "-h":
+ usage(progname)
+ sys.exit(0)
+ if o == "-k":
+ files = [a]
+
+ if len(args) > 1:
+ usage(progname)
+ sys.exit(2)
+
+ if len(args) == 1:
+ # save to the specified file or directory
+ outpath = args[0]
+ if not os.path.isabs(outpath):
+ outpath = os.path.abspath(outpath)
+ else:
+ # save to the same directory as the script
+ outpath = os.path.dirname(argv[0])
+
+ # make sure the outpath is the
+ outpath = os.path.realpath(os.path.normpath(outpath))
+
+ if not getkey(outpath, files):
+ print u"Could not retrieve nook Study key."
+ return 0
+
+
+def gui_main():
+ try:
+ import Tkinter
+ import Tkconstants
+ import tkMessageBox
+ import traceback
+ except:
+ return cli_main()
+
+ class ExceptionDialog(Tkinter.Frame):
+ def __init__(self, root, text):
+ Tkinter.Frame.__init__(self, root, border=5)
+ label = Tkinter.Label(self, text=u"Unexpected error:",
+ anchor=Tkconstants.W, justify=Tkconstants.LEFT)
+ label.pack(fill=Tkconstants.X, expand=0)
+ self.text = Tkinter.Text(self)
+ self.text.pack(fill=Tkconstants.BOTH, expand=1)
+
+ self.text.insert(Tkconstants.END, text)
+
+
+ argv=unicode_argv()
+ root = Tkinter.Tk()
+ root.withdraw()
+ progpath, progname = os.path.split(argv[0])
+ success = False
+ try:
+ keys = nookkeys()
+ keycount = 0
+ for key in keys:
+ print key
+ while True:
+ keycount += 1
+ outfile = os.path.join(progpath,u"nookkey{0:d}.b64".format(keycount))
+ if not os.path.exists(outfile):
+ break
+
+ with file(outfile, 'w') as keyfileout:
+ keyfileout.write(key)
+ success = True
+ tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
+ except DrmException, e:
+ tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
+ except Exception:
+ root.wm_state('normal')
+ root.title(progname)
+ text = traceback.format_exc()
+ ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
+ root.mainloop()
+ if not success:
+ return 1
+ return 0
+
+if __name__ == '__main__':
+ if len(sys.argv) > 1:
+ sys.exit(cli_main())
+ sys.exit(gui_main())
diff --git a/Other_Tools/Kindle_for_Android_Patches/kindle_version_4.8.1.10/Notes on the Patch.txt b/Other_Tools/Kindle_for_Android_Patches/kindle_version_4.8.1.10/Notes on the Patch.txt
new file mode 100644
index 0000000..07e579a
--- /dev/null
+++ b/Other_Tools/Kindle_for_Android_Patches/kindle_version_4.8.1.10/Notes on the Patch.txt
@@ -0,0 +1,11 @@
+Notes from UE01 about this patch:
+
+1. Revised the “Other_Tools/Kindle_for_Android_Patches/” for Kindle 4.8.1.10
+2. Built Kindle 4.8.1.10 with the PID List added to the About activity
+3. Uninstalled the Amazon/Play-store version and installed the patched version
+4. Signed in to Amazon
+5. Opened the book
+6. Did Info > About > PID List and copied the PIDs to Calibre’s Plugins>File type > DeDRM > Mobipocket dialog
+7. **Crucial** copied the PRC file to the PC (because the file’s checksum has changed since it was last copied)
+8. In Calibre, Add Books (from a single directory)
+
diff --git a/Other_Tools/Kindle_for_Android_Patches/kindle_version_4.8.1.10/kindle4.8.1.10.patch b/Other_Tools/Kindle_for_Android_Patches/kindle_version_4.8.1.10/kindle4.8.1.10.patch
new file mode 100644
index 0000000..daebdcb
--- /dev/null
+++ b/Other_Tools/Kindle_for_Android_Patches/kindle_version_4.8.1.10/kindle4.8.1.10.patch
@@ -0,0 +1,157 @@
+diff --git a/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali b/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali
+index 8ea400e..3aefad2 100644
+--- a/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali
++++ b/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali
+@@ -41,6 +41,8 @@
+
+ .field private security:Lcom/mobipocket/android/library/reader/AndroidSecurity;
+
++.field private pidList:Ljava/lang/String;
++
+ .field private totalMemory:J
+
+
+@@ -74,6 +76,10 @@
+ .line 133
+ iput-object p1, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->security:Lcom/mobipocket/android/library/reader/AndroidSecurity;
+
++ const-string v0, "Open DRMed book to show PID list."
++
++ iput-object v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
++
+ .line 134
+ sget-object v0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->TAG:Ljava/lang/String;
+
+@@ -1339,3 +1345,26 @@
+
+ return-wide v0
+ .end method
++
++.method public getPidList()Ljava/lang/String;
++ .locals 1
++
++ .prologue
++ .line 15
++ iget-object v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
++
++ return-object v0
++.end method
++
++.method public setPidList(Ljava/lang/String;)V
++ .locals 0
++ .param p1, "value"
++
++ .prologue
++ .line 11
++ iput-object p1, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
++
++ .line 12
++ return-void
++.end method
++
+diff --git a/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali b/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali
+index e4a3523..2269fab 100644
+--- a/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali
++++ b/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali
+@@ -30,3 +30,9 @@
+
+ .method public abstract getPid()Ljava/lang/String;
+ .end method
++
++.method public abstract getPidList()Ljava/lang/String;
++.end method
++
++.method public abstract setPidList(Ljava/lang/String;)V
++.end method
+diff --git a/smali/com/amazon/kcp/info/AboutActivity.smali b/smali/com/amazon/kcp/info/AboutActivity.smali
+index 5640e9e..e298341 100644
+--- a/smali/com/amazon/kcp/info/AboutActivity.smali
++++ b/smali/com/amazon/kcp/info/AboutActivity.smali
+@@ -493,6 +493,57 @@
+ return-void
+ .end method
+
++.method private populatePIDList()V
++ .locals 7
++
++ .prologue
++ .line 313
++ invoke-static {}, Lcom/amazon/kcp/application/DeviceInformationProviderFactory;->getProvider()Lcom/amazon/kcp/application/IDeviceInformationProvider;
++
++ move-result-object v0
++
++ invoke-interface {v0}, Lcom/amazon/kcp/application/IDeviceInformationProvider;->getPidList()Ljava/lang/String;
++
++ move-result-object v1
++
++ .line 314
++ .local v1, "PidList":Ljava/lang/String;
++ iget-object v3, p0, Lcom/amazon/kcp/info/AboutActivity;->groupItemList:Ljava/util/List;
++
++ new-instance v4, Lcom/amazon/kcp/info/AboutActivity$GroupItem;
++
++ const-string v5, "PID List"
++
++ const v6, 0x1
++
++ invoke-direct {v4, p0, v5, v6}, Lcom/amazon/kcp/info/AboutActivity$GroupItem;-><init>(Lcom/amazon/kcp/info/AboutActivity;Ljava/lang/String;Z)V
++
++ invoke-interface {v3, v4}, Ljava/util/List;->add(Ljava/lang/Object;)Z
++
++ .line 315
++ new-instance v2, Ljava/util/ArrayList;
++
++ invoke-direct {v2}, Ljava/util/ArrayList;-><init>()V
++
++ .line 316
++ .local v2, "children":Ljava/util/List;,"Ljava/util/List<Lcom/amazon/kcp/info/AboutActivity$DetailItem;>;"
++ new-instance v3, Lcom/amazon/kcp/info/AboutActivity$DetailItem;
++
++ const-string v4, "PIDs"
++
++ invoke-direct {v3, p0, v4, v1}, Lcom/amazon/kcp/info/AboutActivity$DetailItem;-><init>(Lcom/amazon/kcp/info/AboutActivity;Ljava/lang/String;Ljava/lang/String;)V
++
++ invoke-interface {v2, v3}, Ljava/util/List;->add(Ljava/lang/Object;)Z
++
++ .line 317
++ iget-object v3, p0, Lcom/amazon/kcp/info/AboutActivity;->detailItemList:Ljava/util/List;
++
++ invoke-interface {v3, v2}, Ljava/util/List;->add(Ljava/lang/Object;)Z
++
++ .line 318
++ return-void
++.end method
++
+ .method private populateDisplayItems()V
+ .locals 1
+
+@@ -538,6 +589,8 @@
+ .line 173
+ invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populateDisplayInformation()V
+
++ invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populatePIDList()V
++
+ .line 174
+ return-void
+
+diff --git a/smali/com/amazon/system/security/Security.smali b/smali/com/amazon/system/security/Security.smali
+index 04ea997..e88fe08 100644
+--- a/smali/com/amazon/system/security/Security.smali
++++ b/smali/com/amazon/system/security/Security.smali
+@@ -940,6 +940,16 @@
+
+ aput-object v0, v6, v8
+
++ invoke-static {}, Lcom/amazon/kcp/application/DeviceInformationProviderFactory;->getProvider()Lcom/amazon/kcp/application/IDeviceInformationProvider;
++
++ move-result-object v5
++
++ invoke-static {v6}, Ljava/util/Arrays;->toString([Ljava/lang/Object;)Ljava/lang/String;
++
++ move-result-object v2
++
++ invoke-interface {v5, v2}, Lcom/amazon/kcp/application/IDeviceInformationProvider;->setPidList(Ljava/lang/String;)V
++
+ .line 347
+ return-object v6
+ .end method \ No newline at end of file
diff --git a/Other_Tools/Kobo/obok.py b/Other_Tools/Kobo/obok.py
new file mode 100644
index 0000000..42620d2
--- /dev/null
+++ b/Other_Tools/Kobo/obok.py
@@ -0,0 +1,490 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Version 3.1.2 January 2015
+# Add coding, version number and version announcement
+#
+# Version 3.05 October 2014
+# Identifies DRM-free books in the dialog
+#
+# Version 3.04 September 2014
+# Handles DRM-free books as well (sometimes Kobo Library doesn't
+# show download link for DRM-free books)
+#
+# Version 3.03 August 2014
+# If PyCrypto is unavailable try to use libcrypto for AES_ECB.
+#
+# Version 3.02 August 2014
+# Relax checking of application/xhtml+xml and image/jpeg content.
+#
+# Version 3.01 June 2014
+# Check image/jpeg as well as application/xhtml+xml content. Fix typo
+# in Windows ipconfig parsing.
+#
+# Version 3.0 June 2014
+# Made portable for Mac and Windows, and the only module dependency
+# not part of python core is PyCrypto. Major code cleanup/rewrite.
+# No longer tries the first MAC address; tries them all if it detects
+# the decryption failed.
+#
+# Updated September 2013 by Anon
+# Version 2.02
+# Incorporated minor fixes posted at Apprentice Alf's.
+#
+# Updates July 2012 by Michael Newton
+# PWSD ID is no longer a MAC address, but should always
+# be stored in the registry. Script now works with OS X
+# and checks plist for values instead of registry. Must
+# have biplist installed for OS X support.
+#
+# Original comments left below; note the "AUTOPSY" is inaccurate. See
+# KoboLibrary.userkeys and KoboFile.decrypt()
+#
+##########################################################
+# KOBO DRM CRACK BY #
+# PHYSISTICATED #
+##########################################################
+# This app was made for Python 2.7 on Windows 32-bit
+#
+# This app needs pycrypto - get from here:
+# http://www.voidspace.org.uk/python/modules.shtml
+#
+# Usage: obok.py
+# Choose the book you want to decrypt
+#
+# Shouts to my krew - you know who you are - and one in
+# particular who gave me a lot of help with this - thank
+# you so much!
+#
+# Kopimi /K\
+# Keep sharing, keep copying, but remember that nothing is
+# for free - make sure you compensate your favorite
+# authors - and cut out the middle man whenever possible
+# ;) ;) ;)
+#
+# DRM AUTOPSY
+# The Kobo DRM was incredibly easy to crack, but it took
+# me months to get around to making this. Here's the
+# basics of how it works:
+# 1: Get MAC address of first NIC in ipconfig (sometimes
+# stored in registry as pwsdid)
+# 2: Get user ID (stored in tons of places, this gets it
+# from HKEY_CURRENT_USER\Software\Kobo\Kobo Desktop
+# Edition\Browser\cookies)
+# 3: Concatenate and SHA256, take the second half - this
+# is your master key
+# 4: Open %LOCALAPPDATA%\Kobo Desktop Editions\Kobo.sqlite
+# and dump content_keys
+# 5: Unbase64 the keys, then decode these with the master
+# key - these are your page keys
+# 6: Unzip EPUB of your choice, decrypt each page with its
+# page key, then zip back up again
+#
+# WHY USE THIS WHEN INEPT WORKS FINE? (adobe DRM stripper)
+# Inept works very well, but authors on Kobo can choose
+# what DRM they want to use - and some have chosen not to
+# let people download them with Adobe Digital Editions -
+# they would rather lock you into a single platform.
+#
+# With Obok, you can sync Kobo Desktop, decrypt all your
+# ebooks, and then use them on whatever device you want
+# - you bought them, you own them, you can do what you
+# like with them.
+#
+# Obok is Kobo backwards, but it is also means "next to"
+# in Polish.
+# When you buy a real book, it is right next to you. You
+# can read it at home, at work, on a train, you can lend
+# it to a friend, you can scribble on it, and add your own
+# explanations/translations.
+#
+# Obok gives you this power over your ebooks - no longer
+# are you restricted to one device. This allows you to
+# embed foreign fonts into your books, as older Kobo's
+# can't display them properly. You can read your books
+# on your phones, in different PC readers, and different
+# ereader devices. You can share them with your friends
+# too, if you like - you can do that with a real book
+# after all.
+#
+"""Manage all Kobo books, either encrypted or DRM-free."""
+
+__version__ = '3.1.1'
+
+import sys
+import os
+import subprocess
+import sqlite3
+import base64
+import binascii
+import re
+import zipfile
+import hashlib
+import xml.etree.ElementTree as ET
+import string
+import shutil
+
+class ENCRYPTIONError(Exception):
+ pass
+
+def _load_crypto_libcrypto():
+ from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
+ Structure, c_ulong, create_string_buffer, cast
+ from ctypes.util import find_library
+
+ if sys.platform.startswith('win'):
+ libcrypto = find_library('libeay32')
+ else:
+ libcrypto = find_library('crypto')
+
+ if libcrypto is None:
+ raise ENCRYPTIONError('libcrypto not found')
+ libcrypto = CDLL(libcrypto)
+
+ AES_MAXNR = 14
+
+ c_char_pp = POINTER(c_char_p)
+ c_int_p = POINTER(c_int)
+
+ class AES_KEY(Structure):
+ _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
+ ('rounds', c_int)]
+ AES_KEY_p = POINTER(AES_KEY)
+
+ def F(restype, name, argtypes):
+ func = getattr(libcrypto, name)
+ func.restype = restype
+ func.argtypes = argtypes
+ return func
+
+ AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
+ [c_char_p, c_int, AES_KEY_p])
+ AES_ecb_encrypt = F(None, 'AES_ecb_encrypt',
+ [c_char_p, c_char_p, AES_KEY_p, c_int])
+
+ class AES(object):
+ def __init__(self, userkey):
+ self._blocksize = len(userkey)
+ if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
+ raise ENCRYPTIONError(_('AES improper key used'))
+ return
+ key = self._key = AES_KEY()
+ rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
+ if rv < 0:
+ raise ENCRYPTIONError(_('Failed to initialize AES key'))
+
+ def decrypt(self, data):
+ clear = ''
+ for i in range(0, len(data), 16):
+ out = create_string_buffer(16)
+ rv = AES_ecb_encrypt(data[i:i+16], out, self._key, 0)
+ if rv == 0:
+ raise ENCRYPTIONError(_('AES decryption failed'))
+ clear += out.raw
+ return clear
+
+ return AES
+
+def _load_crypto_pycrypto():
+ from Crypto.Cipher import AES as _AES
+ class AES(object):
+ def __init__(self, key):
+ self._aes = _AES.new(key, _AES.MODE_ECB)
+
+ def decrypt(self, data):
+ return self._aes.decrypt(data)
+
+ return AES
+
+def _load_crypto():
+ AES = None
+ cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
+ for loader in cryptolist:
+ try:
+ AES = loader()
+ break
+ except (ImportError, ENCRYPTIONError):
+ pass
+ return AES
+
+AES = _load_crypto()
+
+class KoboLibrary(object):
+ """The Kobo library.
+
+ This class represents all the information available from the data
+ written by the Kobo Desktop Edition application, including the list
+ of books, their titles, and the user's encryption key(s)."""
+
+ def __init__ (self):
+ print u"Obok v{0}\nCopyright © 2012-2014 Physisticated et al.".format(__version__)
+ if sys.platform.startswith('win'):
+ if sys.getwindowsversion().major > 5:
+ self.kobodir = os.environ['LOCALAPPDATA']
+ else:
+ self.kobodir = os.path.join(os.environ['USERPROFILE'], 'Local Settings', 'Application Data')
+ self.kobodir = os.path.join(self.kobodir, 'Kobo', 'Kobo Desktop Edition')
+ elif sys.platform.startswith('darwin'):
+ self.kobodir = os.path.join(os.environ['HOME'], 'Library', 'Application Support', 'Kobo', 'Kobo Desktop Edition')
+ self.bookdir = os.path.join(self.kobodir, 'kepub')
+ kobodb = os.path.join(self.kobodir, 'Kobo.sqlite')
+ self.__sqlite = sqlite3.connect(kobodb)
+ self.__cursor = self.__sqlite.cursor()
+ self._userkeys = []
+ self._books = []
+ self._volumeID = []
+
+ def close (self):
+ """Closes the database used by the library."""
+ self.__cursor.close()
+ self.__sqlite.close()
+
+ @property
+ def userkeys (self):
+ """The list of potential userkeys being used by this library.
+ Only one of these will be valid.
+ """
+ if len(self._userkeys) != 0:
+ return self._userkeys
+ userid = self.__getuserid()
+ for macaddr in self.__getmacaddrs():
+ self._userkeys.append(self.__getuserkey(macaddr, userid))
+ return self._userkeys
+
+ @property
+ def books (self):
+ """The list of KoboBook objects in the library."""
+ if len(self._books) != 0:
+ return self._books
+ """Drm-ed kepub"""
+ for row in self.__cursor.execute('SELECT DISTINCT volumeid, Title, Attribution, Series FROM content_keys, content WHERE contentid = volumeid'):
+ self._books.append(KoboBook(row[0], row[1], self.__bookfile(row[0]), 'kepub', self.__cursor, author=row[2], series=row[3]))
+ self._volumeID.append(row[0])
+ """Drm-free"""
+ for f in os.listdir(self.bookdir):
+ if(f not in self._volumeID):
+ row = self.__cursor.execute("SELECT Title, Attribution, Series FROM content WHERE ContentID = '" + f + "'").fetchone()
+ if row is not None:
+ fTitle = row[0]
+ self._books.append(KoboBook(f, fTitle, self.__bookfile(f), 'drm-free', self.__cursor, author=row[1], series=row[2]))
+ self._volumeID.append(f)
+ """Sort"""
+ self._books.sort(key=lambda x: x.title)
+ return self._books
+
+ def __bookfile (self, volumeid):
+ """The filename needed to open a given book."""
+ return os.path.join(self.kobodir, 'kepub', volumeid)
+
+ def __getmacaddrs (self):
+ """The list of all MAC addresses on this machine."""
+ macaddrs = []
+ if sys.platform.startswith('win'):
+ c = re.compile('\s(' + '[0-9a-f]{2}-' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
+ for line in os.popen('ipconfig /all'):
+ m = c.search(line)
+ if m:
+ macaddrs.append(re.sub("-", ":", m.group(1)).upper())
+ elif sys.platform.startswith('darwin'):
+ c = re.compile('\s(' + '[0-9a-f]{2}:' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
+ output = subprocess.check_output('/sbin/ifconfig -a', shell=True)
+ matches = c.findall(output)
+ for m in matches:
+ # print "m:",m[0]
+ macaddrs.append(m[0].upper())
+ return macaddrs
+
+ def __getuserid (self):
+ return self.__cursor.execute('SELECT UserID FROM user WHERE HasMadePurchase = "true"').fetchone()[0]
+
+ def __getuserkey (self, macaddr, userid):
+ deviceid = hashlib.sha256('NoCanLook' + macaddr).hexdigest()
+ userkey = hashlib.sha256(deviceid + userid).hexdigest()
+ return binascii.a2b_hex(userkey[32:])
+
+class KoboBook(object):
+ """A Kobo book.
+
+ A Kobo book contains a number of unencrypted and encrypted files.
+ This class provides a list of the encrypted files.
+
+ Each book has the following instance variables:
+ volumeid - a UUID which uniquely refers to the book in this library.
+ title - the human-readable book title.
+ filename - the complete path and filename of the book.
+ type - either kepub or drm-free"""
+ def __init__ (self, volumeid, title, filename, type, cursor, author=None, series=None):
+ self.volumeid = volumeid
+ self.title = title
+ self.author = author
+ self.series = series
+ self.series_index = None
+ self.filename = filename
+ self.type = type
+ self.__cursor = cursor
+ self._encryptedfiles = {}
+
+ @property
+ def encryptedfiles (self):
+ """A dictionary of KoboFiles inside the book.
+
+ The dictionary keys are the relative pathnames, which are
+ the same as the pathnames inside the book 'zip' file."""
+ if (self.type == 'drm-free'):
+ return self._encryptedfiles
+ if len(self._encryptedfiles) != 0:
+ return self._encryptedfiles
+ # Read the list of encrypted files from the DB
+ for row in self.__cursor.execute('SELECT elementid,elementkey FROM content_keys,content WHERE volumeid = ? AND volumeid = contentid',(self.volumeid,)):
+ self._encryptedfiles[row[0]] = KoboFile(row[0], None, base64.b64decode(row[1]))
+
+ # Read the list of files from the kepub OPF manifest so that
+ # we can get their proper MIME type.
+ # NOTE: this requires that the OPF file is unencrypted!
+ zin = zipfile.ZipFile(self.filename, "r")
+ xmlns = {
+ 'ocf': 'urn:oasis:names:tc:opendocument:xmlns:container',
+ 'opf': 'http://www.idpf.org/2007/opf'
+ }
+ ocf = ET.fromstring(zin.read('META-INF/container.xml'))
+ opffile = ocf.find('.//ocf:rootfile', xmlns).attrib['full-path']
+ basedir = re.sub('[^/]+$', '', opffile)
+ opf = ET.fromstring(zin.read(opffile))
+ zin.close()
+
+ c = re.compile('/')
+ for item in opf.findall('.//opf:item', xmlns):
+ mimetype = item.attrib['media-type']
+
+ # Convert relative URIs
+ href = item.attrib['href']
+ if not c.match(href):
+ href = string.join((basedir, href), '')
+
+ # Update books we've found from the DB.
+ if href in self._encryptedfiles:
+ self._encryptedfiles[href].mimetype = mimetype
+ return self._encryptedfiles
+
+ @property
+ def has_drm (self):
+ return not self.type == 'drm-free'
+
+
+class KoboFile(object):
+ """An encrypted file in a KoboBook.
+
+ Each file has the following instance variables:
+ filename - the relative pathname inside the book zip file.
+ mimetype - the file's MIME type, e.g. 'image/jpeg'
+ key - the encrypted page key."""
+
+ def __init__ (self, filename, mimetype, key):
+ self.filename = filename
+ self.mimetype = mimetype
+ self.key = key
+ def decrypt (self, userkey, contents):
+ """
+ Decrypt the contents using the provided user key and the
+ file page key. The caller must determine if the decrypted
+ data is correct."""
+ # The userkey decrypts the page key (self.key)
+ keyenc = AES(userkey)
+ decryptedkey = keyenc.decrypt(self.key)
+ # The decrypted page key decrypts the content
+ pageenc = AES(decryptedkey)
+ return self.__removeaespadding(pageenc.decrypt(contents))
+
+ def check (self, contents):
+ """
+ If the contents uses some known MIME types, check if it
+ conforms to the type. Throw a ValueError exception if not.
+ If the contents uses an uncheckable MIME type, don't check
+ it and don't throw an exception.
+ Returns True if the content was checked, False if it was not
+ checked."""
+ if self.mimetype == 'application/xhtml+xml':
+ if contents[:5]=="<?xml":
+ return True
+ else:
+ print "Bad XML: ",contents[:5]
+ raise ValueError
+ if self.mimetype == 'image/jpeg':
+ if contents[:3] == '\xff\xd8\xff':
+ return True
+ else:
+ print "Bad JPEG: ", contents[:3].encode('hex')
+ raise ValueError()
+ return False
+
+ def __removeaespadding (self, contents):
+ """
+ Remove the trailing padding, using what appears to be the CMS
+ algorithm from RFC 5652 6.3"""
+ lastchar = binascii.b2a_hex(contents[-1:])
+ strlen = int(lastchar, 16)
+ padding = strlen
+ if strlen == 1:
+ return contents[:-1]
+ if strlen < 16:
+ for i in range(strlen):
+ testchar = binascii.b2a_hex(contents[-strlen:-(strlen-1)])
+ if testchar != lastchar:
+ padding = 0
+ if padding > 0:
+ contents = contents[:-padding]
+ return contents
+
+if __name__ == '__main__':
+
+ lib = KoboLibrary()
+
+ for i, book in enumerate(lib.books):
+ print ('%d: %s' % (i + 1, book.title)).encode('ascii', 'ignore')
+
+ num_string = raw_input("Convert book number... ")
+ try:
+ num = int(num_string)
+ book = lib.books[num - 1]
+ except (ValueError, IndexError):
+ exit()
+
+ print "Converting", book.title
+
+ zin = zipfile.ZipFile(book.filename, "r")
+ # make filename out of Unicode alphanumeric and whitespace equivalents from title
+ outname = "%s.epub" % (re.sub('[^\s\w]', '', book.title, 0, re.UNICODE))
+
+ if (book.type == 'drm-free'):
+ print "DRM-free book, conversion is not needed"
+ shutil.copyfile(book.filename, outname)
+ print "Book saved as", os.path.join(os.getcwd(), outname)
+ exit(0)
+
+ result = 1
+ for userkey in lib.userkeys:
+ # print "Trying key: ",userkey.encode('hex_codec')
+ confirmedGood = False
+ try:
+ zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
+ for filename in zin.namelist():
+ contents = zin.read(filename)
+ if filename in book.encryptedfiles:
+ file = book.encryptedfiles[filename]
+ contents = file.decrypt(userkey, contents)
+ # Parse failures mean the key is probably wrong.
+ if not confirmedGood:
+ confirmedGood = file.check(contents)
+ zout.writestr(filename, contents)
+ zout.close()
+ print "Book saved as", os.path.join(os.getcwd(), outname)
+ result = 0
+ break
+ except ValueError:
+ print "Decryption failed, trying next key"
+ zout.close()
+ os.remove(outname)
+
+ zin.close()
+ lib.close()
+ exit(result)
diff --git a/Other_Tools/Kobo/obok_2.01.py b/Other_Tools/Kobo/obok_2.01.py
deleted file mode 100644
index 3ed7cbd..0000000
--- a/Other_Tools/Kobo/obok_2.01.py
+++ /dev/null
@@ -1,231 +0,0 @@
-#!/usr/bin/env python
-#
-# Updated September 2013 by Anon
-# Version 2.01
-# Incorporated minor fixes posted at Apprentice Alf's.
-#
-# Updates July 2012 by Michael Newton
-# PWSD ID is no longer a MAC address, but should always
-# be stored in the registry. Script now works with OS X
-# and checks plist for values instead of registry. Must
-# have biplist installed for OS X support.
-#
-##########################################################
-# KOBO DRM CRACK BY #
-# PHYSISTICATED #
-##########################################################
-# This app was made for Python 2.7 on Windows 32-bit
-#
-# This app needs pycrypto - get from here:
-# http://www.voidspace.org.uk/python/modules.shtml
-#
-# Usage: obok.py
-# Choose the book you want to decrypt
-#
-# Shouts to my krew - you know who you are - and one in
-# particular who gave me a lot of help with this - thank
-# you so much!
-#
-# Kopimi /K\
-# Keep sharing, keep copying, but remember that nothing is
-# for free - make sure you compensate your favorite
-# authors - and cut out the middle man whenever possible
-# ;) ;) ;)
-#
-# DRM AUTOPSY
-# The Kobo DRM was incredibly easy to crack, but it took
-# me months to get around to making this. Here's the
-# basics of how it works:
-# 1: Get MAC address of first NIC in ipconfig (sometimes
-# stored in registry as pwsdid)
-# 2: Get user ID (stored in tons of places, this gets it
-# from HKEY_CURRENT_USER\Software\Kobo\Kobo Desktop
-# Edition\Browser\cookies)
-# 3: Concatenate and SHA256, take the second half - this
-# is your master key
-# 4: Open %LOCALAPPDATA%\Kobo Desktop Editions\Kobo.sqlite
-# and dump content_keys
-# 5: Unbase64 the keys, then decode these with the master
-# key - these are your page keys
-# 6: Unzip EPUB of your choice, decrypt each page with its
-# page key, then zip back up again
-#
-# WHY USE THIS WHEN INEPT WORKS FINE? (adobe DRM stripper)
-# Inept works very well, but authors on Kobo can choose
-# what DRM they want to use - and some have chosen not to
-# let people download them with Adobe Digital Editions -
-# they would rather lock you into a single platform.
-#
-# With Obok, you can sync Kobo Desktop, decrypt all your
-# ebooks, and then use them on whatever device you want
-# - you bought them, you own them, you can do what you
-# like with them.
-#
-# Obok is Kobo backwards, but it is also means "next to"
-# in Polish.
-# When you buy a real book, it is right next to you. You
-# can read it at home, at work, on a train, you can lend
-# it to a friend, you can scribble on it, and add your own
-# explanations/translations.
-#
-# Obok gives you this power over your ebooks - no longer
-# are you restricted to one device. This allows you to
-# embed foreign fonts into your books, as older Kobo's
-# can't display them properly. You can read your books
-# on your phones, in different PC readers, and different
-# ereader devices. You can share them with your friends
-# too, if you like - you can do that with a real book
-# after all.
-#
-"""
-Decrypt Kobo encrypted EPUB books.
-"""
-
-import os
-import sys
-if sys.platform.startswith('win'):
- import _winreg
-elif sys.platform.startswith('darwin'):
- from biplist import readPlist
-import re
-import string
-import hashlib
-import sqlite3
-import base64
-import binascii
-import zipfile
-from Crypto.Cipher import AES
-
-def SHA256(raw):
- return hashlib.sha256(raw).hexdigest()
-
-def RemoveAESPadding(contents):
- lastchar = binascii.b2a_hex(contents[-1:])
- strlen = int(lastchar, 16)
- padding = strlen
- if(strlen == 1):
- return contents[:-1]
- if(strlen < 16):
- for i in range(strlen):
- testchar = binascii.b2a_hex(contents[-strlen:-(strlen-1)])
- if(testchar != lastchar):
- padding = 0
- if(padding > 0):
- contents = contents[:-padding]
- return contents
-
-def GetVolumeKeys(dbase, enc):
- volumekeys = {}
- for row in dbase.execute("SELECT * from content_keys"):
- if(row[0] not in volumekeys):
- volumekeys[row[0]] = {}
- volumekeys[row[0]][row[1]] = {}
- volumekeys[row[0]][row[1]]["encryptedkey"] = base64.b64decode(row[2])
- volumekeys[row[0]][row[1]]["decryptedkey"] = enc.decrypt(volumekeys[row[0]][row[1]]["encryptedkey"])
- # get book name
- for key in volumekeys.keys():
- volumekeys[key]["title"] = dbase.execute("SELECT Title from content where ContentID = '%s'" % (key)).fetchone()[0]
- return volumekeys
-
-def ByteArrayToString(bytearr):
- wincheck = re.match("@ByteArray\\((.+)\\)", bytearr)
- if wincheck:
- return wincheck.group(1)
- return bytearr
-
-def GetUserHexKey(prefs = ""):
- "find wsuid and pwsdid"
- wsuid = ""
- pwsdid = ""
- if sys.platform.startswith('win'):
- regkey_browser = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Software\\Kobo\\Kobo Desktop Edition\\Browser")
- cookies = _winreg.QueryValueEx(regkey_browser, "cookies")
- bytearrays = cookies[0]
- elif sys.platform.startswith('darwin'):
- cookies = readPlist(prefs)
- bytearrays = cookies["Browser.cookies"]
- for bytearr in bytearrays:
- cookie = ByteArrayToString(bytearr)
- print cookie
- wsuidcheck = re.match("^wsuid=([0-9a-f-]+)", cookie)
- if(wsuidcheck):
- wsuid = wsuidcheck.group(1)
- pwsdidcheck = re.match("^pwsdid=([0-9a-f-]+)", cookie)
- if (pwsdidcheck):
- pwsdid = pwsdidcheck.group(1)
-
- if(wsuid == "" or pwsdid == ""):
- print "wsuid or pwsdid key not found :/"
- exit()
- preuserkey = string.join((pwsdid, wsuid), "")
- print SHA256(pwsdid)
- userkey = SHA256(preuserkey)
- return userkey[32:]
-
-# get dirs
-if sys.platform.startswith('win'):
- delim = "\\"
- if (sys.getwindowsversion().major > 5):
- kobodir = string.join((os.environ['LOCALAPPDATA'], "Kobo\\Kobo Desktop Edition"), delim)
- else:
- kobodir = string.join((os.environ['USERPROFILE'], "Local Settings\\Application Data\\Kobo\\Kobo Desktop Edition"), delim)
- prefs = ""
-elif sys.platform.startswith('darwin'):
- delim = "/"
- kobodir = string.join((os.environ['HOME'], "Library/Application Support/Kobo/Kobo Desktop Edition"), delim)
- prefs = string.join((os.environ['HOME'], "Library/Preferences/com.kobo.Kobo Desktop Edition.plist"), delim)
-sqlitefile = string.join((kobodir, "Kobo.sqlite"), delim)
-bookdir = string.join((kobodir, "kepub"), delim)
-
-# get key
-userkeyhex = GetUserHexKey(prefs)
-# load into AES
-userkey = binascii.a2b_hex(userkeyhex)
-enc = AES.new(userkey, AES.MODE_ECB)
-
-# open sqlite
-conn = sqlite3.connect(sqlitefile)
-dbcursor = conn.cursor()
-# get volume keys
-volumekeys = GetVolumeKeys(dbcursor, enc)
-
-# choose a volumeID
-
-volumeid = ""
-print "Choose a book to decrypt:"
-i = 1
-for key in volumekeys.keys():
- print "%d: %s" % (i, volumekeys[key]["title"])
- i += 1
-
-num = input("...")
-
-i = 1
-for key in volumekeys.keys():
- if(i == num):
- volumeid = key
- i += 1
-
-if(volumeid == ""):
- exit()
-
-zippath = string.join((bookdir, volumeid), delim)
-
-z = zipfile.ZipFile(zippath, "r")
-# make filename out of Unicode alphanumeric and whitespace equivalents from title
-outname = "%s.epub" % (re.sub("[^\s\w]", "", volumekeys[volumeid]["title"], 0, re.UNICODE))
-zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
-for filename in z.namelist():
- #print filename
- # read in and decrypt
- if(filename in volumekeys[volumeid]):
- # do decrypted version
- pagekey = volumekeys[volumeid][filename]["decryptedkey"]
- penc = AES.new(pagekey, AES.MODE_ECB)
- contents = RemoveAESPadding(penc.decrypt(z.read(filename)))
- # need to fix padding
- zout.writestr(filename, contents)
- else:
- zout.writestr(filename, z.read(filename))
-
-print "Book saved as %s%s%s" % (os.getcwd(), delim, outname) \ No newline at end of file