MobiDeDRM fixes

Change handling of PIDs to cope with byte arrays or strings passed in. Also fixed handling of a very old default key format.
This commit is contained in:
Apprentice Harper 2020-12-26 15:58:42 +00:00
parent d33f679eae
commit fdf0389936
2 changed files with 23 additions and 14 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.DS_Store

36
DeDRM_plugin/mobidedrm.py Normal file → Executable file
View file

@ -191,19 +191,21 @@ def PC1(key, src, decryption=True):
dst+=bytes([curByte]) dst+=bytes([curByte])
return dst return dst
# accepts unicode returns unicode
def checksumPid(s): def checksumPid(s):
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789' letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
crc = (~binascii.crc32(s,-1))&0xFFFFFFFF crc = (~binascii.crc32(s.encode('utf-8'),-1))&0xFFFFFFFF
crc = crc ^ (crc >> 16) crc = crc ^ (crc >> 16)
res = s res = s
l = len(letters) l = len(letters)
for i in (0,1): for i in (0,1):
b = crc & 0xff b = crc & 0xff
pos = (b // l) ^ (b % l) pos = (b // l) ^ (b % l)
res += letters[pos%l].encode('ascii') res += letters[pos%l]
crc >>= 8 crc >>= 8
return res return res
# expects bytearray
def getSizeOfTrailingDataEntries(ptr, size, flags): def getSizeOfTrailingDataEntries(ptr, size, flags):
def getSizeOfTrailingDataEntry(ptr, size): def getSizeOfTrailingDataEntry(ptr, size):
bitpos, result = 0, 0 bitpos, result = 0, 0
@ -282,7 +284,7 @@ class MobiBook:
self.mobi_codepage = 1252 self.mobi_codepage = 1252
self.mobi_version = -1 self.mobi_version = -1
if self.magic == 'TEXtREAd': if self.magic == b'TEXtREAd':
print("PalmDoc format book detected.") print("PalmDoc format book detected.")
return return
@ -301,7 +303,7 @@ class MobiBook:
# if exth region exists parse it for metadata array # if exth region exists parse it for metadata array
try: try:
exth_flag, = struct.unpack('>L', self.sect[0x80:0x84]) exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
exth = '' exth = b''
if exth_flag & 0x40: if exth_flag & 0x40:
exth = self.sect[16 + self.mobi_length:] exth = self.sect[16 + self.mobi_length:]
if (len(exth) >= 12) and (exth[:4] == b'EXTH'): if (len(exth) >= 12) and (exth[:4] == b'EXTH'):
@ -323,12 +325,13 @@ class MobiBook:
except Exception as e: except Exception as e:
print("Cannot set meta_array: Error: {:s}".format(e.args[0])) print("Cannot set meta_array: Error: {:s}".format(e.args[0]))
#returns unicode
def getBookTitle(self): def getBookTitle(self):
codec_map = { codec_map = {
1252 : 'windows-1252', 1252 : 'windows-1252',
65001 : 'utf-8', 65001 : 'utf-8',
} }
title = '' title = b''
codec = 'windows-1252' codec = 'windows-1252'
if self.magic == b'BOOKMOBI': if self.magic == b'BOOKMOBI':
if 503 in self.meta_array: if 503 in self.meta_array:
@ -339,7 +342,7 @@ class MobiBook:
title = self.sect[toff:tend] title = self.sect[toff:tend]
if self.mobi_codepage in codec_map.keys(): if self.mobi_codepage in codec_map.keys():
codec = codec_map[self.mobi_codepage] codec = codec_map[self.mobi_codepage]
if title == '': if title == b'':
title = self.header[:32] title = self.header[:32]
title = title.split(b'\0')[0] title = title.split(b'\0')[0]
return title.decode(codec) return title.decode(codec)
@ -355,13 +358,15 @@ class MobiBook:
# if that key exists in the meta_array, append its contents to the token # if that key exists in the meta_array, append its contents to the token
for i in range(0,len(data),5): for i in range(0,len(data),5):
val, = struct.unpack('>I',data[i+1:i+5]) val, = struct.unpack('>I',data[i+1:i+5])
sval = self.meta_array.get(val,'') sval = self.meta_array.get(val,b'')
token += sval token += sval
return rec209, token return rec209, token
# new must be byte array
def patch(self, off, new): def patch(self, off, new):
self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):] self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
# new must be byte array
def patchSection(self, section, new, in_off = 0): def patchSection(self, section, new, in_off = 0):
if (section + 1 == self.num_sections): if (section + 1 == self.num_sections):
endoff = len(self.data_file) endoff = len(self.data_file)
@ -371,12 +376,12 @@ class MobiBook:
assert off + in_off + len(new) <= endoff assert off + in_off + len(new) <= endoff
self.patch(off + in_off, new) self.patch(off + in_off, new)
# pids in pidlist must be unicode, returned key is byte array, pid is unicode
def parseDRM(self, data, count, pidlist): def parseDRM(self, data, count, pidlist):
found_key = None found_key = None
keyvec1 = b'\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96' keyvec1 = b'\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96'
for pid in pidlist: for pid in pidlist:
bigpid = pid.ljust(16,b'\0') bigpid = pid.encode('utf-8').ljust(16,b'\0')
bigpid = bigpid
temp_key = PC1(keyvec1, bigpid, False) temp_key = PC1(keyvec1, bigpid, False)
temp_key_sum = sum(temp_key) & 0xff temp_key_sum = sum(temp_key) & 0xff
found_key = None found_key = None
@ -424,6 +429,7 @@ class MobiBook:
return ".azw3" return ".azw3"
return ".mobi" return ".mobi"
# pids in pidlist may be unicode or bytearrays or bytes
def processBook(self, pidlist): def processBook(self, pidlist):
crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
print("Crypto Type is: {0:d}".format(crypto_type)) print("Crypto Type is: {0:d}".format(crypto_type))
@ -445,6 +451,8 @@ class MobiBook:
goodpids = [] goodpids = []
# print("DEBUG ==== pidlist = ", pidlist) # print("DEBUG ==== pidlist = ", pidlist)
for pid in pidlist: for pid in pidlist:
if isinstance(pid,(bytearray,bytes)):
pid = pid.decode('utf-8')
if len(pid)==10: if len(pid)==10:
if checksumPid(pid[0:-2]) != pid: if checksumPid(pid[0:-2]) != pid:
print("Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2]))) print("Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2])))
@ -457,8 +465,8 @@ class MobiBook:
# print("======= DEBUG good pids = ", goodpids) # print("======= DEBUG good pids = ", goodpids)
if self.crypto_type == 1: if self.crypto_type == 1:
t1_keyvec = 'QDCVEPMU675RUBSZ' t1_keyvec = b'QDCVEPMU675RUBSZ'
if self.magic == 'TEXtREAd': if self.magic == b'TEXtREAd':
bookkey_data = self.sect[0x0E:0x0E+16] bookkey_data = self.sect[0x0E:0x0E+16]
elif self.mobi_version < 0: elif self.mobi_version < 0:
bookkey_data = self.sect[0x90:0x90+16] bookkey_data = self.sect[0x90:0x90+16]
@ -473,7 +481,7 @@ class MobiBook:
raise DrmException("Encryption not initialised. Must be opened with Mobipocket Reader first.") raise DrmException("Encryption not initialised. Must be opened with Mobipocket Reader first.")
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids) found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
if not found_key: if not found_key:
raise DrmException("No key found in {0:d} keys tried.".format(len(goodpids))) raise DrmException("No key found in {0:d} PIDs tried.".format(len(goodpids)))
# kill the drm keys # kill the drm keys
self.patchSection(0, b'\0' * drm_size, drm_ptr) self.patchSection(0, b'\0' * drm_size, drm_ptr)
# kill the drm pointers # kill the drm pointers
@ -509,6 +517,7 @@ class MobiBook:
print("done") print("done")
return return
# pids in pidlist must be unicode
def getUnencryptedBook(infile,pidlist): def getUnencryptedBook(infile,pidlist):
if not os.path.isfile(infile): if not os.path.isfile(infile):
raise DrmException("Input File Not Found.") raise DrmException("Input File Not Found.")
@ -530,8 +539,7 @@ def cli_main():
infile = argv[1] infile = argv[1]
outfile = argv[2] outfile = argv[2]
if len(argv) == 4: if len(argv) == 4:
# convert from unicode to bytearray before splitting. pidlist = argv[3].split(',')
pidlist = argv[3].encode('utf-8').split(b',')
else: else:
pidlist = [] pidlist = []
try: try: