Guest User

Obok - Kobo DRM removal

a guest
Apr 29th, 2012
2,073
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.69 KB | None | 0 0
  1. #!/usr/bin/python
  2. ##########################################################
  3. #                    KOBO DRM CRACK BY                   #
  4. #                      PHYSISTICATED                     #
  5. #                      8====D~~~~ O:                     #
  6. ##########################################################
  7. # This app was made for Python 2.7 on Windows 32-bit
  8. #
  9. # This app needs pycrypto - get from here:
  10. # http://www.voidspace.org.uk/python/modules.shtml
  11. #
  12. # Usage: obok.py
  13. # Choose the book you want to decrypt
  14. #
  15. # Shouts to my krew - you know who you are - and one in
  16. # particular who gave me a lot of help with this - thank
  17. # you so much!
  18. #
  19. # Kopimi /K\
  20. # Keep sharing, keep copying, but remember that nothing is
  21. # for free - make sure you compensate your favorite
  22. # authors - and cut out the middle man whenever possible
  23. # ;) ;) ;)
  24. #
  25. # DRM AUTOPSY
  26. # The Kobo DRM was incredibly easy to crack, but it took
  27. # me months to get around to making this. Here's the
  28. # basics of how it works:
  29. # 1: Get MAC address of first NIC in ipconfig (sometimes
  30. # stored in registry as pwsdid)
  31. # 2: Get user ID (stored in tons of places, this gets it
  32. # from HKEY_CURRENT_USER\Software\Kobo\Kobo Desktop
  33. # Edition\Browser\cookies)
  34. # 3: Concatenate and SHA256, take the second half - this
  35. # is your master key
  36. # 4: Open %LOCALAPPDATA%\Kobo Desktop Editions\Kobo.sqlite
  37. # and dump content_keys
  38. # 5: Unbase64 the keys, then decode these with the master
  39. # key - these are your page keys
  40. # 6: Unzip EPUB of your choice, decrypt each page with its
  41. # page key, then zip back up again
  42. #
  43. # WHY USE THIS WHEN INEPT WORKS FINE? (adobe DRM stripper)
  44. # Inept works very well, but authors on Kobo can choose
  45. # what DRM they want to use - and some have chosen not to
  46. # let people download them with Adobe Digital Editions -
  47. # they would rather lock you into a single platform.
  48. #
  49. # With Obok, you can sync Kobo Desktop, decrypt all your
  50. # ebooks, and then use them on whatever device you want
  51. # - you bought them, you own them, you can do what you
  52. # like with them.
  53. #
  54. # Obok is Kobo backwards, but it is also means "next to"
  55. # in Polish.
  56. # When you buy a real book, it is right next to you. You
  57. # can read it at home, at work, on a train, you can lend
  58. # it to a friend, you can scribble on it, and add your own
  59. # explanations/translations.
  60. #
  61. # Obok gives you this power over your ebooks - no longer
  62. # are you restricted to one device. This allows you to
  63. # embed foreign fonts into your books, as older Kobo's
  64. # can't display them properly. You can read your books
  65. # on your phones, in different PC readers, and different
  66. # ereader devices. You can share them with your friends
  67. # too, if you like - you can do that with a real book
  68. # after all.
  69. #
  70.  
  71. import os
  72. import _winreg
  73. import re
  74. import string
  75. import hashlib
  76. import sqlite3
  77. import base64
  78. import binascii
  79. import zipfile
  80. from Crypto.Cipher import AES
  81. from uuid import getnode as get_mac
  82.  
  83. def SHA256(raw):
  84.     return hashlib.sha256(raw).hexdigest()
  85.  
  86. def RemoveAESPadding(contents):
  87.     lastchar = binascii.b2a_hex(contents[-1:])
  88.     len = int(lastchar, 16)
  89.     padding = len
  90.     if(len == 1):
  91.         return contents[:-1]
  92.     if(len < 16):
  93.         for i in range(len):
  94.             testchar = binascii.b2a_hex(contents[-len:-(len-1)])
  95.             if(testchar != lastchar):
  96.                 padding = 0
  97.     if(padding > 0):
  98.         contents = contents[:-padding]
  99.     return contents
  100.    
  101. def GetVolumeKeys(c):
  102.     volumekeys = {}
  103.     for row in c.execute("SELECT * from content_keys"):
  104.         if(row[0] not in volumekeys):
  105.             volumekeys[row[0]] = {}
  106.         volumekeys[row[0]][row[1]] = {}
  107.         volumekeys[row[0]][row[1]]["encryptedkey"] = base64.b64decode(row[2])
  108.         volumekeys[row[0]][row[1]]["decryptedkey"] = enc.decrypt(volumekeys[row[0]][row[1]]["encryptedkey"])
  109.     # get book name
  110.     for key in volumekeys.keys():
  111.         volumekeys[key]["title"] = c.execute("SELECT Title from content where ContentID = '%s'" % (key)).fetchone()[0]
  112.     return volumekeys
  113.  
  114.  
  115. def GetMacString():
  116.     # terrible way, but get_mac() only returns proper interfaces
  117.     # this will return the first in ipconfig
  118.     # Kobo appears to do the same
  119.     ipconfig = os.popen('ipconfig /all').read()
  120.     c = re.compile("Physical Address[^0-9A-F]+([0-9A-Fa-f-]+)", re.MULTILINE)
  121.     m = c.search(ipconfig)
  122.     mac = re.sub("-", ":", m.group(1))
  123.     return mac
  124.  
  125.     #macnum = get_mac()
  126.     #macarray = []
  127.     #for i in range(6):
  128.     #   macarray.append((hex((macnum >> (40-(i*8)) & 0xFF))[2:-1]).zfill(2))
  129.     #return string.upper(string.join(macarray, ":"))
  130.  
  131. def ByteArrayToString(bytearray):
  132.     return re.match("@ByteArray\\((.+)\\)", bytearray).group(1)
  133.  
  134. def GetUserHexKey():
  135.     regkey_browser = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Software\\Kobo\\Kobo Desktop Edition\\Browser")
  136.     cookies = _winreg.QueryValueEx(regkey_browser, "cookies")
  137.     bytearrays = cookies[0]
  138.     # find wsuid and pwsdid
  139.     # pwsdid isn't always stored, but GetMacString() will get it for us
  140.     wsuid = ""
  141.     pwsdid = GetMacString()
  142.     for bytearray in bytearrays:
  143.         cookie = ByteArrayToString(bytearray)
  144.         wsuidcheck = re.match("^wsuid=([0-9a-f-]+)", cookie)
  145.         if(wsuidcheck):
  146.             wsuid = wsuidcheck.group(1)
  147.     if(wsuid == ""):
  148.         print "wsuid key not found :/"
  149.         exit()
  150.     preuserkey = string.join((pwsdid, wsuid), "")
  151.     userkey = SHA256(preuserkey)
  152.     return userkey[32:]
  153.  
  154. # get key
  155. userkeyhex = GetUserHexKey()
  156. # load into AES
  157. userkey = binascii.a2b_hex(userkeyhex)
  158. enc = AES.new(userkey, AES.MODE_ECB)
  159. # get dirs
  160. kobodir = string.join((os.environ['LOCALAPPDATA'], "Kobo\\Kobo Desktop Edition"), "\\")
  161. sqlitefile = string.join((kobodir, "Kobo.sqlite"), "\\")
  162. bookdir = string.join((kobodir, "kepub"), "\\")
  163.  
  164. # open sqlite
  165. conn = sqlite3.connect(sqlitefile)
  166. c = conn.cursor()
  167. # get volume keys
  168. volumekeys = GetVolumeKeys(c)
  169.  
  170. # choose a volumeID
  171.  
  172. volumeid = ""
  173. print "Choose a book to decrypt:"
  174. i = 1
  175. for key in volumekeys.keys():
  176.     print "%d: %s" % (i, volumekeys[key]["title"])
  177.     i+=1
  178.  
  179. num = input("...")
  180.  
  181. i = 1
  182. for key in volumekeys.keys():
  183.     if(i==num):
  184.         volumeid = key
  185.     i+=1
  186.  
  187.  
  188. if(volumeid == ""):
  189.     exit()
  190.  
  191. zippath = string.join((bookdir, volumeid), "\\")
  192.  
  193. z = zipfile.ZipFile(zippath, "r")
  194. # make filename out of a-zA-Z0-9 from title
  195. outname = "%s.epub" % (re.sub("[^a-zA-Z0-9 ]", "", volumekeys[volumeid]["title"]))
  196. zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
  197. for filename in z.namelist():
  198.     #print filename
  199.     # read in and decrypt
  200.     if(filename in volumekeys[volumeid]):
  201.         # do decrypted version
  202.         pagekey = volumekeys[volumeid][filename]["decryptedkey"]
  203.         penc = AES.new(pagekey, AES.MODE_ECB)
  204.         contents = RemoveAESPadding(penc.decrypt(z.read(filename)))
  205.         # need to fix padding
  206.         zout.writestr(filename, contents)
  207.     else:
  208.         zout.writestr(filename, z.read(filename))
  209.  
  210. print "Book saved as %s\\%s" % (os.getcwd(), outname)
Add Comment
Please, Sign In to add comment