diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist index d1feae25..4f3beb79 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist @@ -24,7 +24,7 @@ CFBundleExecutable droplet CFBundleGetInfoString - DeDRM 2.3, Copyright © 2010–2011 by Apprentice Alf and others. + DeDRM 2.4, Copyright © 2010–2011 by Apprentice Alf and others. CFBundleIconFile droplet CFBundleInfoDictionaryVersion @@ -34,7 +34,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.3 + 2.4 CFBundleSignature dplt LSMinimumSystemVersion diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py index 0255a3c8..3a0000e9 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py @@ -29,7 +29,7 @@ # and import that ZIP into Calibre using its plugin configuration GUI. -__version__ = '2.4' +__version__ = '2.6' class Unbuffered: def __init__(self, stream): @@ -250,7 +250,7 @@ class K4DeDRM(FileTypePlugin): 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, 2, 4) # The version number of this plugin + version = (0, 2, 6) # The version number of this plugin file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to on_import = True # Run this plugin during the import priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py index 6dcbf73b..039daf9a 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py @@ -224,13 +224,11 @@ def pidFromSerial(s, l): # Parse the EXTH header records and use the Kindle serial number to calculate the book pid. def getKindlePid(pidlst, rec209, token, serialnum): - - if rec209 != None and token != None: - # Compute book PID - pidHash = SHA1(serialnum+rec209+token) - bookPID = encodePID(pidHash) - bookPID = checksumPid(bookPID) - pidlst.append(bookPID) + # Compute book PID + pidHash = SHA1(serialnum+rec209+token) + bookPID = encodePID(pidHash) + bookPID = checksumPid(bookPID) + pidlst.append(bookPID) # compute fixed pid for old pre 2.5 firmware update pid as well bookPID = pidFromSerial(serialnum, 7) + "*" @@ -276,9 +274,6 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None): pidlst.append(devicePID) # Compute book PID - if rec209 == None or token == None: - print "\nNo EXTH record type 209 or token - Perhaps not a K4 file?" - return pidlst # Get the kindle account token kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens") diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py index ec756b91..e660a1a2 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py @@ -47,8 +47,9 @@ # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!) +# 0.28 - slight additional changes to metadata token generation (None -> '') -__version__ = '0.27' +__version__ = '0.28' import sys @@ -237,12 +238,11 @@ def getBookTitle(self): return title def getPIDMetaInfo(self): - rec209 = None - token = None + rec209 = '' + token = '' if 209 in self.meta_array: rec209 = self.meta_array[209] data = rec209 - token = '' # The 209 data comes in five byte groups. Interpret the last four bytes # of each group as a big endian unsigned integer to get a key value # if that key exists in the meta_array, append its contents to the token diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/topazextract.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/topazextract.py index 732bbaeb..59bc5faa 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/topazextract.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/topazextract.py @@ -157,18 +157,22 @@ def parseMetadata(self): raise TpzDRMError("Parse Error : Record Names Don't Match") flags = ord(self.fo.read(1)) nbRecords = ord(self.fo.read(1)) + # print nbRecords for i in range (0,nbRecords) : - record = [bookReadString(self.fo), bookReadString(self.fo)] - self.bookMetadata[record[0]] = record[1] + keyval = bookReadString(self.fo) + content = bookReadString(self.fo) + # print keyval + # print content + self.bookMetadata[keyval] = content return self.bookMetadata def getPIDMetaInfo(self): - keysRecord = None - keysRecordRecord = None - if 'keys' in self.bookMetadata: - keysRecord = self.bookMetadata['keys'] - if keysRecord in self.bookMetadata: - keysRecordRecord = self.bookMetadata[keysRecord] + keysRecord = self.bookMetadata.get('keys','') + keysRecordRecord = '' + if keysRecord != '': + keylst = keysRecord.split(',') + for keyval in keylst: + keysRecordRecord += self.bookMetadata.get(keyval,'') return keysRecord, keysRecordRecord def getBookTitle(self): diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py index 0255a3c8..3a0000e9 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py @@ -29,7 +29,7 @@ # and import that ZIP into Calibre using its plugin configuration GUI. -__version__ = '2.4' +__version__ = '2.6' class Unbuffered: def __init__(self, stream): @@ -250,7 +250,7 @@ class K4DeDRM(FileTypePlugin): 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, 2, 4) # The version number of this plugin + version = (0, 2, 6) # The version number of this plugin file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to on_import = True # Run this plugin during the import priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/kgenpids.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/kgenpids.py index 6dcbf73b..039daf9a 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/kgenpids.py +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/kgenpids.py @@ -224,13 +224,11 @@ def pidFromSerial(s, l): # Parse the EXTH header records and use the Kindle serial number to calculate the book pid. def getKindlePid(pidlst, rec209, token, serialnum): - - if rec209 != None and token != None: - # Compute book PID - pidHash = SHA1(serialnum+rec209+token) - bookPID = encodePID(pidHash) - bookPID = checksumPid(bookPID) - pidlst.append(bookPID) + # Compute book PID + pidHash = SHA1(serialnum+rec209+token) + bookPID = encodePID(pidHash) + bookPID = checksumPid(bookPID) + pidlst.append(bookPID) # compute fixed pid for old pre 2.5 firmware update pid as well bookPID = pidFromSerial(serialnum, 7) + "*" @@ -276,9 +274,6 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None): pidlst.append(devicePID) # Compute book PID - if rec209 == None or token == None: - print "\nNo EXTH record type 209 or token - Perhaps not a K4 file?" - return pidlst # Get the kindle account token kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens") diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py index ec756b91..e660a1a2 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py @@ -47,8 +47,9 @@ # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!) +# 0.28 - slight additional changes to metadata token generation (None -> '') -__version__ = '0.27' +__version__ = '0.28' import sys @@ -237,12 +238,11 @@ def getBookTitle(self): return title def getPIDMetaInfo(self): - rec209 = None - token = None + rec209 = '' + token = '' if 209 in self.meta_array: rec209 = self.meta_array[209] data = rec209 - token = '' # The 209 data comes in five byte groups. Interpret the last four bytes # of each group as a big endian unsigned integer to get a key value # if that key exists in the meta_array, append its contents to the token diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/topazextract.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/topazextract.py index 732bbaeb..59bc5faa 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/topazextract.py +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/topazextract.py @@ -157,18 +157,22 @@ def parseMetadata(self): raise TpzDRMError("Parse Error : Record Names Don't Match") flags = ord(self.fo.read(1)) nbRecords = ord(self.fo.read(1)) + # print nbRecords for i in range (0,nbRecords) : - record = [bookReadString(self.fo), bookReadString(self.fo)] - self.bookMetadata[record[0]] = record[1] + keyval = bookReadString(self.fo) + content = bookReadString(self.fo) + # print keyval + # print content + self.bookMetadata[keyval] = content return self.bookMetadata def getPIDMetaInfo(self): - keysRecord = None - keysRecordRecord = None - if 'keys' in self.bookMetadata: - keysRecord = self.bookMetadata['keys'] - if keysRecord in self.bookMetadata: - keysRecordRecord = self.bookMetadata[keysRecord] + keysRecord = self.bookMetadata.get('keys','') + keysRecordRecord = '' + if keysRecord != '': + keylst = keysRecord.split(',') + for keyval in keylst: + keysRecordRecord += self.bookMetadata.get(keyval,'') return keysRecord, keysRecordRecord def getBookTitle(self): diff --git a/DeDRM_Windows_Application/ReadMe_DeDRM_WinApp.txt b/DeDRM_Windows_Application/ReadMe_DeDRM_WinApp.txt index 111d2b95..b260625e 100644 --- a/DeDRM_Windows_Application/ReadMe_DeDRM_WinApp.txt +++ b/DeDRM_Windows_Application/ReadMe_DeDRM_WinApp.txt @@ -1,4 +1,4 @@ -ReadMe_DeDRM_WinApp_v1.5 +ReadMe_DeDRM_WinApp_vX.X ----------------------- DeDRM_WinApp is a pure python drag and drop application that allows users to drag and drop ebooks or folders of ebooks onto theDeDRM_Drop_Target to have the DRM removed. It repackages the"tools" python software in one easy to use program. diff --git a/KindleBooks_Tools/KindleBooks/lib/k4mobidedrm.py b/KindleBooks_Tools/KindleBooks/lib/k4mobidedrm.py index 0255a3c8..3a0000e9 100644 --- a/KindleBooks_Tools/KindleBooks/lib/k4mobidedrm.py +++ b/KindleBooks_Tools/KindleBooks/lib/k4mobidedrm.py @@ -29,7 +29,7 @@ # and import that ZIP into Calibre using its plugin configuration GUI. -__version__ = '2.4' +__version__ = '2.6' class Unbuffered: def __init__(self, stream): @@ -250,7 +250,7 @@ class K4DeDRM(FileTypePlugin): 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, 2, 4) # The version number of this plugin + version = (0, 2, 6) # The version number of this plugin file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to on_import = True # Run this plugin during the import priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm diff --git a/KindleBooks_Tools/KindleBooks/lib/kgenpids.py b/KindleBooks_Tools/KindleBooks/lib/kgenpids.py index 6dcbf73b..039daf9a 100644 --- a/KindleBooks_Tools/KindleBooks/lib/kgenpids.py +++ b/KindleBooks_Tools/KindleBooks/lib/kgenpids.py @@ -224,13 +224,11 @@ def pidFromSerial(s, l): # Parse the EXTH header records and use the Kindle serial number to calculate the book pid. def getKindlePid(pidlst, rec209, token, serialnum): - - if rec209 != None and token != None: - # Compute book PID - pidHash = SHA1(serialnum+rec209+token) - bookPID = encodePID(pidHash) - bookPID = checksumPid(bookPID) - pidlst.append(bookPID) + # Compute book PID + pidHash = SHA1(serialnum+rec209+token) + bookPID = encodePID(pidHash) + bookPID = checksumPid(bookPID) + pidlst.append(bookPID) # compute fixed pid for old pre 2.5 firmware update pid as well bookPID = pidFromSerial(serialnum, 7) + "*" @@ -276,9 +274,6 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None): pidlst.append(devicePID) # Compute book PID - if rec209 == None or token == None: - print "\nNo EXTH record type 209 or token - Perhaps not a K4 file?" - return pidlst # Get the kindle account token kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens") diff --git a/KindleBooks_Tools/KindleBooks/lib/mobidedrm.py b/KindleBooks_Tools/KindleBooks/lib/mobidedrm.py index ec756b91..e660a1a2 100644 --- a/KindleBooks_Tools/KindleBooks/lib/mobidedrm.py +++ b/KindleBooks_Tools/KindleBooks/lib/mobidedrm.py @@ -47,8 +47,9 @@ # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!) +# 0.28 - slight additional changes to metadata token generation (None -> '') -__version__ = '0.27' +__version__ = '0.28' import sys @@ -237,12 +238,11 @@ def getBookTitle(self): return title def getPIDMetaInfo(self): - rec209 = None - token = None + rec209 = '' + token = '' if 209 in self.meta_array: rec209 = self.meta_array[209] data = rec209 - token = '' # The 209 data comes in five byte groups. Interpret the last four bytes # of each group as a big endian unsigned integer to get a key value # if that key exists in the meta_array, append its contents to the token diff --git a/KindleBooks_Tools/KindleBooks/lib/topazextract.py b/KindleBooks_Tools/KindleBooks/lib/topazextract.py index 732bbaeb..59bc5faa 100644 --- a/KindleBooks_Tools/KindleBooks/lib/topazextract.py +++ b/KindleBooks_Tools/KindleBooks/lib/topazextract.py @@ -157,18 +157,22 @@ def parseMetadata(self): raise TpzDRMError("Parse Error : Record Names Don't Match") flags = ord(self.fo.read(1)) nbRecords = ord(self.fo.read(1)) + # print nbRecords for i in range (0,nbRecords) : - record = [bookReadString(self.fo), bookReadString(self.fo)] - self.bookMetadata[record[0]] = record[1] + keyval = bookReadString(self.fo) + content = bookReadString(self.fo) + # print keyval + # print content + self.bookMetadata[keyval] = content return self.bookMetadata def getPIDMetaInfo(self): - keysRecord = None - keysRecordRecord = None - if 'keys' in self.bookMetadata: - keysRecord = self.bookMetadata['keys'] - if keysRecord in self.bookMetadata: - keysRecordRecord = self.bookMetadata[keysRecord] + keysRecord = self.bookMetadata.get('keys','') + keysRecordRecord = '' + if keysRecord != '': + keylst = keysRecord.split(',') + for keyval in keylst: + keysRecordRecord += self.bookMetadata.get(keyval,'') return keysRecord, keysRecordRecord def getBookTitle(self): diff --git a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py b/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py index ec756b91..e660a1a2 100644 --- a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py +++ b/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py @@ -47,8 +47,9 @@ # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!) +# 0.28 - slight additional changes to metadata token generation (None -> '') -__version__ = '0.27' +__version__ = '0.28' import sys @@ -237,12 +238,11 @@ def getBookTitle(self): return title def getPIDMetaInfo(self): - rec209 = None - token = None + rec209 = '' + token = '' if 209 in self.meta_array: rec209 = self.meta_array[209] data = rec209 - token = '' # The 209 data comes in five byte groups. Interpret the last four bytes # of each group as a big endian unsigned integer to get a key value # if that key exists in the meta_array, append its contents to the token diff --git a/KindleBooks_Tools/Kindle_4_PC_Unswindle/mobidedrm.py b/KindleBooks_Tools/Kindle_4_PC_Unswindle/mobidedrm.py index 183432cc..e660a1a2 100644 --- a/KindleBooks_Tools/Kindle_4_PC_Unswindle/mobidedrm.py +++ b/KindleBooks_Tools/Kindle_4_PC_Unswindle/mobidedrm.py @@ -24,7 +24,7 @@ # 0.14 - Working out when the extra data flags are present has been problematic # Versions 7 through 9 have tried to tweak the conditions, but have been # only partially successful. Closer examination of lots of sample -# files reveals that a confusin has arisen because trailing data entries +# files reveals that a confusion has arisen because trailing data entries # are not encrypted, but it turns out that the multibyte entries # in utf8 file are encrypted. (Although neither kind gets compressed.) # This knowledge leads to a simplification of the test for the @@ -39,13 +39,19 @@ # 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. -# 0.20 - Corretion: It seems that multibyte entries are encrypted in a v6 file. +# 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file. +# 0.21 - Added support for multiple pids +# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface +# 0.23 - fixed problem with older files with no EXTH section +# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well +# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption +# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% +# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!) +# 0.28 - slight additional changes to metadata token generation (None -> '') -__version__ = '0.20' +__version__ = '0.28' import sys -import struct -import binascii class Unbuffered: def __init__(self, stream): @@ -55,10 +61,20 @@ def write(self, data): self.stream.flush() def __getattr__(self, attr): return getattr(self.stream, attr) +sys.stdout=Unbuffered(sys.stdout) + +import os +import struct +import binascii class DrmException(Exception): pass + +# +# MobiBook Utility Routines +# + # Implementation of Pukall Cipher 1 def PC1(key, src, decryption=True): sum1 = 0; @@ -70,7 +86,6 @@ def PC1(key, src, decryption=True): wkey = [] for i in xrange(8): wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) - dst = "" for i in xrange(len(src)): temp1 = 0; @@ -131,7 +146,9 @@ def getSizeOfTrailingDataEntry(ptr, size): num += (ord(ptr[size - num - 1]) & 0x3) + 1 return num -class DrmStripper: + + +class MobiBook: def loadSection(self, section): if (section + 1 == self.num_sections): endoff = len(self.data_file) @@ -140,6 +157,101 @@ def loadSection(self, section): off = self.sections[section][0] return self.data_file[off:endoff] + def __init__(self, infile): + # initial sanity check on file + self.data_file = file(infile, 'rb').read() + self.header = self.data_file[0:78] + if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd': + raise DrmException("invalid file format") + self.magic = self.header[0x3C:0x3C+8] + self.crypto_type = -1 + + # build up section offset and flag info + self.num_sections, = struct.unpack('>H', self.header[76:78]) + self.sections = [] + for i in xrange(self.num_sections): + offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8]) + flags, val = a1, a2<<16|a3<<8|a4 + self.sections.append( (offset, flags, val) ) + + # parse information from section 0 + self.sect = self.loadSection(0) + self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) + + if self.magic == 'TEXtREAd': + print "Book has format: ", self.magic + self.extra_data_flags = 0 + self.mobi_length = 0 + self.mobi_version = -1 + self.meta_array = {} + return + self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18]) + self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C]) + print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length) + self.extra_data_flags = 0 + if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): + self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) + print "Extra Data Flags = %d" % self.extra_data_flags + if self.mobi_version < 7: + # multibyte utf8 data is included in the encryption for mobi_version 6 and below + # so clear that byte so that we leave it to be decrypted. + self.extra_data_flags &= 0xFFFE + + # if exth region exists parse it for metadata array + self.meta_array = {} + try: + exth_flag, = struct.unpack('>L', self.sect[0x80:0x84]) + exth = 'NONE' + if exth_flag & 0x40: + exth = self.sect[16 + self.mobi_length:] + if (len(exth) >= 4) and (exth[:4] == 'EXTH'): + nitems, = struct.unpack('>I', exth[8:12]) + pos = 12 + for i in xrange(nitems): + type, size = struct.unpack('>II', exth[pos: pos + 8]) + content = exth[pos + 8: pos + size] + self.meta_array[type] = content + # reset the text to speech flag and clipping limit, if present + if type == 401 and size == 9: + # set clipping limit to 100% + self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8) + elif type == 404 and size == 9: + # make sure text to speech is enabled + self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) + # print type, size, content, content.encode('hex') + pos += size + except: + self.meta_array = {} + pass + + def getBookTitle(self): + title = '' + if 503 in self.meta_array: + title = self.meta_array[503] + else : + toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c]) + tend = toff + tlen + title = self.sect[toff:tend] + if title == '': + title = self.header[:32] + title = title.split("\0")[0] + return title + + def getPIDMetaInfo(self): + rec209 = '' + token = '' + if 209 in self.meta_array: + rec209 = self.meta_array[209] + data = rec209 + # The 209 data comes in five byte groups. Interpret the last four bytes + # of each group as a big endian unsigned integer to get a key value + # if that key exists in the meta_array, append its contents to the token + for i in xrange(0,len(data),5): + val, = struct.unpack('>I',data[i+1:i+5]) + sval = self.meta_array.get(val,'') + token += sval + return rec209, token + def patch(self, off, new): self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):] @@ -152,134 +264,136 @@ def patchSection(self, section, new, in_off = 0): assert off + in_off + len(new) <= endoff self.patch(off + in_off, new) - def parseDRM(self, data, count, pid): - pid = pid.ljust(16,'\0') - keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96" - temp_key = PC1(keyvec1, pid, False) - temp_key_sum = sum(map(ord,temp_key)) & 0xff + def parseDRM(self, data, count, pidlist): found_key = None - for i in xrange(count): - verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) - cookie = PC1(temp_key, cookie) - ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) - if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1: - found_key = finalkey + keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96" + for pid in pidlist: + bigpid = pid.ljust(16,'\0') + temp_key = PC1(keyvec1, bigpid, False) + temp_key_sum = sum(map(ord,temp_key)) & 0xff + found_key = None + for i in xrange(count): + verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) + if cksum == temp_key_sum: + cookie = PC1(temp_key, cookie) + ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) + if verification == ver and (flags & 0x1F) == 1: + found_key = finalkey + break + if found_key != None: break if not found_key: # Then try the default encoding that doesn't require a PID + pid = "00000000" temp_key = keyvec1 temp_key_sum = sum(map(ord,temp_key)) & 0xff for i in xrange(count): verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) - cookie = PC1(temp_key, cookie) - ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) - if verification == ver and cksum == temp_key_sum: - found_key = finalkey - break - return found_key - - def __init__(self, data_file, pid): - 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") + if cksum == temp_key_sum: + cookie = PC1(temp_key, cookie) + ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) + if verification == ver: + found_key = finalkey + break + return [found_key,pid] - self.data_file = data_file - header = data_file[0:72] - if header[0x3C:0x3C+8] != 'BOOKMOBI': - raise DrmException("invalid file format") - self.num_sections, = struct.unpack('>H', data_file[76:78]) - - self.sections = [] - for i in xrange(self.num_sections): - offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', data_file[78+i*8:78+i*8+8]) - flags, val = a1, a2<<16|a3<<8|a4 - self.sections.append( (offset, flags, val) ) - - sect = self.loadSection(0) - records, = struct.unpack('>H', sect[0x8:0x8+2]) - mobi_length, = struct.unpack('>L',sect[0x14:0x18]) - mobi_version, = struct.unpack('>L',sect[0x68:0x6C]) - extra_data_flags = 0 - print "MOBI header version = %d, length = %d" %(mobi_version, mobi_length) - 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 6 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]) + def processBook(self, pidlist): + crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) + print 'Crypto Type is: ', crypto_type + self.crypto_type = crypto_type if crypto_type == 0: print "This book is not encrypted." - else: - if crypto_type == 1: - raise DrmException("cannot decode Mobipocket encryption type 1") - if crypto_type != 2: - raise DrmException("unknown encryption type: %d" % crypto_type) + return self.data_file + if crypto_type != 2 and crypto_type != 1: + raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type) + + goodpids = [] + for pid in pidlist: + if len(pid)==10: + if checksumPid(pid[0:-2]) != pid: + print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2]) + goodpids.append(pid[0:-2]) + elif len(pid)==8: + goodpids.append(pid) + if self.crypto_type == 1: + t1_keyvec = "QDCVEPMU675RUBSZ" + if self.magic == 'TEXtREAd': + bookkey_data = self.sect[0x0E:0x0E+16] + elif self.mobi_version < 0: + bookkey_data = self.sect[0x90:0x90+16] + else: + bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32] + pid = "00000000" + found_key = PC1(t1_keyvec, bookkey_data) + else : # calculate the keys - drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', sect[0xA8:0xA8+16]) + drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16]) if drm_count == 0: - raise DrmException("no PIDs found in this file") - found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid) + raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.") + found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids) if not found_key: - raise DrmException("no key found. maybe the PID is incorrect") - + raise DrmException("No key found. Most likely the correct PID has not been given.") # kill the drm keys self.patchSection(0, "\0" * drm_size, drm_ptr) # kill the drm pointers self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8) - # clear the crypto type - self.patchSection(0, "\0" * 2, 0xC) - - # decrypt sections - print "Decrypting. Please wait . . .", - new_data = self.data_file[:self.sections[1][0]] - for i in xrange(1, records+1): - data = self.loadSection(i) - extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags) - if i%100 == 0: - print ".", - # print "record %d, extra_size %d" %(i,extra_size) - new_data += PC1(found_key, data[0:len(data) - extra_size]) - if extra_size > 0: - new_data += data[-extra_size:] - #self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size])) - if self.num_sections > records+1: - new_data += self.data_file[self.sections[records+1][0]:] - self.data_file = new_data - print "done" - - def getResult(self): + + if pid=="00000000": + print "File has default encryption, no specific PID." + else: + print "File is encoded with PID "+checksumPid(pid)+"." + + # clear the crypto type + self.patchSection(0, "\0" * 2, 0xC) + + # decrypt sections + print "Decrypting. Please wait . . .", + new_data = self.data_file[:self.sections[1][0]] + for i in xrange(1, self.records+1): + data = self.loadSection(i) + extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) + if i%100 == 0: + print ".", + # print "record %d, extra_size %d" %(i,extra_size) + new_data += PC1(found_key, data[0:len(data) - extra_size]) + if extra_size > 0: + new_data += data[-extra_size:] + if self.num_sections > self.records+1: + new_data += self.data_file[self.sections[self.records+1][0]:] + self.data_file = new_data + print "done" return self.data_file def getUnencryptedBook(infile,pid): - sys.stdout=Unbuffered(sys.stdout) - data_file = file(infile, 'rb').read() - strippedFile = DrmStripper(data_file, pid) - return strippedFile.getResult() + if not os.path.isfile(infile): + raise DrmException('Input File Not Found') + book = MobiBook(infile) + return book.processBook([pid]) + +def getUnencryptedBookWithList(infile,pidlist): + if not os.path.isfile(infile): + raise DrmException('Input File Not Found') + book = MobiBook(infile) + return book.processBook(pidlist) def main(argv=sys.argv): - sys.stdout=Unbuffered(sys.stdout) print ('MobiDeDrm v%(__version__)s. ' 'Copyright 2008-2010 The Dark Reverser.' % globals()) - if len(argv)<4: + if len(argv)<3 or len(argv)>4: print "Removes protection from Mobipocket books" print "Usage:" - print " %s " % sys.argv[0] + print " %s []" % sys.argv[0] return 1 else: infile = argv[1] outfile = argv[2] - pid = argv[3] + if len(argv) is 4: + pidlist = argv[3].split(',') + else: + pidlist = {} try: - stripped_file = getUnencryptedBook(infile, pid) + stripped_file = getUnencryptedBookWithList(infile, pidlist) file(outfile, 'wb').write(stripped_file) except DrmException, e: print "Error: %s" % e diff --git a/Mobi_Additional_Tools/lib/mobidedrm.py b/Mobi_Additional_Tools/lib/mobidedrm.py index ec756b91..e660a1a2 100644 --- a/Mobi_Additional_Tools/lib/mobidedrm.py +++ b/Mobi_Additional_Tools/lib/mobidedrm.py @@ -47,8 +47,9 @@ # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!) +# 0.28 - slight additional changes to metadata token generation (None -> '') -__version__ = '0.27' +__version__ = '0.28' import sys @@ -237,12 +238,11 @@ def getBookTitle(self): return title def getPIDMetaInfo(self): - rec209 = None - token = None + rec209 = '' + token = '' if 209 in self.meta_array: rec209 = self.meta_array[209] data = rec209 - token = '' # The 209 data comes in five byte groups. Interpret the last four bytes # of each group as a big endian unsigned integer to get a key value # if that key exists in the meta_array, append its contents to the token