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