diff options
author | Apprentice Harper <[email protected]> | 2015-03-09 07:38:31 +0000 |
---|---|---|
committer | Apprentice Alf <[email protected]> | 2015-03-09 07:41:07 +0000 |
commit | 9d9c879413e8ea2bc805399c15f2e94b02f48e1d (patch) | |
tree | ad223063f4bf8e3de5abdb68c28277ab41cc4c19 /Other_Tools | |
parent | c4fc10395b1a21ae9b0f76f4bbbf553e52129bba (diff) |
tools v6.2.0
Updated for B&N new scheme, added obok plugin, and many minor fixes,
Diffstat (limited to 'Other_Tools')
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 |