SHARE
TWEET

Untitled

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