SHARE
TWEET

Untitled

a guest Nov 25th, 2009 1,658 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. # MobiDeDRM 0.06 converted to run as a Calibre plugin
  2. # Save as mobidedrm_plugin.py
  3. # Create a zip file containing it
  4. # Use 'calibre-customize -a $ZIPFILE.zip' to import it
  5.  
  6. # MobiDeDRM Changelog
  7. #  0.01 - Initial version
  8. #  0.02 - Huffdic compressed books were not properly decrypted
  9. #  0.04 - Wasn't sanity checking size of data record
  10. #  0.05 - It seems that the extra data flags take two bytes not four
  11. #  0.06 - And that low bit does mean something after all :-)
  12.  
  13. import sys,struct,binascii
  14. import os, subprocess
  15. from calibre.customize import FileTypePlugin
  16.  
  17. class DrmException(Exception):
  18.         pass
  19.  
  20. #implementation of Pukall Cipher 1
  21. def PC1(key, src, decryption=True):
  22.     sum1 = 0;
  23.     sum2 = 0;
  24.     keyXorVal = 0;
  25.     if len(key)!=16:
  26.         print "Bad key length!"
  27.         return None
  28.     wkey = []
  29.     for i in xrange(8):
  30.         wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
  31.  
  32.     dst = ""
  33.     for i in xrange(len(src)):
  34.         temp1 = 0;
  35.         byteXorVal = 0;
  36.         for j in xrange(8):
  37.             temp1 ^= wkey[j]
  38.             sum2  = (sum2+j)*20021 + sum1
  39.             sum1  = (temp1*346)&0xFFFF
  40.             sum2  = (sum2+sum1)&0xFFFF
  41.             temp1 = (temp1*20021+1)&0xFFFF
  42.             byteXorVal ^= temp1 ^ sum2
  43.         curByte = ord(src[i])
  44.         if not decryption:
  45.             keyXorVal = curByte * 257;
  46.         curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
  47.         if decryption:
  48.             keyXorVal = curByte * 257;
  49.         for j in xrange(8):
  50.             wkey[j] ^= keyXorVal;
  51.         dst+=chr(curByte)
  52.     return dst
  53.  
  54. def checksumPid(s):
  55.         letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
  56.         crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
  57.         crc = crc ^ (crc >> 16)
  58.         res = s
  59.         l = len(letters)
  60.         for i in (0,1):
  61.                 b = crc & 0xff
  62.                 pos = (b // l) ^ (b % l)
  63.                 res += letters[pos%l]
  64.                 crc >>= 8
  65.         return res
  66.  
  67. def getSizeOfTrailingDataEntries(ptr, size, flags):
  68.         def getSizeOfTrailingDataEntry(ptr, size):
  69.                 bitpos, result = 0, 0
  70.                 if size <= 0:
  71.                         return result
  72.                 while True:
  73.                         v = ord(ptr[size-1])
  74.                         result |= (v & 0x7F) << bitpos
  75.                         bitpos += 7
  76.                         size -= 1
  77.                         if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
  78.                                 return result
  79.         num = 0
  80.         testflags = flags >> 1
  81.         while testflags:
  82.                 if testflags & 1:
  83.                         num += getSizeOfTrailingDataEntry(ptr, size - num)
  84.                 testflags >>= 1
  85.         if flags & 1:
  86.                 num += (ord(ptr[size - num - 1]) & 0x3) + 1
  87.         return num
  88. class DrmStripper:
  89.         def loadSection(self, section):
  90.                 if (section + 1 == self.num_sections):
  91.                         endoff = len(self.data_file)
  92.                 else:
  93.                         endoff = self.sections[section + 1][0]
  94.                 off = self.sections[section][0]
  95.                 return self.data_file[off:endoff]
  96.  
  97.         def patch(self, off, new):     
  98.                 self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
  99.  
  100.         def patchSection(self, section, new, in_off = 0):
  101.                 if (section + 1 == self.num_sections):
  102.                         endoff = len(self.data_file)
  103.                 else:
  104.                         endoff = self.sections[section + 1][0]
  105.                 off = self.sections[section][0]
  106.                 assert off + in_off + len(new) <= endoff
  107.                 self.patch(off + in_off, new)
  108.  
  109.         def parseDRM(self, data, count, pid):
  110.                 pid = pid.ljust(16,'\0')
  111.                 keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
  112.                 temp_key = PC1(keyvec1, pid, False)
  113.                 temp_key_sum = sum(map(ord,temp_key)) & 0xff
  114.                 found_key = None
  115.                 for i in xrange(count):
  116.                         verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
  117.                         cookie = PC1(temp_key, cookie)
  118.                         ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
  119.                         if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1:
  120.                                 found_key = finalkey
  121.                                 break
  122.                 return found_key               
  123.  
  124.  
  125.         def __init__(self, data_file, pid):
  126.  
  127.                 if checksumPid(pid[0:-2]) != pid:
  128.                         raise DrmException("invalid PID checksum")
  129.                 pid = pid[0:-2]
  130.                
  131.                 self.data_file = data_file
  132.                 header = data_file[0:72]
  133.                 if header[0x3C:0x3C+8] != 'BOOKMOBI':
  134.                         raise DrmException("invalid file format")
  135.                 self.num_sections, = struct.unpack('>H', data_file[76:78])
  136.  
  137.                 self.sections = []
  138.                 for i in xrange(self.num_sections):
  139.                         offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', data_file[78+i*8:78+i*8+8])
  140.                         flags, val = a1, a2<<16|a3<<8|a4
  141.                         self.sections.append( (offset, flags, val) )
  142.  
  143.                 sect = self.loadSection(0)
  144.                 records, = struct.unpack('>H', sect[0x8:0x8+2])
  145.                 mobi_length, = struct.unpack('>L',sect[0x14:0x18])
  146.                 extra_data_flags = 0
  147.                 if mobi_length >= 0xE4:
  148.                         extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4])
  149.                 crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
  150.                 if crypto_type != 2:
  151.                         raise DrmException("invalid encryption type: %d" % crypto_type)
  152.  
  153.                 # calculate the keys
  154.                 drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', sect[0xA8:0xA8+16])
  155.                 found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid)
  156.                 if not found_key:
  157.                         raise DrmException("no key found. maybe the PID is incorrect")
  158.  
  159.                 # kill the drm keys
  160.                 self.patchSection(0, "\0" * drm_size, drm_ptr)
  161.                 # kill the drm pointers
  162.                 self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
  163.                 # clear the crypto type
  164.                 self.patchSection(0, "\0" * 2, 0xC)
  165.  
  166.                 # decrypt sections
  167.                 print "Decrypting. Please wait...",
  168.                 for i in xrange(1, records+1):
  169.                         data = self.loadSection(i)
  170.                         extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
  171.                         self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
  172.                 print "done"
  173.         def getResult(self):
  174.                 return self.data_file
  175.  
  176.  
  177. class MobiDeDRM(FileTypePlugin):
  178.  
  179.         name                = 'MobiDeDRM' # Name of the plugin
  180.         description         = 'Removes DRM from secure Mobi files'
  181.         supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
  182.         author              = 'Who Says' # The author of this plugin
  183.         version             = (0, 0, 6)   # The version number of this plugin
  184.         file_types          = set(['prc']) # The file types that this plugin will be applied to
  185.         on_import           = True # Run this plugin during the import
  186.  
  187.        
  188.         def run(self, path_to_ebook):
  189.                 of = self.temporary_file('.mobi')
  190.                 PID = self.site_customization
  191.                 data_file = file(path_to_ebook, 'rb').read()
  192.                 try:
  193.                         file(of.name, 'wb').write(DrmStripper(data_file, PID).getResult())
  194.                 except Exception, e:
  195.                         strexcept = 'echo exception: %s > /dev/tty' % e
  196.                         subprocess.call(strexcept,shell=True)
  197.                         raise e
  198.  
  199.                 return of.name
  200.                
  201.         def customization_help(self, gui=False):
  202.                 return 'Enter PID'
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top