Advertisement
Guest User

Untitled

a guest
May 7th, 2012
26
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.21 KB | None | 0 0
  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. # 0.10 - Outputs unencrypted files as-is, so that when run as a Calibre
  25. # import filter it works when importing unencrypted files.
  26. # Also now handles encrypted files that don't need a specific PID.
  27. # 0.11 - use autoflushed stdout and proper return values
  28. # 0.12 - Fix for problems with metadata import as Calibre plugin, report errors
  29. # 0.13 - Formatting fixes: retabbed file, removed trailing whitespace
  30. # and extra blank lines, converted CR/LF pairs at ends of each line,
  31. # and other cosmetic fixes.
  32.  
  33. __version__ = '0.13'
  34.  
  35. import sys
  36. import struct
  37. import binascii
  38.  
  39. class Unbuffered:
  40. def __init__(self, stream):
  41. self.stream = stream
  42. def write(self, data):
  43. self.stream.write(data)
  44. self.stream.flush()
  45. def __getattr__(self, attr):
  46. return getattr(self.stream, attr)
  47.  
  48. class DrmException(Exception):
  49. pass
  50.  
  51. # Implementation of Pukall Cipher 1
  52. def PC1(key, src, decryption=True):
  53. sum1 = 0;
  54. sum2 = 0;
  55. keyXorVal = 0;
  56. if len(key)!=16:
  57. print "Bad key length!"
  58. return None
  59. wkey = []
  60. for i in xrange(8):
  61. wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
  62.  
  63. dst = ""
  64. for i in xrange(len(src)):
  65. temp1 = 0;
  66. byteXorVal = 0;
  67. for j in xrange(8):
  68. temp1 ^= wkey[j]
  69. sum2 = (sum2+j)*20021 + sum1
  70. sum1 = (temp1*346)&0xFFFF
  71. sum2 = (sum2+sum1)&0xFFFF
  72. temp1 = (temp1*20021+1)&0xFFFF
  73. byteXorVal ^= temp1 ^ sum2
  74. curByte = ord(src[i])
  75. if not decryption:
  76. keyXorVal = curByte * 257;
  77. curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
  78. if decryption:
  79. keyXorVal = curByte * 257;
  80. for j in xrange(8):
  81. wkey[j] ^= keyXorVal;
  82. dst+=chr(curByte)
  83. return dst
  84.  
  85. def checksumPid(s):
  86. letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
  87. crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
  88. crc = crc ^ (crc >> 16)
  89. res = s
  90. l = len(letters)
  91. for i in (0,1):
  92. b = crc & 0xff
  93. pos = (b // l) ^ (b % l)
  94. res += letters[pos%l]
  95. crc >>= 8
  96. return res
  97.  
  98. def getSizeOfTrailingDataEntries(ptr, size, flags):
  99. def getSizeOfTrailingDataEntry(ptr, size):
  100. bitpos, result = 0, 0
  101. if size <= 0:
  102. return result
  103. while True:
  104. v = ord(ptr[size-1])
  105. result |= (v & 0x7F) << bitpos
  106. bitpos += 7
  107. size -= 1
  108. if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
  109. return result
  110. num = 0
  111. testflags = flags >> 1
  112. while testflags:
  113. if testflags & 1:
  114. num += getSizeOfTrailingDataEntry(ptr, size - num)
  115. testflags >>= 1
  116. if flags & 1:
  117. num += (ord(ptr[size - num - 1]) & 0x3) + 1
  118. return num
  119.  
  120. class DrmStripper:
  121. def loadSection(self, section):
  122. if (section + 1 == self.num_sections):
  123. endoff = len(self.data_file)
  124. else:
  125. endoff = self.sections[section + 1][0]
  126. off = self.sections[section][0]
  127. return self.data_file[off:endoff]
  128.  
  129. def patch(self, off, new):
  130. self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
  131.  
  132. def patchSection(self, section, new, in_off = 0):
  133. if (section + 1 == self.num_sections):
  134. endoff = len(self.data_file)
  135. else:
  136. endoff = self.sections[section + 1][0]
  137. off = self.sections[section][0]
  138. assert off + in_off + len(new) <= endoff
  139. self.patch(off + in_off, new)
  140.  
  141. def parseDRM(self, data, count, pid):
  142. pid = pid.ljust(16,'\0')
  143. keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
  144. temp_key = PC1(keyvec1, pid, False)
  145. temp_key_sum = sum(map(ord,temp_key)) & 0xff
  146. found_key = None
  147. for i in xrange(count):
  148. verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
  149. cookie = PC1(temp_key, cookie)
  150. ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
  151. if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1:
  152. found_key = finalkey
  153. break
  154. if not found_key:
  155. # Then try the default encoding that doesn't require a PID
  156. temp_key = keyvec1
  157. temp_key_sum = sum(map(ord,temp_key)) & 0xff
  158. for i in xrange(count):
  159. verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
  160. cookie = PC1(temp_key, cookie)
  161. ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
  162. if verification == ver and cksum == temp_key_sum:
  163. found_key = finalkey
  164. break
  165. return found_key
  166.  
  167. def __init__(self, data_file, pid):
  168. if checksumPid(pid[0:-2]) != pid:
  169. raise DrmException("invalid PID checksum")
  170. pid = pid[0:-2]
  171.  
  172. self.data_file = data_file
  173. header = data_file[0:72]
  174. if header[0x3C:0x3C+8] != 'BOOKMOBI':
  175. raise DrmException("invalid file format")
  176. self.num_sections, = struct.unpack('>H', data_file[76:78])
  177.  
  178. self.sections = []
  179. for i in xrange(self.num_sections):
  180. offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', data_file[78+i*8:78+i*8+8])
  181. flags, val = a1, a2<<16|a3<<8|a4
  182. self.sections.append( (offset, flags, val) )
  183.  
  184. sect = self.loadSection(0)
  185. records, = struct.unpack('>H', sect[0x8:0x8+2])
  186. mobi_length, = struct.unpack('>L',sect[0x14:0x18])
  187. mobi_version, = struct.unpack('>L',sect[0x68:0x6C])
  188. extra_data_flags = 0
  189. print "MOBI header length = %d" %mobi_length
  190. print "MOBI header version = %d" %mobi_version
  191. if (mobi_length >= 0xE4) and (mobi_version > 5):
  192. extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4])
  193. print "Extra Data Flags = %d" %extra_data_flags
  194.  
  195. crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
  196. if crypto_type == 0:
  197. print "This book is not encrypted."
  198. else:
  199. if crypto_type == 1:
  200. raise DrmException("cannot decode Mobipocket encryption type 1")
  201. if crypto_type != 2:
  202. raise DrmException("unknown encryption type: %d" % crypto_type)
  203.  
  204. # calculate the keys
  205. drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', sect[0xA8:0xA8+16])
  206. if drm_count == 0:
  207. raise DrmException("no PIDs found in this file")
  208. found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid)
  209. if not found_key:
  210. raise DrmException("no key found. maybe the PID is incorrect")
  211.  
  212. # kill the drm keys
  213. self.patchSection(0, "\0" * drm_size, drm_ptr)
  214. # kill the drm pointers
  215. self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
  216. # clear the crypto type
  217. self.patchSection(0, "\0" * 2, 0xC)
  218.  
  219. # decrypt sections
  220. print "Decrypting. Please wait...",
  221. for i in xrange(1, records+1):
  222. data = self.loadSection(i)
  223. extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
  224. # print "record %d, extra_size %d" %(i,extra_size)
  225. self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
  226. print "done"
  227.  
  228. def getResult(self):
  229. return self.data_file
  230.  
  231. if not __name__ == "__main__":
  232. from calibre.customize import FileTypePlugin
  233.  
  234. class MobiDeDRM(FileTypePlugin):
  235. name = 'MobiDeDRM' # Name of the plugin
  236. description = 'Removes DRM from secure Mobi files'
  237. supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
  238. author = 'The Dark Reverser' # The author of this plugin
  239. version = (0, 1, 2) # The version number of this plugin
  240. file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
  241. on_import = True # Run this plugin during the import
  242.  
  243. def run(self, path_to_ebook):
  244. from calibre.gui2 import is_ok_to_use_qt
  245. from PyQt4.Qt import QMessageBox
  246. PID = self.site_customization
  247. data_file = file(path_to_ebook, 'rb').read()
  248. ar = PID.split(',')
  249. for i in ar:
  250. try:
  251. unlocked_file = DrmStripper(data_file, i).getResult()
  252. except DrmException:
  253. # ignore the error
  254. pass
  255. else:
  256. of = self.temporary_file('.mobi')
  257. of.write(unlocked_file)
  258. of.close()
  259. return of.name
  260. if is_ok_to_use_qt():
  261. d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Couldn't decode: %s\n\nImporting encrypted version." % path_to_ebook)
  262. d.show()
  263. d.raise_()
  264. d.exec_()
  265. return path_to_ebook
  266.  
  267. def customization_help(self, gui=False):
  268. return 'Enter PID (separate multiple PIDs with comma)'
  269.  
  270. if __name__ == "__main__":
  271. sys.stdout=Unbuffered(sys.stdout)
  272. print ('MobiDeDrm v%(__version__)s. '
  273. 'Copyright 2008-2010 The Dark Reverser.' % globals())
  274. if len(sys.argv)<4:
  275. print "Removes protection from Mobipocket books"
  276. print "Usage:"
  277. print " %s <infile> <outfile> <PID>" % sys.argv[0]
  278. sys.exit(1)
  279. else:
  280. infile = sys.argv[1]
  281. outfile = sys.argv[2]
  282. pid = sys.argv[3]
  283. data_file = file(infile, 'rb').read()
  284. try:
  285. strippedFile = DrmStripper(data_file, pid)
  286. file(outfile, 'wb').write(strippedFile.getResult())
  287. except DrmException, e:
  288. print "Error: %s" % e
  289. sys.exit(1)
  290. sys.exit(0)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement