SHARE
TWEET

Untitled

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