First Applescript for calling mobidedrm python script

This commit is contained in:
Paul Durrant 2008-08-27 19:00:23 +01:00 committed by Apprentice Alf
parent 87630e2bd4
commit 1096e48342
10 changed files with 355 additions and 0 deletions

View file

@ -0,0 +1,55 @@
Mobipocket Unlocker AppleScript Version 3
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 decrypter.
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.
If you run the script directly, you'll be asked to select a folder of Mobipocket files, and then your PID. The script will attempt to unlock all Mobipocket files in the folder selected, including files in subfolders.
If you drag and drop files and/or folders onto the script, you will be asked for your PID and then the script will attampt to unlock all the Mobipocket files dragged directly, and all Mobipocket files in the dragged folders (& subfolders).
If the Python script returns an error, the AppleScript will report it, otherwise it works without any visible feedback.
The MobiDeDRM.py scripts out there don't work perfectly. If you get the 0.02 version of the script, you can fix up a few problems by apply these changes:
After line 63:
[tab][tab]bitpos, result = 0,0
add in the following two lines:
[tab][tab]if size <= 0:
[tab][tab][tab]return result
After line 135:
[tab][tab]records, = struct.unpack('>H', sect[0x8:0x8+2])
add in the following three lines
[tab][tab]mobi_length, = struct.unpack('>L',sect[0x14:0x18])
[tab][tab]extra_data_flags = 0
[tab][tab]if mobi_length >= 0xE4:
(note that tricky comma after the first instance of mobi_length)
Add a tab to line 136:
[tab][tab]extra_data_flags, = struct.unpack('>L', sect[0xF0:0xF4])
to make it
[tab][tab][tab]extra_data_flags, = struct.unpack('>L', sect[0xF0:0xF4])
and change line 165:
print "MobiDeDrm v0.02. Copyright (c) 2008 The Dark Reverser"
to
print "MobiDeDrm v0.04. Copyright (c) 2008 The Dark Reverser"
just so that you don't get confused at the command line.
Oh -- [tab] just means a single tab character - not the five literal characters [tab].

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleAllowMixedLocalizations</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>*</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>****</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>droplet</string>
<key>CFBundleIconFile</key>
<string>droplet</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Mobipocket Unlocker 4</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>dplt</string>
<key>LSRequiresCarbon</key>
<true/>
<key>WindowState</key>
<dict>
<key>name</key>
<string>ScriptWindowState</string>
<key>positionOfDivider</key>
<real>435</real>
<key>savedFrame</key>
<string>53 78 661 691 0 0 1280 778 </string>
<key>selectedTabView</key>
<string>result</string>
</dict>
</dict>
</plist>

View file

@ -0,0 +1 @@
APPLdplt

View file

@ -0,0 +1,186 @@
# 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 - Huffdic compressed books were not properly decrypted
# 0.03 - fix 0.02 to work with all Mobipocket eBooks
# 0.04 - Wasn't checking MOBI header length
import sys,struct,binascii
class DrmException(Exception):
pass
#implementation of Pukall Cipher 1
def PC1(key, src, decryption=True):
sum1 = 0;
sum2 = 0;
keyXorVal = 0;
if len(key)!=16:
print "Bad key length!"
return None
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;
byteXorVal = 0;
for j in xrange(8):
temp1 ^= wkey[j]
sum2 = (sum2+j)*20021 + sum1
sum1 = (temp1*346)&0xFFFF
sum2 = (sum2+sum1)&0xFFFF
temp1 = (temp1*20021+1)&0xFFFF
byteXorVal ^= temp1 ^ sum2
curByte = ord(src[i])
if not decryption:
keyXorVal = curByte * 257;
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
if decryption:
keyXorVal = curByte * 257;
for j in xrange(8):
wkey[j] ^= keyXorVal;
dst+=chr(curByte)
return dst
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
def getSizeOfTrailingDataEntries(ptr, size, flags):
def getSizeOfTrailingDataEntry(ptr, size):
bitpos, result = 0, 0
if size <= 0:
return result
while True:
v = ord(ptr[size-1])
result |= (v & 0x7F) << bitpos
bitpos += 7
size -= 1
if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
return result
num = 0
flags >>= 1
while flags:
if flags & 1:
num += getSizeOfTrailingDataEntry(ptr, size - num)
flags >>= 1
return num
class DrmStripper:
def loadSection(self, section):
if (section + 1 == self.num_sections):
endoff = len(self.data_file)
else:
endoff = self.sections[section + 1][0]
off = self.sections[section][0]
return self.data_file[off:endoff]
def patch(self, off, new):
self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
def patchSection(self, section, new, in_off = 0):
if (section + 1 == self.num_sections):
endoff = len(self.data_file)
else:
endoff = self.sections[section + 1][0]
off = self.sections[section][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
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
break
return found_key
def __init__(self, data_file, pid):
if checksumPid(pid[0:-2]) != pid:
raise DrmException("invalid PID checksum")
pid = pid[0:-2]
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])
extra_data_flags = 0
if mobi_length >= 0xE4:
extra_data_flags, = struct.unpack('>L', sect[0xF0:0xF4])
crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
if crypto_type != 2:
raise DrmException("invalid encryption type: %d" % crypto_type)
# calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', sect[0xA8:0xA8+16])
found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid)
if not found_key:
raise DrmException("no key found. maybe the PID is incorrect")
# 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...",
for i in xrange(1, records+1):
data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
print "done"
def getResult(self):
return self.data_file
print "MobiDeDrm v0.04. Copyright (c) 2008 The Dark Reverser"
if len(sys.argv)<4:
print "Removes protection from Mobipocket books"
print "Usage:"
print " mobidedrm infile.mobi outfile.mobi PID"
else:
infile = sys.argv[1]
outfile = sys.argv[2]
pid = sys.argv[3]
data_file = file(infile, 'rb').read()
try:
file(outfile, 'wb').write(DrmStripper(data_file, pid).getResult())
except DrmException, e:
print "Error: %s" % e

View file

@ -0,0 +1,59 @@
on unlockfile(encryptedFile, MobiDeDRMPath, 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 \"" & MobiDeDRMPath & "\" \"" & encryptedFilePath & "\" \"" & unlockedFilePath & "\" '" & 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
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")
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" then
unlockfile(this_item as text, MobiDeDRMPath, encryptionKey)
end if
end if
end repeat
end open

View file

@ -0,0 +1,4 @@
{\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf330
{\fonttbl}
{\colortbl;\red255\green255\blue255;}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B