diff --git a/Adobe_EPUB_Tools/ineptkey.pyw b/Adobe_EPUB_Tools/ineptkey.pyw index e0ac72e..bd66e78 100644 --- a/Adobe_EPUB_Tools/ineptkey.pyw +++ b/Adobe_EPUB_Tools/ineptkey.pyw @@ -31,6 +31,7 @@ # 5 - Clean up and improve 4.x changes; # Clean up and merge OS X support by unknown # 5.1 - add support for using OpenSSL on Windows in place of PyCrypto +# 5.2 - added support for output of key to a particular file """ Retrieve Adobe ADEPT user key. @@ -415,6 +416,20 @@ class ExceptionDialog(Tkinter.Frame): self.text.pack(fill=Tkconstants.BOTH, expand=1) self.text.insert(Tkconstants.END, text) +def cli_main(argv=sys.argv): + keypath = argv[1] + try: + success = retrieve_key(keypath) + except ADEPTError, e: + print "Key generation Error: " + str(e) + return 1 + except Exception, e: + print "General Error: " + str(e) + return 1 + if not success: + return 1 + return 0 + def main(argv=sys.argv): root = Tkinter.Tk() root.withdraw() @@ -438,4 +453,6 @@ def main(argv=sys.argv): return 0 if __name__ == '__main__': + if len(sys.argv) > 1: + sys.exit(cli_main()) sys.exit(main()) diff --git a/Calibre_Plugins/k4mobidedrm_plugin.zip b/Calibre_Plugins/k4mobidedrm_plugin.zip index a8dbe1c..21a61cb 100644 Binary files a/Calibre_Plugins/k4mobidedrm_plugin.zip and b/Calibre_Plugins/k4mobidedrm_plugin.zip differ diff --git a/Calibre_Plugins/k4mobidedrm_plugin/k4mobidedrm_plugin.py b/Calibre_Plugins/k4mobidedrm_plugin/k4mobidedrm_plugin.py index 4fc0337..1ad83bc 100644 --- a/Calibre_Plugins/k4mobidedrm_plugin/k4mobidedrm_plugin.py +++ b/Calibre_Plugins/k4mobidedrm_plugin/k4mobidedrm_plugin.py @@ -28,7 +28,7 @@ from __future__ import with_statement -__version__ = '1.1' +__version__ = '1.2' class Unbuffered: def __init__(self, stream): @@ -284,7 +284,7 @@ def getK4Pids(exth, title, kInfoFile=None): global kindleDatabase try: kindleDatabase = parseKindleInfo(kInfoFile) - except Exception as message: + except Exception, message: print(message) if kindleDatabase != None : @@ -313,6 +313,8 @@ def getK4Pids(exth, title, kInfoFile=None): exth_records = {} nitems, = unpack('>I', exth[8:12]) pos = 12 + + exth_records[209] = None # Parse the exth records, storing data indexed by type for i in xrange(nitems): type, size = unpack('>II', exth[pos: pos + 8]) @@ -397,6 +399,7 @@ def main(argv=sys.argv): kindleDatabase = None infile = args[0] outfile = args[1] + DecodeErrorString = "" try: # first try with K4PC/K4M ex = MobiPeek(infile) @@ -405,11 +408,15 @@ def main(argv=sys.argv): return 2 title = ex.getBookTitle() exth = ex.getexthData() + if exth=='': + raise DrmException("Not a Kindle Mobipocket file") pid = getK4Pids(exth, title) unlocked_file = mobidedrm.getUnencryptedBook(infile, pid) - except DrmException: + except DrmException, e: + DecodeErrorString += "Error trying default K4 info: " + str(e) + "\n" pass - except mobidedrm.DrmException: + except mobidedrm.DrmException, e: + DecodeErrorString += "Error trying default K4 info: " + str(e) + "\n" pass else: file(outfile, 'wb').write(unlocked_file) @@ -422,11 +429,15 @@ def main(argv=sys.argv): try: title = ex.getBookTitle() exth = ex.getexthData() + if exth=='': + raise DrmException("Not a Kindle Mobipocket file") pid = getK4Pids(exth, title, infoFile) unlocked_file = mobidedrm.getUnencryptedBook(infile, pid) - except DrmException: + except DrmException, e: + DecodeErrorString += "Error trying " + infoFile + " K4 info: " + str(e) + "\n" pass - except mobidedrm.DrmException: + except mobidedrm.DrmException, e: + DecodeErrorString += "Error trying " + infoFile + " K4 info: " + str(e) + "\n" pass else: file(outfile, 'wb').write(unlocked_file) @@ -445,6 +456,7 @@ def main(argv=sys.argv): return 0 # we could not unencrypt book + print DecodeErrorString print "Error: Could Not Unencrypt Book" return 1 @@ -463,7 +475,7 @@ if not __name__ == "__main__" and inCalibre: Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.' supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on author = 'DiapDealer, SomeUpdates' # The author of this plugin - version = (0, 1, 1) # The version number of this plugin + version = (0, 1, 3) # The version number of this plugin file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to on_import = True # Run this plugin during the import priority = 200 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm @@ -509,6 +521,8 @@ if not __name__ == "__main__" and inCalibre: return path_to_ebook title = ex.getBookTitle() exth = ex.getexthData() + if exth=='': + raise DrmException("Not a Kindle Mobipocket file") pid = getK4Pids(exth, title) unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid) except DrmException: @@ -528,6 +542,8 @@ if not __name__ == "__main__" and inCalibre: try: title = ex.getBookTitle() exth = ex.getexthData() + if exth=='': + raise DrmException("Not a Kindle Mobipocket file") pid = getK4Pids(exth, title, infoFile) unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid) except DrmException: diff --git a/Calibre_Plugins/k4mobidedrm_plugin/k4mutils.py b/Calibre_Plugins/k4mobidedrm_plugin/k4mutils.py index 977d81c..33771eb 100644 --- a/Calibre_Plugins/k4mobidedrm_plugin/k4mutils.py +++ b/Calibre_Plugins/k4mobidedrm_plugin/k4mutils.py @@ -237,24 +237,36 @@ LibCrypto = _load_crypto() # # uses a sub process to get the Hard Drive Serial Number using ioreg -# returns with the first found serial number in that class +# returns with the serial number of drive whose BSD Name is "disk0" def GetVolumeSerialNumber(): - cmdline = '/usr/sbin/ioreg -r -c AppleAHCIDiskDriver' + sernum = os.getenv('MYSERIALNUMBER') + if sernum != None: + return sernum + cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver' cmdline = cmdline.encode(sys.getfilesystemencoding()) p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) poll = p.wait('wait') results = p.read() reslst = results.split('\n') - sernum = '9999999999' cnt = len(reslst) + bsdname = None + sernum = None + foundIt = False for j in xrange(cnt): resline = reslst[j] pp = resline.find('"Serial Number" = "') if pp >= 0: - sernum = resline[pp+19:] - sernum = sernum[:-1] - sernum = sernum.lstrip() - break + sernum = resline[pp+19:-1] + sernum = sernum.strip() + bb = resline.find('"BSD Name" = "') + if bb >= 0: + bsdname = resline[bb+14:-1] + bsdname = bsdname.strip() + if (bsdname == 'disk0') and (sernum != None): + foundIt = True + break + if not foundIt: + sernum = '9999999999' return sernum # uses unix env to get username instead of using sysctlbyname @@ -319,4 +331,4 @@ def openKindleInfo(kInfoFile=None): raise K4MDrmException('Error: .kindle-info file can not be found') return open(kinfopath,'r') else: - return open(kInfoFile, 'r') \ No newline at end of file + return open(kInfoFile, 'r') diff --git a/Calibre_Plugins/k4mobidedrm_plugin/mobidedrm.py b/Calibre_Plugins/k4mobidedrm_plugin/mobidedrm.py index eed1cce..9f17a3b 100644 --- a/Calibre_Plugins/k4mobidedrm_plugin/mobidedrm.py +++ b/Calibre_Plugins/k4mobidedrm_plugin/mobidedrm.py @@ -3,14 +3,6 @@ # This is a python script. You need a Python interpreter to run it. # For example, ActiveState Python, which exists for windows. # -# It can run standalone to convert files, or it can be installed as a -# plugin for Calibre (http://calibre-ebook.com/about) so that -# importing files with DRM 'Just Works'. -# -# To create a Calibre plugin, rename this file so that the filename -# ends in '_plugin.py', put it into a ZIP file and import that Calibre -# using its plugin configuration GUI. -# # Changelog # 0.01 - Initial version # 0.02 - Huffdic compressed books were not properly decrypted @@ -46,8 +38,9 @@ # 0.18 - It seems that multibyte entries aren't encrypted in a v7 file... # Removed the disabled Calibre plug-in code # Permit use of 8-digit PIDs +# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either. -__version__ = '0.18' +__version__ = '0.19' import sys import struct @@ -215,8 +208,8 @@ class DrmStripper: if (mobi_length >= 0xE4) and (mobi_version >= 5): extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4]) print "Extra Data Flags = %d" %extra_data_flags - if mobi_version < 7: - # multibyte utf8 data is included in the encryption for mobi_version 5 (& 6?) + if mobi_version <= 5: + # multibyte utf8 data is included in the encryption for mobi_version 5 and below # so clear that byte so that we leave it to be decrypted. extra_data_flags &= 0xFFFE diff --git a/DeDRM_Macintosh_Application/DeDRM.app.txt b/DeDRM_Macintosh_Application/DeDRM.app.txt new file mode 100644 index 0000000..099e256 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app.txt @@ -0,0 +1,1154 @@ +property prefsFileName : "com.apprenticealf.dedrm.plist" +property prefsFolderName : "com.apprenticealf.dedrm" +property handledExtensions : {"epub", "pdf", "prc", "azw", "mobi", "pdb", "der", "b64"} + +global eReaderTool +global MobipocketTool +global BNKeyGenTool +global BNePubTool +global AdobeKeyGenTool +global AdobeePubTool +global AdobePDFTool +global ZipFixTool +global ProgressApp + +global PIDs +global bnKeys +global KindleInfoList +global AdeptKeyList + +global ErrorList +global WarningList +global CompletedList +global ErrorCount +global WarningCount +global CompletedCount +global totalebooks +global completedebooks + +on folderexists(inputFolder) + try + do shell script "test -d " & quoted form of (POSIX path of inputFolder) + return true + end try + + return false +end folderexists + +on fileexists(inputFile) + try + do shell script "test -f " & quoted form of (POSIX path of inputFile) + return true + end try + + return false +end fileexists + +on GetTools() + set eReaderTool to POSIX path of file (path to me as text) & "Contents/Resources/erdr2pml.py" + set MobipocketTool to POSIX path of file (path to me as text) & "Contents/Resources/k4mobidedrm.py" + set BNKeyGenTool to POSIX path of file (path to me as text) & "Contents/Resources/ignoblekeygen.pyw" + set BNePubTool to POSIX path of file (path to me as text) & "Contents/Resources/ignobleepub.pyw" + set AdobeKeyGenTool to POSIX path of file (path to me as text) & "Contents/Resources/ineptkey.pyw" + set AdobeePubTool to POSIX path of file (path to me as text) & "Contents/Resources/ineptepub.pyw" + set AdobePDFTool to POSIX path of file (path to me as text) & "Contents/Resources/ineptpdf.pyw" + set ZipFixTool to POSIX path of file (path to me as text) & "Contents/Resources/zipfix.py" + set ProgressApp to POSIX path of file ((path to resource "DeDRM Progress.app") as string) + if not fileexists(eReaderTool) or not fileexists(MobipocketTool) or not fileexists(BNKeyGenTool) or not fileexists(BNePubTool) or not fileexists(AdobeKeyGenTool) or not fileexists(AdobeePubTool) or not folderexists(ProgressApp) then + display dialog "De-drm scripts or support files are missing from this package. Get a fresh copy." buttons {"Bother"} default button 1 with title "DeDRM Applescript" with icon stop + return false + end if + return true +end GetTools + +on unlockmobifile(encryptedFile) + --check it's a mobi file. + set BOOKMOBI to "NOTAMOBI" + try + set BOOKMOBI to read file encryptedFile from 61 for 8 + end try + if BOOKMOBI is not "BOOKMOBI" then + set TOPAZ to "NOT" + try + set TOPAZ to read file encryptedFile from 1 for 4 + end try + set ErrorCount to ErrorCount + 1 + if TOPAZ is "TPZ0" then + set ErrorList to ErrorList & encryptedFile & " is a TOPAZ file. + +" + else + set ErrorList to ErrorList & encryptedFile & " is not a Mobipocket file. + +" + end if + return + end if + set encryptedFilePath to POSIX path of file encryptedFile + tell application "Finder" + set parent_folder to (container of file encryptedFile) as text + set fileName to (name of file encryptedFile) as text + end tell + set fileExtension to "" + if fileName contains "." then + set AppleScript's text item delimiters to "." + set fileExtension to "." & the last text item of fileName + set fileName to (text items 1 through -2 of fileName) as string + end if + set unlockedFilePath to POSIX path of file (parent_folder & fileName & "_dedrmed" & fileExtension) + set shellcommand to "python " & (quoted form of MobipocketTool) + repeat with KindleInfoPath in KindleInfoList + set shellcommand to shellcommand & " -k " & quoted form of KindleInfoPath + end repeat + set PIDstring to GetPIDstring() + if PIDstring is not "" then set shellcommand to shellcommand & " -p " & PIDstring + set shellcommand to shellcommand & " " & (quoted form of encryptedFilePath) & " " & (quoted form of unlockedFilePath) + --display dialog "shellcommand: " default answer shellcommand buttons {"OK"} default button 1 giving up after 10 + try + set shellresult to do shell script shellcommand + on error errmsg + set shellresult to errmsg + end try + try + if (offset of "Error" in shellresult) > 0 then + set ErrorCount to ErrorCount + 1 + set ErrorList to (ErrorList & fileName & fileExtension & " couldn't be decoded: +" & shellresult as text) & " + +" + else if (offset of "not encrypted" in shellresult) > 0 then + set WarningCount to WarningCount + 1 + set WarningList to WarningList & fileName & fileExtension & " is not encrypted. + +" + else + set CompletedCount to CompletedCount + 1 + if CompletedList is "" then + set CompletedList to CompletedList & fileName & fileExtension + else + set CompletedList to CompletedList & ", " & fileName & fileExtension + end if + end if + end try +end unlockmobifile + +on unlockpdbfile(encryptedFile) + --check it's an eReader file. + set PNRdPPrs to "NOT_eREADER" + try + set PNRdPPrs to read file encryptedFile from 61 for 8 + end try + if PNRdPPrs is not "PNRdPPrs" then + set ErrorList to ErrorList & encryptedFile & " is not an eReader file. + +" + return + end if + set encryptedFilePath to POSIX path of file encryptedFile + tell application "Finder" + set parent_folder to (container of file encryptedFile) as text + set fileName to (name of file encryptedFile) as text + end tell + set fileExtension to "" + if fileName contains "." then + set AppleScript's text item delimiters to "." + set fileExtension to "." & the last text item of fileName + set fileName to (text items 1 through -2 of fileName) as string + end if + set pmlFile to parent_folder & fileName & "_Source:" & fileName & ".pml" + set clearFile to parent_folder & fileName & "_Source:" & fileName & ".pdb" + set clearFileNew to parent_folder & fileName & "_Source:" & fileName & "_dedrmed.pdb" + set clearFileNewName to fileName & "_dedrmed.pdb" + set sourcePath to (POSIX path of file parent_folder) & fileName & "_Source/" + set pmlzFilePath to POSIX path of file parent_folder & fileName & "_dedrmed.pmlz" + if length of bnKeys is 0 then + GetKeys() + end if + set shellresult to "Error: No eReader/Barnes & Noble Name:Number keys supplied. Can't decrypt." + repeat with BNKey in bnKeys + set encryptionName to first item of BNKey + set encryptionNumber to second item of BNKey + if length of encryptionName > 0 and length of encryptionNumber > 0 then + set shellcommand to "python " & (quoted form of eReaderTool) & " " & (quoted form of encryptedFilePath) & " " & (quoted form of encryptionName) & " " & (quoted form of encryptionNumber) + --display dialog "shellcommand: " & shellcommand buttons {"OK"} default button 1 giving up after 10 + set shellresult to "no result" + try + set shellresult to do shell script shellcommand + on error errmsg + set shellresult to errmsg + end try + --display dialog shellresult + if (offset of "Error" in shellresult) is 0 then + -- try to run dropbook and move compiled book to same folder as encrypted book + try + try + tell application "Finder" to set DropBook to (application file id "PPDr") as string + on error + error "Couldn't find DropBook application" + end try + -- now also compile back up into pdb file using DropBook if it's on this system + tell application DropBook to open file pmlFile + delay 1 -- probably don't need this, but just in case. + tell application DropBook to quit + try + tell application "Finder" + set name of file clearFile to clearFileNewName + move clearFileNew to parent_folder + end tell + on error + error "DropBook failed to compile the source folder into a pdb file." + end try + on error errmsg + set WarningCount to WarningCount + 1 + set WarningList to (WarningList & fileName & fileExtension & " couldn't be compiled back to pdb: +" & errmsg as text) & " + +" + end try + -- try to compress source folder contensts to a pmlz file for easy import into Calibre + try + set shellcommand to "cd " & quoted form of sourcePath & "; zip -r " & quoted form of pmlzFilePath & " *" + --display dialog shellcommand + set zipshellresult to do shell script shellcommand + --display dialog zipshellresult + tell application "Finder" + -- move source folder to the trash + move parent_folder & fileName & "_Source" to trash + end tell + on error errmsg + set WarningCount to WarningCount + 1 + set WarningList to (WarningList & fileName & fileExtension & "source folder couldn't be compressed to pmlz: +" & errmsg as text) & " + +" + end try + -- we've done the decrypting so exit the loop. + exit repeat + end if + end if + end repeat + try + if (offset of "incorrect eReader version 10" in shellresult) > 0 then + set WarningCount to WarningCount + 1 + set WarningList to WarningList & fileName & fileExtension & " is not encrypted. + +" + else if (offset of "Error" in shellresult) > 0 then + set ErrorCount to ErrorCount + 1 + set ErrorList to (ErrorList & fileName & fileExtension & " couldn't be decoded: +" & shellresult as text) & " + +" + else + set CompletedCount to CompletedCount + 1 + if CompletedList is "" then + set CompletedList to CompletedList & fileName & fileExtension + else + set CompletedList to CompletedList & ", " & fileName & fileExtension + end if + end if + end try +end unlockpdbfile + +on unlockepubfile(encryptedFile) + --check it's an ePub file. + set ePubSig to "NOT_ePub" + try + set ePubSig to read file encryptedFile from 31 for 28 + end try + --display dialog ePubSig + if ePubSig is not "mimetypeapplication/epub+zip" then + set ErrorList to ErrorList & encryptedFile & " is not an ePub file. + +" + return + end if + set encryptedFilePath to POSIX path of file encryptedFile + tell application "Finder" + set parent_folder to (container of file encryptedFile) as text + set fileName to (name of file encryptedFile) as text + end tell + set fileExtension to "" + if fileName contains "." then + set AppleScript's text item delimiters to "." + set fileExtension to "." & the last text item of fileName + set fileName to (text items 1 through -2 of fileName) as string + end if + + set decoded to "NO" + -- first we must fix any possible zip problems with the epub + set fixedFilePath to POSIX path of file (parent_folder & fileName & "_fixed" & fileExtension) + set shellcommand to "python " & (quoted form of ZipFixTool) & " " & (quoted form of encryptedFilePath) & " " & (quoted form of fixedFilePath) + set shellresult to "no result" + try + --display dialog "shellcommand: " default answer shellcommand buttons {"OK"} default button 1 giving up after 10 + set shellresult to do shell script shellcommand + on error errmsg + set shellresult to "Error: " & errmsg + end try + + if (offset of "Error" in shellresult) > 0 then + set ErrorCount to ErrorCount + 1 + set ErrorList to ((ErrorList & fileName & fileExtension & " had a problem with ZipFix: +" & shellresult as text) & " + +") + return + end if + + set unlockedFilePath to POSIX path of file (parent_folder & fileName & "_dedrmed" & fileExtension) + set shellresult to "no keys" + + -- first we'll try the Barnes & Noble keys + repeat with BNKey in bnKeys + + set keyfilepath to third item of BNKey + if length of keyfilepath > 0 then + set shellcommand to "python " & (quoted form of BNePubTool) & " " & (quoted form of keyfilepath) & " " & (quoted form of fixedFilePath) & " " & (quoted form of unlockedFilePath) + --display dialog "shellcommand: " & shellcommand buttons {"OK"} default button 1 giving up after 10 + set shellresult to "no result" + try + set shellresult to do shell script shellcommand + on error errmsg + set shellresult to "Error: " & errmsg + end try + if (offset of "Error" in shellresult) is 0 then + set decoded to "YES" + exit repeat + end if + if (offset of "improper key" in shellresult) is 0 then + exit repeat + end if + end if + end repeat + + if decoded is "NO" then + -- now try Adobe ePub + repeat with AdeptKey in AdeptKeyList + set shellcommand to "python " & (quoted form of AdobeePubTool) & " " & (quoted form of AdeptKey) & " " & (quoted form of fixedFilePath) & " " & (quoted form of unlockedFilePath) + --display dialog "shellcommand: " default answer shellcommand buttons {"OK"} default button 1 giving up after 10 + set shellresult to "no result" + try + set shellresult to do shell script shellcommand + on error errmsg + set shellresult to "Error: " & errmsg + end try + if (offset of "Error" in shellresult) is 0 then + set decoded to "YES" + exit repeat + end if + end repeat + end if + + if decoded is "YES" then + set CompletedCount to CompletedCount + 1 + if CompletedList is "" then + set CompletedList to CompletedList & fileName & fileExtension + else + set CompletedList to CompletedList & ", " & fileName & fileExtension + end if + else if shellresult is "no keys" then + set ErrorCount to ErrorCount + 1 + set ErrorList to (ErrorList & fileName & fileExtension & " couldn't be decoded: no keys. + +") + else + set ErrorCount to ErrorCount + 1 + set ErrorList to ((ErrorList & fileName & fileExtension & " couldn't be decoded: +" & shellresult as text) & " + +") + end if + + tell application "Finder" + move alias ((POSIX file fixedFilePath) as text) to trash + end tell +end unlockepubfile + +on unlockpdffile(encryptedFile) + --check it's an ePub file. + set PDFSig to "NOT_DF" + try + set PDFSig to read file encryptedFile from 1 for 4 + end try + --display dialog PDFSig + if PDFSig is not "%PDF" then + set ErrorCount to ErrorCount + 1 + set ErrorList to ErrorList & encryptedFile & " is not a PDF file. + +" + return + end if + set encryptedFilePath to POSIX path of file encryptedFile + tell application "Finder" + set parent_folder to (container of file encryptedFile) as text + set fileName to (name of file encryptedFile) as text + end tell + set fileExtension to "" + if fileName contains "." then + set AppleScript's text item delimiters to "." + set fileExtension to "." & the last text item of fileName + set fileName to (text items 1 through -2 of fileName) as string + end if + + set decoded to "NO" + -- first we must check we have a PDF script + if not fileexists(AdobePDFTool) then + set newFile to AdobePDFTool + try + tell me to activate + set newFile to ((choose file with prompt "Please find the ineptpdf script") as text) + end try + if not fileexists(newFile) then + set ErrorCount to ErrorCount + 1 + set ErrorList to ErrorList & encryptedFile & " is a PDF file and no ineptpdf script found. + +" + return + end if + -- set AdobePDFTool to POSIX path of file CopyToPrefs(newFile) + set AdobePDFTool to POSIX path of file newFile + WritePrefs() + end if + if not fileexists(AdobePDFTool) then + set ErrorCount to ErrorCount + 1 + set ErrorList to ErrorList & encryptedFile & " is a PDF file and no ineptpdf script found. + +" + return + end if + + set unlockedFilePath to POSIX path of file (parent_folder & fileName & "_dedrmed" & fileExtension) + set decoded to false + repeat with AdeptKey in AdeptKeyList + set shellcommand to "python " & (quoted form of AdobePDFTool) & " " & (quoted form of AdeptKey) & " " & (quoted form of encryptedFilePath) & " " & (quoted form of unlockedFilePath) + --display dialog "shellcommand: " default answer shellcommand buttons {"OK"} default button 1 giving up after 10 + set shellresult to "no result" + try + set shellresult to do shell script shellcommand + on error errmsg + set shellresult to "Error: " & errmsg + end try + if (offset of "Error" in shellresult) is 0 then + set decoded to true + exit repeat + end if + end repeat + + if decoded then + set CompletedCount to CompletedCount + 1 + if CompletedList is "" then + set CompletedList to CompletedList & fileName & fileExtension + else + set CompletedList to CompletedList & ", " & fileName & fileExtension + end if + else if shellresult is "no keys" then + set ErrorCount to ErrorCount + 1 + set ErrorList to (ErrorList & fileName & fileExtension & " couldn't be decoded: no keys. + +") + else + set ErrorCount to ErrorCount + 1 + set ErrorList to ((ErrorList & fileName & fileExtension & " couldn't be decoded: +" & shellresult as text) & " + +") + end if +end unlockpdffile + +on handlefile(droppedFile) + tell application "Finder" + set fileName to (name of file droppedFile) as text + end tell + set fileExtension to "" + if fileName contains "." then + set AppleScript's text item delimiters to "." + set fileExtension to the last text item of fileName + set fileName to (text items 1 through -2 of fileName) as string + end if + if fileExtension is "prc" or fileExtension is "mobi" or fileExtension is "azw" then + set completedebooks to completedebooks + 1 + IncProgress(fileName, completedebooks) + unlockmobifile(droppedFile as text) + else if fileExtension is "pdb" then + set completedebooks to completedebooks + 1 + IncProgress(fileName, completedebooks) + unlockpdbfile(droppedFile as text) + else if fileExtension is "epub" then + set completedebooks to completedebooks + 1 + IncProgress(fileName, completedebooks) + unlockepubfile(droppedFile as text) + else if fileExtension is "pdf" then + set completedebooks to completedebooks + 1 + IncProgress(fileName, completedebooks) + unlockpdffile(droppedFile as text) + else if fileExtension is "der" then + AddAdeptKey(droppedFile as text) + else if fileExtension is "b64" then + AddbnKey(droppedFile as text) + end if +end handlefile + +on handlefolder(droppedFolder) + tell application "Finder" to set fileList to (every file in folder droppedFolder) + tell application "Finder" to set folderList to (every folder in folder droppedFolder) + repeat with this_item in fileList + if not ProgressActive() then + exit repeat + end if + handlefile(this_item as text) + end repeat + repeat with this_item in folderList + if not ProgressActive() then + exit repeat + end if + handlefolder(this_item as text) + end repeat +end handlefolder + +on countfile(droppedFile) + tell application "Finder" + set fileName to (name of file droppedFile) as text + end tell + set fileExtension to "" + if fileName contains "." then + set AppleScript's text item delimiters to "." + set fileExtension to the last text item of fileName + set fileName to (text items 1 through -2 of fileName) as string + end if + if fileExtension is in handledExtensions then + set totalebooks to totalebooks + 1 + end if +end countfile + +on GetPIDstring() + set PIDstring to "" + repeat with PID in PIDs + if PIDstring is "" then + set PIDstring to PID + else + set PIDstring to PIDstring & "," & PID + end if + end repeat + return PIDstring +end GetPIDstring + +on GetKindleInfostring() + set KInfostring to "" + repeat with KindleInfo in KindleInfoList + if KInfostring is "" then + set KInfostring to (KindleInfo as text) + else + set KInfostring to KInfostring & " +" & (KindleInfo as text) + end if + end repeat + return KInfostring +end GetKindleInfostring + +on GetAdeptstring() + set Adeptstring to "" + repeat with AdeptKeyFile in AdeptKeyList + set AppleTypeFile to POSIX file (AdeptKeyFile as text) + tell application "Finder" + set fileName to name of file (AppleTypeFile as text) + end tell + + if Adeptstring is "" then + set Adeptstring to fileName + else + set Adeptstring to Adeptstring & " +" & fileName + end if + end repeat + return Adeptstring +end GetAdeptstring + +on GetBNKeystring() + set BNKeystring to "" + repeat with BNKey in bnKeys + if BNKeystring is "" then + set BNKeystring to first item of BNKey & ":" & second item of BNKey + else + set BNKeystring to BNKeystring & " +" & first item of BNKey & ":" & second item of BNKey + end if + if length of third item of BNKey > 0 then + set BNKeystring to BNKeystring & "*" + end if + if length of second item of BNKey > 0 then + set BNKeystring to BNKeystring & " " + end if + end repeat + return BNKeystring +end GetBNKeystring + + +on GetPIDs() + repeat + set PIDstring to GetPIDstring() + if PIDstring is "" then + set DialogPrompt to "Enter any Mobipocket PIDs one at a time:" + set FinishedButton to "None" + else + set DialogPrompt to "Current PIDs: " & PIDstring & ". + +Enter any additional Mobipocket PIDs for your Mobipocket books one at a time:" + set FinishedButton to "No More" + end if + set dialogresult to (display dialog DialogPrompt default answer "" buttons {"Delete All", "Add", FinishedButton} with title "DeDRM Applescript" default button 2) + if button returned of dialogresult is "Add" then + set PID to text returned of dialogresult + set PIDlength to length of PID + if PIDlength is 8 or PIDlength is 10 then + set PIDs to PIDs & PID + else + display dialog "PIDs must be 8 or 10 characters long." buttons {"OK"} default button 1 with title "DeDRM Applescript" with icon caution + end if + else if button returned of dialogresult is "Delete All" then + if PIDstring is not "" then + try + set dialogresult to (display dialog "Are you sure you want to delete all stored PIDs?" buttons {"Cancel", "Delete"} default button 1 with title "DeDRM Applescript") + end try + if button returned of dialogresult is "Delete" then + set PIDs to {} + end if + end if + else + exit repeat + end if + end repeat +end GetPIDs + +on GetKindleInfoFiles() + repeat + set KInfostring to GetKindleInfostring() + if KInfostring is "" then + set DialogPrompt to "To add extra Kindle Info files, click the AddÉ button. You do not need to add the Kindle Info file for the copy of Kindle for Mac on this machine." + set FinishedButton to "None" + else + set DialogPrompt to "Known extra Kindle Info file: +" & KInfostring & " + +To add extra Kindle Info files, click the AddÉ button. You do not need to add the Kindle Info file for the copy of Kindle for Mac on this machine." + set FinishedButton to "No More" + end if + set dialogresult to (display dialog DialogPrompt buttons {"Forget All", "AddÉ", FinishedButton} with title "DeDRM Applescript" default button 2) + if button returned of dialogresult is "AddÉ" then + try + set newFile to POSIX path of file ((choose file with prompt "Please select a Kindle Info file") as text) + set KindleInfoList to KindleInfoList & newFile + on error errmsg + display dialog errmsg buttons {"Bother"} default button 1 + end try + + else if button returned of dialogresult is "Forget All" then + if KInfostring is not "" then + try + set dialogresult to (display dialog "Are you sure you want to forget all extra Kindel Info files?" buttons {"Cancel", "Forget"} default button 1 with title "DeDRM Applescript") + end try + if button returned of dialogresult is "Forget" then + set KindleInfoList to {} + end if + end if + else + exit repeat + end if + end repeat +end GetKindleInfoFiles + +on GetKeys() + set bnKeyText to "" + repeat + set BNKeystring to GetBNKeystring() + if BNKeystring is "" then + set DialogPrompt to "Please enter any " + set FinishedButton to "None" + else + set DialogPrompt to "Current B&N keys*, eReader keys : +" & BNKeystring & " + +Please enter any additional " + set FinishedButton to "No More" + end if + set DialogPrompt to DialogPrompt & "eReader/Barnes & Noble Name:Number key pairs one at a time. If you're only decoding eReader files, the last 8 digits of the Number will do. The full 16 are only needed for Barnes & Noble ePubs. Only the last eight will be stored or displayed. Please separate the name and number with a colon and click \"Add\". Or to add a an already generated .b64 file, just click \"Add\" with nothing in the text field." + + set dialogresult to (display dialog DialogPrompt default answer bnKeyText buttons {"Delete All", "Add", FinishedButton} with title "DeDRM Applescript" default button 2) + if button returned of dialogresult is "Add" then + set bnKeyText to text returned of dialogresult + if bnKeyText is "" then + try + set newFile to (choose file with prompt "Please select a B&N .b64 key file") as text + -- copy to prefs + AddbnKey(newFile) + on error message + if message does not contain "cancel" then + display dialog message + end if + end try + else if not (bnKeyText contains ":") then + display dialog "Name and Number must be separated by a colon (:)." buttons {"OK"} default button 1 with title "DeDRM Applescript" with icon caution + else + set AppleScript's text item delimiters to ":" + set keyNumber to the last text item of bnKeyText + set keyName to (text items 1 through -2 of bnKeyText) as string + + if ((length of keyNumber) = 16 or (length of keyNumber) = 8) and (length of keyName) > 0 then + set shellresult to "" + set keyfilepath to "" + if (length of keyNumber) = 16 then + -- get the B&N key from this pair + set shellresult to "no result" + set scriptError to "Key Gen Script failed." + set prefsFolder to (path to preferences from user domain as string) & prefsFolderName & ":" + set keyfileName to GetUniqueName(prefsFolder, keyName & (text -8 thru -1 of keyNumber) & ".b64") + set keyfilepath to POSIX path of (prefsFolder & keyfileName) + try + set shellresult to do shell script "python " & (quoted form of BNKeyGenTool) & " " & (quoted form of keyName) & " " & (quoted form of keyNumber) & " " & quoted form of keyfilepath + on error errmsg + set shellresult to "no result" + set scriptError to errmsg + end try + end if + if shellresult is not "no result" then + set bnKeys to bnKeys & {{keyName, (text -8 thru -1 of keyNumber), keyfilepath}} + set bnKeyText to "" + else + display dialog "Error generating key from this info, error message was: " & scriptError buttons {"OK"} default button 1 with title "DeDRM Applescript" with icon caution + end if + else + display dialog "Key numbers must be 8 or 16 characters long (no spaces) and the key name must not be absent." buttons {"OK"} default button 1 with title "DeDRM Applescript" with icon caution + end if + end if + else if button returned of dialogresult is "Delete All" then + if BNKeystring is not "" then + try + set dialogresult to (display dialog "Are you sure you want to delete all stored Name:Number key pairs?" buttons {"Cancel", "Delete"} default button 1 with title "DeDRM Applescript") + end try + if button returned of dialogresult is "Delete" then + set bnKeys to {} + end if + end if + set bnKeyText to "" + else + set bnKeyText to text returned of dialogresult + if bnKeyText is not "" then + try + set dialogresult to (display dialog "You entered some text, but didn't click Add. Are you sure you want to more on to the next dialog?" buttons {"Whoops", "Yes, Move on"} default button 1 with title "DeDRM Applescript") + end try + if button returned of dialogresult is "Yes, Move on" then + exit repeat + end if + else + exit repeat + end if + end if + end repeat +end GetKeys + +on GetAdeptKeyFiles() + repeat + set Adeptstring to GetAdeptstring() + if Adeptstring is "" then + set DialogPrompt to "To add Adobe Digital Editions (Adept) key files, click the AddÉ button. " + set FinishedButton to "None" + else + set DialogPrompt to "Current Adobe Digital Editions (Adept) key files: +" & Adeptstring & " + +To add extra key files (.der), click the AddÉ button." + set FinishedButton to "No More" + end if + set dialogresult to (display dialog DialogPrompt buttons {"Forget All", "AddÉ", FinishedButton} with title "DeDRM Applescript" default button 2) + if button returned of dialogresult is "AddÉ" then + try + set newFile to (choose file with prompt "Please select an Adept key file") as text + -- copy to prefs + AddAdeptKey(newFile) + on error message + if message does not contain "cancel" then + display dialog message + end if + end try + + else if button returned of dialogresult is "Forget All" then + if Adeptstring is not "" then + try + set dialogresult to (display dialog "Are you sure you want to forget all extra Adobe Digital Editions Adept key files?" buttons {"Cancel", "Forget"} default button 1 with title "DeDRM Applescript") + end try + if button returned of dialogresult is "Forget" then + set AdeptKeyList to {} + GetAdeptKey(false) + end if + end if + else + exit repeat + end if + end repeat +end GetAdeptKeyFiles + +on GetAdeptKey(always) + set userPrefsPath to path to preferences from user domain as string + set preferencesFolderPath to userPrefsPath & prefsFolderName & ":" + if not folderexists(preferencesFolderPath) then + tell application "Finder" + set preferencesFolder to make new folder at userPrefsPath with properties {name:prefsFolderName} + end tell + end if + set userAdeptKeyPath to POSIX path of (preferencesFolderPath & "Local Adept Key.der") + if ((count of AdeptKeyList) is 0) or always then + set shellresult to "no result" + set success to true + try + set scriptError to do shell script "python " & (quoted form of AdobeKeyGenTool) & " " & quoted form of userAdeptKeyPath + on error errmsg + set success to false + set scriptError to errmsg + end try + if success then + if not (AdeptKeyList contains userAdeptKeyPath) then + set AdeptKeyList to AdeptKeyList & userAdeptKeyPath + end if + else + if (always) then + display dialog "Error generating key for Adobe Editions: " & scriptError & " + +You must have Adobe Digital Editions installed for this script to work with Adobe ePubs, or add a key file manually. " buttons {"OK"} default button 1 with title "DeDRM Applescript" with icon caution + end if + end if + end if +end GetAdeptKey + +on encodeAsBase64(theFilePath) + set posixfilepath to POSIX path of file theFilePath + set base64path to POSIX path of file ((path to me as text) & "Contents:Resources:encodebase64.py") + return do shell script "python " & quoted form of base64path & " " & quoted form of posixfilepath +end encodeAsBase64 + +on AddAdeptKey(keyfile) + set fileExtension to "" + if keyfile contains "." then + set AppleScript's text item delimiters to "." + set fileExtension to the last text item of keyfile + end if + if fileExtension is "der" then + set newPrefsFile to CopyToPrefs(keyfile) + set AdeptKeyList to AdeptKeyList & POSIX path of newPrefsFile + else + display dialog "Adobe Adept key files generated by adeptkey.pyw must have a .der extension." buttons {"OK"} default button 1 with title "DeDRM Applescript" with icon caution + end if +end AddAdeptKey + +on AddbnKey(keyfile) + set fileExtension to "" + if keyfile contains "." then + set AppleScript's text item delimiters to "." + set fileExtension to the last text item of keyfile + end if + if fileExtension is "b64" then + set newPrefsFile to CopyToPrefs(keyfile) + tell application "Finder" + set keyfileName to (name of file newPrefsFile) as text + end tell + set fileExtension to "" + set fileName to keyfileName + if keyfileName contains "." then + set AppleScript's text item delimiters to "." + set fileExtension to the last text item of keyfileName + set fileName to (text items 1 through -2 of keyfileName) as string + end if + set bnKeys to bnKeys & {{fileName, "", POSIX path of newPrefsFile}} + else + display dialog "Barnes & Noble key files generated by ignoblekeygen.pyw must have a .b64 extension." buttons {"OK"} default button 1 with title "DeDRM Applescript" with icon caution + end if +end AddbnKey + +on GetUniqueName(folderPath, fileName) + set newFileName to fileName + if fileexists(folderPath & fileName) then + -- first find new name that's unique to folder (& temporary items folder) + set namecount to 2 + set fileExtension to "" + set fileNameOnly to fileName + if fileName contains "." then + set AppleScript's text item delimiters to "." + set fileExtension to the last text item of fileName + set fileNameOnly to (text items 1 through -2 of fileName) as string + end if + repeat + set newFileName to fileNameOnly & " " & (namecount as text) & "." & fileExtension + if not fileexists(folderPath & newFileName) then + exit repeat + end if + set namecount to namecount + 1 + end repeat + end if + return newFileName +end GetUniqueName + +on CopyToPrefs(keyfilepath) + tell application "Finder" + set keyfileName to (name of file keyfilepath) as text + end tell + set keyfileApplepath to POSIX file (POSIX path of keyfilepath) + set userPrefsPath to path to preferences from user domain as string + set userTempPath to path to temporary items from user domain as string + set preferencesFolderPath to userPrefsPath & prefsFolderName & ":" + if not folderexists(preferencesFolderPath) then + tell application "Finder" + set preferencesFolder to make new folder at userPrefsPath with properties {name:prefsFolderName} + end tell + end if + if fileexists(preferencesFolderPath & keyfileName) then + -- copy to temporary items folder, rename, and then we can copy from there to the preferences folder + -- first find new name + set namecount to 2 + set fileExtension to "" + set keyfileNameOnly to keyfileName + if keyfileName contains "." then + set AppleScript's text item delimiters to "." + set fileExtension to the last text item of keyfileName + set keyfileNameOnly to (text items 1 through -2 of keyfileName) as string + end if + repeat + set newkeyfileName to keyfileNameOnly & " " & (namecount as text) & "." & fileExtension + if not fileexists(preferencesFolderPath & newkeyfileName) and not fileexists(userTempPath & newkeyfileName) then + exit repeat + end if + set namecount to namecount + 1 + end repeat + -- rename to new name + set newkeyfilepath to userTempPath & newkeyfileName + tell application "Finder" + set tempfilepath to duplicate file keyfileApplepath to folder userTempPath with replacing + set name of tempfilepath to newkeyfileName + move file newkeyfilepath to preferencesFolderPath + end tell + set newFile to preferencesFolderPath & newkeyfileName + else + tell application "Finder" + set newFile to duplicate file keyfileApplepath to folder preferencesFolderPath with replacing + end tell + end if + + return newFile as text +end CopyToPrefs + +on ReadPrefs() + -- read any existing keys from the preferences + set preferencesFilePath to (path to preferences from user domain as string) & prefsFileName + set PIDs to {} + set bnKeys to {} + set KindleInfoList to {} + set AdeptKeyList to {} + if fileexists(POSIX path of file preferencesFilePath) then + tell application "System Events" + try + set PIDs to value of property list item "PIDs" of property list file preferencesFilePath + end try + try + set bnKeys to value of property list item "bnKeys" of property list file preferencesFilePath + end try + try + set AdeptKeyList to value of property list item "AdeptKeys" of property list file preferencesFilePath + end try + try + set AdobePDFTool to value of property list item "IneptPDF" of property list file preferencesFilePath + end try + end tell + end if + set newList to {} + repeat with i from 1 to count AdeptKeyList + if fileexists(AdeptKeyList's item i) then set newList's end to AdeptKeyList's item i + end repeat + set AdeptKeyList to newList + + set newList to {} + repeat with i from 1 to count bnKeys + if (item 3 of bnKeys's item i) is "" or fileexists(item 3 of bnKeys's item i) then set newList's end to bnKeys's item i + end repeat + set bnKeys to newList + +end ReadPrefs + +on WritePrefs() + -- write keys to the preferences + set preferencesFilePath to (path to preferences from user domain as string) & prefsFileName + tell application "System Events" + set the base_dict to make new property list item with properties {kind:record} + set myPrefs to make new property list file with properties {contents:base_dict, name:preferencesFilePath} + make new property list item at end of property list items of contents of myPrefs with properties {kind:list, name:"PIDs", value:PIDs} + make new property list item at end of property list items of contents of myPrefs with properties {kind:list, name:"bnKeys", value:bnKeys} + make new property list item at end of property list items of contents of myPrefs with properties {kind:list, name:"AdeptKeys", value:AdeptKeyList} + make new property list item at end of property list items of contents of myPrefs with properties {kind:string, name:"IneptPDF", value:AdobePDFTool} + end tell +end WritePrefs + + +on InitProgress(maxcount) + try + if totalebooks > 1 then + launch application ((path to resource "DeDRM Progress.app") as string) + tell application ((path to resource "DeDRM Progress.app") as string) + activate + set contents of Çclass texFÈ "maintext" of window "main" to "Decrypting " & maxcount & " ebooks." + set contents of Çclass texFÈ "subtext" of window "main" to "Decrypting book 0: " + set Çclass maxVÈ of Çclass proIÈ "bar" of window "main" to maxcount + set Çclass conTÈ of Çclass proIÈ "bar" of window "main" to 0 + end tell + end if + end try + +end InitProgress + +on ProgressActive() + return application ((path to resource "DeDRM Progress.app") as string) is running + --tell application "System Events" to set progactive to count (every process whose name is "DeDRM Progress") + --if progactive > 0 then return true + --return false +end ProgressActive + +on IncProgress(booktitle, doingcount) + try + if ProgressActive() then + tell application ((path to resource "DeDRM Progress.app") as string) + set contents of Çclass texFÈ "subtext" of window "main" to "Decrypting book " & doingcount & ": " & booktitle + set Çclass conTÈ of Çclass proIÈ "bar" of window "main" to doingcount + end tell + end if + end try +end IncProgress + +on endProgress() + try + if ProgressActive() then tell application ((path to resource "DeDRM Progress.app") as string) to quit + end try +end endProgress + + +on run + if GetTools() then + display dialog "Drag&Drop encrypted ebooks onto this AppleScript's icon in the Finder to decode them after you have finished configuring it and it has quit. + +Click the Continue button to enter any PIDs for Mobipocket/Kindle ebooks, to enter name/number key pairs for Barnes & Noble/eReader ebooks, and to select Barnes & Noble .b64 key files and Adobe Adept .der key files. + +***You do not need to enter any extra info if decoding ebooks downloaded to your installation of Kindle for Mac, or Adobe Digital Editions. If you do not have any PIDS, name/number keys or .b64 or .der files to add, just click the Cancel button.*** + +Please only use to get access to your own books. Authors, publishers and ebook stores need to make money to produce more ebooks. Don't cheat them. + +This AppleScript is by Apprentice Alf and uses python scripts produced by CMBDTC, IHeartCabbages, DarkReverser, DiapDealer, some_updates, ApprenticeAlf and others. + +This AppleScript (but not necessarily the enclosed python scripts) is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. + +For more information, please refer to + +" with title "DeDRM Applescript" buttons {"Cancel", "Continue"} default button 2 + ReadPrefs() + GetPIDs() + GetKeys() + GetAdeptKey(true) + GetAdeptKeyFiles() + --GetKindleInfoFiles() + WritePrefs() + end if +end run + +on open some_items + if GetTools() then + try + ReadPrefs() + GetAdeptKey(false) + set ErrorList to "" + set WarningList to "" + set CompletedList to "" + set ErrorCount to 0 + set WarningCount to 0 + set CompletedCount to 0 + set totalebooks to 0 + set completedebooks to 0 + repeat with this_item in some_items + if (folder of (info for this_item) is true) then + tell application "Finder" to set totalebooks to totalebooks + (count of (every file in entire contents of folder this_item whose name extension is in handledExtensions)) + else + countfile(this_item as text) + end if + end repeat + InitProgress(totalebooks) + repeat with this_item in some_items + if totalebooks > 1 and not ProgressActive() then + exit repeat + end if + if (folder of (info for this_item) is true) then + handlefolder(this_item as text) + else + handlefile(this_item as text) + end if + end repeat + endProgress() + on error errmsg number errnum from fromobj + display dialog "An unexpected error occurred. Please report on Apprentice Alf's blog. + +Error Message: " & errmsg & " +Error Number: " & errnum & " +Error From: " & (fromobj as string) with title "DeDRM Applescript" buttons {"Bother"} default button 1 + end try + if WarningList is not "" then + set WarningText to "" + if WarningCount is 1 then + set WarningText to "There was a warning with 1 ebook:" + else + set WarningText to "There were warnings with " & WarningCount & " ebooks:" + end if + set dialogresult to display dialog WarningText & " + +" & WarningList buttons {"SaveÉ", "OK"} with title "DeDRM Applescript" default button 1 + if button returned of dialogresult is "SaveÉ" then + try + set savefile to choose file name with prompt "Where would you like to save the warnings report?" default name "DeDRM Warnings Report.txt" + set fileRef to open for access savefile with write permission + set eof fileRef to 0 + write WarningList to fileRef + close access fileRef + end try + end if + end if + if ErrorList is not "" then + set ErrorText to "" + if ErrorCount is 1 then + set ErrorText to "There was an error with 1 ebook:" + else + set ErrorText to "There were errors with " & ErrorCount & " ebooks:" + end if + set dialogresult to display dialog ErrorText & " + +" & ErrorList buttons {"SaveÉ", "Bother"} with title "DeDRM Applescript" default button 1 + if button returned of dialogresult is "SaveÉ" then + try + set savefile to choose file name with prompt "Where would you like to save the errors report?" default name "DeDRM Errors Report.txt" + set fileRef to open for access savefile with write permission + set eof fileRef to 0 + write ErrorList to fileRef + close access fileRef + end try + end if + end if + if CompletedCount > 0 then + set CompletedText to "" + if CompletedCount is 1 then + set CompletedText to "Successfully de-drmed 1 ebook:" + else + set CompletedText to "Successfully de-drmed " & CompletedCount & " ebooks:" + end if + set dialogresult to display dialog CompletedText & " + +" & CompletedList buttons {"Thanks"} with title "DeDRM Applescript" default button 1 + end if + if CompletedCount is 0 and ErrorCount is 0 and WarningCount is 0 and totalebooks is 0 then + set dialogresult to display dialog "No ebooks found in the dropped items. DeDRM can handle Mobipocket/Kindle, eReader, Adobe ePub, Barnes & noble ePub and Adobe PDF. Please try again." buttons {"OK"} with title "DeDRM Applescript" default button 1 + end if + WritePrefs() + end if +end open + diff --git a/Macintosh_Applications/eReader Unlocker.app/Contents/Info.plist b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist similarity index 78% rename from Macintosh_Applications/eReader Unlocker.app/Contents/Info.plist rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist index 2ae3b8a..330ec11 100644 --- a/Macintosh_Applications/eReader Unlocker.app/Contents/Info.plist +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist @@ -23,16 +23,22 @@ CFBundleExecutable droplet + CFBundleGetInfoString + DeDRM 1.2, Copyright © 2010 by Apprentice Alf. CFBundleIconFile droplet CFBundleInfoDictionaryVersion 6.0 CFBundleName - Mobipocket Unlocker + DeDRM CFBundlePackageType APPL + CFBundleShortVersionString + 1.2 CFBundleSignature dplt + LSMinimumSystemVersion + 10.5.0 LSRequiresCarbon WindowState @@ -40,9 +46,9 @@ name ScriptWindowState positionOfDivider - 627 + 885 savedFrame - 53 78 661 691 0 0 1280 778 + 1507 -64 1262 964 1440 -150 1680 1050 selectedTabView result diff --git a/Macintosh_Applications/Mobipocket Unlocker.app/Contents/MacOS/droplet b/DeDRM_Macintosh_Application/DeDRM.app/Contents/MacOS/droplet similarity index 100% rename from Macintosh_Applications/Mobipocket Unlocker.app/Contents/MacOS/droplet rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/MacOS/droplet diff --git a/Macintosh_Applications/Mobipocket Unlocker.app/Contents/PkgInfo b/DeDRM_Macintosh_Application/DeDRM.app/Contents/PkgInfo similarity index 100% rename from Macintosh_Applications/Mobipocket Unlocker.app/Contents/PkgInfo rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/PkgInfo diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress Source.zip b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress Source.zip new file mode 100644 index 0000000..81eb3a3 Binary files /dev/null and b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress Source.zip differ diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Info.plist b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Info.plist new file mode 100644 index 0000000..ceaa16f --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + DeDRM Progress + CFBundleIdentifier + com.apprenticealf.DeDRMProgress + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + DeDRM Progress + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + NSAppleScriptEnabled + YES + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/Macintosh_Applications/eReader Unlocker.app/Contents/MacOS/droplet b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/MacOS/DeDRM Progress similarity index 58% rename from Macintosh_Applications/eReader Unlocker.app/Contents/MacOS/droplet rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/MacOS/DeDRM Progress index c715860..2ff942b 100644 Binary files a/Macintosh_Applications/eReader Unlocker.app/Contents/MacOS/droplet and b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/MacOS/DeDRM Progress differ diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/PkgInfo b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/PkgInfo new file mode 100644 index 0000000..bd04210 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/InfoPlist.strings b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/InfoPlist.strings new file mode 100644 index 0000000..efaa0d7 Binary files /dev/null and b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/InfoPlist.strings differ diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/MainMenu.nib/classes.nib b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/MainMenu.nib/classes.nib new file mode 100644 index 0000000..cbaf739 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/MainMenu.nib/classes.nib @@ -0,0 +1,19 @@ + + + + + IBClasses + + + CLASS + FirstResponder + LANGUAGE + ObjC + SUPERCLASS + NSObject + + + IBVersion + 1 + + diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/MainMenu.nib/info.nib b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/MainMenu.nib/info.nib new file mode 100644 index 0000000..a101e01 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/MainMenu.nib/info.nib @@ -0,0 +1,18 @@ + + + + + IBFramework Version + 680 + IBLastKnownRelativeProjectPath + ../Display Panel.xcodeproj + IBOldestOS + 5 + IBOpenObjects + + IBSystem Version + 9L31a + targetFramework + IBCocoaFramework + + diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib new file mode 100644 index 0000000..83ceb9e Binary files /dev/null and b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib differ diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/Scripts/Window.scpt b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/Scripts/Window.scpt new file mode 100644 index 0000000..5274a48 Binary files /dev/null and b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/Scripts/Window.scpt differ diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/Scripts/main.scpt b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/Scripts/main.scpt new file mode 100644 index 0000000..2a37d51 Binary files /dev/null and b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/Scripts/main.scpt differ diff --git a/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/description.rtfd/TXT.rtf b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/description.rtfd/TXT.rtf similarity index 100% rename from Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/description.rtfd/TXT.rtf rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/description.rtfd/TXT.rtf diff --git a/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/droplet.icns b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/droplet.icns similarity index 100% rename from Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/droplet.icns rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/droplet.icns diff --git a/Macintosh_Applications/eReader Unlocker.app/Contents/Resources/droplet.rsrc b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/droplet.rsrc similarity index 70% rename from Macintosh_Applications/eReader Unlocker.app/Contents/Resources/droplet.rsrc rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/droplet.rsrc index e6a4338..802f370 100644 Binary files a/Macintosh_Applications/eReader Unlocker.app/Contents/Resources/droplet.rsrc and b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/droplet.rsrc differ diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/encodebase64.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/encodebase64.py new file mode 100644 index 0000000..f4a0202 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/encodebase64.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# base64.py, version 1.0 +# Copyright © 2010 Apprentice Alf + +# Released under the terms of the GNU General Public Licence, version 3 or +# later. + +# Revision history: +# 1 - Initial release. To allow Applescript to do base64 encoding + +""" +Provide base64 encoding. +""" + +from __future__ import with_statement + +__license__ = 'GPL v3' + +import sys +import os +import base64 + +def usage(progname): + print "Applies base64 encoding to the supplied file, sending to standard output" + print "Usage:" + print " %s " % progname + +def cli_main(argv=sys.argv): + progname = os.path.basename(argv[0]) + + if len(argv)<2: + usage(progname) + sys.exit(2) + + keypath = argv[1] + with open(keypath, 'rb') as f: + keyder = f.read() + print keyder.encode('base64') + return 0 + + +if __name__ == '__main__': + sys.exit(cli_main()) + \ No newline at end of file diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/erdr2pml.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/erdr2pml.py new file mode 100644 index 0000000..daa6b21 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/erdr2pml.py @@ -0,0 +1,476 @@ +#!/usr/bin/env python +# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab +# +# erdr2pml.py +# +# This is a python script. You need a Python interpreter to run it. +# For example, ActiveState Python, which exists for windows. +# Changelog +# +# Based on ereader2html version 0.08 plus some later small fixes +# +# 0.01 - Initial version +# 0.02 - Support more eReader files. Support bold text and links. Fix PML decoder parsing bug. +# 0.03 - Fix incorrect variable usage at one place. +# 0.03b - enhancement by DeBockle (version 259 support) +# Custom version 0.03 - no change to eReader support, only usability changes +# - start of pep-8 indentation (spaces not tab), fix trailing blanks +# - version variable, only one place to change +# - added main routine, now callable as a library/module, +# means tools can add optional support for ereader2html +# - outdir is no longer a mandatory parameter (defaults based on input name if missing) +# - time taken output to stdout +# - Psyco support - reduces runtime by a factor of (over) 3! +# E.g. (~600Kb file) 90 secs down to 24 secs +# - newstyle classes +# - changed map call to list comprehension +# may not work with python 2.3 +# without Psyco this reduces runtime to 90% +# E.g. 90 secs down to 77 secs +# Psyco with map calls takes longer, do not run with map in Psyco JIT! +# - izip calls used instead of zip (if available), further reduction +# in run time (factor of 4.5). +# E.g. (~600Kb file) 90 secs down to 20 secs +# - Python 2.6+ support, avoid DeprecationWarning with sha/sha1 +# 0.04 - Footnote support, PML output, correct charset in html, support more PML tags +# - Feature change, dump out PML file +# - Added supprt for footnote tags. NOTE footnote ids appear to be bad (not usable) +# in some pdb files :-( due to the same id being used multiple times +# - Added correct charset encoding (pml is based on cp1252) +# - Added logging support. +# 0.05 - Improved type 272 support for sidebars, links, chapters, metainfo, etc +# 0.06 - Merge of 0.04 and 0.05. Improved HTML output +# Placed images in subfolder, so that it's possible to just +# drop the book.pml file onto DropBook to make an unencrypted +# copy of the eReader file. +# Using that with Calibre works a lot better than the HTML +# conversion in this code. +# 0.07 - Further Improved type 272 support for sidebars with all earlier fixes +# 0.08 - fixed typos, removed extraneous things +# 0.09 - fixed typos in first_pages to first_page to again support older formats +# 0.10 - minor cleanups +# 0.11 - fixups for using correct xml for footnotes and sidebars for use with Dropbook +# 0.12 - Fix added to prevent lowercasing of image names when the pml code itself uses a different case in the link name. +# 0.13 - change to unbuffered stdout for use with gui front ends +# 0.14 - contributed enhancement to support --make-pmlz switch +# 0.15 - enabled high-ascii to pml character encoding. DropBook now works on Mac. +# 0.16 - convert to use openssl DES (very very fast) or pure python DES if openssl's libcrypto is not available + +Des = None + +import openssl_des +Des = openssl_des.load_libcrypto() + +# if that did not work then use pure python implementation +# of DES and try to speed it up with Psycho +if Des == None: + import python_des + Des = python_des.Des + # Import Psyco if available + try: + # Dumb speed hack 1 + # http://psyco.sourceforge.net + import psyco + psyco.full() + pass + except ImportError: + pass + + +__version__='0.16' + +class Unbuffered: + def __init__(self, stream): + self.stream = stream + def write(self, data): + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +import sys +sys.stdout=Unbuffered(sys.stdout) + +import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile + +try: + from hashlib import sha1 +except ImportError: + # older Python release + import sha + sha1 = lambda s: sha.new(s) +import cgi +import logging + +logging.basicConfig() +#logging.basicConfig(level=logging.DEBUG) + +class Sectionizer(object): + def __init__(self, filename, ident): + self.contents = file(filename, 'rb').read() + self.header = self.contents[0:72] + self.num_sections, = struct.unpack('>H', self.contents[76:78]) + if self.header[0x3C:0x3C+8] != ident: + raise ValueError('Invalid file format') + self.sections = [] + for i in xrange(self.num_sections): + offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.contents[78+i*8:78+i*8+8]) + flags, val = a1, a2<<16|a3<<8|a4 + self.sections.append( (offset, flags, val) ) + def loadSection(self, section): + if section + 1 == self.num_sections: + end_off = len(self.contents) + else: + end_off = self.sections[section + 1][0] + off = self.sections[section][0] + return self.contents[off:end_off] + +def sanitizeFileName(s): + r = '' + for c in s: + if c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-": + r += c + return r + +def fixKey(key): + def fixByte(b): + return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80) + return "".join([chr(fixByte(ord(a))) for a in key]) + +def deXOR(text, sp, table): + r='' + j = sp + for i in xrange(len(text)): + r += chr(ord(table[j]) ^ ord(text[i])) + j = j + 1 + if j == len(table): + j = 0 + return r + +class EreaderProcessor(object): + def __init__(self, section_reader, username, creditcard): + self.section_reader = section_reader + data = section_reader(0) + version, = struct.unpack('>H', data[0:2]) + self.version = version + logging.info('eReader file format version %s', version) + if version != 272 and version != 260 and version != 259: + raise ValueError('incorrect eReader version %d (error 1)' % version) + data = section_reader(1) + self.data = data + des = Des(fixKey(data[0:8])) + cookie_shuf, cookie_size = struct.unpack('>LL', des.decrypt(data[-8:])) + if cookie_shuf < 3 or cookie_shuf > 0x14 or cookie_size < 0xf0 or cookie_size > 0x200: + raise ValueError('incorrect eReader version (error 2)') + input = des.decrypt(data[-cookie_size:]) + def unshuff(data, shuf): + r = [''] * len(data) + j = 0 + for i in xrange(len(data)): + j = (j + shuf) % len(data) + r[j] = data[i] + assert len("".join(r)) == len(data) + return "".join(r) + r = unshuff(input[0:-8], cookie_shuf) + + def fixUsername(s): + r = '' + for c in s.lower(): + if (c >= 'a' and c <= 'z' or c >= '0' and c <= '9'): + r += c + return r + + user_key = struct.pack('>LL', binascii.crc32(fixUsername(username)) & 0xffffffff, binascii.crc32(creditcard[-8:])& 0xffffffff) + drm_sub_version = struct.unpack('>H', r[0:2])[0] + self.num_text_pages = struct.unpack('>H', r[2:4])[0] - 1 + self.num_image_pages = struct.unpack('>H', r[26:26+2])[0] + self.first_image_page = struct.unpack('>H', r[24:24+2])[0] + if self.version == 272: + self.num_footnote_pages = struct.unpack('>H', r[46:46+2])[0] + self.first_footnote_page = struct.unpack('>H', r[44:44+2])[0] + self.num_sidebar_pages = struct.unpack('>H', r[38:38+2])[0] + self.first_sidebar_page = struct.unpack('>H', r[36:36+2])[0] + # self.num_bookinfo_pages = struct.unpack('>H', r[34:34+2])[0] + # self.first_bookinfo_page = struct.unpack('>H', r[32:32+2])[0] + # self.num_chapter_pages = struct.unpack('>H', r[22:22+2])[0] + # self.first_chapter_page = struct.unpack('>H', r[20:20+2])[0] + # self.num_link_pages = struct.unpack('>H', r[30:30+2])[0] + # self.first_link_page = struct.unpack('>H', r[28:28+2])[0] + # self.num_xtextsize_pages = struct.unpack('>H', r[54:54+2])[0] + # self.first_xtextsize_page = struct.unpack('>H', r[52:52+2])[0] + + # **before** data record 1 was decrypted and unshuffled, it contained data + # to create an XOR table and which is used to fix footnote record 0, link records, chapter records, etc + self.xortable_offset = struct.unpack('>H', r[40:40+2])[0] + self.xortable_size = struct.unpack('>H', r[42:42+2])[0] + self.xortable = self.data[self.xortable_offset:self.xortable_offset + self.xortable_size] + else: + self.num_footnote_pages = 0 + self.num_sidebar_pages = 0 + self.first_footnote_page = -1 + self.first_sidebar_page = -1 + # self.num_bookinfo_pages = 0 + # self.num_chapter_pages = 0 + # self.num_link_pages = 0 + # self.num_xtextsize_pages = 0 + # self.first_bookinfo_page = -1 + # self.first_chapter_page = -1 + # self.first_link_page = -1 + # self.first_xtextsize_page = -1 + + logging.debug('self.num_text_pages %d', self.num_text_pages) + logging.debug('self.num_footnote_pages %d, self.first_footnote_page %d', self.num_footnote_pages , self.first_footnote_page) + logging.debug('self.num_sidebar_pages %d, self.first_sidebar_page %d', self.num_sidebar_pages , self.first_sidebar_page) + self.flags = struct.unpack('>L', r[4:8])[0] + reqd_flags = (1<<9) | (1<<7) | (1<<10) + if (self.flags & reqd_flags) != reqd_flags: + print "Flags: 0x%X" % self.flags + raise ValueError('incompatible eReader file') + des = Des(fixKey(user_key)) + if version == 259: + if drm_sub_version != 7: + raise ValueError('incorrect eReader version %d (error 3)' % drm_sub_version) + encrypted_key_sha = r[44:44+20] + encrypted_key = r[64:64+8] + elif version == 260: + if drm_sub_version != 13: + raise ValueError('incorrect eReader version %d (error 3)' % drm_sub_version) + encrypted_key = r[44:44+8] + encrypted_key_sha = r[52:52+20] + elif version == 272: + encrypted_key = r[172:172+8] + encrypted_key_sha = r[56:56+20] + self.content_key = des.decrypt(encrypted_key) + if sha1(self.content_key).digest() != encrypted_key_sha: + raise ValueError('Incorrect Name and/or Credit Card') + + def getNumImages(self): + return self.num_image_pages + + def getImage(self, i): + sect = self.section_reader(self.first_image_page + i) + name = sect[4:4+32].strip('\0') + data = sect[62:] + return sanitizeFileName(name), data + + + # def getChapterNamePMLOffsetData(self): + # cv = '' + # if self.num_chapter_pages > 0: + # for i in xrange(self.num_chapter_pages): + # chaps = self.section_reader(self.first_chapter_page + i) + # j = i % self.xortable_size + # offname = deXOR(chaps, j, self.xortable) + # offset = struct.unpack('>L', offname[0:4])[0] + # name = offname[4:].strip('\0') + # cv += '%d|%s\n' % (offset, name) + # return cv + + # def getLinkNamePMLOffsetData(self): + # lv = '' + # if self.num_link_pages > 0: + # for i in xrange(self.num_link_pages): + # links = self.section_reader(self.first_link_page + i) + # j = i % self.xortable_size + # offname = deXOR(links, j, self.xortable) + # offset = struct.unpack('>L', offname[0:4])[0] + # name = offname[4:].strip('\0') + # lv += '%d|%s\n' % (offset, name) + # return lv + + # def getExpandedTextSizesData(self): + # ts = '' + # if self.num_xtextsize_pages > 0: + # tsize = deXOR(self.section_reader(self.first_xtextsize_page), 0, self.xortable) + # for i in xrange(self.num_text_pages): + # xsize = struct.unpack('>H', tsize[0:2])[0] + # ts += "%d\n" % xsize + # tsize = tsize[2:] + # return ts + + # def getBookInfo(self): + # bkinfo = '' + # if self.num_bookinfo_pages > 0: + # info = self.section_reader(self.first_bookinfo_page) + # bkinfo = deXOR(info, 0, self.xortable) + # bkinfo = bkinfo.replace('\0','|') + # bkinfo += '\n' + # return bkinfo + + def getText(self): + des = Des(fixKey(self.content_key)) + r = '' + for i in xrange(self.num_text_pages): + logging.debug('get page %d', i) + r += zlib.decompress(des.decrypt(self.section_reader(1 + i))) + + # now handle footnotes pages + if self.num_footnote_pages > 0: + r += '\n' + # the record 0 of the footnote section must pass through the Xor Table to make it useful + sect = self.section_reader(self.first_footnote_page) + fnote_ids = deXOR(sect, 0, self.xortable) + # the remaining records of the footnote sections need to be decoded with the content_key and zlib inflated + des = Des(fixKey(self.content_key)) + for i in xrange(1,self.num_footnote_pages): + logging.debug('get footnotepage %d', i) + id_len = ord(fnote_ids[2]) + id = fnote_ids[3:3+id_len] + fmarker = '\n' % id + fmarker += zlib.decompress(des.decrypt(self.section_reader(self.first_footnote_page + i))) + fmarker += '\n\n' + r += fmarker + fnote_ids = fnote_ids[id_len+4:] + + # now handle sidebar pages + if self.num_sidebar_pages > 0: + r += '\n' + # the record 0 of the sidebar section must pass through the Xor Table to make it useful + sect = self.section_reader(self.first_sidebar_page) + sbar_ids = deXOR(sect, 0, self.xortable) + # the remaining records of the sidebar sections need to be decoded with the content_key and zlib inflated + des = Des(fixKey(self.content_key)) + for i in xrange(1,self.num_sidebar_pages): + id_len = ord(sbar_ids[2]) + id = sbar_ids[3:3+id_len] + smarker = '\n' % id + smarker += zlib.decompress(des.decrypt(self.section_reader(self.first_footnote_page + i))) + smarker += '\n\n' + r += smarker + sbar_ids = sbar_ids[id_len+4:] + + return r + +def cleanPML(pml): + # Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255) + pml2 = pml + for k in xrange(128,256): + badChar = chr(k) + pml2 = pml2.replace(badChar, '\\a%03d' % k) + return pml2 + +def convertEreaderToPml(infile, name, cc, outdir): + if not os.path.exists(outdir): + os.makedirs(outdir) + + print " Decoding File" + sect = Sectionizer(infile, 'PNRdPPrs') + er = EreaderProcessor(sect.loadSection, name, cc) + + if er.getNumImages() > 0: + print " Extracting images" + imagedir = bookname + '_img/' + imagedirpath = os.path.join(outdir,imagedir) + if not os.path.exists(imagedirpath): + os.makedirs(imagedirpath) + for i in xrange(er.getNumImages()): + name, contents = er.getImage(i) + file(os.path.join(imagedirpath, name), 'wb').write(contents) + + print " Extracting pml" + pml_string = er.getText() + pmlfilename = bookname + ".pml" + file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string)) + + # bkinfo = er.getBookInfo() + # if bkinfo != '': + # print " Extracting book meta information" + # file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo) + + +def usage(): + print "Converts DRMed eReader books to PML Source" + print "Usage:" + print " erdr2pml [options] infile.pdb [outdir] \"your name\" credit_card_number " + print " " + print "Options: " + print " -h prints this message" + print " --make-pmlz create PMLZ instead of using output directory" + print " " + print "Note:" + print " if ommitted, outdir defaults based on 'infile.pdb'" + print " It's enough to enter the last 8 digits of the credit card number" + return + +def main(argv=None): + global bookname + try: + opts, args = getopt.getopt(sys.argv[1:], "h", ["make-pmlz"]) + except getopt.GetoptError, err: + print str(err) + usage() + return 1 + make_pmlz = False + zipname = None + for o, a in opts: + if o == "-h": + usage() + return 0 + elif o == "--make-pmlz": + make_pmlz = True + zipname = '' + + print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__ + + if len(args)!=3 and len(args)!=4: + usage() + return 1 + else: + if len(args)==3: + infile, name, cc = args[0], args[1], args[2] + outdir = infile[:-4] + '_Source' + elif len(args)==4: + infile, outdir, name, cc = args[0], args[1], args[2], args[3] + + if make_pmlz : + # ignore specified outdir, use tempdir instead + outdir = tempfile.mkdtemp() + + bookname = os.path.splitext(os.path.basename(infile))[0] + + try: + print "Processing..." + import time + start_time = time.time() + convertEreaderToPml(infile, name, cc, outdir) + + if make_pmlz : + import zipfile + import shutil + print " Creating PMLZ file" + zipname = infile[:-4] + '.pmlz' + myZipFile = zipfile.ZipFile(zipname,'w',zipfile.ZIP_STORED, False) + list = os.listdir(outdir) + for file in list: + localname = file + filePath = os.path.join(outdir,file) + if os.path.isfile(filePath): + myZipFile.write(filePath, localname) + elif os.path.isdir(filePath): + imageList = os.listdir(filePath) + localimgdir = os.path.basename(filePath) + for image in imageList: + localname = os.path.join(localimgdir,image) + imagePath = os.path.join(filePath,image) + if os.path.isfile(imagePath): + myZipFile.write(imagePath, localname) + myZipFile.close() + # remove temporary directory + shutil.rmtree(outdir) + + end_time = time.time() + search_time = end_time - start_time + print 'elapsed time: %.2f seconds' % (search_time, ) + if make_pmlz : + print 'output is %s' % zipname + else : + print 'output in %s' % outdir + print "done" + except ValueError, e: + print "Error: %s" % e + return 1 + return 0 + +if __name__ == "__main__": + sys.exit(main()) + diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignobleepub.pyw b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignobleepub.pyw new file mode 100644 index 0000000..46cd4e8 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignobleepub.pyw @@ -0,0 +1,323 @@ +#! /usr/bin/python + +# ignobleepub.pyw, version 3 + +# To run this program install Python 2.6 from +# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto +# (make sure to install the version for Python 2.6). Save this script file as +# ignobleepub.pyw and double-click on it to run it. + +# Revision history: +# 1 - Initial release +# 2 - Added OS X support by using OpenSSL when available +# 3 - screen out improper key lengths to prevent segfaults on Linux +# 3.1 - Allow Windows versions of libcrypto to be found + +from __future__ import with_statement + +__license__ = 'GPL v3' + +import sys +import os +import zlib +import zipfile +from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED +from contextlib import closing +import xml.etree.ElementTree as etree +import Tkinter +import Tkconstants +import tkFileDialog +import tkMessageBox + +class IGNOBLEError(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 IGNOBLEError('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_cbc_encrypt = F(None, 'AES_cbc_encrypt', + [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p, + c_int]) + AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key', + [c_char_p, c_int, AES_KEY_p]) + AES_cbc_encrypt = F(None, 'AES_cbc_encrypt', + [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_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 IGNOBLEError('AES improper key used') + return + key = self._key = AES_KEY() + rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key) + if rv < 0: + raise IGNOBLEError('Failed to initialize AES key') + + def decrypt(self, data): + out = create_string_buffer(len(data)) + iv = ("\x00" * self._blocksize) + rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0) + if rv == 0: + raise IGNOBLEError('AES decryption failed') + return out.raw + + 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_CBC) + + def decrypt(self, data): + return self._aes.decrypt(data) + + return AES + +def _load_crypto(): + AES = None + for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): + try: + AES = loader() + break + except (ImportError, IGNOBLEError): + pass + return AES +AES = _load_crypto() + + + + +""" +Decrypt Barnes & Noble ADEPT encrypted EPUB books. +""" + + +META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml') +NSMAP = {'adept': 'http://ns.adobe.com/adept', + 'enc': 'http://www.w3.org/2001/04/xmlenc#'} + +class ZipInfo(zipfile.ZipInfo): + def __init__(self, *args, **kwargs): + if 'compress_type' in kwargs: + compress_type = kwargs.pop('compress_type') + super(ZipInfo, self).__init__(*args, **kwargs) + self.compress_type = compress_type + +class Decryptor(object): + def __init__(self, bookkey, encryption): + enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag) + # self._aes = AES.new(bookkey, AES.MODE_CBC) + self._aes = AES(bookkey) + encryption = etree.fromstring(encryption) + self._encrypted = encrypted = set() + expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'), + enc('CipherReference')) + for elem in encryption.findall(expr): + path = elem.get('URI', None) + if path is not None: + encrypted.add(path) + + def decompress(self, bytes): + dc = zlib.decompressobj(-15) + bytes = dc.decompress(bytes) + ex = dc.decompress('Z') + dc.flush() + if ex: + bytes = bytes + ex + return bytes + + def decrypt(self, path, data): + if path in self._encrypted: + data = self._aes.decrypt(data)[16:] + data = data[:-ord(data[-1])] + data = self.decompress(data) + return data + + + +def cli_main(argv=sys.argv): + progname = os.path.basename(argv[0]) + if AES is None: + print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \ + "separately. Read the top-of-script comment for details." % \ + (progname,) + return 1 + if len(argv) != 4: + print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,) + return 1 + keypath, inpath, outpath = argv[1:] + with open(keypath, 'rb') as f: + keyb64 = f.read() + key = keyb64.decode('base64')[:16] + # aes = AES.new(key, AES.MODE_CBC) + aes = AES(key) + + with closing(ZipFile(open(inpath, 'rb'))) as inf: + namelist = set(inf.namelist()) + if 'META-INF/rights.xml' not in namelist or \ + 'META-INF/encryption.xml' not in namelist: + raise IGNOBLEError('%s: not an B&N ADEPT EPUB' % (inpath,)) + for name in META_NAMES: + namelist.remove(name) + rights = etree.fromstring(inf.read('META-INF/rights.xml')) + adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) + expr = './/%s' % (adept('encryptedKey'),) + bookkey = ''.join(rights.findtext(expr)) + bookkey = aes.decrypt(bookkey.decode('base64')) + bookkey = bookkey[:-ord(bookkey[-1])] + encryption = inf.read('META-INF/encryption.xml') + decryptor = Decryptor(bookkey[-16:], encryption) + kwds = dict(compression=ZIP_DEFLATED, allowZip64=False) + with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf: + zi = ZipInfo('mimetype', compress_type=ZIP_STORED) + outf.writestr(zi, inf.read('mimetype')) + for path in namelist: + data = inf.read(path) + outf.writestr(path, decryptor.decrypt(path, data)) + return 0 + + +class DecryptionDialog(Tkinter.Frame): + def __init__(self, root): + Tkinter.Frame.__init__(self, root, border=5) + self.status = Tkinter.Label(self, text='Select files for decryption') + self.status.pack(fill=Tkconstants.X, expand=1) + body = Tkinter.Frame(self) + body.pack(fill=Tkconstants.X, expand=1) + sticky = Tkconstants.E + Tkconstants.W + body.grid_columnconfigure(1, weight=2) + Tkinter.Label(body, text='Key file').grid(row=0) + self.keypath = Tkinter.Entry(body, width=30) + self.keypath.grid(row=0, column=1, sticky=sticky) + if os.path.exists('bnepubkey.b64'): + self.keypath.insert(0, 'bnepubkey.b64') + button = Tkinter.Button(body, text="...", command=self.get_keypath) + button.grid(row=0, column=2) + Tkinter.Label(body, text='Input file').grid(row=1) + self.inpath = Tkinter.Entry(body, width=30) + self.inpath.grid(row=1, column=1, sticky=sticky) + button = Tkinter.Button(body, text="...", command=self.get_inpath) + button.grid(row=1, column=2) + Tkinter.Label(body, text='Output file').grid(row=2) + self.outpath = Tkinter.Entry(body, width=30) + self.outpath.grid(row=2, column=1, sticky=sticky) + button = Tkinter.Button(body, text="...", command=self.get_outpath) + button.grid(row=2, column=2) + buttons = Tkinter.Frame(self) + buttons.pack() + botton = Tkinter.Button( + buttons, text="Decrypt", width=10, command=self.decrypt) + botton.pack(side=Tkconstants.LEFT) + Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) + button = Tkinter.Button( + buttons, text="Quit", width=10, command=self.quit) + button.pack(side=Tkconstants.RIGHT) + + def get_keypath(self): + keypath = tkFileDialog.askopenfilename( + parent=None, title='Select B&N EPUB key file', + defaultextension='.b64', + filetypes=[('base64-encoded files', '.b64'), + ('All Files', '.*')]) + if keypath: + keypath = os.path.normpath(keypath) + self.keypath.delete(0, Tkconstants.END) + self.keypath.insert(0, keypath) + return + + def get_inpath(self): + inpath = tkFileDialog.askopenfilename( + parent=None, title='Select B&N-encrypted EPUB file to decrypt', + defaultextension='.epub', filetypes=[('EPUB files', '.epub'), + ('All files', '.*')]) + if inpath: + inpath = os.path.normpath(inpath) + self.inpath.delete(0, Tkconstants.END) + self.inpath.insert(0, inpath) + return + + def get_outpath(self): + outpath = tkFileDialog.asksaveasfilename( + parent=None, title='Select unencrypted EPUB file to produce', + defaultextension='.epub', filetypes=[('EPUB files', '.epub'), + ('All files', '.*')]) + if outpath: + outpath = os.path.normpath(outpath) + self.outpath.delete(0, Tkconstants.END) + self.outpath.insert(0, outpath) + return + + def decrypt(self): + keypath = self.keypath.get() + inpath = self.inpath.get() + outpath = self.outpath.get() + if not keypath or not os.path.exists(keypath): + self.status['text'] = 'Specified key file does not exist' + return + if not inpath or not os.path.exists(inpath): + self.status['text'] = 'Specified input file does not exist' + return + if not outpath: + self.status['text'] = 'Output file not specified' + return + if inpath == outpath: + self.status['text'] = 'Must have different input and output files' + return + argv = [sys.argv[0], keypath, inpath, outpath] + self.status['text'] = 'Decrypting...' + try: + cli_main(argv) + except Exception, e: + self.status['text'] = 'Error: ' + str(e) + return + self.status['text'] = 'File successfully decrypted' + +def gui_main(): + root = Tkinter.Tk() + if AES is None: + root.withdraw() + tkMessageBox.showerror( + "Ignoble EPUB Decrypter", + "This script requires OpenSSL or PyCrypto, which must be installed " + "separately. Read the top-of-script comment for details.") + return 1 + root.title('Ignoble EPUB Decrypter') + root.resizable(True, False) + root.minsize(300, 0) + DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) + root.mainloop() + return 0 + +if __name__ == '__main__': + if len(sys.argv) > 1: + sys.exit(cli_main()) + sys.exit(gui_main()) diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekeygen.pyw b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekeygen.pyw new file mode 100644 index 0000000..479c11d --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekeygen.pyw @@ -0,0 +1,232 @@ +#! /usr/bin/python + +# ignoblekeygen.pyw, version 2 + +# To run this program install Python 2.6 from +# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto +# (make sure to install the version for Python 2.6). Save this script file as +# ignoblekeygen.pyw and double-click on it to run it. + +# Revision history: +# 1 - Initial release +# 2 - Add OS X support by using OpenSSL when available (taken/modified from ineptepub v5) +# 2.1 - Allow Windows versions of libcrypto to be found + +""" +Generate Barnes & Noble EPUB user key from name and credit card number. +""" + +from __future__ import with_statement + +__license__ = 'GPL v3' + +import sys +import os +import hashlib +import Tkinter +import Tkconstants +import tkFileDialog +import tkMessageBox + + + +# use openssl's libcrypt if it exists in place of pycrypto +# code extracted from the Adobe Adept DRM removal code also by I HeartCabbages +class IGNOBLEError(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: + print 'libcrypto not found' + raise IGNOBLEError('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_encrypt_key = F(c_int, 'AES_set_encrypt_key', + [c_char_p, c_int, AES_KEY_p]) + AES_cbc_encrypt = F(None, 'AES_cbc_encrypt', + [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p, + c_int]) + class AES(object): + def __init__(self, userkey, iv): + self._blocksize = len(userkey) + self._iv = iv + key = self._key = AES_KEY() + rv = AES_set_encrypt_key(userkey, len(userkey) * 8, key) + if rv < 0: + raise IGNOBLEError('Failed to initialize AES Encrypt key') + + def encrypt(self, data): + out = create_string_buffer(len(data)) + rv = AES_cbc_encrypt(data, out, len(data), self._key, self._iv, 1) + if rv == 0: + raise IGNOBLEError('AES encryption failed') + return out.raw + + return AES + + +def _load_crypto_pycrypto(): + from Crypto.Cipher import AES as _AES + + class AES(object): + def __init__(self, key, iv): + self._aes = _AES.new(key, _AES.MODE_CBC, iv) + + def encrypt(self, data): + return self._aes.encrypt(data) + + return AES + + + +def _load_crypto(): + AES = None + for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): + try: + AES = loader() + break + except (ImportError, IGNOBLEError): + pass + return AES + +AES = _load_crypto() + +def normalize_name(name): + return ''.join(x for x in name.lower() if x != ' ') + +def generate_keyfile(name, ccn, outpath): + name = normalize_name(name) + '\x00' + ccn = ccn + '\x00' + name_sha = hashlib.sha1(name).digest()[:16] + ccn_sha = hashlib.sha1(ccn).digest()[:16] + both_sha = hashlib.sha1(name + ccn).digest() + aes = AES(ccn_sha, name_sha) + crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c)) + userkey = hashlib.sha1(crypt).digest() + with open(outpath, 'wb') as f: + f.write(userkey.encode('base64')) + return userkey + +def cli_main(argv=sys.argv): + progname = os.path.basename(argv[0]) + if AES is None: + print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \ + "separately. Read the top-of-script comment for details." % \ + (progname,) + return 1 + if len(argv) != 4: + print "usage: %s NAME CC# OUTFILE" % (progname,) + return 1 + name, ccn, outpath = argv[1:] + generate_keyfile(name, ccn, outpath) + return 0 + +class DecryptionDialog(Tkinter.Frame): + def __init__(self, root): + Tkinter.Frame.__init__(self, root, border=5) + self.status = Tkinter.Label(self, text='Enter parameters') + self.status.pack(fill=Tkconstants.X, expand=1) + body = Tkinter.Frame(self) + body.pack(fill=Tkconstants.X, expand=1) + sticky = Tkconstants.E + Tkconstants.W + body.grid_columnconfigure(1, weight=2) + Tkinter.Label(body, text='Name').grid(row=1) + self.name = Tkinter.Entry(body, width=30) + self.name.grid(row=1, column=1, sticky=sticky) + Tkinter.Label(body, text='CC#').grid(row=2) + self.ccn = Tkinter.Entry(body, width=30) + self.ccn.grid(row=2, column=1, sticky=sticky) + Tkinter.Label(body, text='Output file').grid(row=0) + self.keypath = Tkinter.Entry(body, width=30) + self.keypath.grid(row=0, column=1, sticky=sticky) + self.keypath.insert(0, 'bnepubkey.b64') + button = Tkinter.Button(body, text="...", command=self.get_keypath) + button.grid(row=0, column=2) + buttons = Tkinter.Frame(self) + buttons.pack() + botton = Tkinter.Button( + buttons, text="Generate", width=10, command=self.generate) + botton.pack(side=Tkconstants.LEFT) + Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) + button = Tkinter.Button( + buttons, text="Quit", width=10, command=self.quit) + button.pack(side=Tkconstants.RIGHT) + + def get_keypath(self): + keypath = tkFileDialog.asksaveasfilename( + parent=None, title='Select B&N EPUB key file to produce', + defaultextension='.b64', + filetypes=[('base64-encoded files', '.b64'), + ('All Files', '.*')]) + if keypath: + keypath = os.path.normpath(keypath) + self.keypath.delete(0, Tkconstants.END) + self.keypath.insert(0, keypath) + return + + def generate(self): + name = self.name.get() + ccn = self.ccn.get() + keypath = self.keypath.get() + if not name: + self.status['text'] = 'Name not specified' + return + if not ccn: + self.status['text'] = 'Credit card number not specified' + return + if not keypath: + self.status['text'] = 'Output keyfile path not specified' + return + self.status['text'] = 'Generating...' + try: + generate_keyfile(name, ccn, keypath) + except Exception, e: + self.status['text'] = 'Error: ' + str(e) + return + self.status['text'] = 'Keyfile successfully generated' + +def gui_main(): + root = Tkinter.Tk() + if AES is None: + root.withdraw() + tkMessageBox.showerror( + "Ignoble EPUB Keyfile Generator", + "This script requires OpenSSL or PyCrypto, which must be installed " + "separately. Read the top-of-script comment for details.") + return 1 + root.title('Ignoble EPUB Keyfile Generator') + root.resizable(True, False) + root.minsize(300, 0) + DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) + root.mainloop() + return 0 + +if __name__ == '__main__': + if len(sys.argv) > 1: + sys.exit(cli_main()) + sys.exit(gui_main()) diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptepub.pyw b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptepub.pyw new file mode 100644 index 0000000..701fc2e --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptepub.pyw @@ -0,0 +1,462 @@ +#! /usr/bin/python +# -*- coding: utf-8 -*- + +# ineptepub.pyw, version 5.2 +# Copyright © 2009-2010 i♥cabbages + +# Released under the terms of the GNU General Public Licence, version 3 or +# later. + +# Windows users: Before running this program, you must first install Python 2.6 +# from and PyCrypto from +# (make sure to +# install the version for Python 2.6). Save this script file as +# ineptepub.pyw and double-click on it to run it. +# +# Mac OS X users: Save this script file as ineptepub.pyw. You can run this +# program from the command line (pythonw ineptepub.pyw) or by double-clicking +# it when it has been associated with PythonLauncher. + +# Revision history: +# 1 - Initial release +# 2 - Rename to INEPT, fix exit code +# 5 - Version bump to avoid (?) confusion; +# Improve OS X support by using OpenSSL when available +# 5.1 - Improve OpenSSL error checking +# 5.2 - Fix ctypes error causing segfaults on some systems +# 5.3 - add support for OpenSSL on Windows, fix bug with some versions of libcrypto 0.9.8 prior to path level o +""" +Decrypt Adobe ADEPT-encrypted EPUB books. +""" + +from __future__ import with_statement + +__license__ = 'GPL v3' + +import sys +import os +import zlib +import zipfile +from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED +from contextlib import closing +import xml.etree.ElementTree as etree +import Tkinter +import Tkconstants +import tkFileDialog +import tkMessageBox + +class ADEPTError(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 ADEPTError('libcrypto not found') + libcrypto = CDLL(libcrypto) + + RSA_NO_PADDING = 3 + AES_MAXNR = 14 + + c_char_pp = POINTER(c_char_p) + c_int_p = POINTER(c_int) + + class RSA(Structure): + pass + RSA_p = POINTER(RSA) + + 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 + + d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey', + [RSA_p, c_char_pp, c_long]) + RSA_size = F(c_int, 'RSA_size', [RSA_p]) + RSA_private_decrypt = F(c_int, 'RSA_private_decrypt', + [c_int, c_char_p, c_char_p, RSA_p, c_int]) + RSA_free = F(None, 'RSA_free', [RSA_p]) + AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key', + [c_char_p, c_int, AES_KEY_p]) + AES_cbc_encrypt = F(None, 'AES_cbc_encrypt', + [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p, + c_int]) + + class RSA(object): + def __init__(self, der): + buf = create_string_buffer(der) + pp = c_char_pp(cast(buf, c_char_p)) + rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der)) + if rsa is None: + raise ADEPTError('Error parsing ADEPT user key DER') + + def decrypt(self, from_): + rsa = self._rsa + to = create_string_buffer(RSA_size(rsa)) + dlen = RSA_private_decrypt(len(from_), from_, to, rsa, + RSA_NO_PADDING) + if dlen < 0: + raise ADEPTError('RSA decryption failed') + return to[:dlen] + + def __del__(self): + if self._rsa is not None: + RSA_free(self._rsa) + self._rsa = None + + class AES(object): + def __init__(self, userkey): + self._blocksize = len(userkey) + if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : + raise ADEPTError('AES improper key used') + return + key = self._key = AES_KEY() + rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key) + if rv < 0: + raise ADEPTError('Failed to initialize AES key') + + def decrypt(self, data): + out = create_string_buffer(len(data)) + iv = ("\x00" * self._blocksize) + rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0) + if rv == 0: + raise ADEPTError('AES decryption failed') + return out.raw + + return (AES, RSA) + +def _load_crypto_pycrypto(): + from Crypto.Cipher import AES as _AES + from Crypto.PublicKey import RSA as _RSA + + # ASN.1 parsing code from tlslite + class ASN1Error(Exception): + pass + + class ASN1Parser(object): + class Parser(object): + def __init__(self, bytes): + self.bytes = bytes + self.index = 0 + + def get(self, length): + if self.index + length > len(self.bytes): + raise ASN1Error("Error decoding ASN.1") + x = 0 + for count in range(length): + x <<= 8 + x |= self.bytes[self.index] + self.index += 1 + return x + + def getFixBytes(self, lengthBytes): + bytes = self.bytes[self.index : self.index+lengthBytes] + self.index += lengthBytes + return bytes + + def getVarBytes(self, lengthLength): + lengthBytes = self.get(lengthLength) + return self.getFixBytes(lengthBytes) + + def getFixList(self, length, lengthList): + l = [0] * lengthList + for x in range(lengthList): + l[x] = self.get(length) + return l + + def getVarList(self, length, lengthLength): + lengthList = self.get(lengthLength) + if lengthList % length != 0: + raise ASN1Error("Error decoding ASN.1") + lengthList = int(lengthList/length) + l = [0] * lengthList + for x in range(lengthList): + l[x] = self.get(length) + return l + + def startLengthCheck(self, lengthLength): + self.lengthCheck = self.get(lengthLength) + self.indexCheck = self.index + + def setLengthCheck(self, length): + self.lengthCheck = length + self.indexCheck = self.index + + def stopLengthCheck(self): + if (self.index - self.indexCheck) != self.lengthCheck: + raise ASN1Error("Error decoding ASN.1") + + def atLengthCheck(self): + if (self.index - self.indexCheck) < self.lengthCheck: + return False + elif (self.index - self.indexCheck) == self.lengthCheck: + return True + else: + raise ASN1Error("Error decoding ASN.1") + + def __init__(self, bytes): + p = self.Parser(bytes) + p.get(1) + self.length = self._getASN1Length(p) + self.value = p.getFixBytes(self.length) + + def getChild(self, which): + p = self.Parser(self.value) + for x in range(which+1): + markIndex = p.index + p.get(1) + length = self._getASN1Length(p) + p.getFixBytes(length) + return ASN1Parser(p.bytes[markIndex:p.index]) + + def _getASN1Length(self, p): + firstLength = p.get(1) + if firstLength<=127: + return firstLength + else: + lengthLength = firstLength & 0x7F + return p.get(lengthLength) + + class AES(object): + def __init__(self, key): + self._aes = _AES.new(key, _AES.MODE_CBC) + + def decrypt(self, data): + return self._aes.decrypt(data) + + class RSA(object): + def __init__(self, der): + key = ASN1Parser([ord(x) for x in der]) + key = [key.getChild(x).value for x in xrange(1, 4)] + key = [self.bytesToNumber(v) for v in key] + self._rsa = _RSA.construct(key) + + def bytesToNumber(self, bytes): + total = 0L + for byte in bytes: + total = (total << 8) + byte + return total + + def decrypt(self, data): + return self._rsa.decrypt(data) + + return (AES, RSA) + +def _load_crypto(): + AES = RSA = None + for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): + try: + AES, RSA = loader() + break + except (ImportError, ADEPTError): + pass + return (AES, RSA) +AES, RSA = _load_crypto() + +META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml') +NSMAP = {'adept': 'http://ns.adobe.com/adept', + 'enc': 'http://www.w3.org/2001/04/xmlenc#'} + +class ZipInfo(zipfile.ZipInfo): + def __init__(self, *args, **kwargs): + if 'compress_type' in kwargs: + compress_type = kwargs.pop('compress_type') + super(ZipInfo, self).__init__(*args, **kwargs) + self.compress_type = compress_type + +class Decryptor(object): + def __init__(self, bookkey, encryption): + enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag) + self._aes = AES(bookkey) + encryption = etree.fromstring(encryption) + self._encrypted = encrypted = set() + expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'), + enc('CipherReference')) + for elem in encryption.findall(expr): + path = elem.get('URI', None) + if path is not None: + encrypted.add(path) + + def decompress(self, bytes): + dc = zlib.decompressobj(-15) + bytes = dc.decompress(bytes) + ex = dc.decompress('Z') + dc.flush() + if ex: + bytes = bytes + ex + return bytes + + def decrypt(self, path, data): + if path in self._encrypted: + data = self._aes.decrypt(data)[16:] + data = data[:-ord(data[-1])] + data = self.decompress(data) + return data + +def cli_main(argv=sys.argv): + progname = os.path.basename(argv[0]) + if AES is None: + print "%s: This script requires OpenSSL or PyCrypto, which must be" \ + " installed separately. Read the top-of-script comment for" \ + " details." % (progname,) + return 1 + if len(argv) != 4: + print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,) + return 1 + keypath, inpath, outpath = argv[1:] + with open(keypath, 'rb') as f: + keyder = f.read() + rsa = RSA(keyder) + with closing(ZipFile(open(inpath, 'rb'))) as inf: + namelist = set(inf.namelist()) + if 'META-INF/rights.xml' not in namelist or \ + 'META-INF/encryption.xml' not in namelist: + raise ADEPTError('%s: not an ADEPT EPUB' % (inpath,)) + for name in META_NAMES: + namelist.remove(name) + rights = etree.fromstring(inf.read('META-INF/rights.xml')) + adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) + expr = './/%s' % (adept('encryptedKey'),) + bookkey = ''.join(rights.findtext(expr)) + bookkey = rsa.decrypt(bookkey.decode('base64')) + # Padded as per RSAES-PKCS1-v1_5 + if bookkey[-17] != '\x00': + raise ADEPTError('problem decrypting session key') + encryption = inf.read('META-INF/encryption.xml') + decryptor = Decryptor(bookkey[-16:], encryption) + kwds = dict(compression=ZIP_DEFLATED, allowZip64=False) + with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf: + zi = ZipInfo('mimetype', compress_type=ZIP_STORED) + outf.writestr(zi, inf.read('mimetype')) + for path in namelist: + data = inf.read(path) + outf.writestr(path, decryptor.decrypt(path, data)) + return 0 + +class DecryptionDialog(Tkinter.Frame): + def __init__(self, root): + Tkinter.Frame.__init__(self, root, border=5) + self.status = Tkinter.Label(self, text='Select files for decryption') + self.status.pack(fill=Tkconstants.X, expand=1) + body = Tkinter.Frame(self) + body.pack(fill=Tkconstants.X, expand=1) + sticky = Tkconstants.E + Tkconstants.W + body.grid_columnconfigure(1, weight=2) + Tkinter.Label(body, text='Key file').grid(row=0) + self.keypath = Tkinter.Entry(body, width=30) + self.keypath.grid(row=0, column=1, sticky=sticky) + if os.path.exists('adeptkey.der'): + self.keypath.insert(0, 'adeptkey.der') + button = Tkinter.Button(body, text="...", command=self.get_keypath) + button.grid(row=0, column=2) + Tkinter.Label(body, text='Input file').grid(row=1) + self.inpath = Tkinter.Entry(body, width=30) + self.inpath.grid(row=1, column=1, sticky=sticky) + button = Tkinter.Button(body, text="...", command=self.get_inpath) + button.grid(row=1, column=2) + Tkinter.Label(body, text='Output file').grid(row=2) + self.outpath = Tkinter.Entry(body, width=30) + self.outpath.grid(row=2, column=1, sticky=sticky) + button = Tkinter.Button(body, text="...", command=self.get_outpath) + button.grid(row=2, column=2) + buttons = Tkinter.Frame(self) + buttons.pack() + botton = Tkinter.Button( + buttons, text="Decrypt", width=10, command=self.decrypt) + botton.pack(side=Tkconstants.LEFT) + Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) + button = Tkinter.Button( + buttons, text="Quit", width=10, command=self.quit) + button.pack(side=Tkconstants.RIGHT) + + def get_keypath(self): + keypath = tkFileDialog.askopenfilename( + parent=None, title='Select ADEPT key file', + defaultextension='.der', filetypes=[('DER-encoded files', '.der'), + ('All Files', '.*')]) + if keypath: + keypath = os.path.normpath(keypath) + self.keypath.delete(0, Tkconstants.END) + self.keypath.insert(0, keypath) + return + + def get_inpath(self): + inpath = tkFileDialog.askopenfilename( + parent=None, title='Select ADEPT-encrypted EPUB file to decrypt', + defaultextension='.epub', filetypes=[('EPUB files', '.epub'), + ('All files', '.*')]) + if inpath: + inpath = os.path.normpath(inpath) + self.inpath.delete(0, Tkconstants.END) + self.inpath.insert(0, inpath) + return + + def get_outpath(self): + outpath = tkFileDialog.asksaveasfilename( + parent=None, title='Select unencrypted EPUB file to produce', + defaultextension='.epub', filetypes=[('EPUB files', '.epub'), + ('All files', '.*')]) + if outpath: + outpath = os.path.normpath(outpath) + self.outpath.delete(0, Tkconstants.END) + self.outpath.insert(0, outpath) + return + + def decrypt(self): + keypath = self.keypath.get() + inpath = self.inpath.get() + outpath = self.outpath.get() + if not keypath or not os.path.exists(keypath): + self.status['text'] = 'Specified key file does not exist' + return + if not inpath or not os.path.exists(inpath): + self.status['text'] = 'Specified input file does not exist' + return + if not outpath: + self.status['text'] = 'Output file not specified' + return + if inpath == outpath: + self.status['text'] = 'Must have different input and output files' + return + argv = [sys.argv[0], keypath, inpath, outpath] + self.status['text'] = 'Decrypting...' + try: + cli_main(argv) + except Exception, e: + self.status['text'] = 'Error: ' + str(e) + return + self.status['text'] = 'File successfully decrypted' + +def gui_main(): + root = Tkinter.Tk() + if AES is None: + root.withdraw() + tkMessageBox.showerror( + "INEPT EPUB Decrypter", + "This script requires OpenSSL or PyCrypto, which must be" + " installed separately. Read the top-of-script comment for" + " details.") + return 1 + root.title('INEPT EPUB Decrypter') + root.resizable(True, False) + root.minsize(300, 0) + DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) + root.mainloop() + return 0 + +if __name__ == '__main__': + if len(sys.argv) > 1: + sys.exit(cli_main()) + sys.exit(gui_main()) diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptkey.pyw b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptkey.pyw new file mode 100644 index 0000000..bd66e78 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptkey.pyw @@ -0,0 +1,458 @@ +#! /usr/bin/python +# -*- coding: utf-8 -*- + +# ineptkey.pyw, version 5 +# Copyright © 2009-2010 i♥cabbages + +# Released under the terms of the GNU General Public Licence, version 3 or +# later. + +# Windows users: Before running this program, you must first install Python 2.6 +# from and PyCrypto from +# (make certain +# to install the version for Python 2.6). Then save this script file as +# ineptkey.pyw and double-click on it to run it. It will create a file named +# adeptkey.der in the same directory. This is your ADEPT user key. +# +# Mac OS X users: Save this script file as ineptkey.pyw. You can run this +# program from the command line (pythonw ineptkey.pyw) or by double-clicking +# it when it has been associated with PythonLauncher. It will create a file +# named adeptkey.der in the same directory. This is your ADEPT user key. + +# Revision history: +# 1 - Initial release, for Adobe Digital Editions 1.7 +# 2 - Better algorithm for finding pLK; improved error handling +# 3 - Rename to INEPT +# 4 - Series of changes by joblack (and others?) -- +# 4.1 - quick beta fix for ADE 1.7.2 (anon) +# 4.2 - added old 1.7.1 processing +# 4.3 - better key search +# 4.4 - Make it working on 64-bit Python +# 5 - Clean up and improve 4.x changes; +# Clean up and merge OS X support by unknown +# 5.1 - add support for using OpenSSL on Windows in place of PyCrypto +# 5.2 - added support for output of key to a particular file + +""" +Retrieve Adobe ADEPT user key. +""" + +from __future__ import with_statement + +__license__ = 'GPL v3' + +import sys +import os +import struct +import Tkinter +import Tkconstants +import tkMessageBox +import traceback + +class ADEPTError(Exception): + pass + +if sys.platform.startswith('win'): + from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \ + create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \ + string_at, Structure, c_void_p, cast, c_size_t, memmove, CDLL, c_int, \ + c_long, c_ulong + + from ctypes.wintypes import LPVOID, DWORD, BOOL + import _winreg as winreg + + def _load_crypto_libcrypto(): + from ctypes.util import find_library + libcrypto = find_library('libeay32') + if libcrypto is None: + raise ADEPTError('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_cbc_encrypt = F(None, 'AES_cbc_encrypt', + [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_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 ADEPTError('AES improper key used') + key = self._key = AES_KEY() + rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key) + if rv < 0: + raise ADEPTError('Failed to initialize AES key') + def decrypt(self, data): + out = create_string_buffer(len(data)) + iv = ("\x00" * self._blocksize) + rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0) + if rv == 0: + raise ADEPTError('AES decryption failed') + return out.raw + 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_CBC) + def decrypt(self, data): + return self._aes.decrypt(data) + return AES + + def _load_crypto(): + AES = None + for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): + try: + AES = loader() + break + except (ImportError, ADEPTError): + pass + return AES + + AES = _load_crypto() + + + DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device' + PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation' + + MAX_PATH = 255 + + kernel32 = windll.kernel32 + advapi32 = windll.advapi32 + crypt32 = windll.crypt32 + + def GetSystemDirectory(): + GetSystemDirectoryW = kernel32.GetSystemDirectoryW + GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint] + GetSystemDirectoryW.restype = c_uint + def GetSystemDirectory(): + buffer = create_unicode_buffer(MAX_PATH + 1) + GetSystemDirectoryW(buffer, len(buffer)) + return buffer.value + return GetSystemDirectory + GetSystemDirectory = GetSystemDirectory() + + def GetVolumeSerialNumber(): + GetVolumeInformationW = kernel32.GetVolumeInformationW + GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint, + POINTER(c_uint), POINTER(c_uint), + POINTER(c_uint), c_wchar_p, c_uint] + GetVolumeInformationW.restype = c_uint + def GetVolumeSerialNumber(path): + vsn = c_uint(0) + GetVolumeInformationW( + path, None, 0, byref(vsn), None, None, None, 0) + return vsn.value + return GetVolumeSerialNumber + GetVolumeSerialNumber = GetVolumeSerialNumber() + + def GetUserName(): + GetUserNameW = advapi32.GetUserNameW + GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)] + GetUserNameW.restype = c_uint + def GetUserName(): + buffer = create_unicode_buffer(32) + size = c_uint(len(buffer)) + while not GetUserNameW(buffer, byref(size)): + buffer = create_unicode_buffer(len(buffer) * 2) + size.value = len(buffer) + return buffer.value.encode('utf-16-le')[::2] + return GetUserName + GetUserName = GetUserName() + + PAGE_EXECUTE_READWRITE = 0x40 + MEM_COMMIT = 0x1000 + MEM_RESERVE = 0x2000 + + def VirtualAlloc(): + _VirtualAlloc = kernel32.VirtualAlloc + _VirtualAlloc.argtypes = [LPVOID, c_size_t, DWORD, DWORD] + _VirtualAlloc.restype = LPVOID + def VirtualAlloc(addr, size, alloctype=(MEM_COMMIT | MEM_RESERVE), + protect=PAGE_EXECUTE_READWRITE): + return _VirtualAlloc(addr, size, alloctype, protect) + return VirtualAlloc + VirtualAlloc = VirtualAlloc() + + MEM_RELEASE = 0x8000 + + def VirtualFree(): + _VirtualFree = kernel32.VirtualFree + _VirtualFree.argtypes = [LPVOID, c_size_t, DWORD] + _VirtualFree.restype = BOOL + def VirtualFree(addr, size=0, freetype=MEM_RELEASE): + return _VirtualFree(addr, size, freetype) + return VirtualFree + VirtualFree = VirtualFree() + + class NativeFunction(object): + def __init__(self, restype, argtypes, insns): + self._buf = buf = VirtualAlloc(None, len(insns)) + memmove(buf, insns, len(insns)) + ftype = CFUNCTYPE(restype, *argtypes) + self._native = ftype(buf) + + def __call__(self, *args): + return self._native(*args) + + def __del__(self): + if self._buf is not None: + VirtualFree(self._buf) + self._buf = None + + if struct.calcsize("P") == 4: + CPUID0_INSNS = ( + "\x53" # push %ebx + "\x31\xc0" # xor %eax,%eax + "\x0f\xa2" # cpuid + "\x8b\x44\x24\x08" # mov 0x8(%esp),%eax + "\x89\x18" # mov %ebx,0x0(%eax) + "\x89\x50\x04" # mov %edx,0x4(%eax) + "\x89\x48\x08" # mov %ecx,0x8(%eax) + "\x5b" # pop %ebx + "\xc3" # ret + ) + CPUID1_INSNS = ( + "\x53" # push %ebx + "\x31\xc0" # xor %eax,%eax + "\x40" # inc %eax + "\x0f\xa2" # cpuid + "\x5b" # pop %ebx + "\xc3" # ret + ) + else: + CPUID0_INSNS = ( + "\x49\x89\xd8" # mov %rbx,%r8 + "\x49\x89\xc9" # mov %rcx,%r9 + "\x48\x31\xc0" # xor %rax,%rax + "\x0f\xa2" # cpuid + "\x4c\x89\xc8" # mov %r9,%rax + "\x89\x18" # mov %ebx,0x0(%rax) + "\x89\x50\x04" # mov %edx,0x4(%rax) + "\x89\x48\x08" # mov %ecx,0x8(%rax) + "\x4c\x89\xc3" # mov %r8,%rbx + "\xc3" # retq + ) + CPUID1_INSNS = ( + "\x53" # push %rbx + "\x48\x31\xc0" # xor %rax,%rax + "\x48\xff\xc0" # inc %rax + "\x0f\xa2" # cpuid + "\x5b" # pop %rbx + "\xc3" # retq + ) + + def cpuid0(): + _cpuid0 = NativeFunction(None, [c_char_p], CPUID0_INSNS) + buf = create_string_buffer(12) + def cpuid0(): + _cpuid0(buf) + return buf.raw + return cpuid0 + cpuid0 = cpuid0() + + cpuid1 = NativeFunction(c_uint, [], CPUID1_INSNS) + + class DataBlob(Structure): + _fields_ = [('cbData', c_uint), + ('pbData', c_void_p)] + DataBlob_p = POINTER(DataBlob) + + def CryptUnprotectData(): + _CryptUnprotectData = crypt32.CryptUnprotectData + _CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p, + c_void_p, c_void_p, c_uint, DataBlob_p] + _CryptUnprotectData.restype = c_uint + def CryptUnprotectData(indata, entropy): + indatab = create_string_buffer(indata) + indata = DataBlob(len(indata), cast(indatab, c_void_p)) + entropyb = create_string_buffer(entropy) + entropy = DataBlob(len(entropy), cast(entropyb, c_void_p)) + outdata = DataBlob() + if not _CryptUnprotectData(byref(indata), None, byref(entropy), + None, None, 0, byref(outdata)): + raise ADEPTError("Failed to decrypt user key key (sic)") + return string_at(outdata.pbData, outdata.cbData) + return CryptUnprotectData + CryptUnprotectData = CryptUnprotectData() + + def retrieve_key(keypath): + if AES is None: + tkMessageBox.showerror( + "ADEPT Key", + "This script requires PyCrypto or OpenSSL which must be installed " + "separately. Read the top-of-script comment for details.") + return False + root = GetSystemDirectory().split('\\')[0] + '\\' + serial = GetVolumeSerialNumber(root) + vendor = cpuid0() + signature = struct.pack('>I', cpuid1())[1:] + user = GetUserName() + entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user) + cuser = winreg.HKEY_CURRENT_USER + try: + regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH) + except WindowsError: + raise ADEPTError("Adobe Digital Editions not activated") + device = winreg.QueryValueEx(regkey, 'key')[0] + keykey = CryptUnprotectData(device, entropy) + userkey = None + try: + plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH) + except WindowsError: + raise ADEPTError("Could not locate ADE activation") + for i in xrange(0, 16): + try: + plkparent = winreg.OpenKey(plkroot, "%04d" % (i,)) + except WindowsError: + break + ktype = winreg.QueryValueEx(plkparent, None)[0] + if ktype != 'credentials': + continue + for j in xrange(0, 16): + try: + plkkey = winreg.OpenKey(plkparent, "%04d" % (j,)) + except WindowsError: + break + ktype = winreg.QueryValueEx(plkkey, None)[0] + if ktype != 'privateLicenseKey': + continue + userkey = winreg.QueryValueEx(plkkey, 'value')[0] + break + if userkey is not None: + break + if userkey is None: + raise ADEPTError('Could not locate privateLicenseKey') + userkey = userkey.decode('base64') + aes = AES(keykey) + userkey = aes.decrypt(userkey) + userkey = userkey[26:-ord(userkey[-1])] + with open(keypath, 'wb') as f: + f.write(userkey) + return True + +elif sys.platform.startswith('darwin'): + import xml.etree.ElementTree as etree + import Carbon.File + import Carbon.Folder + import Carbon.Folders + import MacOS + + ACTIVATION_PATH = 'Adobe/Digital Editions/activation.dat' + NSMAP = {'adept': 'http://ns.adobe.com/adept', + 'enc': 'http://www.w3.org/2001/04/xmlenc#'} + + def find_folder(domain, dtype): + try: + fsref = Carbon.Folder.FSFindFolder(domain, dtype, False) + return Carbon.File.pathname(fsref) + except MacOS.Error: + return None + + def find_app_support_file(subpath): + dtype = Carbon.Folders.kApplicationSupportFolderType + for domain in Carbon.Folders.kUserDomain, Carbon.Folders.kLocalDomain: + path = find_folder(domain, dtype) + if path is None: + continue + path = os.path.join(path, subpath) + if os.path.isfile(path): + return path + return None + + def retrieve_key(keypath): + actpath = find_app_support_file(ACTIVATION_PATH) + if actpath is None: + raise ADEPTError("Could not locate ADE activation") + tree = etree.parse(actpath) + adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) + expr = '//%s/%s' % (adept('credentials'), adept('privateLicenseKey')) + userkey = tree.findtext(expr) + userkey = userkey.decode('base64') + userkey = userkey[26:] + with open(keypath, 'wb') as f: + f.write(userkey) + return True + +elif sys.platform.startswith('cygwin'): + def retrieve_key(keypath): + tkMessageBox.showerror( + "ADEPT Key", + "This script requires a Windows-native Python, and cannot be run " + "under Cygwin. Please install a Windows-native Python and/or " + "check your file associations.") + return False + +else: + def retrieve_key(keypath): + tkMessageBox.showerror( + "ADEPT Key", + "This script only supports Windows and Mac OS X. For Linux " + "you should be able to run ADE and this script under Wine (with " + "an appropriate version of Windows Python installed).") + return False + +class ExceptionDialog(Tkinter.Frame): + def __init__(self, root, text): + Tkinter.Frame.__init__(self, root, border=5) + label = Tkinter.Label(self, text="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) + +def cli_main(argv=sys.argv): + keypath = argv[1] + try: + success = retrieve_key(keypath) + except ADEPTError, e: + print "Key generation Error: " + str(e) + return 1 + except Exception, e: + print "General Error: " + str(e) + return 1 + if not success: + return 1 + return 0 + +def main(argv=sys.argv): + root = Tkinter.Tk() + root.withdraw() + progname = os.path.basename(argv[0]) + keypath = 'adeptkey.der' + success = False + try: + success = retrieve_key(keypath) + except ADEPTError, e: + tkMessageBox.showerror("ADEPT Key", "Error: " + str(e)) + except Exception: + root.wm_state('normal') + root.title('ADEPT Key') + text = traceback.format_exc() + ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1) + root.mainloop() + if not success: + return 1 + tkMessageBox.showinfo( + "ADEPT Key", "Key successfully retrieved to %s" % (keypath)) + return 0 + +if __name__ == '__main__': + if len(sys.argv) > 1: + sys.exit(cli_main()) + sys.exit(main()) diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py new file mode 100644 index 0000000..df05f89 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py @@ -0,0 +1,582 @@ +#!/usr/bin/env python + +# engine to remove drm from Kindle for Mac and Kindle for PC books +# for personal use for archiving and converting your ebooks + +# PLEASE DO NOT PIRATE EBOOKS! + +# We want all authors and publishers, and eBook stores to live +# long and prosperous lives but at the same time we just want to +# be able to read OUR books on whatever device we want and to keep +# readable for a long, long time + +# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, +# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates +# and many many others + +# It can run standalone to convert K4M/K4PC/Mobi files, or it can be installed as a +# plugin for Calibre (http://calibre-ebook.com/about) so that importing +# K4 or Mobi with DRM is no londer a multi-step process. +# +# ***NOTE*** If you are using this script as a calibre plugin for a K4M or K4PC ebook +# then calibre must be installed on the same machine and in the same account as K4PC or K4M +# for the plugin version to function properly. +# +# To create a Calibre plugin, rename this file so that the filename +# ends in '_plugin.py', put it into a ZIP file with all its supporting python routines +# and import that ZIP into Calibre using its plugin configuration GUI. + +from __future__ import with_statement + +__version__ = '1.2' + +class Unbuffered: + def __init__(self, stream): + self.stream = stream + def write(self, data): + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +import sys +import os, csv, getopt +import binascii +import zlib +import re +from struct import pack, unpack, unpack_from + + +#Exception Handling +class DrmException(Exception): + pass + +# +# crypto digestroutines +# + +import hashlib + +def MD5(message): + ctx = hashlib.md5() + ctx.update(message) + return ctx.digest() + +def SHA1(message): + ctx = hashlib.sha1() + ctx.update(message) + return ctx.digest() + +# determine if we are running as a calibre plugin +if 'calibre' in sys.modules: + inCalibre = True + global openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4 +else: + inCalibre = False + +# +# start of Kindle specific routines +# + +if not inCalibre: + import mobidedrm + if sys.platform.startswith('win'): + from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4 + if sys.platform.startswith('darwin'): + from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4 + +global kindleDatabase + +# Encode the bytes in data with the characters in map +def encode(data, map): + result = "" + for char in data: + value = ord(char) + Q = (value ^ 0x80) // len(map) + R = value % len(map) + result += map[Q] + result += map[R] + return result + +# Hash the bytes in data and then encode the digest with the characters in map +def encodeHash(data,map): + return encode(MD5(data),map) + +# Decode the string in data with the characters in map. Returns the decoded bytes +def decode(data,map): + result = "" + for i in range (0,len(data)-1,2): + high = map.find(data[i]) + low = map.find(data[i+1]) + if (high == -1) or (low == -1) : + break + value = (((high * len(map)) ^ 0x80) & 0xFF) + low + result += pack("B",value) + return result + + +# Parse the Kindle.info file and return the records as a list of key-values +def parseKindleInfo(kInfoFile): + DB = {} + infoReader = openKindleInfo(kInfoFile) + infoReader.read(1) + data = infoReader.read() + if sys.platform.startswith('win'): + items = data.split('{') + else : + items = data.split('[') + for item in items: + splito = item.split(':') + DB[splito[0]] =splito[1] + return DB + +# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record +def getKindleInfoValueForHash(hashedKey): + global kindleDatabase + encryptedValue = decode(kindleDatabase[hashedKey],charMap2) + if sys.platform.startswith('win'): + return CryptUnprotectData(encryptedValue,"") + else: + cleartext = CryptUnprotectData(encryptedValue) + return decode(cleartext, charMap1) + +# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record +def getKindleInfoValueForKey(key): + return getKindleInfoValueForHash(encodeHash(key,charMap2)) + +# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string. +def findNameForHash(hash): + names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"] + result = "" + for name in names: + if hash == encodeHash(name, charMap2): + result = name + break + return result + +# Print all the records from the kindle.info file (option -i) +def printKindleInfo(): + for record in kindleDatabase: + name = findNameForHash(record) + if name != "" : + print (name) + print ("--------------------------") + else : + print ("Unknown Record") + print getKindleInfoValueForHash(record) + print "\n" + +# +# PID generation routines +# + +# Returns two bit at offset from a bit field +def getTwoBitsFromBitField(bitField,offset): + byteNumber = offset // 4 + bitPosition = 6 - 2*(offset % 4) + return ord(bitField[byteNumber]) >> bitPosition & 3 + +# Returns the six bits at offset from a bit field +def getSixBitsFromBitField(bitField,offset): + offset *= 3 + value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2) + return value + +# 8 bits to six bits encoding from hash to generate PID string +def encodePID(hash): + global charMap3 + PID = "" + for position in range (0,8): + PID += charMap3[getSixBitsFromBitField(hash,position)] + return PID + +# Encryption table used to generate the device PID +def generatePidEncryptionTable() : + table = [] + for counter1 in range (0,0x100): + value = counter1 + for counter2 in range (0,8): + if (value & 1 == 0) : + value = value >> 1 + else : + value = value >> 1 + value = value ^ 0xEDB88320 + table.append(value) + return table + +# Seed value used to generate the device PID +def generatePidSeed(table,dsn) : + value = 0 + for counter in range (0,4) : + index = (ord(dsn[counter]) ^ value) &0xFF + value = (value >> 8) ^ table[index] + return value + +# Generate the device PID +def generateDevicePID(table,dsn,nbRoll): + seed = generatePidSeed(table,dsn) + pidAscii = "" + pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF] + index = 0 + for counter in range (0,nbRoll): + pid[index] = pid[index] ^ ord(dsn[counter]) + index = (index+1) %8 + for counter in range (0,8): + index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7) + pidAscii += charMap4[index] + return pidAscii + +# convert from 8 digit PID to 10 digit PID with checksum +def checksumPid(s): + letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" + crc = (~binascii.crc32(s,-1))&0xFFFFFFFF + crc = crc ^ (crc >> 16) + res = s + l = len(letters) + for i in (0,1): + b = crc & 0xff + pos = (b // l) ^ (b % l) + res += letters[pos%l] + crc >>= 8 + return res + + +class MobiPeek: + def loadSection(self, section): + before, after = self.sections[section:section+2] + self.f.seek(before) + return self.f.read(after - before) + def __init__(self, filename): + self.f = file(filename, 'rb') + self.header = self.f.read(78) + self.ident = self.header[0x3C:0x3C+8] + if self.ident != 'BOOKMOBI' and self.ident != 'TEXtREAd': + raise DrmException('invalid file format') + self.num_sections, = unpack_from('>H', self.header, 76) + sections = self.f.read(self.num_sections*8) + self.sections = unpack_from('>%dL' % (self.num_sections*2), sections, 0)[::2] + (0xfffffff, ) + self.sect0 = self.loadSection(0) + self.f.close() + def getBookTitle(self): + # get book title + toff, tlen = unpack('>II', self.sect0[0x54:0x5c]) + tend = toff + tlen + title = self.sect0[toff:tend] + return title + def getexthData(self): + # if exth region exists then grab it + # get length of this header + length, type, codepage, unique_id, version = unpack('>LLLLL', self.sect0[20:40]) + exth_flag, = unpack('>L', self.sect0[0x80:0x84]) + exth = '' + if exth_flag & 0x40: + exth = self.sect0[16 + length:] + return exth + def isNotEncrypted(self): + lock_type, = unpack('>H', self.sect0[0xC:0xC+2]) + if lock_type == 0: + return True + return False + +# DiapDealer's stuff: Parse the EXTH header records and parse the Kindleinfo +# file to calculate the book pid. +def getK4Pids(exth, title, kInfoFile=None): + global kindleDatabase + try: + kindleDatabase = parseKindleInfo(kInfoFile) + except Exception, message: + print(message) + + if kindleDatabase != None : + # Get the Mazama Random number + MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber") + + # Get the HDD serial + encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1) + + # Get the current user name + encodedUsername = encodeHash(GetUserName(),charMap1) + + # concat, hash and encode to calculate the DSN + DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1) + + print("\nDSN: " + DSN) + + # Compute the device PID (for which I can tell, is used for nothing). + # But hey, stuff being printed out is apparently cool. + table = generatePidEncryptionTable() + devicePID = generateDevicePID(table,DSN,4) + + print("Device PID: " + checksumPid(devicePID)) + + # Compute book PID + exth_records = {} + nitems, = unpack('>I', exth[8:12]) + pos = 12 + + exth_records[209] = None + # Parse the exth records, storing data indexed by type + for i in xrange(nitems): + type, size = unpack('>II', exth[pos: pos + 8]) + content = exth[pos + 8: pos + size] + + exth_records[type] = content + pos += size + + # Grab the contents of the type 209 exth record + if exth_records[209] != None: + data = exth_records[209] + else: + raise DrmException("\nNo EXTH record type 209 - Perhaps not a K4 file?") + + # Parse the 209 data to find the the exth record with the token data. + # The last character of the 209 data points to the record with the token. + # Always 208 from my experience, but I'll leave the logic in case that changes. + for i in xrange(len(data)): + if ord(data[i]) != 0: + if exth_records[ord(data[i])] != None: + token = exth_records[ord(data[i])] + + # Get the kindle account token + kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens") + + print("Account Token: " + kindleAccountToken) + + pidHash = SHA1(DSN+kindleAccountToken+exth_records[209]+token) + + bookPID = encodePID(pidHash) + bookPID = checksumPid(bookPID) + + if exth_records[503] != None: + print "Pid for " + exth_records[503] + ": " + bookPID + else: + print "Pid for " + title + ":" + bookPID + return bookPID + + raise DrmException("\nCould not access K4 data - Perhaps K4 is not installed/configured?") + return null + +def usage(progname): + print "Removes DRM protection from K4PC, K4M, and Mobi ebooks" + print "Usage:" + print " %s [-k ] [-p ] " % progname + +# +# Main +# +def main(argv=sys.argv): + global kindleDatabase + import mobidedrm + + progname = os.path.basename(argv[0]) + kInfoFiles = [] + pidnums = "" + + print ('K4MobiDeDrm v%(__version__)s ' + 'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals()) + + try: + opts, args = getopt.getopt(sys.argv[1:], "k:p:") + except getopt.GetoptError, err: + print str(err) + usage(progname) + sys.exit(2) + + if len(args)<2: + usage(progname) + sys.exit(2) + + for o, a in opts: + if o == "-k": + if a == None : + raise DrmException("Invalid parameter for -k") + kInfoFiles.append(a) + if o == "-p": + if a == None : + raise DrmException("Invalid parameter for -p") + pidnums = a + + kindleDatabase = None + infile = args[0] + outfile = args[1] + DecodeErrorString = "" + try: + # first try with K4PC/K4M + ex = MobiPeek(infile) + if ex.isNotEncrypted(): + print "File was Not Encrypted" + return 2 + title = ex.getBookTitle() + exth = ex.getexthData() + if exth=='': + raise DrmException("Not a Kindle Mobipocket file") + pid = getK4Pids(exth, title) + unlocked_file = mobidedrm.getUnencryptedBook(infile, pid) + except DrmException, e: + DecodeErrorString += "Error trying default K4 info: " + str(e) + "\n" + pass + except mobidedrm.DrmException, e: + DecodeErrorString += "Error trying default K4 info: " + str(e) + "\n" + pass + else: + file(outfile, 'wb').write(unlocked_file) + return 0 + + # now try alternate kindle.info files + if kInfoFiles: + for infoFile in kInfoFiles: + kindleDatabase = None + try: + title = ex.getBookTitle() + exth = ex.getexthData() + if exth=='': + raise DrmException("Not a Kindle Mobipocket file") + pid = getK4Pids(exth, title, infoFile) + unlocked_file = mobidedrm.getUnencryptedBook(infile, pid) + except DrmException, e: + DecodeErrorString += "Error trying " + infoFile + " K4 info: " + str(e) + "\n" + pass + except mobidedrm.DrmException, e: + DecodeErrorString += "Error trying " + infoFile + " K4 info: " + str(e) + "\n" + pass + else: + file(outfile, 'wb').write(unlocked_file) + return 0 + + # Lastly, try from the pid list + pids = pidnums.split(',') + for pid in pids: + try: + print 'Trying: "'+ pid + '"' + unlocked_file = mobidedrm.getUnencryptedBook(infile, pid) + except mobidedrm.DrmException: + pass + else: + file(outfile, 'wb').write(unlocked_file) + return 0 + + # we could not unencrypt book + print DecodeErrorString + print "Error: Could Not Unencrypt Book" + return 1 + + +if __name__ == '__main__': + sys.stdout=Unbuffered(sys.stdout) + sys.exit(main()) + + +if not __name__ == "__main__" and inCalibre: + from calibre.customize import FileTypePlugin + + class K4DeDRM(FileTypePlugin): + name = 'K4PC, K4Mac, Mobi DeDRM' # Name of the plugin + description = 'Removes DRM from K4PC, K4Mac, and Mobi files. \ + Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.' + supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on + author = 'DiapDealer, SomeUpdates' # The author of this plugin + version = (0, 1, 2) # The version number of this plugin + file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to + on_import = True # Run this plugin during the import + priority = 200 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm + + def run(self, path_to_ebook): + from calibre.gui2 import is_ok_to_use_qt + from PyQt4.Qt import QMessageBox + global kindleDatabase + global openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4 + if sys.platform.startswith('win'): + from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4 + if sys.platform.startswith('darwin'): + from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4 + import mobidedrm + + # Get supplied list of PIDs to try from plugin customization. + pidnums = self.site_customization + + # Load any kindle info files (*.info) included Calibre's config directory. + kInfoFiles = [] + try: + # Find Calibre's configuration directory. + confpath = os.path.split(os.path.split(self.plugin_path)[0])[0] + print 'K4MobiDeDRM: Calibre configuration directory = %s' % confpath + files = os.listdir(confpath) + filefilter = re.compile("\.info$", re.IGNORECASE) + files = filter(filefilter.search, files) + + if files: + for filename in files: + fpath = os.path.join(confpath, filename) + kInfoFiles.append(fpath) + print 'K4MobiDeDRM: Kindle info file %s found in config folder.' % filename + except IOError: + print 'K4MobiDeDRM: Error reading kindle info files from config directory.' + pass + + # first try with book specifc pid from K4PC or K4M + try: + kindleDatabase = None + ex = MobiPeek(path_to_ebook) + if ex.isNotEncrypted(): + return path_to_ebook + title = ex.getBookTitle() + exth = ex.getexthData() + if exth=='': + raise DrmException("Not a Kindle Mobipocket file") + pid = getK4Pids(exth, title) + unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid) + except DrmException: + pass + except mobidedrm.DrmException: + pass + else: + of = self.temporary_file('.mobi') + of.write(unlocked_file) + of.close() + return of.name + + # Now try alternate kindle info files + if kInfoFiles: + for infoFile in kInfoFiles: + kindleDatabase = None + try: + title = ex.getBookTitle() + exth = ex.getexthData() + if exth=='': + raise DrmException("Not a Kindle Mobipocket file") + pid = getK4Pids(exth, title, infoFile) + unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid) + except DrmException: + pass + except mobidedrm.DrmException: + pass + else: + of = self.temporary_file('.mobi') + of.write(unlocked_file) + of.close() + return of.name + + # now try from the pid list + pids = pidnums.split(',') + for pid in pids: + try: + unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook, pid) + except mobidedrm.DrmException: + pass + else: + of = self.temporary_file('.mobi') + of.write(unlocked_file) + of.close() + return of.name + + #if you reached here then no luck raise and exception + if is_ok_to_use_qt(): + d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook) + d.show() + d.raise_() + d.exec_() + raise Exception("K4MobiDeDRM plugin could not decode the file") + return "" + + def customization_help(self, gui=False): + return 'Enter each 10 character PID separated by a comma (no spaces).' diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mutils.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mutils.py new file mode 100644 index 0000000..33771eb --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mutils.py @@ -0,0 +1,334 @@ +# standlone set of Mac OSX specific routines needed for K4DeDRM + +from __future__ import with_statement + +import sys +import os + +#Exception Handling +class K4MDrmException(Exception): + pass + +import signal +import threading +import subprocess +from subprocess import Popen, PIPE, STDOUT + +# **heavily** chopped up and modfied version of asyncproc.py +# to make it actually work on Windows as well as Mac/Linux +# For the original see: +# "http://www.lysator.liu.se/~bellman/download/" +# author is "Thomas Bellman " +# available under GPL version 3 or Later + +# create an asynchronous subprocess whose output can be collected in +# a non-blocking manner + +# What a mess! Have to use threads just to get non-blocking io +# in a cross-platform manner + +# luckily all thread use is hidden within this class + +class Process(object): + def __init__(self, *params, **kwparams): + if len(params) <= 3: + kwparams.setdefault('stdin', subprocess.PIPE) + if len(params) <= 4: + kwparams.setdefault('stdout', subprocess.PIPE) + if len(params) <= 5: + kwparams.setdefault('stderr', subprocess.PIPE) + self.__pending_input = [] + self.__collected_outdata = [] + self.__collected_errdata = [] + self.__exitstatus = None + self.__lock = threading.Lock() + self.__inputsem = threading.Semaphore(0) + self.__quit = False + + self.__process = subprocess.Popen(*params, **kwparams) + + if self.__process.stdin: + self.__stdin_thread = threading.Thread( + name="stdin-thread", + target=self.__feeder, args=(self.__pending_input, + self.__process.stdin)) + self.__stdin_thread.setDaemon(True) + self.__stdin_thread.start() + + if self.__process.stdout: + self.__stdout_thread = threading.Thread( + name="stdout-thread", + target=self.__reader, args=(self.__collected_outdata, + self.__process.stdout)) + self.__stdout_thread.setDaemon(True) + self.__stdout_thread.start() + + if self.__process.stderr: + self.__stderr_thread = threading.Thread( + name="stderr-thread", + target=self.__reader, args=(self.__collected_errdata, + self.__process.stderr)) + self.__stderr_thread.setDaemon(True) + self.__stderr_thread.start() + + def pid(self): + return self.__process.pid + + def kill(self, signal): + self.__process.send_signal(signal) + + # check on subprocess (pass in 'nowait') to act like poll + def wait(self, flag): + if flag.lower() == 'nowait': + rc = self.__process.poll() + else: + rc = self.__process.wait() + if rc != None: + if self.__process.stdin: + self.closeinput() + if self.__process.stdout: + self.__stdout_thread.join() + if self.__process.stderr: + self.__stderr_thread.join() + return self.__process.returncode + + def terminate(self): + if self.__process.stdin: + self.closeinput() + self.__process.terminate() + + # thread gets data from subprocess stdout + def __reader(self, collector, source): + while True: + data = os.read(source.fileno(), 65536) + self.__lock.acquire() + collector.append(data) + self.__lock.release() + if data == "": + source.close() + break + return + + # thread feeds data to subprocess stdin + def __feeder(self, pending, drain): + while True: + self.__inputsem.acquire() + self.__lock.acquire() + if not pending and self.__quit: + drain.close() + self.__lock.release() + break + data = pending.pop(0) + self.__lock.release() + drain.write(data) + + # non-blocking read of data from subprocess stdout + def read(self): + self.__lock.acquire() + outdata = "".join(self.__collected_outdata) + del self.__collected_outdata[:] + self.__lock.release() + return outdata + + # non-blocking read of data from subprocess stderr + def readerr(self): + self.__lock.acquire() + errdata = "".join(self.__collected_errdata) + del self.__collected_errdata[:] + self.__lock.release() + return errdata + + # non-blocking write to stdin of subprocess + def write(self, data): + if self.__process.stdin is None: + raise ValueError("Writing to process with stdin not a pipe") + self.__lock.acquire() + self.__pending_input.append(data) + self.__inputsem.release() + self.__lock.release() + + # close stdinput of subprocess + def closeinput(self): + self.__lock.acquire() + self.__quit = True + self.__inputsem.release() + self.__lock.release() + + +# interface to needed routines in openssl's libcrypto +def _load_crypto_libcrypto(): + from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \ + Structure, c_ulong, create_string_buffer, addressof, string_at, cast + from ctypes.util import find_library + + libcrypto = find_library('crypto') + if libcrypto is None: + raise K4MDrmException('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_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,c_int]) + + AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p]) + + PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', + [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p]) + + class LibCrypto(object): + def __init__(self): + self._blocksize = 0 + self._keyctx = None + self.iv = 0 + + def set_decrypt_key(self, userkey, iv): + self._blocksize = len(userkey) + if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : + raise K4MDrmException('AES improper key used') + return + keyctx = self._keyctx = AES_KEY() + self.iv = iv + rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) + if rv < 0: + raise K4MDrmException('Failed to initialize AES key') + + def decrypt(self, data): + out = create_string_buffer(len(data)) + rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0) + if rv == 0: + raise K4MDrmException('AES decryption failed') + return out.raw + + def keyivgen(self, passwd): + salt = '16743' + saltlen = 5 + passlen = len(passwd) + iter = 0x3e8 + keylen = 80 + out = create_string_buffer(keylen) + rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out) + return out.raw + return LibCrypto + +def _load_crypto(): + LibCrypto = None + try: + LibCrypto = _load_crypto_libcrypto() + except (ImportError, K4MDrmException): + pass + return LibCrypto + +LibCrypto = _load_crypto() + +# +# Utility Routines +# + +# uses a sub process to get the Hard Drive Serial Number using ioreg +# returns with the serial number of drive whose BSD Name is "disk0" +def GetVolumeSerialNumber(): + sernum = os.getenv('MYSERIALNUMBER') + if sernum != None: + return sernum + cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver' + cmdline = cmdline.encode(sys.getfilesystemencoding()) + p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) + poll = p.wait('wait') + results = p.read() + reslst = results.split('\n') + cnt = len(reslst) + bsdname = None + sernum = None + foundIt = False + for j in xrange(cnt): + resline = reslst[j] + pp = resline.find('"Serial Number" = "') + if pp >= 0: + sernum = resline[pp+19:-1] + sernum = sernum.strip() + bb = resline.find('"BSD Name" = "') + if bb >= 0: + bsdname = resline[bb+14:-1] + bsdname = bsdname.strip() + if (bsdname == 'disk0') and (sernum != None): + foundIt = True + break + if not foundIt: + sernum = '9999999999' + return sernum + +# uses unix env to get username instead of using sysctlbyname +def GetUserName(): + username = os.getenv('USER') + return username + +# Various character maps used to decrypt books. Probably supposed to act as obfuscation +charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" +charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" +charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" +charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" + +def encode(data, map): + result = "" + for char in data: + value = ord(char) + Q = (value ^ 0x80) // len(map) + R = value % len(map) + result += map[Q] + result += map[R] + return result + +import hashlib + +def SHA256(message): + ctx = hashlib.sha256() + ctx.update(message) + return ctx.digest() + +# implements an Pseudo Mac Version of Windows built-in Crypto routine +def CryptUnprotectData(encryptedData): + sp = GetVolumeSerialNumber() + '!@#' + GetUserName() + passwdData = encode(SHA256(sp),charMap1) + crp = LibCrypto() + key_iv = crp.keyivgen(passwdData) + key = key_iv[0:32] + iv = key_iv[32:48] + crp.set_decrypt_key(key,iv) + cleartext = crp.decrypt(encryptedData) + return cleartext + +# Locate and open the .kindle-info file +def openKindleInfo(kInfoFile=None): + if kInfoFile == None: + home = os.getenv('HOME') + cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"' + cmdline = cmdline.encode(sys.getfilesystemencoding()) + p1 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) + poll = p1.wait('wait') + results = p1.read() + reslst = results.split('\n') + kinfopath = 'NONE' + cnt = len(reslst) + for j in xrange(cnt): + resline = reslst[j] + pp = resline.find('.kindle-info') + if pp >= 0: + kinfopath = resline + break + if not os.path.exists(kinfopath): + raise K4MDrmException('Error: .kindle-info file can not be found') + return open(kinfopath,'r') + else: + return open(kInfoFile, 'r') diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4pcutils.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4pcutils.py new file mode 100644 index 0000000..337b992 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4pcutils.py @@ -0,0 +1,110 @@ +# K4PC Windows specific routines + +from __future__ import with_statement + +import sys, os + +from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \ + create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \ + string_at, Structure, c_void_p, cast + +import _winreg as winreg + +import traceback + +MAX_PATH = 255 + +kernel32 = windll.kernel32 +advapi32 = windll.advapi32 +crypt32 = windll.crypt32 + + +# +# Various character maps used to decrypt books. Probably supposed to act as obfuscation +# +charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" +charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_" +charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" +charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" + +# +# Exceptions for all the problems that might happen during the script +# +class DrmException(Exception): + pass + + +class DataBlob(Structure): + _fields_ = [('cbData', c_uint), + ('pbData', c_void_p)] +DataBlob_p = POINTER(DataBlob) + + +def GetSystemDirectory(): + GetSystemDirectoryW = kernel32.GetSystemDirectoryW + GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint] + GetSystemDirectoryW.restype = c_uint + def GetSystemDirectory(): + buffer = create_unicode_buffer(MAX_PATH + 1) + GetSystemDirectoryW(buffer, len(buffer)) + return buffer.value + return GetSystemDirectory +GetSystemDirectory = GetSystemDirectory() + +def GetVolumeSerialNumber(): + GetVolumeInformationW = kernel32.GetVolumeInformationW + GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint, + POINTER(c_uint), POINTER(c_uint), + POINTER(c_uint), c_wchar_p, c_uint] + GetVolumeInformationW.restype = c_uint + def GetVolumeSerialNumber(path = GetSystemDirectory().split('\\')[0] + '\\'): + vsn = c_uint(0) + GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0) + return str(vsn.value) + return GetVolumeSerialNumber +GetVolumeSerialNumber = GetVolumeSerialNumber() + + +def GetUserName(): + GetUserNameW = advapi32.GetUserNameW + GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)] + GetUserNameW.restype = c_uint + def GetUserName(): + buffer = create_unicode_buffer(32) + size = c_uint(len(buffer)) + while not GetUserNameW(buffer, byref(size)): + buffer = create_unicode_buffer(len(buffer) * 2) + size.value = len(buffer) + return buffer.value.encode('utf-16-le')[::2] + return GetUserName +GetUserName = GetUserName() + + +def CryptUnprotectData(): + _CryptUnprotectData = crypt32.CryptUnprotectData + _CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p, + c_void_p, c_void_p, c_uint, DataBlob_p] + _CryptUnprotectData.restype = c_uint + def CryptUnprotectData(indata, entropy): + indatab = create_string_buffer(indata) + indata = DataBlob(len(indata), cast(indatab, c_void_p)) + entropyb = create_string_buffer(entropy) + entropy = DataBlob(len(entropy), cast(entropyb, c_void_p)) + outdata = DataBlob() + if not _CryptUnprotectData(byref(indata), None, byref(entropy), + None, None, 0, byref(outdata)): + raise DrmException("Failed to Unprotect Data") + return string_at(outdata.pbData, outdata.cbData) + return CryptUnprotectData +CryptUnprotectData = CryptUnprotectData() + +# +# Locate and open the Kindle.info file. +# +def openKindleInfo(kInfoFile=None): + if kInfoFile == None: + regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") + path = winreg.QueryValueEx(regkey, 'Local AppData')[0] + return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r') + else: + return open(kInfoFile, 'r') diff --git a/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/MobiDeDRM.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py similarity index 77% rename from Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/MobiDeDRM.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py index 07d5f6f..9f17a3b 100644 --- a/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/MobiDeDRM.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py @@ -3,14 +3,6 @@ # This is a python script. You need a Python interpreter to run it. # For example, ActiveState Python, which exists for windows. # -# It can run standalone to convert files, or it can be installed as a -# plugin for Calibre (http://calibre-ebook.com/about) so that -# importing files with DRM 'Just Works'. -# -# To create a Calibre plugin, rename this file so that the filename -# ends in '_plugin.py', put it into a ZIP file and import that Calibre -# using its plugin configuration GUI. -# # Changelog # 0.01 - Initial version # 0.02 - Huffdic compressed books were not properly decrypted @@ -37,10 +29,18 @@ # in utf8 file are encrypted. (Although neither kind gets compressed.) # This knowledge leads to a simplification of the test for the # trailing data byte flags - version 5 and higher AND header size >= 0xE4. -# 0.15 - Now outputs 'hearbeat', and is also quicker for long files. +# 0.15 - Now outputs 'heartbeat', and is also quicker for long files. # 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility. +# 0.17 - added modifications to support its use as an imported python module +# both inside calibre and also in other places (ie K4DeDRM tools) +# 0.17a- disabled the standalone plugin feature since a plugin can not import +# a plugin +# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file... +# Removed the disabled Calibre plug-in code +# Permit use of 8-digit PIDs +# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either. -__version__ = '0.16' +__version__ = '0.19' import sys import struct @@ -123,10 +123,11 @@ def getSizeOfTrailingDataEntries(ptr, size, flags): if testflags & 1: num += getSizeOfTrailingDataEntry(ptr, size - num) testflags >>= 1 - # Multibyte data, if present, is included in the encryption, so - # we do not need to check the low bit. - # if flags & 1: - # num += (ord(ptr[size - num - 1]) & 0x3) + 1 + # Check the low bit to see if there's multibyte data present. + # if multibyte data is included in the encryped data, we'll + # have already cleared this flag. + if flags & 1: + num += (ord(ptr[size - num - 1]) & 0x3) + 1 return num class DrmStripper: @@ -177,9 +178,14 @@ class DrmStripper: return found_key def __init__(self, data_file, pid): - if checksumPid(pid[0:-2]) != pid: - raise DrmException("invalid PID checksum") - pid = pid[0:-2] + if len(pid)==10: + if checksumPid(pid[0:-2]) != pid: + raise DrmException("invalid PID checksum") + pid = pid[0:-2] + elif len(pid)==8: + print "PID without checksum given. With checksum PID is "+checksumPid(pid) + else: + raise DrmException("Invalid PID length") self.data_file = data_file header = data_file[0:72] @@ -202,6 +208,10 @@ class DrmStripper: if (mobi_length >= 0xE4) and (mobi_version >= 5): extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4]) print "Extra Data Flags = %d" %extra_data_flags + if mobi_version <= 5: + # multibyte utf8 data is included in the encryption for mobi_version 5 and below + # so clear that byte so that we leave it to be decrypted. + extra_data_flags &= 0xFFFE crypto_type, = struct.unpack('>H', sect[0xC:0xC+2]) if crypto_type == 0: @@ -248,63 +258,33 @@ class DrmStripper: def getResult(self): return self.data_file -if not __name__ == "__main__": - from calibre.customize import FileTypePlugin +def getUnencryptedBook(infile,pid): + sys.stdout=Unbuffered(sys.stdout) + data_file = file(infile, 'rb').read() + strippedFile = DrmStripper(data_file, pid) + return strippedFile.getResult() - class MobiDeDRM(FileTypePlugin): - name = 'MobiDeDRM' # Name of the plugin - description = 'Removes DRM from secure Mobi files' - supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on - author = 'The Dark Reverser' # The author of this plugin - version = (0, 1, 6) # The version number of this plugin - file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to - on_import = True # Run this plugin during the import - - def run(self, path_to_ebook): - from calibre.gui2 import is_ok_to_use_qt - from PyQt4.Qt import QMessageBox - PID = self.site_customization - data_file = file(path_to_ebook, 'rb').read() - ar = PID.split(',') - for i in ar: - try: - unlocked_file = DrmStripper(data_file, i).getResult() - except DrmException: - # ignore the error - pass - else: - of = self.temporary_file('.mobi') - of.write(unlocked_file) - of.close() - return of.name - if is_ok_to_use_qt(): - d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Couldn't decode: %s\n\nImporting encrypted version." % path_to_ebook) - d.show() - d.raise_() - d.exec_() - return path_to_ebook - - def customization_help(self, gui=False): - return 'Enter PID (separate multiple PIDs with comma)' - -if __name__ == "__main__": +def main(argv=sys.argv): sys.stdout=Unbuffered(sys.stdout) print ('MobiDeDrm v%(__version__)s. ' 'Copyright 2008-2010 The Dark Reverser.' % globals()) - if len(sys.argv)<4: + if len(argv)<4: print "Removes protection from Mobipocket books" print "Usage:" print " %s " % sys.argv[0] - sys.exit(1) + return 1 else: - infile = sys.argv[1] - outfile = sys.argv[2] - pid = sys.argv[3] - data_file = file(infile, 'rb').read() + infile = argv[1] + outfile = argv[2] + pid = argv[3] try: - strippedFile = DrmStripper(data_file, pid) - file(outfile, 'wb').write(strippedFile.getResult()) + stripped_file = getUnencryptedBook(infile, pid) + file(outfile, 'wb').write(stripped_file) except DrmException, e: print "Error: %s" % e - sys.exit(1) - sys.exit(0) \ No newline at end of file + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/openssl_des.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/openssl_des.py new file mode 100644 index 0000000..8a044fa --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/openssl_des.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python +# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab + +# implement just enough of des from openssl to make erdr2pml.py happy + +def load_libcrypto(): + from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_char, c_int, c_long, \ + Structure, c_ulong, create_string_buffer, cast + from ctypes.util import find_library + import sys + + if sys.platform.startswith('win'): + libcrypto = find_library('libeay32') + else: + libcrypto = find_library('crypto') + + if libcrypto is None: + return None + + libcrypto = CDLL(libcrypto) + + # typedef struct DES_ks + # { + # union + # { + # DES_cblock cblock; + # /* make sure things are correct size on machines with + # * 8 byte longs */ + # DES_LONG deslong[2]; + # } ks[16]; + # } DES_key_schedule; + + # just create a big enough place to hold everything + # it will have alignment of structure so we should be okay (16 byte aligned?) + class DES_KEY_SCHEDULE(Structure): + _fields_ = [('DES_cblock1', c_char * 16), + ('DES_cblock2', c_char * 16), + ('DES_cblock3', c_char * 16), + ('DES_cblock4', c_char * 16), + ('DES_cblock5', c_char * 16), + ('DES_cblock6', c_char * 16), + ('DES_cblock7', c_char * 16), + ('DES_cblock8', c_char * 16), + ('DES_cblock9', c_char * 16), + ('DES_cblock10', c_char * 16), + ('DES_cblock11', c_char * 16), + ('DES_cblock12', c_char * 16), + ('DES_cblock13', c_char * 16), + ('DES_cblock14', c_char * 16), + ('DES_cblock15', c_char * 16), + ('DES_cblock16', c_char * 16)] + + DES_KEY_SCHEDULE_p = POINTER(DES_KEY_SCHEDULE) + + def F(restype, name, argtypes): + func = getattr(libcrypto, name) + func.restype = restype + func.argtypes = argtypes + return func + + DES_set_key = F(None, 'DES_set_key',[c_char_p, DES_KEY_SCHEDULE_p]) + DES_ecb_encrypt = F(None, 'DES_ecb_encrypt',[c_char_p, c_char_p, DES_KEY_SCHEDULE_p, c_int]) + + + class DES(object): + def __init__(self, key): + if len(key) != 8 : + raise Error('DES improper key used') + return + self.key = key + self.keyschedule = DES_KEY_SCHEDULE() + DES_set_key(self.key, self.keyschedule) + def desdecrypt(self, data): + ob = create_string_buffer(len(data)) + DES_ecb_encrypt(data, ob, self.keyschedule, 0) + return ob.raw + def decrypt(self, data): + if not data: + return '' + i = 0 + result = [] + while i < len(data): + block = data[i:i+8] + processed_block = self.desdecrypt(block) + result.append(processed_block) + i += 8 + return ''.join(result) + + return DES + diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/subasyncio.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/subasyncio.py new file mode 100644 index 0000000..ed13aa1 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/subasyncio.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python +# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab + +import os, sys +import signal +import threading +import subprocess +from subprocess import Popen, PIPE, STDOUT + +# **heavily** chopped up and modfied version of asyncproc.py +# to make it actually work on Windows as well as Mac/Linux +# For the original see: +# "http://www.lysator.liu.se/~bellman/download/" +# author is "Thomas Bellman " +# available under GPL version 3 or Later + +# create an asynchronous subprocess whose output can be collected in +# a non-blocking manner + +# What a mess! Have to use threads just to get non-blocking io +# in a cross-platform manner + +# luckily all thread use is hidden within this class + +class Process(object): + def __init__(self, *params, **kwparams): + if len(params) <= 3: + kwparams.setdefault('stdin', subprocess.PIPE) + if len(params) <= 4: + kwparams.setdefault('stdout', subprocess.PIPE) + if len(params) <= 5: + kwparams.setdefault('stderr', subprocess.PIPE) + self.__pending_input = [] + self.__collected_outdata = [] + self.__collected_errdata = [] + self.__exitstatus = None + self.__lock = threading.Lock() + self.__inputsem = threading.Semaphore(0) + self.__quit = False + + self.__process = subprocess.Popen(*params, **kwparams) + + if self.__process.stdin: + self.__stdin_thread = threading.Thread( + name="stdin-thread", + target=self.__feeder, args=(self.__pending_input, + self.__process.stdin)) + self.__stdin_thread.setDaemon(True) + self.__stdin_thread.start() + + if self.__process.stdout: + self.__stdout_thread = threading.Thread( + name="stdout-thread", + target=self.__reader, args=(self.__collected_outdata, + self.__process.stdout)) + self.__stdout_thread.setDaemon(True) + self.__stdout_thread.start() + + if self.__process.stderr: + self.__stderr_thread = threading.Thread( + name="stderr-thread", + target=self.__reader, args=(self.__collected_errdata, + self.__process.stderr)) + self.__stderr_thread.setDaemon(True) + self.__stderr_thread.start() + + def pid(self): + return self.__process.pid + + def kill(self, signal): + self.__process.send_signal(signal) + + # check on subprocess (pass in 'nowait') to act like poll + def wait(self, flag): + if flag.lower() == 'nowait': + rc = self.__process.poll() + else: + rc = self.__process.wait() + if rc != None: + if self.__process.stdin: + self.closeinput() + if self.__process.stdout: + self.__stdout_thread.join() + if self.__process.stderr: + self.__stderr_thread.join() + return self.__process.returncode + + def terminate(self): + if self.__process.stdin: + self.closeinput() + self.__process.terminate() + + # thread gets data from subprocess stdout + def __reader(self, collector, source): + while True: + data = os.read(source.fileno(), 65536) + self.__lock.acquire() + collector.append(data) + self.__lock.release() + if data == "": + source.close() + break + return + + # thread feeds data to subprocess stdin + def __feeder(self, pending, drain): + while True: + self.__inputsem.acquire() + self.__lock.acquire() + if not pending and self.__quit: + drain.close() + self.__lock.release() + break + data = pending.pop(0) + self.__lock.release() + drain.write(data) + + # non-blocking read of data from subprocess stdout + def read(self): + self.__lock.acquire() + outdata = "".join(self.__collected_outdata) + del self.__collected_outdata[:] + self.__lock.release() + return outdata + + # non-blocking read of data from subprocess stderr + def readerr(self): + self.__lock.acquire() + errdata = "".join(self.__collected_errdata) + del self.__collected_errdata[:] + self.__lock.release() + return errdata + + # non-blocking write to stdin of subprocess + def write(self, data): + if self.__process.stdin is None: + raise ValueError("Writing to process with stdin not a pipe") + self.__lock.acquire() + self.__pending_input.append(data) + self.__inputsem.release() + self.__lock.release() + + # close stdinput of subprocess + def closeinput(self): + self.__lock.acquire() + self.__quit = True + self.__inputsem.release() + self.__lock.release() + diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/zipfix.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/zipfix.py new file mode 100644 index 0000000..536a21d --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/zipfix.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python + +import sys +import zlib +import zipfile +import os +import os.path +import getopt +from struct import unpack + + +_FILENAME_LEN_OFFSET = 26 +_EXTRA_LEN_OFFSET = 28 +_FILENAME_OFFSET = 30 +_MAX_SIZE = 64 * 1024 + +class fixZip: + def __init__(self, zinput, zoutput): + self.inzip = zipfile.ZipFile(zinput,'r') + self.outzip = zipfile.ZipFile(zoutput,'w') + # open the input zip for reading only as a raw file + self.bzf = file(zinput,'rb') + + def getlocalname(self, zi): + local_header_offset = zi.header_offset + self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET) + leninfo = self.bzf.read(2) + local_name_length, = unpack(' 0: + if len(cmpdata) > _MAX_SIZE : + newdata = cmpdata[0:_MAX_SIZE] + cmpdata = cmpdata[_MAX_SIZE:] + else: + newdata = cmpdata + cmpdata = '' + newdata = dc.decompress(newdata) + unprocessed = dc.unconsumed_tail + if len(unprocessed) == 0: + newdata += dc.flush() + data += newdata + cmpdata += unprocessed + unprocessed = '' + return data + + def getfiledata(self, zi): + # get file name length and exta data length to find start of file data + local_header_offset = zi.header_offset + + self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET) + leninfo = self.bzf.read(2) + local_name_length, = unpack('> bitPosition & 3 + +# Returns the six bits at offset from a bit field +def getSixBitsFromBitField(bitField,offset): + offset *= 3 + value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2) + return value + +# 8 bits to six bits encoding from hash to generate PID string +def encodePID(hash): + global charMap3 + PID = "" + for position in range (0,8): + PID += charMap3[getSixBitsFromBitField(hash,position)] + return PID + + +# +# Main +# + +def main(argv=sys.argv): + global kindleDatabase + + kindleDatabase = None + + # + # Read the encrypted database + # + + try: + kindleDatabase = parseKindleInfo() + except Exception, message: + print(message) + + if kindleDatabase != None : + printKindleInfo() + + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/k4mobidedrm.py b/Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/k4mobidedrm.py index 4fc0337..df05f89 100644 --- a/Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/k4mobidedrm.py +++ b/Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/k4mobidedrm.py @@ -28,7 +28,7 @@ from __future__ import with_statement -__version__ = '1.1' +__version__ = '1.2' class Unbuffered: def __init__(self, stream): @@ -284,7 +284,7 @@ def getK4Pids(exth, title, kInfoFile=None): global kindleDatabase try: kindleDatabase = parseKindleInfo(kInfoFile) - except Exception as message: + except Exception, message: print(message) if kindleDatabase != None : @@ -313,6 +313,8 @@ def getK4Pids(exth, title, kInfoFile=None): exth_records = {} nitems, = unpack('>I', exth[8:12]) pos = 12 + + exth_records[209] = None # Parse the exth records, storing data indexed by type for i in xrange(nitems): type, size = unpack('>II', exth[pos: pos + 8]) @@ -397,6 +399,7 @@ def main(argv=sys.argv): kindleDatabase = None infile = args[0] outfile = args[1] + DecodeErrorString = "" try: # first try with K4PC/K4M ex = MobiPeek(infile) @@ -405,11 +408,15 @@ def main(argv=sys.argv): return 2 title = ex.getBookTitle() exth = ex.getexthData() + if exth=='': + raise DrmException("Not a Kindle Mobipocket file") pid = getK4Pids(exth, title) unlocked_file = mobidedrm.getUnencryptedBook(infile, pid) - except DrmException: + except DrmException, e: + DecodeErrorString += "Error trying default K4 info: " + str(e) + "\n" pass - except mobidedrm.DrmException: + except mobidedrm.DrmException, e: + DecodeErrorString += "Error trying default K4 info: " + str(e) + "\n" pass else: file(outfile, 'wb').write(unlocked_file) @@ -422,11 +429,15 @@ def main(argv=sys.argv): try: title = ex.getBookTitle() exth = ex.getexthData() + if exth=='': + raise DrmException("Not a Kindle Mobipocket file") pid = getK4Pids(exth, title, infoFile) unlocked_file = mobidedrm.getUnencryptedBook(infile, pid) - except DrmException: + except DrmException, e: + DecodeErrorString += "Error trying " + infoFile + " K4 info: " + str(e) + "\n" pass - except mobidedrm.DrmException: + except mobidedrm.DrmException, e: + DecodeErrorString += "Error trying " + infoFile + " K4 info: " + str(e) + "\n" pass else: file(outfile, 'wb').write(unlocked_file) @@ -445,6 +456,7 @@ def main(argv=sys.argv): return 0 # we could not unencrypt book + print DecodeErrorString print "Error: Could Not Unencrypt Book" return 1 @@ -463,7 +475,7 @@ if not __name__ == "__main__" and inCalibre: Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.' supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on author = 'DiapDealer, SomeUpdates' # The author of this plugin - version = (0, 1, 1) # The version number of this plugin + version = (0, 1, 2) # The version number of this plugin file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to on_import = True # Run this plugin during the import priority = 200 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm @@ -509,6 +521,8 @@ if not __name__ == "__main__" and inCalibre: return path_to_ebook title = ex.getBookTitle() exth = ex.getexthData() + if exth=='': + raise DrmException("Not a Kindle Mobipocket file") pid = getK4Pids(exth, title) unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid) except DrmException: @@ -528,6 +542,8 @@ if not __name__ == "__main__" and inCalibre: try: title = ex.getBookTitle() exth = ex.getexthData() + if exth=='': + raise DrmException("Not a Kindle Mobipocket file") pid = getK4Pids(exth, title, infoFile) unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid) except DrmException: diff --git a/Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/k4mutils.py b/Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/k4mutils.py index 977d81c..33771eb 100644 --- a/Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/k4mutils.py +++ b/Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/k4mutils.py @@ -237,24 +237,36 @@ LibCrypto = _load_crypto() # # uses a sub process to get the Hard Drive Serial Number using ioreg -# returns with the first found serial number in that class +# returns with the serial number of drive whose BSD Name is "disk0" def GetVolumeSerialNumber(): - cmdline = '/usr/sbin/ioreg -r -c AppleAHCIDiskDriver' + sernum = os.getenv('MYSERIALNUMBER') + if sernum != None: + return sernum + cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver' cmdline = cmdline.encode(sys.getfilesystemencoding()) p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) poll = p.wait('wait') results = p.read() reslst = results.split('\n') - sernum = '9999999999' cnt = len(reslst) + bsdname = None + sernum = None + foundIt = False for j in xrange(cnt): resline = reslst[j] pp = resline.find('"Serial Number" = "') if pp >= 0: - sernum = resline[pp+19:] - sernum = sernum[:-1] - sernum = sernum.lstrip() - break + sernum = resline[pp+19:-1] + sernum = sernum.strip() + bb = resline.find('"BSD Name" = "') + if bb >= 0: + bsdname = resline[bb+14:-1] + bsdname = bsdname.strip() + if (bsdname == 'disk0') and (sernum != None): + foundIt = True + break + if not foundIt: + sernum = '9999999999' return sernum # uses unix env to get username instead of using sysctlbyname @@ -319,4 +331,4 @@ def openKindleInfo(kInfoFile=None): raise K4MDrmException('Error: .kindle-info file can not be found') return open(kinfopath,'r') else: - return open(kInfoFile, 'r') \ No newline at end of file + return open(kInfoFile, 'r') diff --git a/Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/mobidedrm.py b/Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/mobidedrm.py index eed1cce..9f17a3b 100644 --- a/Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/mobidedrm.py +++ b/Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/mobidedrm.py @@ -3,14 +3,6 @@ # This is a python script. You need a Python interpreter to run it. # For example, ActiveState Python, which exists for windows. # -# It can run standalone to convert files, or it can be installed as a -# plugin for Calibre (http://calibre-ebook.com/about) so that -# importing files with DRM 'Just Works'. -# -# To create a Calibre plugin, rename this file so that the filename -# ends in '_plugin.py', put it into a ZIP file and import that Calibre -# using its plugin configuration GUI. -# # Changelog # 0.01 - Initial version # 0.02 - Huffdic compressed books were not properly decrypted @@ -46,8 +38,9 @@ # 0.18 - It seems that multibyte entries aren't encrypted in a v7 file... # Removed the disabled Calibre plug-in code # Permit use of 8-digit PIDs +# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either. -__version__ = '0.18' +__version__ = '0.19' import sys import struct @@ -215,8 +208,8 @@ class DrmStripper: if (mobi_length >= 0xE4) and (mobi_version >= 5): extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4]) print "Extra Data Flags = %d" %extra_data_flags - if mobi_version < 7: - # multibyte utf8 data is included in the encryption for mobi_version 5 (& 6?) + if mobi_version <= 5: + # multibyte utf8 data is included in the encryption for mobi_version 5 and below # so clear that byte so that we leave it to be decrypted. extra_data_flags &= 0xFFFE diff --git a/Kindle_Mobi_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r3.txt b/Kindle_Mobi_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r3.txt new file mode 100644 index 0000000..fcae6d6 --- /dev/null +++ b/Kindle_Mobi_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r3.txt @@ -0,0 +1,18 @@ +set verbose 0 +break * 0x00d3dac4 +commands 1 +printf "PID is %s\n", $eax +continue +end +break * 0x0130a384 +commands 2 +printf "File is %s\n", $eax +continue +end +condition 2 $eax != 0 +break * 0x00f0f306 +commands 3 +printf "TOPAZ PID is %s\n", *(long*)($esp+12) +continue +end +run diff --git a/Kindle_Mobi_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r4.txt b/Kindle_Mobi_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r4.txt new file mode 100644 index 0000000..2626fc5 --- /dev/null +++ b/Kindle_Mobi_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r4.txt @@ -0,0 +1,18 @@ +set verbose 0 +break * 0x00d8f72a +commands 1 +printf "PID is %s\n", *(long*)($esp+4) +continue +end +break * 0x0127498c +commands 2 +printf "File is %s\n", $eax +continue +end +condition 2 $eax != 0 +break * 0x00e9aec0 +commands 3 +printf "TOPAZ PID is %s\n", **(long**)($esp+8) +continue +end +run diff --git a/Kindle_Mobi_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py b/Kindle_Mobi_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py index eed1cce..9f17a3b 100644 --- a/Kindle_Mobi_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py +++ b/Kindle_Mobi_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py @@ -3,14 +3,6 @@ # This is a python script. You need a Python interpreter to run it. # For example, ActiveState Python, which exists for windows. # -# It can run standalone to convert files, or it can be installed as a -# plugin for Calibre (http://calibre-ebook.com/about) so that -# importing files with DRM 'Just Works'. -# -# To create a Calibre plugin, rename this file so that the filename -# ends in '_plugin.py', put it into a ZIP file and import that Calibre -# using its plugin configuration GUI. -# # Changelog # 0.01 - Initial version # 0.02 - Huffdic compressed books were not properly decrypted @@ -46,8 +38,9 @@ # 0.18 - It seems that multibyte entries aren't encrypted in a v7 file... # Removed the disabled Calibre plug-in code # Permit use of 8-digit PIDs +# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either. -__version__ = '0.18' +__version__ = '0.19' import sys import struct @@ -215,8 +208,8 @@ class DrmStripper: if (mobi_length >= 0xE4) and (mobi_version >= 5): extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4]) print "Extra Data Flags = %d" %extra_data_flags - if mobi_version < 7: - # multibyte utf8 data is included in the encryption for mobi_version 5 (& 6?) + if mobi_version <= 5: + # multibyte utf8 data is included in the encryption for mobi_version 5 and below # so clear that byte so that we leave it to be decrypted. extra_data_flags &= 0xFFFE diff --git a/Kindle_Mobi_Tools/Kindle_4_PC_Unswindle/mobidedrm.py b/Kindle_Mobi_Tools/Kindle_4_PC_Unswindle/mobidedrm.py index eed1cce..9f17a3b 100644 --- a/Kindle_Mobi_Tools/Kindle_4_PC_Unswindle/mobidedrm.py +++ b/Kindle_Mobi_Tools/Kindle_4_PC_Unswindle/mobidedrm.py @@ -3,14 +3,6 @@ # This is a python script. You need a Python interpreter to run it. # For example, ActiveState Python, which exists for windows. # -# It can run standalone to convert files, or it can be installed as a -# plugin for Calibre (http://calibre-ebook.com/about) so that -# importing files with DRM 'Just Works'. -# -# To create a Calibre plugin, rename this file so that the filename -# ends in '_plugin.py', put it into a ZIP file and import that Calibre -# using its plugin configuration GUI. -# # Changelog # 0.01 - Initial version # 0.02 - Huffdic compressed books were not properly decrypted @@ -46,8 +38,9 @@ # 0.18 - It seems that multibyte entries aren't encrypted in a v7 file... # Removed the disabled Calibre plug-in code # Permit use of 8-digit PIDs +# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either. -__version__ = '0.18' +__version__ = '0.19' import sys import struct @@ -215,8 +208,8 @@ class DrmStripper: if (mobi_length >= 0xE4) and (mobi_version >= 5): extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4]) print "Extra Data Flags = %d" %extra_data_flags - if mobi_version < 7: - # multibyte utf8 data is included in the encryption for mobi_version 5 (& 6?) + if mobi_version <= 5: + # multibyte utf8 data is included in the encryption for mobi_version 5 and below # so clear that byte so that we leave it to be decrypted. extra_data_flags &= 0xFFFE diff --git a/Kindle_Mobi_Tools/MobiDeDRM.py b/Kindle_Mobi_Tools/MobiDeDRM.py index eed1cce..9f17a3b 100644 --- a/Kindle_Mobi_Tools/MobiDeDRM.py +++ b/Kindle_Mobi_Tools/MobiDeDRM.py @@ -3,14 +3,6 @@ # This is a python script. You need a Python interpreter to run it. # For example, ActiveState Python, which exists for windows. # -# It can run standalone to convert files, or it can be installed as a -# plugin for Calibre (http://calibre-ebook.com/about) so that -# importing files with DRM 'Just Works'. -# -# To create a Calibre plugin, rename this file so that the filename -# ends in '_plugin.py', put it into a ZIP file and import that Calibre -# using its plugin configuration GUI. -# # Changelog # 0.01 - Initial version # 0.02 - Huffdic compressed books were not properly decrypted @@ -46,8 +38,9 @@ # 0.18 - It seems that multibyte entries aren't encrypted in a v7 file... # Removed the disabled Calibre plug-in code # Permit use of 8-digit PIDs +# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either. -__version__ = '0.18' +__version__ = '0.19' import sys import struct @@ -215,8 +208,8 @@ class DrmStripper: if (mobi_length >= 0xE4) and (mobi_version >= 5): extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4]) print "Extra Data Flags = %d" %extra_data_flags - if mobi_version < 7: - # multibyte utf8 data is included in the encryption for mobi_version 5 (& 6?) + if mobi_version <= 5: + # multibyte utf8 data is included in the encryption for mobi_version 5 and below # so clear that byte so that we leave it to be decrypted. extra_data_flags &= 0xFFFE diff --git a/Macintosh_Applications/Mobipocket Unlocker ReadMe.txt b/Macintosh_Applications/Mobipocket Unlocker ReadMe.txt deleted file mode 100644 index 832d986..0000000 --- a/Macintosh_Applications/Mobipocket Unlocker ReadMe.txt +++ /dev/null @@ -1,12 +0,0 @@ -Mobipocket Unlocker - -How to get Drag&Drop decryption of DRM-encumbered Mobipocket eBook files. - -You'll need the MobiDeDRM.py python script, as well as an installed version 2.4 or later of python. If you have Mac OS X Leopard (10.5) you already have a suitable version of python installed as part of Leopard. - -Control-click the script and select "Show Package Contents" from the contextual menu. Copy the python script, which must be called "MobiDeDRM.py" into the Resources folder inside the Contents folder. (NB not into the Scripts folder - that's where the Applescript part is stored.) - -Close the package, and you now have a drag&drop Mobipocket unlocker. - -You can use the AppleScript ScriptEditor application to put your Mobipocket code into the script to save you having to enter it in the dialog all the time. - diff --git a/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Info.plist b/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Info.plist deleted file mode 100644 index bea9496..0000000 --- a/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Info.plist +++ /dev/null @@ -1,50 +0,0 @@ - - - - - CFBundleAllowMixedLocalizations - - CFBundleDevelopmentRegion - English - CFBundleDocumentTypes - - - CFBundleTypeExtensions - - * - - CFBundleTypeOSTypes - - **** - - CFBundleTypeRole - Viewer - - - CFBundleExecutable - droplet - CFBundleIconFile - droplet - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - Mobipocket Unlocker 9 - CFBundlePackageType - APPL - CFBundleSignature - dplt - LSRequiresCarbon - - WindowState - - name - ScriptWindowState - positionOfDivider - 422 - savedFrame - 91 171 1059 678 0 0 1440 878 - selectedTabView - result - - - diff --git a/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/Scripts/main.scpt b/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/Scripts/main.scpt deleted file mode 100644 index f781afc..0000000 Binary files a/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/Scripts/main.scpt and /dev/null differ diff --git a/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/Scripts/main.scpt.txt b/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/Scripts/main.scpt.txt deleted file mode 100644 index 4b3883b..0000000 --- a/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/Scripts/main.scpt.txt +++ /dev/null @@ -1,61 +0,0 @@ -on unlockfile(encryptedFile, MobiDeDRMPath, encryptionKey) - set encryptedFilePath to POSIX path of file encryptedFile - -- display dialog "filepath " & encryptedFilePath buttons {"OK"} default button 1 giving up after 10 - tell application "Finder" to  - set parent_folder to (container of file encryptedFile) as text - tell application "Finder" to set fileName to (name of file encryptedFile) as text - set unlockedFilePath to POSIX path of file (parent_folder & "Unlocked_" & fileName) - set shellcommand to "python '" & MobiDeDRMPath & "' '" & encryptedFilePath & "' '" & unlockedFilePath & "' '" & encryptionKey & "'" - -- display dialog "shellcommand: " & shellcommand buttons {"OK"} default button 1 giving up after 10 - try - --with timeout of 5 seconds - -- display dialog "About to Unlock " & fileName buttons {"Unlock"} default button 1 giving up after 1 - --end timeout - end try - set result to do shell script shellcommand - try - if (offset of "Error" in result) > 0 then - with timeout of 5 seconds - display dialog "Can't unlock file " & fileName & ". - -" & result buttons ("OK") default button 1 giving up after 5 - end timeout - end if - end try -end unlockfile - -on unlockfolder(encryptedFolder, MobiDeDRMPath, encryptionKey) - tell application "Finder" to set encryptedFileList to (every file in folder encryptedFolder) whose (name extension is "prc") or (name extension is "mobi") or (name extension is "azw") - tell application "Finder" to set encryptedFolderList to (every folder in folder encryptedFolder) - repeat with this_item in encryptedFileList - unlockfile(this_item as text, MobiDeDRMPath, encryptionKey) - end repeat - repeat with this_item in encryptedFolderList - unlockfolder(this_item as text, MobiDeDRMPath, encryptionKey) - end repeat -end unlockfolder - -on run - set MobiDeDRMPath to POSIX path of file ((path to me as text) & "Contents:Resources:MobiDeDRM.py") - set encryptedFolder to choose folder with prompt "Please choose the folder of encrypted Mobipocket files." - set encryptionKey to (display dialog "Enter Mobipocket key for encrypted Mobipocket files." default answer "X12QIL1M3D" buttons {"Cancel", "OK"} default button 2) - set encryptionKey to text returned of encryptionKey - unlockfolder(encryptedFolder, MobiDeDRMPath, encryptionKey) -end run - -on open some_items - set MobiDeDRMPath to POSIX path of file ((path to me as text) & "Contents:Resources:MobiDeDRM.py") - set encryptionKey to (display dialog "Enter Mobipocket key for encrypted Mobipocket files." default answer "X12QIL1M3D" buttons {"Cancel", "OK"} default button 2) - set encryptionKey to text returned of encryptionKey - repeat with this_item in some_items - if (folder of (info for this_item) is true) then - unlockfolder(this_item as text, MobiDeDRMPath, encryptionKey) - else - tell application "Finder" to set item_extension to name extension of file this_item - if item_extension is "prc" or item_extension is "mobi" or item_extension is "azw" then - unlockfile(this_item as text, MobiDeDRMPath, encryptionKey) - end if - end if - end repeat -end open - diff --git a/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/droplet.rsrc b/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/droplet.rsrc deleted file mode 100644 index 7de74fa..0000000 Binary files a/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/droplet.rsrc and /dev/null differ diff --git a/Macintosh_Applications/eReader Unlocker.app/Contents/PkgInfo b/Macintosh_Applications/eReader Unlocker.app/Contents/PkgInfo deleted file mode 100644 index b999e99..0000000 --- a/Macintosh_Applications/eReader Unlocker.app/Contents/PkgInfo +++ /dev/null @@ -1 +0,0 @@ -APPLdplt \ No newline at end of file diff --git a/Macintosh_Applications/eReader Unlocker.app/Contents/Resources/Scripts/main.scpt b/Macintosh_Applications/eReader Unlocker.app/Contents/Resources/Scripts/main.scpt deleted file mode 100644 index 4e387a7..0000000 Binary files a/Macintosh_Applications/eReader Unlocker.app/Contents/Resources/Scripts/main.scpt and /dev/null differ diff --git a/Macintosh_Applications/eReader Unlocker.app/Contents/Resources/Scripts/main.scpt.txt b/Macintosh_Applications/eReader Unlocker.app/Contents/Resources/Scripts/main.scpt.txt deleted file mode 100644 index 2161d02..0000000 --- a/Macintosh_Applications/eReader Unlocker.app/Contents/Resources/Scripts/main.scpt.txt +++ /dev/null @@ -1,59 +0,0 @@ -on unlockfile(encryptedFile, eReaderDeDRMPath, encryptionNameKey, encryptionKey) - set encryptedFilePath to POSIX path of file encryptedFile - tell application "Finder" to  - set parent_folder to (container of file encryptedFile) as text - tell application "Finder" to set fileName to (name of file encryptedFile) as text - set unlockedFilePath to POSIX path of file (parent_folder & "Unlocked_" & fileName) - set shellcommand to "python \"" & eReaderDeDRMPath & "\" \"" & encryptedFilePath & "\" \"" & unlockedFilePath & "\" \"" & encryptionNameKey & "\" " & encryptionKey - try - --with timeout of 5 seconds - --display dialog "About to Unlock " & fileName buttons {"Unlock"} default button 1 giving up after 1 - --end timeout - end try - set result to do shell script shellcommand - try - --with timeout of 5 seconds - --display dialog "Result" default answer result buttons ("OK") default button 1 --giving up after 2 - --end timeout - end try -end unlockfile - -on unlockfolder(encryptedFolder, eReaderDeDRMPath, encryptionNameKey, encryptionKey) - tell application "Finder" to set encryptedFileList to (every file in folder encryptedFolder) whose (name extension is "pdb") - tell application "Finder" to set encryptedFolderList to (every folder in folder encryptedFolder) - repeat with this_item in encryptedFileList - unlockfile(this_item as text, eReaderDeDRMPath, encryptionNameKey, encryptionKey) - end repeat - repeat with this_item in encryptedFolderList - unlockfolder(this_item as text, eReaderDeDRMPath, encryptionNameKey, encryptionKey) - end repeat -end unlockfolder - -on run - set eReaderDeDRMPath to POSIX path of file ((path to me as text) & "Contents:Resources:eReaderDeDRM.py") - set encryptedFolder to choose folder with prompt "Please choose the folder of encrypted eReader files." - set encryptionNameKey to (display dialog "Enter Name key for encrypted eReader files." default answer "MR PAUL M P DURRANT" buttons {"Cancel", "OK"} default button 2) - set encryptionKey to (display dialog "Enter Number key for encrypted eReader files." default answer "89940827" buttons {"Cancel", "OK"} default button 2) - set encryptionNameKey to text returned of encryptionNameKey - set encryptionKey to text returned of encryptionKey - unlockfolder(encryptedFolder, eReaderDeDRMPath, encryptionNameKey, encryptionKey) -end run - -on open some_items - set eReaderDeDRMPath to POSIX path of file ((path to me as text) & "Contents:Resources:eReaderDeDRM.py") - set encryptionNameKey to (display dialog "Enter Name key for encrypted eReader files." default answer "MR PAUL M P DURRANT" buttons {"Cancel", "OK"} default button 2) - set encryptionKey to (display dialog "Enter Number key for encrypted eReader files." default answer "89940827" buttons {"Cancel", "OK"} default button 2) - set encryptionNameKey to text returned of encryptionNameKey - set encryptionKey to text returned of encryptionKey - repeat with this_item in some_items - if (folder of (info for this_item) is true) then - unlockfolder(this_item as text, eReaderDeDRMPath, encryptionNameKey, encryptionKey) - else - tell application "Finder" to set item_extension to name extension of file this_item - if item_extension is "pdb" then - unlockfile(this_item as text, eReaderDeDRMPath, encryptionNameKey, encryptionKey) - end if - end if - end repeat -end open - diff --git a/Macintosh_Applications/eReader Unlocker.app/Contents/Resources/description.rtfd/TXT.rtf b/Macintosh_Applications/eReader Unlocker.app/Contents/Resources/description.rtfd/TXT.rtf deleted file mode 100644 index 19b3535..0000000 --- a/Macintosh_Applications/eReader Unlocker.app/Contents/Resources/description.rtfd/TXT.rtf +++ /dev/null @@ -1,4 +0,0 @@ -{\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf330 -{\fonttbl} -{\colortbl;\red255\green255\blue255;} -} \ No newline at end of file diff --git a/Macintosh_Applications/eReader Unlocker.app/Contents/Resources/droplet.icns b/Macintosh_Applications/eReader Unlocker.app/Contents/Resources/droplet.icns deleted file mode 100644 index c2706de..0000000 Binary files a/Macintosh_Applications/eReader Unlocker.app/Contents/Resources/droplet.icns and /dev/null differ diff --git a/Macintosh_Applications/eReader Unlocker.app/Contents/Resources/eReaderDeDRM.py b/Macintosh_Applications/eReader Unlocker.app/Contents/Resources/eReaderDeDRM.py deleted file mode 100644 index 79f5215..0000000 --- a/Macintosh_Applications/eReader Unlocker.app/Contents/Resources/eReaderDeDRM.py +++ /dev/null @@ -1,493 +0,0 @@ -# This is a python script. You need a Python interpreter to run it. -# For example, ActiveState Python, which exists for windows. -# Changelog -# 0.01 - Initial version -# 0.02 - Support more eReader files. Support bold text and links. Fix PML decoder parsing bug. -# 0.03 - Fix incorrect variable usage at one place. - -import struct, binascii, zlib, os, sha, sys, os.path - -ECB = 0 -CBC = 1 -class Des: - __pc1 = [56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, - 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, - 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, - 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3] - __left_rotations = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1] - __pc2 = [13, 16, 10, 23, 0, 4,2, 27, 14, 5, 20, 9, - 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, - 40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, - 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31] - __ip = [57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, - 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7, - 56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2, - 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6] - __expansion_table = [31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8, - 7, 8, 9, 10, 11, 12,11, 12, 13, 14, 15, 16, - 15, 16, 17, 18, 19, 20,19, 20, 21, 22, 23, 24, - 23, 24, 25, 26, 27, 28,27, 28, 29, 30, 31, 0] - __sbox = [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, - 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, - 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, - 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13], - [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, - 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, - 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, - 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9], - [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, - 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, - 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, - 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12], - [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, - 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, - 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, - 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14], - [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, - 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, - 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, - 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3], - [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, - 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, - 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, - 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13], - [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, - 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, - 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, - 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12], - [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, - 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, - 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, - 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],] - __p = [15, 6, 19, 20, 28, 11,27, 16, 0, 14, 22, 25, - 4, 17, 30, 9, 1, 7,23,13, 31, 26, 2, 8,18, 12, 29, 5, 21, 10,3, 24] - __fp = [39, 7, 47, 15, 55, 23, 63, 31,38, 6, 46, 14, 54, 22, 62, 30, - 37, 5, 45, 13, 53, 21, 61, 29,36, 4, 44, 12, 52, 20, 60, 28, - 35, 3, 43, 11, 51, 19, 59, 27,34, 2, 42, 10, 50, 18, 58, 26, - 33, 1, 41, 9, 49, 17, 57, 25,32, 0, 40, 8, 48, 16, 56, 24] - # Type of crypting being done - ENCRYPT = 0x00 - DECRYPT = 0x01 - def __init__(self, key, mode=ECB, IV=None): - if len(key) != 8: - raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.") - self.block_size = 8 - self.key_size = 8 - self.__padding = '' - self.setMode(mode) - if IV: - self.setIV(IV) - self.L = [] - self.R = [] - self.Kn = [ [0] * 48 ] * 16 # 16 48-bit keys (K1 - K16) - self.final = [] - self.setKey(key) - def getKey(self): - return self.__key - def setKey(self, key): - self.__key = key - self.__create_sub_keys() - def getMode(self): - return self.__mode - def setMode(self, mode): - self.__mode = mode - def getIV(self): - return self.__iv - def setIV(self, IV): - if not IV or len(IV) != self.block_size: - raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes") - self.__iv = IV - def getPadding(self): - return self.__padding - def __String_to_BitList(self, data): - l = len(data) * 8 - result = [0] * l - pos = 0 - for c in data: - i = 7 - ch = ord(c) - while i >= 0: - if ch & (1 << i) != 0: - result[pos] = 1 - else: - result[pos] = 0 - pos += 1 - i -= 1 - return result - def __BitList_to_String(self, data): - result = '' - pos = 0 - c = 0 - while pos < len(data): - c += data[pos] << (7 - (pos % 8)) - if (pos % 8) == 7: - result += chr(c) - c = 0 - pos += 1 - return result - def __permutate(self, table, block): - return map(lambda x: block[x], table) - def __create_sub_keys(self): - key = self.__permutate(Des.__pc1, self.__String_to_BitList(self.getKey())) - i = 0 - self.L = key[:28] - self.R = key[28:] - while i < 16: - j = 0 - while j < Des.__left_rotations[i]: - self.L.append(self.L[0]) - del self.L[0] - self.R.append(self.R[0]) - del self.R[0] - j += 1 - self.Kn[i] = self.__permutate(Des.__pc2, self.L + self.R) - i += 1 - def __des_crypt(self, block, crypt_type): - block = self.__permutate(Des.__ip, block) - self.L = block[:32] - self.R = block[32:] - if crypt_type == Des.ENCRYPT: - iteration = 0 - iteration_adjustment = 1 - else: - iteration = 15 - iteration_adjustment = -1 - i = 0 - while i < 16: - tempR = self.R[:] - self.R = self.__permutate(Des.__expansion_table, self.R) - self.R = map(lambda x, y: x ^ y, self.R, self.Kn[iteration]) - B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]] - j = 0 - Bn = [0] * 32 - pos = 0 - while j < 8: - m = (B[j][0] << 1) + B[j][5] - n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4] - v = Des.__sbox[j][(m << 4) + n] - Bn[pos] = (v & 8) >> 3 - Bn[pos + 1] = (v & 4) >> 2 - Bn[pos + 2] = (v & 2) >> 1 - Bn[pos + 3] = v & 1 - pos += 4 - j += 1 - self.R = self.__permutate(Des.__p, Bn) - self.R = map(lambda x, y: x ^ y, self.R, self.L) - self.L = tempR - i += 1 - iteration += iteration_adjustment - self.final = self.__permutate(Des.__fp, self.R + self.L) - return self.final - def crypt(self, data, crypt_type): - if not data: - return '' - if len(data) % self.block_size != 0: - if crypt_type == Des.DECRYPT: # Decryption must work on 8 byte blocks - raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.") - if not self.getPadding(): - raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character") - else: - data += (self.block_size - (len(data) % self.block_size)) * self.getPadding() - if self.getMode() == CBC: - if self.getIV(): - iv = self.__String_to_BitList(self.getIV()) - else: - raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering") - i = 0 - dict = {} - result = [] - while i < len(data): - block = self.__String_to_BitList(data[i:i+8]) - if self.getMode() == CBC: - if crypt_type == Des.ENCRYPT: - block = map(lambda x, y: x ^ y, block, iv) - processed_block = self.__des_crypt(block, crypt_type) - if crypt_type == Des.DECRYPT: - processed_block = map(lambda x, y: x ^ y, processed_block, iv) - iv = block - else: - iv = processed_block - else: - processed_block = self.__des_crypt(block, crypt_type) - result.append(self.__BitList_to_String(processed_block)) - i += 8 - if crypt_type == Des.DECRYPT and self.getPadding(): - s = result[-1] - while s[-1] == self.getPadding(): - s = s[:-1] - result[-1] = s - return ''.join(result) - def encrypt(self, data, pad=''): - self.__padding = pad - return self.crypt(data, Des.ENCRYPT) - def decrypt(self, data, pad=''): - self.__padding = pad - return self.crypt(data, Des.DECRYPT) - -class Sectionizer: - def __init__(self, filename, ident): - self.contents = file(filename, 'rb').read() - self.header = self.contents[0:72] - self.num_sections, = struct.unpack('>H', self.contents[76:78]) - if self.header[0x3C:0x3C+8] != ident: - raise ValueError('Invalid file format') - self.sections = [] - for i in xrange(self.num_sections): - offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.contents[78+i*8:78+i*8+8]) - flags, val = a1, a2<<16|a3<<8|a4 - self.sections.append( (offset, flags, val) ) - def loadSection(self, section): - if section + 1 == self.num_sections: - end_off = len(self.contents) - else: - end_off = self.sections[section + 1][0] - off = self.sections[section][0] - return self.contents[off:end_off] - -def sanitizeFileName(s): - r = '' - for c in s.lower(): - if c in "abcdefghijklmnopqrstuvwxyz0123456789_.-": - r += c - return r - -def fixKey(key): - def fixByte(b): - return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80) - return "".join([chr(fixByte(ord(a))) for a in key]) - -class EreaderProcessor: - def __init__(self, section_reader, username, creditcard): - self.section_reader = section_reader - data = section_reader(0) - version, = struct.unpack('>H', data[0:2]) - if version != 272 and version != 260: - raise ValueError('incorrect eReader version %d (error 1)' % version) - data = section_reader(1) - self.data = data - des = Des(fixKey(data[0:8])) - cookie_shuf, cookie_size = struct.unpack('>LL', des.decrypt(data[-8:])) - if cookie_shuf < 3 or cookie_shuf > 0x14 or cookie_size < 0xf0 or cookie_size > 0x200: - raise ValueError('incorrect eReader version (error 2)') - input = des.decrypt(data[-cookie_size:]) - def unshuff(data, shuf): - r = [''] * len(data) - j = 0 - for i in xrange(len(data)): - j = (j + shuf) % len(data) - r[j] = data[i] - assert len("".join(r)) == len(data) - return "".join(r) - r = unshuff(input[0:-8], cookie_shuf) - def fixUsername(s): - r = '' - for c in s.lower(): - if (c >= 'a' and c <= 'z' or c >= '0' and c <= '9'): - r += c - return r - user_key = struct.pack('>LL', binascii.crc32(fixUsername(username)) & 0xffffffff, binascii.crc32(creditcard[-8:])& 0xffffffff) - drm_sub_version = struct.unpack('>H', r[0:2])[0] - self.num_text_pages = struct.unpack('>H', r[2:4])[0] - 1 - self.num_image_pages = struct.unpack('>H', r[26:26+2])[0] - self.first_image_page = struct.unpack('>H', r[24:24+2])[0] - self.flags = struct.unpack('>L', r[4:8])[0] - reqd_flags = (1<<9) | (1<<7) | (1<<10) - if (self.flags & reqd_flags) != reqd_flags: - print "Flags: 0x%X" % self.flags - raise ValueError('incompatible eReader file') - des = Des(fixKey(user_key)) - if version == 260: - if drm_sub_version != 13: - raise ValueError('incorrect eReader version %d (error 3)' % drm_sub_version) - encrypted_key = r[44:44+8] - encrypted_key_sha = r[52:52+20] - elif version == 272: - encrypted_key = r[172:172+8] - encrypted_key_sha = r[56:56+20] - self.content_key = des.decrypt(encrypted_key) - if sha.new(self.content_key).digest() != encrypted_key_sha: - raise ValueError('Incorrect Name and/or Credit Card') - def getNumImages(self): - return self.num_image_pages - def getImage(self, i): - sect = self.section_reader(self.first_image_page + i) - name = sect[4:4+32].strip('\0') - data = sect[62:] - return sanitizeFileName(name), data - def getText(self): - des = Des(fixKey(self.content_key)) - r = '' - for i in xrange(self.num_text_pages): - r += zlib.decompress(des.decrypt(self.section_reader(1 + i))) - return r - -class PmlConverter: - def __init__(self, s): - self.s = s - self.pos = 0 - def nextOptAttr(self): - p = self.pos - if self.s[p:p+2] != '="': - return None - r = '' - p += 2 - while self.s[p] != '"': - r += self.s[p] - p += 1 - self.pos = p + 1 - return r - def next(self): - p = self.pos - if p >= len(self.s): - return None - if self.s[p] != '\\': - res = self.s.find('\\', p) - if res == -1: - res = len(self.s) - self.pos = res - return self.s[p : res], None, None - c = self.s[p+1] - if c in 'pxcriuovtnsblBk-lI\\d': - self.pos = p + 2 - return None, c, None - if c in 'TwmqQ': - self.pos = p + 2 - return None, c, self.nextOptAttr() - if c == 'a': - self.pos = p + 5 - return None, c, int(self.s[p+2:p+5]) - if c == 'U': - self.pos = p + 6 - return None, c, int(self.s[p+2:p+6], 16) - c = self.s[p+1:p+1+2] - if c in ('X0','X1','X2','X3','X4','Sp','Sb'): - self.pos = p + 3 - return None, c, None - if c in ('C0','C1','C2','C3','C4','Fn','Sd'): - self.pos = p + 3 - return None, c, self.nextOptAttr() - print "unknown escape code %s" % c - self.pos = p + 1 - return None, None, None - def linkPrinter(link): - return '' % link - - html_tags = { - 'c' : ('

