Advertisement
Guest User

Untitled

a guest
Nov 25th, 2009
1,752
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.42 KB | None | 0 0
  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'
  203.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement