SHOW:
|
|
- or go back to the newest paste.
1 | #!/usr/bin/env python | |
2 | # | |
3 | # Updated September 2013 by Anon | |
4 | # Version 2.01 | |
5 | # Incorporated minor fixes posted at Apprentice Alf's. | |
6 | # | |
7 | # Updates July 2012 by Michael Newton | |
8 | # PWSD ID is no longer a MAC address, but should always | |
9 | # be stored in the registry. Script now works with OS X | |
10 | # and checks plist for values instead of registry. Must | |
11 | # have biplist installed for OS X support. | |
12 | # | |
13 | ########################################################## | |
14 | # KOBO DRM CRACK BY # | |
15 | # PHYSISTICATED # | |
16 | ########################################################## | |
17 | # This app was made for Python 2.7 on Windows 32-bit | |
18 | # | |
19 | # This app needs pycrypto - get from here: | |
20 | # http://www.voidspace.org.uk/python/modules.shtml | |
21 | # | |
22 | # Usage: obok.py | |
23 | # Choose the book you want to decrypt | |
24 | # | |
25 | # Shouts to my krew - you know who you are - and one in | |
26 | # particular who gave me a lot of help with this - thank | |
27 | # you so much! | |
28 | # | |
29 | # Kopimi /K\ | |
30 | # Keep sharing, keep copying, but remember that nothing is | |
31 | # for free - make sure you compensate your favorite | |
32 | # authors - and cut out the middle man whenever possible | |
33 | # ;) ;) ;) | |
34 | # | |
35 | # DRM AUTOPSY | |
36 | # The Kobo DRM was incredibly easy to crack, but it took | |
37 | # me months to get around to making this. Here's the | |
38 | # basics of how it works: | |
39 | # 1: Get MAC address of first NIC in ipconfig (sometimes | |
40 | # stored in registry as pwsdid) | |
41 | # 2: Get user ID (stored in tons of places, this gets it | |
42 | # from HKEY_CURRENT_USER\Software\Kobo\Kobo Desktop | |
43 | # Edition\Browser\cookies) | |
44 | # 3: Concatenate and SHA256, take the second half - this | |
45 | # is your master key | |
46 | # 4: Open %LOCALAPPDATA%\Kobo Desktop Editions\Kobo.sqlite | |
47 | # and dump content_keys | |
48 | # 5: Unbase64 the keys, then decode these with the master | |
49 | # key - these are your page keys | |
50 | # 6: Unzip EPUB of your choice, decrypt each page with its | |
51 | # page key, then zip back up again | |
52 | # | |
53 | # WHY USE THIS WHEN INEPT WORKS FINE? (adobe DRM stripper) | |
54 | # Inept works very well, but authors on Kobo can choose | |
55 | # what DRM they want to use - and some have chosen not to | |
56 | # let people download them with Adobe Digital Editions - | |
57 | # they would rather lock you into a single platform. | |
58 | # | |
59 | # With Obok, you can sync Kobo Desktop, decrypt all your | |
60 | # ebooks, and then use them on whatever device you want | |
61 | # - you bought them, you own them, you can do what you | |
62 | # like with them. | |
63 | # | |
64 | # Obok is Kobo backwards, but it is also means "next to" | |
65 | # in Polish. | |
66 | # When you buy a real book, it is right next to you. You | |
67 | # can read it at home, at work, on a train, you can lend | |
68 | # it to a friend, you can scribble on it, and add your own | |
69 | # explanations/translations. | |
70 | # | |
71 | # Obok gives you this power over your ebooks - no longer | |
72 | # are you restricted to one device. This allows you to | |
73 | # embed foreign fonts into your books, as older Kobo's | |
74 | # can't display them properly. You can read your books | |
75 | # on your phones, in different PC readers, and different | |
76 | # ereader devices. You can share them with your friends | |
77 | # too, if you like - you can do that with a real book | |
78 | # after all. | |
79 | # | |
80 | """ | |
81 | Decrypt Kobo encrypted EPUB books. | |
82 | """ | |
83 | ||
84 | import os | |
85 | import sys | |
86 | if sys.platform.startswith('win'): | |
87 | import _winreg | |
88 | elif sys.platform.startswith('darwin'): | |
89 | from biplist import readPlist | |
90 | import re | |
91 | import string | |
92 | import hashlib | |
93 | import sqlite3 | |
94 | import base64 | |
95 | import binascii | |
96 | import zipfile | |
97 | from Crypto.Cipher import AES | |
98 | ||
99 | def SHA256(raw): | |
100 | return hashlib.sha256(raw).hexdigest() | |
101 | ||
102 | def RemoveAESPadding(contents): | |
103 | lastchar = binascii.b2a_hex(contents[-1:]) | |
104 | strlen = int(lastchar, 16) | |
105 | padding = strlen | |
106 | if(strlen == 1): | |
107 | return contents[:-1] | |
108 | if(strlen < 16): | |
109 | for i in range(strlen): | |
110 | testchar = binascii.b2a_hex(contents[-strlen:-(strlen-1)]) | |
111 | if(testchar != lastchar): | |
112 | padding = 0 | |
113 | if(padding > 0): | |
114 | contents = contents[:-padding] | |
115 | return contents | |
116 | ||
117 | def GetVolumeKeys(dbase, enc): | |
118 | volumekeys = {} | |
119 | for row in dbase.execute("SELECT * from content_keys"): | |
120 | if(row[0] not in volumekeys): | |
121 | volumekeys[row[0]] = {} | |
122 | volumekeys[row[0]][row[1]] = {} | |
123 | volumekeys[row[0]][row[1]]["encryptedkey"] = base64.b64decode(row[2]) | |
124 | volumekeys[row[0]][row[1]]["decryptedkey"] = enc.decrypt(volumekeys[row[0]][row[1]]["encryptedkey"]) | |
125 | # get book name | |
126 | for key in volumekeys.keys(): | |
127 | volumekeys[key]["title"] = dbase.execute("SELECT Title from content where ContentID = '%s'" % (key)).fetchone()[0] | |
128 | return volumekeys | |
129 | ||
130 | def ByteArrayToString(bytearr): | |
131 | wincheck = re.match("@ByteArray\\((.+)\\)", bytearr) | |
132 | if wincheck: | |
133 | return wincheck.group(1) | |
134 | return bytearr | |
135 | ||
136 | def GetUserHexKey(prefs = ""): | |
137 | "find wsuid and pwsdid" | |
138 | wsuid = "" | |
139 | pwsdid = "" | |
140 | if sys.platform.startswith('win'): | |
141 | regkey_browser = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Software\\Kobo\\Kobo Desktop Edition\\Browser") | |
142 | cookies = _winreg.QueryValueEx(regkey_browser, "cookies") | |
143 | bytearrays = cookies[0] | |
144 | elif sys.platform.startswith('darwin'): | |
145 | cookies = readPlist(prefs) | |
146 | bytearrays = cookies["Browser.cookies"] | |
147 | for bytearr in bytearrays: | |
148 | cookie = ByteArrayToString(bytearr) | |
149 | print cookie | |
150 | wsuidcheck = re.match("^wsuid=([0-9a-f-]+)", cookie) | |
151 | if(wsuidcheck): | |
152 | wsuid = wsuidcheck.group(1) | |
153 | pwsdidcheck = re.match("^pwsdid=([0-9a-f-]+)", cookie) | |
154 | if (pwsdidcheck): | |
155 | pwsdid = pwsdidcheck.group(1) | |
156 | ||
157 | if(wsuid == "" or pwsdid == ""): | |
158 | print "wsuid or pwsdid key not found :/" | |
159 | exit() | |
160 | preuserkey = string.join((pwsdid, wsuid), "") | |
161 | print SHA256(pwsdid) | |
162 | userkey = SHA256(preuserkey) | |
163 | return userkey[32:] | |
164 | - | kobodir = string.join((os.environ['LOCALAPPDATA'], "Kobo\\Kobo Desktop Edition"), delim) |
164 | + | |
165 | # get dirs | |
166 | if sys.platform.startswith('win'): | |
167 | delim = "\\" | |
168 | if (sys.getwindowsversion().major > 5): | |
169 | kobodir = string.join((os.environ['LOCALAPPDATA'], "Kobo\\Kobo Desktop Edition"), delim) | |
170 | else: | |
171 | kobodir = string.join((os.environ['USERPROFILE'], "Local Settings\\Application Data\\Kobo\\Kobo Desktop Edition"), delim) | |
172 | prefs = "" | |
173 | elif sys.platform.startswith('darwin'): | |
174 | delim = "/" | |
175 | kobodir = string.join((os.environ['HOME'], "Library/Application Support/Kobo/Kobo Desktop Edition"), delim) | |
176 | prefs = string.join((os.environ['HOME'], "Library/Preferences/com.kobo.Kobo Desktop Edition.plist"), delim) | |
177 | sqlitefile = string.join((kobodir, "Kobo.sqlite"), delim) | |
178 | bookdir = string.join((kobodir, "kepub"), delim) | |
179 | ||
180 | # get key | |
181 | userkeyhex = GetUserHexKey(prefs) | |
182 | # load into AES | |
183 | userkey = binascii.a2b_hex(userkeyhex) | |
184 | enc = AES.new(userkey, AES.MODE_ECB) | |
185 | ||
186 | # open sqlite | |
187 | conn = sqlite3.connect(sqlitefile) | |
188 | dbcursor = conn.cursor() | |
189 | # get volume keys | |
190 | volumekeys = GetVolumeKeys(dbcursor, enc) | |
191 | ||
192 | # choose a volumeID | |
193 | ||
194 | volumeid = "" | |
195 | print "Choose a book to decrypt:" | |
196 | i = 1 | |
197 | for key in volumekeys.keys(): | |
198 | print "%d: %s" % (i, volumekeys[key]["title"]) | |
199 | i += 1 | |
200 | ||
201 | num = input("...") | |
202 | ||
203 | i = 1 | |
204 | for key in volumekeys.keys(): | |
205 | if(i == num): | |
206 | volumeid = key | |
207 | - | # make filename out of a-zA-Z0-9 from title |
207 | + | |
208 | - | outname = "%s.epub" % (re.sub("[^a-zA-Z0-9]", "", volumekeys[volumeid]["title"])) |
208 | + | |
209 | if(volumeid == ""): | |
210 | exit() | |
211 | ||
212 | zippath = string.join((bookdir, volumeid), delim) | |
213 | ||
214 | z = zipfile.ZipFile(zippath, "r") | |
215 | # make filename out of Unicode alphanumeric and whitespace equivalents from title | |
216 | outname = "%s.epub" % (re.sub("[^\s\w]", "", volumekeys[volumeid]["title"], 0, re.UNICODE)) | |
217 | zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED) | |
218 | for filename in z.namelist(): | |
219 | #print filename | |
220 | # read in and decrypt | |
221 | if(filename in volumekeys[volumeid]): | |
222 | # do decrypted version | |
223 | pagekey = volumekeys[volumeid][filename]["decryptedkey"] | |
224 | penc = AES.new(pagekey, AES.MODE_ECB) | |
225 | contents = RemoveAESPadding(penc.decrypt(z.read(filename))) | |
226 | # need to fix padding | |
227 | zout.writestr(filename, contents) | |
228 | else: | |
229 | zout.writestr(filename, z.read(filename)) | |
230 | ||
231 | print "Book saved as %s%s%s" % (os.getcwd(), delim, outname) |