', '

'), - 'r' : ('

', '

'), - 'i' : ('', ''), - 'u' : ('', ''), - 'b' : ('', ''), - 'B' : ('', ''), - 'o' : ('', ''), - 'v' : (''), - 't' : ('', ''), - 'Sb' : ('', ''), - 'Sp' : ('', ''), - 'X0' : ('

', '

'), - 'X1' : ('

', '

'), - 'X2' : ('

', '

'), - 'X3' : ('

', '

'), - 'X4' : ('
', '
'), - 'l' : ('', ''), - 'q' : (linkPrinter, '
'), - } - html_one_tags = { - 'p' : '

' - } - pml_chars = { - 160 : ' ',130 : '—',131: 'ƒ',132: '„', - 133: '…',134: '†',135: '‡',138: 'Š', - 139: '‹',140: 'Œ',145: '‘',146: '’', - 147: '“',148: '”',149: '•',150: '–', - 151: '—',153: '™',154: 'š',155: '›', - 156: 'œ',159: 'Ÿ' - } - def process(self): - final = '\n' - in_tags = [] - def makeText(s): - s = s.replace('&', '&') - #s = s.replace('"', '"') - s = s.replace('<', '<') - s = s.replace('>', '>') - s = s.replace('\n', '
\n') - return s - while True: - r = self.next() - if not r: - break - text, cmd, attr = r - if text: - final += makeText(text) - if cmd: - def getTag(ti, end): - cmd, attr = ti - r = self.html_tags[cmd][end] - if type(r) != str: - r = r(attr) - return r - - if cmd in self.html_tags: - pair = (cmd, attr) - if cmd not in [a for (a,b) in in_tags]: - final += getTag(pair, False) - in_tags.append(pair) - else: - j = len(in_tags) - while True: - j = j - 1 - final += getTag(in_tags[j], True) - if in_tags[j][0] == cmd: - break - del in_tags[j] - while j < len(in_tags): - final += getTag(in_tags[j], False) - j = j + 1 - - if cmd in self.html_one_tags: - final += self.html_one_tags[cmd] - if cmd == 'm': - final += '' % attr - if cmd == 'Q': - final += ' ' % attr - if cmd == 'a': - final += self.pml_chars.get(attr, '&#%d;' % attr) - if cmd == 'U': - final += '&#%d;' % attr - final += '\n' - while True: - s = final.replace('
\n
\n
\n', '
\n
\n') - if s == final: - break - final = s - return final - -def convertEreaderToHtml(infile, name, cc, outdir): - if not os.path.exists(outdir): - os.makedirs(outdir) - sect = Sectionizer(infile, 'PNRdPPrs') - er = EreaderProcessor(sect.loadSection, name, cc) - - for i in xrange(er.getNumImages()): - name, contents = er.getImage(i) - file(os.path.join(outdir, name), 'wb').write(contents) - - pml = PmlConverter(er.getText()) - file(os.path.join(outdir, 'book.html'),'wb').write(pml.process()) - -print "eReader2Html v0.03. Copyright (c) 2008 The Dark Reverser" -if len(sys.argv)!=5: - print "Converts eReader books to HTML" - print "Usage:" - print " ereader2html infile.pdb outdir \"your name\" credit_card_number " - print "Note:" - print " It's enough to enter the last 8 digits of the credit card number" -else: - infile, outdir, name, cc = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4] - try: - print "Processing...", - convertEreaderToHtml(infile, name, cc, outdir) - print "done" - except ValueError, e: - print "Error: %s" % e \ No newline at end of file diff --git a/Mobi_Additional_Tools/k4mdumpkinfo.py b/Mobi_Additional_Tools/k4mdumpkinfo.py new file mode 100644 index 0000000..e7e119d --- /dev/null +++ b/Mobi_Additional_Tools/k4mdumpkinfo.py @@ -0,0 +1,476 @@ +# engine to remove drm from Kindle for Mac books +# for personal use for archiving and converting your ebooks +# PLEASE DO NOT PIRATE! +# We want all authors and Publishers, and eBook stores to live long and prosperous lives +# +# it borrows heavily from works by CMBDTC, IHeartCabbages, skindle, +# unswindle, DiapDealer, some_updates and many many others + +from __future__ import with_statement + +class Unbuffered: + def __init__(self, stream): + self.stream = stream + def write(self, data): + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +import sys +sys.stdout=Unbuffered(sys.stdout) +import os, csv, getopt +from struct import pack +from struct import unpack +import zlib + +# for handling sub processes +import subprocess +from subprocess import Popen, PIPE, STDOUT + +#Exception Handling +class K4MDEDRMError(Exception): + pass +class K4MDEDRMFatal(Exception): + pass + +# +# crypto routines +# +import hashlib + +def MD5(message): + ctx = hashlib.md5() + ctx.update(message) + return ctx.digest() + +def SHA1(message): + ctx = hashlib.sha1() + ctx.update(message) + return ctx.digest() + +def SHA256(message): + ctx = hashlib.sha256() + ctx.update(message) + return ctx.digest() + +# interface to needed routines in openssl's libcrypto +def _load_crypto_libcrypto(): + from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \ + Structure, c_ulong, create_string_buffer, addressof, string_at, cast + from ctypes.util import find_library + + libcrypto = find_library('crypto') + if libcrypto is None: + raise K4MDEDRMError('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_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,c_int]) + + AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p]) + + PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', + [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p]) + + class LibCrypto(object): + def __init__(self): + self._blocksize = 0 + self._keyctx = None + self.iv = 0 + def set_decrypt_key(self, userkey, iv): + self._blocksize = len(userkey) + if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : + raise K4MDEDRMError('AES improper key used') + return + keyctx = self._keyctx = AES_KEY() + self.iv = iv + rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) + if rv < 0: + raise K4MDEDRMError('Failed to initialize AES key') + def decrypt(self, data): + out = create_string_buffer(len(data)) + rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0) + if rv == 0: + raise K4MDEDRMError('AES decryption failed') + return out.raw + def keyivgen(self, passwd): + salt = '16743' + saltlen = 5 + passlen = len(passwd) + iter = 0x3e8 + keylen = 80 + out = create_string_buffer(keylen) + rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out) + return out.raw + return LibCrypto + +def _load_crypto(): + LibCrypto = None + try: + LibCrypto = _load_crypto_libcrypto() + except (ImportError, K4MDEDRMError): + pass + return LibCrypto + +LibCrypto = _load_crypto() + +# +# Utility Routines +# + +# uses a sub process to get the Hard Drive Serial Number using ioreg +# returns with the first found serial number in that class +def GetVolumeSerialNumber(): + sernum = os.getenv('MYSERIALNUMBER') + if sernum != None: + return sernum + cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver' + cmdline = cmdline.encode(sys.getfilesystemencoding()) + p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) + poll = p.wait('wait') + results = p.read() + reslst = results.split('\n') + cnt = len(reslst) + bsdname = None + sernum = None + foundIt = False + for j in xrange(cnt): + resline = reslst[j] + pp = resline.find('"Serial Number" = "') + if pp >= 0: + sernum = resline[pp+19:-1] + sernum = sernum.strip() + bb = resline.find('"BSD Name" = "') + if bb >= 0: + bsdname = resline[bb+14:-1] + bsdname = bsdname.strip() + if (bsdname == 'disk0') and (sernum != None): + foundIt = True + break + if not foundIt: + sernum = '9999999999' + return sernum + +# uses unix env to get username instead of using sysctlbyname +def GetUserName(): + username = os.getenv('USER') + return username + +MAX_PATH = 255 + +# +# start of Kindle specific routines +# + +global kindleDatabase + +# Various character maps used to decrypt books. Probably supposed to act as obfuscation +charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" +charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" +charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" +charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" + +# Encode the bytes in data with the characters in map +def encode(data, map): + result = "" + for char in data: + value = ord(char) + Q = (value ^ 0x80) // len(map) + R = value % len(map) + result += map[Q] + result += map[R] + return result + +# Hash the bytes in data and then encode the digest with the characters in map +def encodeHash(data,map): + return encode(MD5(data),map) + +# Decode the string in data with the characters in map. Returns the decoded bytes +def decode(data,map): + result = "" + for i in range (0,len(data)-1,2): + high = map.find(data[i]) + low = map.find(data[i+1]) + if (high == -1) or (low == -1) : + break + value = (((high * len(map)) ^ 0x80) & 0xFF) + low + result += pack("B",value) + return result + +# implements an Pseudo Mac Version of Windows built-in Crypto routine +def CryptUnprotectData(encryptedData): + sp = GetVolumeSerialNumber() + '!@#' + GetUserName() + passwdData = encode(SHA256(sp),charMap1) + crp = LibCrypto() + key_iv = crp.keyivgen(passwdData) + key = key_iv[0:32] + iv = key_iv[32:48] + crp.set_decrypt_key(key,iv) + cleartext = crp.decrypt(encryptedData) + return cleartext + +# Locate and open the .kindle-info file +def openKindleInfo(): + home = os.getenv('HOME') + kinfopath = home + '/Library/Application Support/Amazon/Kindle/storage/.kindle-info' + if not os.path.exists(kinfopath): + kinfopath = home + '/Library/Application Support/Amazon/Kindle for Mac/storage/.kindle-info' + if not os.path.exists(kinfopath): + raise K4MDEDRMError('Error: .kindle-info file can not be found') + return open(kinfopath,'r') + +# Parse the Kindle.info file and return the records as a list of key-values +def parseKindleInfo(): + DB = {} + infoReader = openKindleInfo() + infoReader.read(1) + data = infoReader.read() + items = data.split('[') + for item in items: + splito = item.split(':') + DB[splito[0]] =splito[1] + return DB + +# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record +def getKindleInfoValueForHash(hashedKey): + global kindleDatabase + encryptedValue = decode(kindleDatabase[hashedKey],charMap2) + cleartext = CryptUnprotectData(encryptedValue) + return decode(cleartext, charMap1) + +# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record +def getKindleInfoValueForKey(key): + return getKindleInfoValueForHash(encodeHash(key,charMap2)) + +# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string. +def findNameForHash(hash): + names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"] + result = "" + for name in names: + if hash == encodeHash(name, charMap2): + result = name + break + return result + +# Print all the records from the kindle.info file (option -i) +def printKindleInfo(): + for record in kindleDatabase: + name = findNameForHash(record) + if name != "" : + print (name) + print ("--------------------------") + else : + print ("Unknown Record") + print getKindleInfoValueForHash(record) + print "\n" + +# +# PID generation routines +# + +# Returns two bit at offset from a bit field +def getTwoBitsFromBitField(bitField,offset): + byteNumber = offset // 4 + bitPosition = 6 - 2*(offset % 4) + return ord(bitField[byteNumber]) >> bitPosition & 3 + +# Returns the six bits at offset from a bit field +def getSixBitsFromBitField(bitField,offset): + offset *= 3 + value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2) + return value + +# 8 bits to six bits encoding from hash to generate PID string +def encodePID(hash): + global charMap3 + PID = "" + for position in range (0,8): + PID += charMap3[getSixBitsFromBitField(hash,position)] + return PID + + +# +# Main +# + +def main(argv=sys.argv): + global kindleDatabase + + kindleDatabase = None + + # + # Read the encrypted database + # + + try: + kindleDatabase = parseKindleInfo() + except Exception, message: + print(message) + + if kindleDatabase != None : + printKindleInfo() + + return 0 + +import signal +import threading +import subprocess +from subprocess import Popen, PIPE, STDOUT + +# **heavily** chopped up and modfied version of asyncproc.py +# to make it actually work on Windows as well as Mac/Linux +# For the original see: +# "http://www.lysator.liu.se/~bellman/download/" +# author is "Thomas Bellman " +# available under GPL version 3 or Later + +# create an asynchronous subprocess whose output can be collected in +# a non-blocking manner + +# What a mess! Have to use threads just to get non-blocking io +# in a cross-platform manner + +# luckily all thread use is hidden within this class + +class Process(object): + def __init__(self, *params, **kwparams): + if len(params) <= 3: + kwparams.setdefault('stdin', subprocess.PIPE) + if len(params) <= 4: + kwparams.setdefault('stdout', subprocess.PIPE) + if len(params) <= 5: + kwparams.setdefault('stderr', subprocess.PIPE) + self.__pending_input = [] + self.__collected_outdata = [] + self.__collected_errdata = [] + self.__exitstatus = None + self.__lock = threading.Lock() + self.__inputsem = threading.Semaphore(0) + self.__quit = False + + self.__process = subprocess.Popen(*params, **kwparams) + + if self.__process.stdin: + self.__stdin_thread = threading.Thread( + name="stdin-thread", + target=self.__feeder, args=(self.__pending_input, + self.__process.stdin)) + self.__stdin_thread.setDaemon(True) + self.__stdin_thread.start() + + if self.__process.stdout: + self.__stdout_thread = threading.Thread( + name="stdout-thread", + target=self.__reader, args=(self.__collected_outdata, + self.__process.stdout)) + self.__stdout_thread.setDaemon(True) + self.__stdout_thread.start() + + if self.__process.stderr: + self.__stderr_thread = threading.Thread( + name="stderr-thread", + target=self.__reader, args=(self.__collected_errdata, + self.__process.stderr)) + self.__stderr_thread.setDaemon(True) + self.__stderr_thread.start() + + def pid(self): + return self.__process.pid + + def kill(self, signal): + self.__process.send_signal(signal) + + # check on subprocess (pass in 'nowait') to act like poll + def wait(self, flag): + if flag.lower() == 'nowait': + rc = self.__process.poll() + else: + rc = self.__process.wait() + if rc != None: + if self.__process.stdin: + self.closeinput() + if self.__process.stdout: + self.__stdout_thread.join() + if self.__process.stderr: + self.__stderr_thread.join() + return self.__process.returncode + + def terminate(self): + if self.__process.stdin: + self.closeinput() + self.__process.terminate() + + # thread gets data from subprocess stdout + def __reader(self, collector, source): + while True: + data = os.read(source.fileno(), 65536) + self.__lock.acquire() + collector.append(data) + self.__lock.release() + if data == "": + source.close() + break + return + + # thread feeds data to subprocess stdin + def __feeder(self, pending, drain): + while True: + self.__inputsem.acquire() + self.__lock.acquire() + if not pending and self.__quit: + drain.close() + self.__lock.release() + break + data = pending.pop(0) + self.__lock.release() + drain.write(data) + + # non-blocking read of data from subprocess stdout + def read(self): + self.__lock.acquire() + outdata = "".join(self.__collected_outdata) + del self.__collected_outdata[:] + self.__lock.release() + return outdata + + # non-blocking read of data from subprocess stderr + def readerr(self): + self.__lock.acquire() + errdata = "".join(self.__collected_errdata) + del self.__collected_errdata[:] + self.__lock.release() + return errdata + + # non-blocking write to stdin of subprocess + def write(self, data): + if self.__process.stdin is None: + raise ValueError("Writing to process with stdin not a pipe") + self.__lock.acquire() + self.__pending_input.append(data) + self.__inputsem.release() + self.__lock.release() + + # close stdinput of subprocess + def closeinput(self): + self.__lock.acquire() + self.__quit = True + self.__inputsem.release() + self.__lock.release() + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/Mobi_Additional_Tools/lib/mobidedrm.py b/Mobi_Additional_Tools/lib/mobidedrm.py index 18514a0..9f17a3b 100644 --- a/Mobi_Additional_Tools/lib/mobidedrm.py +++ b/Mobi_Additional_Tools/lib/mobidedrm.py @@ -3,14 +3,6 @@ # This is a python script. You need a Python interpreter to run it. # For example, ActiveState Python, which exists for windows. # -# It can run standalone to convert files, or it can be installed as a -# plugin for Calibre (http://calibre-ebook.com/about) so that -# importing files with DRM 'Just Works'. -# -# To create a Calibre plugin, rename this file so that the filename -# ends in '_plugin.py', put it into a ZIP file and import that Calibre -# using its plugin configuration GUI. -# # Changelog # 0.01 - Initial version # 0.02 - Huffdic compressed books were not properly decrypted @@ -37,14 +29,18 @@ # in utf8 file are encrypted. (Although neither kind gets compressed.) # This knowledge leads to a simplification of the test for the # trailing data byte flags - version 5 and higher AND header size >= 0xE4. -# 0.15 - Now outputs 'hearbeat', and is also quicker for long files. +# 0.15 - Now outputs 'heartbeat', and is also quicker for long files. # 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility. # 0.17 - added modifications to support its use as an imported python module -# both inside calibre and also in other places (ie K4MobiDeDRM tools) -# and modified the plugin code so that it will not interfere with other -# mobi/azw plugins if installed at the same time +# both inside calibre and also in other places (ie K4DeDRM tools) +# 0.17a- disabled the standalone plugin feature since a plugin can not import +# a plugin +# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file... +# Removed the disabled Calibre plug-in code +# Permit use of 8-digit PIDs +# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either. -__version__ = '0.17' +__version__ = '0.19' import sys import struct @@ -127,10 +123,11 @@ def getSizeOfTrailingDataEntries(ptr, size, flags): if testflags & 1: num += getSizeOfTrailingDataEntry(ptr, size - num) testflags >>= 1 - # Multibyte data, if present, is included in the encryption, so - # we do not need to check the low bit. - # if flags & 1: - # num += (ord(ptr[size - num - 1]) & 0x3) + 1 + # Check the low bit to see if there's multibyte data present. + # if multibyte data is included in the encryped data, we'll + # have already cleared this flag. + if flags & 1: + num += (ord(ptr[size - num - 1]) & 0x3) + 1 return num class DrmStripper: @@ -181,9 +178,14 @@ class DrmStripper: return found_key def __init__(self, data_file, pid): - if checksumPid(pid[0:-2]) != pid: - raise DrmException("invalid PID checksum") - pid = pid[0:-2] + if len(pid)==10: + if checksumPid(pid[0:-2]) != pid: + raise DrmException("invalid PID checksum") + pid = pid[0:-2] + elif len(pid)==8: + print "PID without checksum given. With checksum PID is "+checksumPid(pid) + else: + raise DrmException("Invalid PID length") self.data_file = data_file header = data_file[0:72] @@ -206,6 +208,10 @@ class DrmStripper: if (mobi_length >= 0xE4) and (mobi_version >= 5): extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4]) print "Extra Data Flags = %d" %extra_data_flags + if mobi_version <= 5: + # multibyte utf8 data is included in the encryption for mobi_version 5 and below + # so clear that byte so that we leave it to be decrypted. + extra_data_flags &= 0xFFFE crypto_type, = struct.unpack('>H', sect[0xC:0xC+2]) if crypto_type == 0: @@ -282,42 +288,3 @@ def main(argv=sys.argv): if __name__ == "__main__": sys.exit(main()) - -#if not __name__ == "__main__": - # note a calibre plugin can not import code with another calibre plugin - # in it as it ends up registering two different plugins - from calibre.customize import FileTypePlugin - - class MobiDeDRM(FileTypePlugin): - name = 'MobiDeDRM' # Name of the plugin - description = 'Removes DRM from secure Mobi files' - supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on - author = 'The Dark Reverser' # The author of this plugin - version = (0, 1, 7) # The version number of this plugin - file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to - on_import = True # Run this plugin during the import - - def run(self, path_to_ebook): - from calibre.gui2 import is_ok_to_use_qt - from PyQt4.Qt import QMessageBox - PID = self.site_customization - data_file = file(path_to_ebook, 'rb').read() - ar = PID.split(',') - for i in ar: - try: - unlocked_file = DrmStripper(data_file, i).getResult() - except DrmException: - if is_ok_to_use_qt(): - d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook) - d.show() - d.raise_() - d.exec_() - raise Exception("MobiDeDRM Plugin: Error decoding ebook") - else: - of = self.temporary_file('.mobi') - of.write(unlocked_file) - of.close() - return of.name - - def customization_help(self, gui=False): - return 'Enter PID (separate multiple PIDs with comma)' diff --git a/Topaz_Tools/lib/cmbtc_dump.py b/Topaz_Tools/lib/cmbtc_dump.py index 9dca393..bb854d2 100644 --- a/Topaz_Tools/lib/cmbtc_dump.py +++ b/Topaz_Tools/lib/cmbtc_dump.py @@ -534,7 +534,7 @@ def getK4Pids(kInfoFile=None): kindleDatabase = None try: kindleDatabase = parseKindleInfo(kInfoFile) - except Exception as message: + except Exception, message: #if verbose > 0: # print(message) pass diff --git a/Topaz_Tools/lib/cmbtc_v2.2.py b/Topaz_Tools/lib/cmbtc_v2.2.py index 98a807f..764e38d 100644 --- a/Topaz_Tools/lib/cmbtc_v2.2.py +++ b/Topaz_Tools/lib/cmbtc_v2.2.py @@ -790,7 +790,7 @@ def main(argv=sys.argv): try: kindleDatabase = parseKindleInfo() - except Exception as message: + except Exception, message: if verbose>0: print(message) diff --git a/Topaz_Tools/lib/k4mutils.py b/Topaz_Tools/lib/k4mutils.py index 977d81c..33771eb 100644 --- a/Topaz_Tools/lib/k4mutils.py +++ b/Topaz_Tools/lib/k4mutils.py @@ -237,24 +237,36 @@ LibCrypto = _load_crypto() # # uses a sub process to get the Hard Drive Serial Number using ioreg -# returns with the first found serial number in that class +# returns with the serial number of drive whose BSD Name is "disk0" def GetVolumeSerialNumber(): - cmdline = '/usr/sbin/ioreg -r -c AppleAHCIDiskDriver' + sernum = os.getenv('MYSERIALNUMBER') + if sernum != None: + return sernum + cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver' cmdline = cmdline.encode(sys.getfilesystemencoding()) p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) poll = p.wait('wait') results = p.read() reslst = results.split('\n') - sernum = '9999999999' cnt = len(reslst) + bsdname = None + sernum = None + foundIt = False for j in xrange(cnt): resline = reslst[j] pp = resline.find('"Serial Number" = "') if pp >= 0: - sernum = resline[pp+19:] - sernum = sernum[:-1] - sernum = sernum.lstrip() - break + sernum = resline[pp+19:-1] + sernum = sernum.strip() + bb = resline.find('"BSD Name" = "') + if bb >= 0: + bsdname = resline[bb+14:-1] + bsdname = bsdname.strip() + if (bsdname == 'disk0') and (sernum != None): + foundIt = True + break + if not foundIt: + sernum = '9999999999' return sernum # uses unix env to get username instead of using sysctlbyname @@ -319,4 +331,4 @@ def openKindleInfo(kInfoFile=None): raise K4MDrmException('Error: .kindle-info file can not be found') return open(kinfopath,'r') else: - return open(kInfoFile, 'r') \ No newline at end of file + return open(kInfoFile, 'r') diff --git a/ePub_Fixer/lib/zipfix.py b/ePub_Fixer/lib/zipfix.py index 40c41d2..536a21d 100644 --- a/ePub_Fixer/lib/zipfix.py +++ b/ePub_Fixer/lib/zipfix.py @@ -82,12 +82,11 @@ class fixZip: # and copy member over to output archive # if problems exist with local vs central filename, fix them - for i, zinfo in enumerate(self.inzip.infolist()): + for zinfo in self.inzip.infolist(): data = None nzinfo = zinfo - try: - data = self.inzip.read(zinfo) + data = self.inzip.read(zinfo.filename) except zipfile.BadZipfile or zipfile.error: local_name = self.getlocalname(zinfo) data = self.getfiledata(zinfo)