Guest User

Alf

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