Move to new positions
This commit is contained in:
@@ -1,26 +0,0 @@
|
||||
// ==UserScript==
|
||||
// @name BN-Dload
|
||||
// @namespace http://www.mailinator.com/J-man
|
||||
// @include https://mynook.barnesandnoble.com/library.html*
|
||||
// @grant none
|
||||
// @version 20121119
|
||||
// ==/UserScript==
|
||||
|
||||
function doIt() {
|
||||
if ($('#adl1').length == 0) {
|
||||
$('[action$="deleteItem"]').each(function(index) {
|
||||
if ($(this).parent().find('[action$="EDSDeliverItem.aspx"]').length == 0) {
|
||||
var delid = $(this).find('input').attr('value');
|
||||
$(this).after('<span class="vb2"></span><form id="adl' + index + '" action="https://edelivery.barnesandnoble.com/EDS/EDSDeliverItem.aspx" class="download"><input value="' + delid + '" type="hidden" name="delid"><input type="hidden" value="Browser" name="clienttype"><input type="hidden" value="browser" name="deviceinfo"><button class="download "name="download">Alternative Download</button></form>');
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
setTimeout (function() {
|
||||
doIt();
|
||||
}, 3000 );
|
||||
}
|
||||
|
||||
doIt();
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
INTRODUCTION
|
||||
============
|
||||
|
||||
To obtain unencrypted content from the B&N, you have to download it directly from the website. Unrooted Nook devices will not let you save your content to your PC.
|
||||
|
||||
If the downloaded file is encrypted, install and configure the ignoble plugin in Calibre to decrypt that.
|
||||
|
||||
|
||||
DOWNLOAD HIDDEN FILES FROM B&N
|
||||
------------------------------
|
||||
|
||||
Some content is not downloadable from the B&N website, notably magazines. A Greasemonkey script (details below) modifies the myNook page of the Barnes and Noble website to show a download button for normally non-downloadable content. This will work until Barnes & Noble changes their website.
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
1) Firefox: http://www.getfirefox.com
|
||||
2) Greasemokey extension: https://addons.mozilla.org/nl/firefox/addon/greasemonkey/
|
||||
|
||||
One time installation
|
||||
---------------------
|
||||
1) Install Firefox if not already done so
|
||||
2) Follow the above link to GreaseMonkey and click Add to Firefox
|
||||
3) Restart Firefox
|
||||
4) Go to http://userscripts.org/scripts/source/152985.user.js
|
||||
5) A popup should appear, stating you are about to install a GreaseMonkey user script.
|
||||
6) Click on install
|
||||
|
||||
Use
|
||||
---
|
||||
1) Log in into your B&N account
|
||||
2) Go to MyNook
|
||||
3) An “Alternative download” should appear next to normally non-downloadable content. Note that this will not work for content such as Nook applications, and some children books.
|
||||
@@ -1,603 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# adobekey.pyw, version 6.0
|
||||
# Copyright © 2009-2010 i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2016 by several people
|
||||
|
||||
# Windows users: Before running this program, you must first install Python.
|
||||
# We recommend ActiveState Python 2.7.X for Windows (x86) from
|
||||
# http://www.activestate.com/activepython/downloads.
|
||||
# You must also install PyCrypto from
|
||||
# http://www.voidspace.org.uk/python/modules.shtml#pycrypto
|
||||
# (make certain to install the version for Python 2.7).
|
||||
# Then save this script file as adobekey.pyw and double-click on it to run it.
|
||||
# It will create a file named adobekey_1.der in in the same directory as the script.
|
||||
# This is your Adobe Digital Editions user key.
|
||||
#
|
||||
# Mac OS X users: Save this script file as adobekey.pyw. You can run this
|
||||
# program from the command line (python adobekey.pyw) or by double-clicking
|
||||
# it when it has been associated with PythonLauncher. It will create a file
|
||||
# named adobekey_1.der in the same directory as the script.
|
||||
# This is your Adobe Digital Editions user key.
|
||||
|
||||
# Revision history:
|
||||
# 1 - Initial release, for Adobe Digital Editions 1.7
|
||||
# 2 - Better algorithm for finding pLK; improved error handling
|
||||
# 3 - Rename to INEPT
|
||||
# 4 - Series of changes by joblack (and others?) --
|
||||
# 4.1 - quick beta fix for ADE 1.7.2 (anon)
|
||||
# 4.2 - added old 1.7.1 processing
|
||||
# 4.3 - better key search
|
||||
# 4.4 - Make it working on 64-bit Python
|
||||
# 5 - Clean up and improve 4.x changes;
|
||||
# Clean up and merge OS X support by unknown
|
||||
# 5.1 - add support for using OpenSSL on Windows in place of PyCrypto
|
||||
# 5.2 - added support for output of key to a particular file
|
||||
# 5.3 - On Windows try PyCrypto first, OpenSSL next
|
||||
# 5.4 - Modify interface to allow use of import
|
||||
# 5.5 - Fix for potential problem with PyCrypto
|
||||
# 5.6 - Revised to allow use in Plugins to eliminate need for duplicate code
|
||||
# 5.7 - Unicode support added, renamed adobekey from ineptkey
|
||||
# 5.8 - Added getkey interface for Windows DeDRM application
|
||||
# 5.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 6.0 - Work if TkInter is missing
|
||||
|
||||
"""
|
||||
Retrieve Adobe ADEPT user key.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '6.0'
|
||||
|
||||
import sys, os, struct, getopt
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
# encoded using "replace" before writing them.
|
||||
class SafeUnbuffered:
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
self.encoding = stream.encoding
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
try:
|
||||
from calibre.constants import iswindows, isosx
|
||||
except:
|
||||
iswindows = sys.platform.startswith('win')
|
||||
isosx = sys.platform.startswith('darwin')
|
||||
|
||||
def unicode_argv():
|
||||
if iswindows:
|
||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
||||
# strings.
|
||||
|
||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
||||
# characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
|
||||
# as a list of Unicode strings and encode them as utf-8
|
||||
|
||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
||||
|
||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
||||
GetCommandLineW.argtypes = []
|
||||
GetCommandLineW.restype = LPCWSTR
|
||||
|
||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
||||
|
||||
cmd = GetCommandLineW()
|
||||
argc = c_int(0)
|
||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
||||
if argc.value > 0:
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"adobekey.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
|
||||
class ADEPTError(Exception):
|
||||
pass
|
||||
|
||||
if iswindows:
|
||||
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
|
||||
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
|
||||
string_at, Structure, c_void_p, cast, c_size_t, memmove, CDLL, c_int, \
|
||||
c_long, c_ulong
|
||||
|
||||
from ctypes.wintypes import LPVOID, DWORD, BOOL
|
||||
import _winreg as winreg
|
||||
|
||||
def _load_crypto_libcrypto():
|
||||
from ctypes.util import find_library
|
||||
libcrypto = find_library('libeay32')
|
||||
if libcrypto is None:
|
||||
raise ADEPTError('libcrypto not found')
|
||||
libcrypto = CDLL(libcrypto)
|
||||
AES_MAXNR = 14
|
||||
c_char_pp = POINTER(c_char_p)
|
||||
c_int_p = POINTER(c_int)
|
||||
class AES_KEY(Structure):
|
||||
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
|
||||
('rounds', c_int)]
|
||||
AES_KEY_p = POINTER(AES_KEY)
|
||||
|
||||
def F(restype, name, argtypes):
|
||||
func = getattr(libcrypto, name)
|
||||
func.restype = restype
|
||||
func.argtypes = argtypes
|
||||
return func
|
||||
|
||||
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
|
||||
[c_char_p, c_int, AES_KEY_p])
|
||||
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
|
||||
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
|
||||
c_int])
|
||||
class AES(object):
|
||||
def __init__(self, userkey):
|
||||
self._blocksize = len(userkey)
|
||||
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
||||
raise ADEPTError('AES improper key used')
|
||||
key = self._key = AES_KEY()
|
||||
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
|
||||
if rv < 0:
|
||||
raise ADEPTError('Failed to initialize AES key')
|
||||
def decrypt(self, data):
|
||||
out = create_string_buffer(len(data))
|
||||
iv = ("\x00" * self._blocksize)
|
||||
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
|
||||
if rv == 0:
|
||||
raise ADEPTError('AES decryption failed')
|
||||
return out.raw
|
||||
return AES
|
||||
|
||||
def _load_crypto_pycrypto():
|
||||
from Crypto.Cipher import AES as _AES
|
||||
class AES(object):
|
||||
def __init__(self, key):
|
||||
self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
|
||||
def decrypt(self, data):
|
||||
return self._aes.decrypt(data)
|
||||
return AES
|
||||
|
||||
def _load_crypto():
|
||||
AES = None
|
||||
for loader in (_load_crypto_pycrypto, _load_crypto_libcrypto):
|
||||
try:
|
||||
AES = loader()
|
||||
break
|
||||
except (ImportError, ADEPTError):
|
||||
pass
|
||||
return AES
|
||||
|
||||
AES = _load_crypto()
|
||||
|
||||
|
||||
DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
|
||||
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
|
||||
|
||||
MAX_PATH = 255
|
||||
|
||||
kernel32 = windll.kernel32
|
||||
advapi32 = windll.advapi32
|
||||
crypt32 = windll.crypt32
|
||||
|
||||
def GetSystemDirectory():
|
||||
GetSystemDirectoryW = kernel32.GetSystemDirectoryW
|
||||
GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
|
||||
GetSystemDirectoryW.restype = c_uint
|
||||
def GetSystemDirectory():
|
||||
buffer = create_unicode_buffer(MAX_PATH + 1)
|
||||
GetSystemDirectoryW(buffer, len(buffer))
|
||||
return buffer.value
|
||||
return GetSystemDirectory
|
||||
GetSystemDirectory = GetSystemDirectory()
|
||||
|
||||
def GetVolumeSerialNumber():
|
||||
GetVolumeInformationW = kernel32.GetVolumeInformationW
|
||||
GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
|
||||
POINTER(c_uint), POINTER(c_uint),
|
||||
POINTER(c_uint), c_wchar_p, c_uint]
|
||||
GetVolumeInformationW.restype = c_uint
|
||||
def GetVolumeSerialNumber(path):
|
||||
vsn = c_uint(0)
|
||||
GetVolumeInformationW(
|
||||
path, None, 0, byref(vsn), None, None, None, 0)
|
||||
return vsn.value
|
||||
return GetVolumeSerialNumber
|
||||
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
||||
|
||||
def GetUserName():
|
||||
GetUserNameW = advapi32.GetUserNameW
|
||||
GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
|
||||
GetUserNameW.restype = c_uint
|
||||
def GetUserName():
|
||||
buffer = create_unicode_buffer(32)
|
||||
size = c_uint(len(buffer))
|
||||
while not GetUserNameW(buffer, byref(size)):
|
||||
buffer = create_unicode_buffer(len(buffer) * 2)
|
||||
size.value = len(buffer)
|
||||
return buffer.value.encode('utf-16-le')[::2]
|
||||
return GetUserName
|
||||
GetUserName = GetUserName()
|
||||
|
||||
PAGE_EXECUTE_READWRITE = 0x40
|
||||
MEM_COMMIT = 0x1000
|
||||
MEM_RESERVE = 0x2000
|
||||
|
||||
def VirtualAlloc():
|
||||
_VirtualAlloc = kernel32.VirtualAlloc
|
||||
_VirtualAlloc.argtypes = [LPVOID, c_size_t, DWORD, DWORD]
|
||||
_VirtualAlloc.restype = LPVOID
|
||||
def VirtualAlloc(addr, size, alloctype=(MEM_COMMIT | MEM_RESERVE),
|
||||
protect=PAGE_EXECUTE_READWRITE):
|
||||
return _VirtualAlloc(addr, size, alloctype, protect)
|
||||
return VirtualAlloc
|
||||
VirtualAlloc = VirtualAlloc()
|
||||
|
||||
MEM_RELEASE = 0x8000
|
||||
|
||||
def VirtualFree():
|
||||
_VirtualFree = kernel32.VirtualFree
|
||||
_VirtualFree.argtypes = [LPVOID, c_size_t, DWORD]
|
||||
_VirtualFree.restype = BOOL
|
||||
def VirtualFree(addr, size=0, freetype=MEM_RELEASE):
|
||||
return _VirtualFree(addr, size, freetype)
|
||||
return VirtualFree
|
||||
VirtualFree = VirtualFree()
|
||||
|
||||
class NativeFunction(object):
|
||||
def __init__(self, restype, argtypes, insns):
|
||||
self._buf = buf = VirtualAlloc(None, len(insns))
|
||||
memmove(buf, insns, len(insns))
|
||||
ftype = CFUNCTYPE(restype, *argtypes)
|
||||
self._native = ftype(buf)
|
||||
|
||||
def __call__(self, *args):
|
||||
return self._native(*args)
|
||||
|
||||
def __del__(self):
|
||||
if self._buf is not None:
|
||||
VirtualFree(self._buf)
|
||||
self._buf = None
|
||||
|
||||
if struct.calcsize("P") == 4:
|
||||
CPUID0_INSNS = (
|
||||
"\x53" # push %ebx
|
||||
"\x31\xc0" # xor %eax,%eax
|
||||
"\x0f\xa2" # cpuid
|
||||
"\x8b\x44\x24\x08" # mov 0x8(%esp),%eax
|
||||
"\x89\x18" # mov %ebx,0x0(%eax)
|
||||
"\x89\x50\x04" # mov %edx,0x4(%eax)
|
||||
"\x89\x48\x08" # mov %ecx,0x8(%eax)
|
||||
"\x5b" # pop %ebx
|
||||
"\xc3" # ret
|
||||
)
|
||||
CPUID1_INSNS = (
|
||||
"\x53" # push %ebx
|
||||
"\x31\xc0" # xor %eax,%eax
|
||||
"\x40" # inc %eax
|
||||
"\x0f\xa2" # cpuid
|
||||
"\x5b" # pop %ebx
|
||||
"\xc3" # ret
|
||||
)
|
||||
else:
|
||||
CPUID0_INSNS = (
|
||||
"\x49\x89\xd8" # mov %rbx,%r8
|
||||
"\x49\x89\xc9" # mov %rcx,%r9
|
||||
"\x48\x31\xc0" # xor %rax,%rax
|
||||
"\x0f\xa2" # cpuid
|
||||
"\x4c\x89\xc8" # mov %r9,%rax
|
||||
"\x89\x18" # mov %ebx,0x0(%rax)
|
||||
"\x89\x50\x04" # mov %edx,0x4(%rax)
|
||||
"\x89\x48\x08" # mov %ecx,0x8(%rax)
|
||||
"\x4c\x89\xc3" # mov %r8,%rbx
|
||||
"\xc3" # retq
|
||||
)
|
||||
CPUID1_INSNS = (
|
||||
"\x53" # push %rbx
|
||||
"\x48\x31\xc0" # xor %rax,%rax
|
||||
"\x48\xff\xc0" # inc %rax
|
||||
"\x0f\xa2" # cpuid
|
||||
"\x5b" # pop %rbx
|
||||
"\xc3" # retq
|
||||
)
|
||||
|
||||
def cpuid0():
|
||||
_cpuid0 = NativeFunction(None, [c_char_p], CPUID0_INSNS)
|
||||
buf = create_string_buffer(12)
|
||||
def cpuid0():
|
||||
_cpuid0(buf)
|
||||
return buf.raw
|
||||
return cpuid0
|
||||
cpuid0 = cpuid0()
|
||||
|
||||
cpuid1 = NativeFunction(c_uint, [], CPUID1_INSNS)
|
||||
|
||||
class DataBlob(Structure):
|
||||
_fields_ = [('cbData', c_uint),
|
||||
('pbData', c_void_p)]
|
||||
DataBlob_p = POINTER(DataBlob)
|
||||
|
||||
def CryptUnprotectData():
|
||||
_CryptUnprotectData = crypt32.CryptUnprotectData
|
||||
_CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
|
||||
c_void_p, c_void_p, c_uint, DataBlob_p]
|
||||
_CryptUnprotectData.restype = c_uint
|
||||
def CryptUnprotectData(indata, entropy):
|
||||
indatab = create_string_buffer(indata)
|
||||
indata = DataBlob(len(indata), cast(indatab, c_void_p))
|
||||
entropyb = create_string_buffer(entropy)
|
||||
entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
|
||||
outdata = DataBlob()
|
||||
if not _CryptUnprotectData(byref(indata), None, byref(entropy),
|
||||
None, None, 0, byref(outdata)):
|
||||
raise ADEPTError("Failed to decrypt user key key (sic)")
|
||||
return string_at(outdata.pbData, outdata.cbData)
|
||||
return CryptUnprotectData
|
||||
CryptUnprotectData = CryptUnprotectData()
|
||||
|
||||
def adeptkeys():
|
||||
if AES is None:
|
||||
raise ADEPTError("PyCrypto or OpenSSL must be installed")
|
||||
root = GetSystemDirectory().split('\\')[0] + '\\'
|
||||
serial = GetVolumeSerialNumber(root)
|
||||
vendor = cpuid0()
|
||||
signature = struct.pack('>I', cpuid1())[1:]
|
||||
user = GetUserName()
|
||||
entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user)
|
||||
cuser = winreg.HKEY_CURRENT_USER
|
||||
try:
|
||||
regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH)
|
||||
device = winreg.QueryValueEx(regkey, 'key')[0]
|
||||
except WindowsError:
|
||||
raise ADEPTError("Adobe Digital Editions not activated")
|
||||
keykey = CryptUnprotectData(device, entropy)
|
||||
userkey = None
|
||||
keys = []
|
||||
try:
|
||||
plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH)
|
||||
except WindowsError:
|
||||
raise ADEPTError("Could not locate ADE activation")
|
||||
for i in xrange(0, 16):
|
||||
try:
|
||||
plkparent = winreg.OpenKey(plkroot, "%04d" % (i,))
|
||||
except WindowsError:
|
||||
break
|
||||
ktype = winreg.QueryValueEx(plkparent, None)[0]
|
||||
if ktype != 'credentials':
|
||||
continue
|
||||
for j in xrange(0, 16):
|
||||
try:
|
||||
plkkey = winreg.OpenKey(plkparent, "%04d" % (j,))
|
||||
except WindowsError:
|
||||
break
|
||||
ktype = winreg.QueryValueEx(plkkey, None)[0]
|
||||
if ktype != 'privateLicenseKey':
|
||||
continue
|
||||
userkey = winreg.QueryValueEx(plkkey, 'value')[0]
|
||||
userkey = userkey.decode('base64')
|
||||
aes = AES(keykey)
|
||||
userkey = aes.decrypt(userkey)
|
||||
userkey = userkey[26:-ord(userkey[-1])]
|
||||
#print "found key:",userkey.encode('hex')
|
||||
keys.append(userkey)
|
||||
if len(keys) == 0:
|
||||
raise ADEPTError('Could not locate privateLicenseKey')
|
||||
print u"Found {0:d} keys".format(len(keys))
|
||||
return keys
|
||||
|
||||
|
||||
elif isosx:
|
||||
import xml.etree.ElementTree as etree
|
||||
import subprocess
|
||||
|
||||
NSMAP = {'adept': 'http://ns.adobe.com/adept',
|
||||
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
|
||||
|
||||
def findActivationDat():
|
||||
import warnings
|
||||
warnings.filterwarnings('ignore', category=FutureWarning)
|
||||
|
||||
home = os.getenv('HOME')
|
||||
cmdline = 'find "' + home + '/Library/Application Support/Adobe/Digital Editions" -name "activation.dat"'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p2 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p2.communicate()
|
||||
reslst = out1.split('\n')
|
||||
cnt = len(reslst)
|
||||
ActDatPath = "activation.dat"
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
pp = resline.find('activation.dat')
|
||||
if pp >= 0:
|
||||
ActDatPath = resline
|
||||
break
|
||||
if os.path.exists(ActDatPath):
|
||||
return ActDatPath
|
||||
return None
|
||||
|
||||
def adeptkeys():
|
||||
actpath = findActivationDat()
|
||||
if actpath is None:
|
||||
raise ADEPTError("Could not find ADE activation.dat file.")
|
||||
tree = etree.parse(actpath)
|
||||
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||
expr = '//%s/%s' % (adept('credentials'), adept('privateLicenseKey'))
|
||||
userkey = tree.findtext(expr)
|
||||
userkey = userkey.decode('base64')
|
||||
userkey = userkey[26:]
|
||||
return [userkey]
|
||||
|
||||
else:
|
||||
def adeptkeys():
|
||||
raise ADEPTError("This script only supports Windows and Mac OS X.")
|
||||
return []
|
||||
|
||||
# interface for Python DeDRM
|
||||
def getkey(outpath):
|
||||
keys = adeptkeys()
|
||||
if len(keys) > 0:
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'wb') as keyfileout:
|
||||
keyfileout.write(keys[0])
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
else:
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(outpath,u"adobekey_{0:d}.der".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
with file(outfile, 'wb') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
return True
|
||||
return False
|
||||
|
||||
def usage(progname):
|
||||
print u"Finds, decrypts and saves the default Adobe Adept encryption key(s)."
|
||||
print u"Keys are saved to the current directory, or a specified output directory."
|
||||
print u"If a file name is passed instead of a directory, only the first key is saved, in that file."
|
||||
print u"Usage:"
|
||||
print u" {0:s} [-h] [<outpath>]".format(progname)
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2009-2013 i♥cabbages and Apprentice Alf".format(progname,__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "h")
|
||||
except getopt.GetoptError, err:
|
||||
print u"Error in options or arguments: {0}".format(err.args[0])
|
||||
usage(progname)
|
||||
sys.exit(2)
|
||||
|
||||
for o, a in opts:
|
||||
if o == "-h":
|
||||
usage(progname)
|
||||
sys.exit(0)
|
||||
|
||||
if len(args) > 1:
|
||||
usage(progname)
|
||||
sys.exit(2)
|
||||
|
||||
if len(args) == 1:
|
||||
# save to the specified file or directory
|
||||
outpath = args[0]
|
||||
if not os.path.isabs(outpath):
|
||||
outpath = os.path.abspath(outpath)
|
||||
else:
|
||||
# save to the same directory as the script
|
||||
outpath = os.path.dirname(argv[0])
|
||||
|
||||
# make sure the outpath is the
|
||||
outpath = os.path.realpath(os.path.normpath(outpath))
|
||||
|
||||
keys = adeptkeys()
|
||||
if len(keys) > 0:
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'wb') as keyfileout:
|
||||
keyfileout.write(keys[0])
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
else:
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(outpath,u"adobekey_{0:d}.der".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
with file(outfile, 'wb') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
else:
|
||||
print u"Could not retrieve Adobe Adept key."
|
||||
return 0
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class ExceptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root, text):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
label = Tkinter.Label(self, text=u"Unexpected error:",
|
||||
anchor=Tkconstants.W, justify=Tkconstants.LEFT)
|
||||
label.pack(fill=Tkconstants.X, expand=0)
|
||||
self.text = Tkinter.Text(self)
|
||||
self.text.pack(fill=Tkconstants.BOTH, expand=1)
|
||||
|
||||
self.text.insert(Tkconstants.END, text)
|
||||
|
||||
|
||||
argv=unicode_argv()
|
||||
root = Tkinter.Tk()
|
||||
root.withdraw()
|
||||
progpath, progname = os.path.split(argv[0])
|
||||
success = False
|
||||
try:
|
||||
keys = adeptkeys()
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(progpath,u"adobekey_{0:d}.der".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
|
||||
with file(outfile, 'wb') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
success = True
|
||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||
except ADEPTError, e:
|
||||
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
|
||||
except Exception:
|
||||
root.wm_state('normal')
|
||||
root.title(progname)
|
||||
text = traceback.format_exc()
|
||||
ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
|
||||
root.mainloop()
|
||||
if not success:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
sys.exit(cli_main())
|
||||
sys.exit(gui_main())
|
||||
@@ -1,336 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignoblekey.py
|
||||
# Copyright © 2015 Apprentice Alf and Apprentice Harper
|
||||
|
||||
# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Initial release
|
||||
# 1.1 - remove duplicates and return last key as single key
|
||||
|
||||
"""
|
||||
Get Barnes & Noble EPUB user key from nook Studio log file
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "1.1"
|
||||
|
||||
import sys
|
||||
import os
|
||||
import hashlib
|
||||
import getopt
|
||||
import re
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
# encoded using "replace" before writing them.
|
||||
class SafeUnbuffered:
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
self.encoding = stream.encoding
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
try:
|
||||
from calibre.constants import iswindows, isosx
|
||||
except:
|
||||
iswindows = sys.platform.startswith('win')
|
||||
isosx = sys.platform.startswith('darwin')
|
||||
|
||||
def unicode_argv():
|
||||
if iswindows:
|
||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
||||
# strings.
|
||||
|
||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
||||
# characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
|
||||
# as a list of Unicode strings and encode them as utf-8
|
||||
|
||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
||||
|
||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
||||
GetCommandLineW.argtypes = []
|
||||
GetCommandLineW.restype = LPCWSTR
|
||||
|
||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
||||
|
||||
cmd = GetCommandLineW()
|
||||
argc = c_int(0)
|
||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
||||
if argc.value > 0:
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"ignoblekey.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
|
||||
class DrmException(Exception):
|
||||
pass
|
||||
|
||||
# Locate all of the nookStudy/nook for PC/Mac log file and return as list
|
||||
def getNookLogFiles():
|
||||
logFiles = []
|
||||
found = False
|
||||
if iswindows:
|
||||
import _winreg as winreg
|
||||
|
||||
# some 64 bit machines do not have the proper registry key for some reason
|
||||
# or the python interface to the 32 vs 64 bit registry is broken
|
||||
paths = set()
|
||||
if 'LOCALAPPDATA' in os.environ.keys():
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||
if os.path.isdir(path):
|
||||
paths.add(path)
|
||||
if 'USERPROFILE' in os.environ.keys():
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Local"
|
||||
if os.path.isdir(path):
|
||||
paths.add(path)
|
||||
path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Roaming"
|
||||
if os.path.isdir(path):
|
||||
paths.add(path)
|
||||
# User Shell Folders show take precedent over Shell Folders if present
|
||||
try:
|
||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
|
||||
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
||||
if os.path.isdir(path):
|
||||
paths.add(path)
|
||||
except WindowsError:
|
||||
pass
|
||||
try:
|
||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
|
||||
path = winreg.QueryValueEx(regkey, 'AppData')[0]
|
||||
if os.path.isdir(path):
|
||||
paths.add(path)
|
||||
except WindowsError:
|
||||
pass
|
||||
try:
|
||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
|
||||
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
||||
if os.path.isdir(path):
|
||||
paths.add(path)
|
||||
except WindowsError:
|
||||
pass
|
||||
try:
|
||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
|
||||
path = winreg.QueryValueEx(regkey, 'AppData')[0]
|
||||
if os.path.isdir(path):
|
||||
paths.add(path)
|
||||
except WindowsError:
|
||||
pass
|
||||
|
||||
for path in paths:
|
||||
# look for nookStudy log file
|
||||
logpath = path +'\\Barnes & Noble\\NOOKstudy\\logs\\BNClientLog.txt'
|
||||
if os.path.isfile(logpath):
|
||||
found = True
|
||||
print('Found nookStudy log file: ' + logpath.encode('ascii','ignore'))
|
||||
logFiles.append(logpath)
|
||||
else:
|
||||
home = os.getenv('HOME')
|
||||
# check for BNClientLog.txt in various locations
|
||||
testpath = home + '/Library/Application Support/Barnes & Noble/DesktopReader/logs/BNClientLog.txt'
|
||||
if os.path.isfile(testpath):
|
||||
logFiles.append(testpath)
|
||||
print('Found nookStudy log file: ' + testpath)
|
||||
found = True
|
||||
testpath = home + '/Library/Application Support/Barnes & Noble/DesktopReader/indices/BNClientLog.txt'
|
||||
if os.path.isfile(testpath):
|
||||
logFiles.append(testpath)
|
||||
print('Found nookStudy log file: ' + testpath)
|
||||
found = True
|
||||
testpath = home + '/Library/Application Support/Barnes & Noble/BNDesktopReader/logs/BNClientLog.txt'
|
||||
if os.path.isfile(testpath):
|
||||
logFiles.append(testpath)
|
||||
print('Found nookStudy log file: ' + testpath)
|
||||
found = True
|
||||
testpath = home + '/Library/Application Support/Barnes & Noble/BNDesktopReader/indices/BNClientLog.txt'
|
||||
if os.path.isfile(testpath):
|
||||
logFiles.append(testpath)
|
||||
print('Found nookStudy log file: ' + testpath)
|
||||
found = True
|
||||
|
||||
if not found:
|
||||
print('No nook Study log files have been found.')
|
||||
return logFiles
|
||||
|
||||
|
||||
# Extract CCHash key(s) from log file
|
||||
def getKeysFromLog(kLogFile):
|
||||
keys = []
|
||||
regex = re.compile("ccHash: \"(.{28})\"");
|
||||
for line in open(kLogFile):
|
||||
for m in regex.findall(line):
|
||||
keys.append(m)
|
||||
return keys
|
||||
|
||||
# interface for calibre plugin
|
||||
def nookkeys(files = []):
|
||||
keys = []
|
||||
if files == []:
|
||||
files = getNookLogFiles()
|
||||
for file in files:
|
||||
fileKeys = getKeysFromLog(file)
|
||||
if fileKeys:
|
||||
print u"Found {0} keys in the Nook Study log files".format(len(fileKeys))
|
||||
keys.extend(fileKeys)
|
||||
return list(set(keys))
|
||||
|
||||
# interface for Python DeDRM
|
||||
# returns single key or multiple keys, depending on path or file passed in
|
||||
def getkey(outpath, files=[]):
|
||||
keys = nookkeys(files)
|
||||
if len(keys) > 0:
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(keys[-1])
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
else:
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(outpath,u"nookkey{0:d}.b64".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
return True
|
||||
return False
|
||||
|
||||
def usage(progname):
|
||||
print u"Finds the nook Study encryption keys."
|
||||
print u"Keys are saved to the current directory, or a specified output directory."
|
||||
print u"If a file name is passed instead of a directory, only the first key is saved, in that file."
|
||||
print u"Usage:"
|
||||
print u" {0:s} [-h] [-k <logFile>] [<outpath>]".format(progname)
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2015 Apprentice Alf".format(progname,__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "hk:")
|
||||
except getopt.GetoptError, err:
|
||||
print u"Error in options or arguments: {0}".format(err.args[0])
|
||||
usage(progname)
|
||||
sys.exit(2)
|
||||
|
||||
files = []
|
||||
for o, a in opts:
|
||||
if o == "-h":
|
||||
usage(progname)
|
||||
sys.exit(0)
|
||||
if o == "-k":
|
||||
files = [a]
|
||||
|
||||
if len(args) > 1:
|
||||
usage(progname)
|
||||
sys.exit(2)
|
||||
|
||||
if len(args) == 1:
|
||||
# save to the specified file or directory
|
||||
outpath = args[0]
|
||||
if not os.path.isabs(outpath):
|
||||
outpath = os.path.abspath(outpath)
|
||||
else:
|
||||
# save to the same directory as the script
|
||||
outpath = os.path.dirname(argv[0])
|
||||
|
||||
# make sure the outpath is the
|
||||
outpath = os.path.realpath(os.path.normpath(outpath))
|
||||
|
||||
if not getkey(outpath, files):
|
||||
print u"Could not retrieve nook Study key."
|
||||
return 0
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class ExceptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root, text):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
label = Tkinter.Label(self, text=u"Unexpected error:",
|
||||
anchor=Tkconstants.W, justify=Tkconstants.LEFT)
|
||||
label.pack(fill=Tkconstants.X, expand=0)
|
||||
self.text = Tkinter.Text(self)
|
||||
self.text.pack(fill=Tkconstants.BOTH, expand=1)
|
||||
|
||||
self.text.insert(Tkconstants.END, text)
|
||||
|
||||
|
||||
argv=unicode_argv()
|
||||
root = Tkinter.Tk()
|
||||
root.withdraw()
|
||||
progpath, progname = os.path.split(argv[0])
|
||||
success = False
|
||||
try:
|
||||
keys = nookkeys()
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
print key
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(progpath,u"nookkey{0:d}.b64".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
success = True
|
||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||
except DrmException, e:
|
||||
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
|
||||
except Exception:
|
||||
root.wm_state('normal')
|
||||
root.title(progname)
|
||||
text = traceback.format_exc()
|
||||
ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
|
||||
root.mainloop()
|
||||
if not success:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
sys.exit(cli_main())
|
||||
sys.exit(gui_main())
|
||||
@@ -1,258 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignoblekeyfetch.pyw, version 1.1
|
||||
# Copyright © 2015 Apprentice Harper
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Based on discoveries by "Nobody You Know"
|
||||
# Code partly based on ignoblekeygen.py by several people.
|
||||
|
||||
# Windows users: Before running this program, you must first install Python.
|
||||
# We recommend ActiveState Python 2.7.X for Windows from
|
||||
# http://www.activestate.com/activepython/downloads.
|
||||
# Then save this script file as ignoblekeyfetch.pyw and double-click on it to run it.
|
||||
#
|
||||
# Mac OS X users: Save this script file as ignoblekeyfetch.pyw. You can run this
|
||||
# program from the command line (python ignoblekeyfetch.pyw) or by double-clicking
|
||||
# it when it has been associated with PythonLauncher.
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Initial version
|
||||
# 1.1 - Try second URL if first one fails
|
||||
|
||||
"""
|
||||
Fetch Barnes & Noble EPUB user key from B&N servers using email and password
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "1.1"
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
# encoded using "replace" before writing them.
|
||||
class SafeUnbuffered:
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
self.encoding = stream.encoding
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
try:
|
||||
from calibre.constants import iswindows, isosx
|
||||
except:
|
||||
iswindows = sys.platform.startswith('win')
|
||||
isosx = sys.platform.startswith('darwin')
|
||||
|
||||
def unicode_argv():
|
||||
if iswindows:
|
||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
||||
# strings.
|
||||
|
||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
||||
# characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
|
||||
# as a list of Unicode strings and encode them as utf-8
|
||||
|
||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
||||
|
||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
||||
GetCommandLineW.argtypes = []
|
||||
GetCommandLineW.restype = LPCWSTR
|
||||
|
||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
||||
|
||||
cmd = GetCommandLineW()
|
||||
argc = c_int(0)
|
||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
||||
if argc.value > 0:
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"ignoblekeyfetch.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
|
||||
|
||||
class IGNOBLEError(Exception):
|
||||
pass
|
||||
|
||||
def fetch_key(email, password):
|
||||
# change email and password to utf-8 if unicode
|
||||
if type(email)==unicode:
|
||||
email = email.encode('utf-8')
|
||||
if type(password)==unicode:
|
||||
password = password.encode('utf-8')
|
||||
|
||||
import random
|
||||
random = "%030x" % random.randrange(16**30)
|
||||
|
||||
import urllib, urllib2, re
|
||||
|
||||
# try the URL from nook for PC
|
||||
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
||||
fetch_url += urllib.quote(password,'')+"&devID=PC_BN_2.5.6.9575_"+random+"&emailAddress="
|
||||
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
||||
#print fetch_url
|
||||
|
||||
found = ''
|
||||
try:
|
||||
req = urllib2.Request(fetch_url)
|
||||
response = urllib2.urlopen(req)
|
||||
the_page = response.read()
|
||||
#print the_page
|
||||
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
||||
except:
|
||||
found = ''
|
||||
if len(found)!=28:
|
||||
# try the URL from android devices
|
||||
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
||||
fetch_url += urllib.quote(password,'')+"&devID=hobbes_9.3.50818_"+random+"&emailAddress="
|
||||
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
||||
#print fetch_url
|
||||
|
||||
found = ''
|
||||
try:
|
||||
req = urllib2.Request(fetch_url)
|
||||
response = urllib2.urlopen(req)
|
||||
the_page = response.read()
|
||||
#print the_page
|
||||
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
||||
except:
|
||||
found = ''
|
||||
|
||||
return found
|
||||
|
||||
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
print u"usage: {0} <email> <password> <keyfileout.b64>".format(progname)
|
||||
return 1
|
||||
email, password, keypath = argv[1:]
|
||||
userkey = fetch_key(email, password)
|
||||
if len(userkey) == 28:
|
||||
open(keypath,'wb').write(userkey)
|
||||
return 0
|
||||
print u"Failed to fetch key."
|
||||
return 1
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import tkFileDialog
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"Enter parameters")
|
||||
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||
body = Tkinter.Frame(self)
|
||||
body.pack(fill=Tkconstants.X, expand=1)
|
||||
sticky = Tkconstants.E + Tkconstants.W
|
||||
body.grid_columnconfigure(1, weight=2)
|
||||
Tkinter.Label(body, text=u"Account email address").grid(row=0)
|
||||
self.name = Tkinter.Entry(body, width=40)
|
||||
self.name.grid(row=0, column=1, sticky=sticky)
|
||||
Tkinter.Label(body, text=u"Account password").grid(row=1)
|
||||
self.ccn = Tkinter.Entry(body, width=40)
|
||||
self.ccn.grid(row=1, column=1, sticky=sticky)
|
||||
Tkinter.Label(body, text=u"Output file").grid(row=2)
|
||||
self.keypath = Tkinter.Entry(body, width=40)
|
||||
self.keypath.grid(row=2, column=1, sticky=sticky)
|
||||
self.keypath.insert(2, u"bnepubkey.b64")
|
||||
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||
button.grid(row=2, column=2)
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
botton = Tkinter.Button(
|
||||
buttons, text=u"Fetch", width=10, command=self.generate)
|
||||
botton.pack(side=Tkconstants.LEFT)
|
||||
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||
button = Tkinter.Button(
|
||||
buttons, text=u"Quit", width=10, command=self.quit)
|
||||
button.pack(side=Tkconstants.RIGHT)
|
||||
|
||||
def get_keypath(self):
|
||||
keypath = tkFileDialog.asksaveasfilename(
|
||||
parent=None, title=u"Select B&N ePub key file to produce",
|
||||
defaultextension=u".b64",
|
||||
filetypes=[('base64-encoded files', '.b64'),
|
||||
('All Files', '.*')])
|
||||
if keypath:
|
||||
keypath = os.path.normpath(keypath)
|
||||
self.keypath.delete(0, Tkconstants.END)
|
||||
self.keypath.insert(0, keypath)
|
||||
return
|
||||
|
||||
def generate(self):
|
||||
email = self.name.get()
|
||||
password = self.ccn.get()
|
||||
keypath = self.keypath.get()
|
||||
if not email:
|
||||
self.status['text'] = u"Email address not given"
|
||||
return
|
||||
if not password:
|
||||
self.status['text'] = u"Account password not given"
|
||||
return
|
||||
if not keypath:
|
||||
self.status['text'] = u"Output keyfile path not set"
|
||||
return
|
||||
self.status['text'] = u"Fetching..."
|
||||
try:
|
||||
userkey = fetch_key(email, password)
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||
return
|
||||
if len(userkey) == 28:
|
||||
open(keypath,'wb').write(userkey)
|
||||
self.status['text'] = u"Keyfile fetched successfully"
|
||||
else:
|
||||
self.status['text'] = u"Keyfile fetch failed."
|
||||
|
||||
root = Tkinter.Tk()
|
||||
root.title(u"Barnes & Noble ePub Keyfile Fetch v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(300, 0)
|
||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
sys.exit(cli_main())
|
||||
sys.exit(gui_main())
|
||||
@@ -1,332 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignoblekeygen.pyw, version 2.5
|
||||
# Copyright © 2009-2010 i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||
|
||||
# Windows users: Before running this program, you must first install Python.
|
||||
# We recommend ActiveState Python 2.7.X for Windows (x86) from
|
||||
# http://www.activestate.com/activepython/downloads.
|
||||
# You must also install PyCrypto from
|
||||
# http://www.voidspace.org.uk/python/modules.shtml#pycrypto
|
||||
# (make certain to install the version for Python 2.7).
|
||||
# Then save this script file as ignoblekeygen.pyw and double-click on it to run it.
|
||||
#
|
||||
# Mac OS X users: Save this script file as ignoblekeygen.pyw. You can run this
|
||||
# program from the command line (python ignoblekeygen.pyw) or by double-clicking
|
||||
# it when it has been associated with PythonLauncher.
|
||||
|
||||
# Revision history:
|
||||
# 1 - Initial release
|
||||
# 2 - Add OS X support by using OpenSSL when available (taken/modified from ineptepub v5)
|
||||
# 2.1 - Allow Windows versions of libcrypto to be found
|
||||
# 2.2 - On Windows try PyCrypto first and then OpenSSL next
|
||||
# 2.3 - Modify interface to allow use of import
|
||||
# 2.4 - Improvements to UI and now works in plugins
|
||||
# 2.5 - Additional improvement for unicode and plugin support
|
||||
# 2.6 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 2.7 - Work if TkInter is missing
|
||||
# 2.8 - Fix bug in stand-alone use (import tkFileDialog)
|
||||
|
||||
"""
|
||||
Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "2.8"
|
||||
|
||||
import sys
|
||||
import os
|
||||
import hashlib
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
# encoded using "replace" before writing them.
|
||||
class SafeUnbuffered:
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
self.encoding = stream.encoding
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
try:
|
||||
from calibre.constants import iswindows, isosx
|
||||
except:
|
||||
iswindows = sys.platform.startswith('win')
|
||||
isosx = sys.platform.startswith('darwin')
|
||||
|
||||
def unicode_argv():
|
||||
if iswindows:
|
||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
||||
# strings.
|
||||
|
||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
||||
# characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
|
||||
# as a list of Unicode strings and encode them as utf-8
|
||||
|
||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
||||
|
||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
||||
GetCommandLineW.argtypes = []
|
||||
GetCommandLineW.restype = LPCWSTR
|
||||
|
||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
||||
|
||||
cmd = GetCommandLineW()
|
||||
argc = c_int(0)
|
||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
||||
if argc.value > 0:
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"ignoblekeygen.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
|
||||
|
||||
class IGNOBLEError(Exception):
|
||||
pass
|
||||
|
||||
def _load_crypto_libcrypto():
|
||||
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
|
||||
Structure, c_ulong, create_string_buffer, cast
|
||||
from ctypes.util import find_library
|
||||
|
||||
if iswindows:
|
||||
libcrypto = find_library('libeay32')
|
||||
else:
|
||||
libcrypto = find_library('crypto')
|
||||
|
||||
if libcrypto is None:
|
||||
raise IGNOBLEError('libcrypto not found')
|
||||
libcrypto = CDLL(libcrypto)
|
||||
|
||||
AES_MAXNR = 14
|
||||
|
||||
c_char_pp = POINTER(c_char_p)
|
||||
c_int_p = POINTER(c_int)
|
||||
|
||||
class AES_KEY(Structure):
|
||||
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
|
||||
('rounds', c_int)]
|
||||
AES_KEY_p = POINTER(AES_KEY)
|
||||
|
||||
def F(restype, name, argtypes):
|
||||
func = getattr(libcrypto, name)
|
||||
func.restype = restype
|
||||
func.argtypes = argtypes
|
||||
return func
|
||||
|
||||
AES_set_encrypt_key = F(c_int, 'AES_set_encrypt_key',
|
||||
[c_char_p, c_int, AES_KEY_p])
|
||||
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
|
||||
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
|
||||
c_int])
|
||||
|
||||
class AES(object):
|
||||
def __init__(self, userkey, iv):
|
||||
self._blocksize = len(userkey)
|
||||
self._iv = iv
|
||||
key = self._key = AES_KEY()
|
||||
rv = AES_set_encrypt_key(userkey, len(userkey) * 8, key)
|
||||
if rv < 0:
|
||||
raise IGNOBLEError('Failed to initialize AES Encrypt key')
|
||||
|
||||
def encrypt(self, data):
|
||||
out = create_string_buffer(len(data))
|
||||
rv = AES_cbc_encrypt(data, out, len(data), self._key, self._iv, 1)
|
||||
if rv == 0:
|
||||
raise IGNOBLEError('AES encryption failed')
|
||||
return out.raw
|
||||
|
||||
return AES
|
||||
|
||||
def _load_crypto_pycrypto():
|
||||
from Crypto.Cipher import AES as _AES
|
||||
|
||||
class AES(object):
|
||||
def __init__(self, key, iv):
|
||||
self._aes = _AES.new(key, _AES.MODE_CBC, iv)
|
||||
|
||||
def encrypt(self, data):
|
||||
return self._aes.encrypt(data)
|
||||
|
||||
return AES
|
||||
|
||||
def _load_crypto():
|
||||
AES = None
|
||||
cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
|
||||
if sys.platform.startswith('win'):
|
||||
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
|
||||
for loader in cryptolist:
|
||||
try:
|
||||
AES = loader()
|
||||
break
|
||||
except (ImportError, IGNOBLEError):
|
||||
pass
|
||||
return AES
|
||||
|
||||
AES = _load_crypto()
|
||||
|
||||
def normalize_name(name):
|
||||
return ''.join(x for x in name.lower() if x != ' ')
|
||||
|
||||
|
||||
def generate_key(name, ccn):
|
||||
# remove spaces and case from name and CC numbers.
|
||||
if type(name)==unicode:
|
||||
name = name.encode('utf-8')
|
||||
if type(ccn)==unicode:
|
||||
ccn = ccn.encode('utf-8')
|
||||
|
||||
name = normalize_name(name) + '\x00'
|
||||
ccn = normalize_name(ccn) + '\x00'
|
||||
|
||||
name_sha = hashlib.sha1(name).digest()[:16]
|
||||
ccn_sha = hashlib.sha1(ccn).digest()[:16]
|
||||
both_sha = hashlib.sha1(name + ccn).digest()
|
||||
aes = AES(ccn_sha, name_sha)
|
||||
crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c))
|
||||
userkey = hashlib.sha1(crypt).digest()
|
||||
return userkey.encode('base64')
|
||||
|
||||
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if AES is None:
|
||||
print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
|
||||
"separately. Read the top-of-script comment for details." % \
|
||||
(progname,)
|
||||
return 1
|
||||
if len(argv) != 4:
|
||||
print u"usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname)
|
||||
return 1
|
||||
name, ccn, keypath = argv[1:]
|
||||
userkey = generate_key(name, ccn)
|
||||
open(keypath,'wb').write(userkey)
|
||||
return 0
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import tkFileDialog
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"Enter parameters")
|
||||
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||
body = Tkinter.Frame(self)
|
||||
body.pack(fill=Tkconstants.X, expand=1)
|
||||
sticky = Tkconstants.E + Tkconstants.W
|
||||
body.grid_columnconfigure(1, weight=2)
|
||||
Tkinter.Label(body, text=u"Account Name").grid(row=0)
|
||||
self.name = Tkinter.Entry(body, width=40)
|
||||
self.name.grid(row=0, column=1, sticky=sticky)
|
||||
Tkinter.Label(body, text=u"CC#").grid(row=1)
|
||||
self.ccn = Tkinter.Entry(body, width=40)
|
||||
self.ccn.grid(row=1, column=1, sticky=sticky)
|
||||
Tkinter.Label(body, text=u"Output file").grid(row=2)
|
||||
self.keypath = Tkinter.Entry(body, width=40)
|
||||
self.keypath.grid(row=2, column=1, sticky=sticky)
|
||||
self.keypath.insert(2, u"bnepubkey.b64")
|
||||
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||
button.grid(row=2, column=2)
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
botton = Tkinter.Button(
|
||||
buttons, text=u"Generate", width=10, command=self.generate)
|
||||
botton.pack(side=Tkconstants.LEFT)
|
||||
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||
button = Tkinter.Button(
|
||||
buttons, text=u"Quit", width=10, command=self.quit)
|
||||
button.pack(side=Tkconstants.RIGHT)
|
||||
|
||||
def get_keypath(self):
|
||||
keypath = tkFileDialog.asksaveasfilename(
|
||||
parent=None, title=u"Select B&N ePub key file to produce",
|
||||
defaultextension=u".b64",
|
||||
filetypes=[('base64-encoded files', '.b64'),
|
||||
('All Files', '.*')])
|
||||
if keypath:
|
||||
keypath = os.path.normpath(keypath)
|
||||
self.keypath.delete(0, Tkconstants.END)
|
||||
self.keypath.insert(0, keypath)
|
||||
return
|
||||
|
||||
def generate(self):
|
||||
name = self.name.get()
|
||||
ccn = self.ccn.get()
|
||||
keypath = self.keypath.get()
|
||||
if not name:
|
||||
self.status['text'] = u"Name not specified"
|
||||
return
|
||||
if not ccn:
|
||||
self.status['text'] = u"Credit card number not specified"
|
||||
return
|
||||
if not keypath:
|
||||
self.status['text'] = u"Output keyfile path not specified"
|
||||
return
|
||||
self.status['text'] = u"Generating..."
|
||||
try:
|
||||
userkey = generate_key(name, ccn)
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: (0}".format(e.args[0])
|
||||
return
|
||||
open(keypath,'wb').write(userkey)
|
||||
self.status['text'] = u"Keyfile successfully generated"
|
||||
|
||||
root = Tkinter.Tk()
|
||||
if AES is None:
|
||||
root.withdraw()
|
||||
tkMessageBox.showerror(
|
||||
"Ignoble EPUB Keyfile Generator",
|
||||
"This script requires OpenSSL or PyCrypto, which must be installed "
|
||||
"separately. Read the top-of-script comment for details.")
|
||||
return 1
|
||||
root.title(u"Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(300, 0)
|
||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
sys.exit(cli_main())
|
||||
sys.exit(gui_main())
|
||||
@@ -1,468 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# androidkindlekey.py
|
||||
# Copyright © 2013-15 by Thom and Apprentice Harper
|
||||
# Some portions Copyright © 2010-15 by some_updates and Apprentice Alf
|
||||
#
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - AmazonSecureStorage.xml decryption to serial number
|
||||
# 1.1 - map_data_storage.db decryption to serial number
|
||||
# 1.2 - Changed to be callable from AppleScript by returning only serial number
|
||||
# - and changed name to androidkindlekey.py
|
||||
# - and added in unicode command line support
|
||||
# 1.3 - added in TkInter interface, output to a file
|
||||
# 1.4 - Fix some problems identified by Aldo Bleeker
|
||||
# 1.5 - Fix another problem identified by Aldo Bleeker
|
||||
|
||||
"""
|
||||
Retrieve Kindle for Android Serial Number.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '1.5'
|
||||
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
import getopt
|
||||
import tempfile
|
||||
import zlib
|
||||
import tarfile
|
||||
from hashlib import md5
|
||||
from cStringIO import StringIO
|
||||
from binascii import a2b_hex, b2a_hex
|
||||
|
||||
# Routines common to Mac and PC
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
# encoded using "replace" before writing them.
|
||||
class SafeUnbuffered:
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
self.encoding = stream.encoding
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
try:
|
||||
from calibre.constants import iswindows, isosx
|
||||
except:
|
||||
iswindows = sys.platform.startswith('win')
|
||||
isosx = sys.platform.startswith('darwin')
|
||||
|
||||
def unicode_argv():
|
||||
if iswindows:
|
||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
||||
# strings.
|
||||
|
||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
||||
# characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
|
||||
# as a list of Unicode strings and encode them as utf-8
|
||||
|
||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
||||
|
||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
||||
GetCommandLineW.argtypes = []
|
||||
GetCommandLineW.restype = LPCWSTR
|
||||
|
||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
||||
|
||||
cmd = GetCommandLineW()
|
||||
argc = c_int(0)
|
||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
||||
if argc.value > 0:
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"kindlekey.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
|
||||
class DrmException(Exception):
|
||||
pass
|
||||
|
||||
STORAGE = u"backup.ab"
|
||||
STORAGE1 = u"AmazonSecureStorage.xml"
|
||||
STORAGE2 = u"map_data_storage.db"
|
||||
|
||||
class AndroidObfuscation(object):
|
||||
'''AndroidObfuscation
|
||||
For the key, it's written in java, and run in android dalvikvm
|
||||
'''
|
||||
|
||||
key = a2b_hex('0176e04c9408b1702d90be333fd53523')
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
cipher = self._get_cipher()
|
||||
padding = len(self.key) - len(plaintext) % len(self.key)
|
||||
plaintext += chr(padding) * padding
|
||||
return b2a_hex(cipher.encrypt(plaintext))
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
cipher = self._get_cipher()
|
||||
plaintext = cipher.decrypt(a2b_hex(ciphertext))
|
||||
return plaintext[:-ord(plaintext[-1])]
|
||||
|
||||
def _get_cipher(self):
|
||||
try:
|
||||
from Crypto.Cipher import AES
|
||||
return AES.new(self.key)
|
||||
except ImportError:
|
||||
from aescbc import AES, noPadding
|
||||
return AES(self.key, padding=noPadding())
|
||||
|
||||
class AndroidObfuscationV2(AndroidObfuscation):
|
||||
'''AndroidObfuscationV2
|
||||
'''
|
||||
|
||||
count = 503
|
||||
password = 'Thomsun was here!'
|
||||
|
||||
def __init__(self, salt):
|
||||
key = self.password + salt
|
||||
for _ in range(self.count):
|
||||
key = md5(key).digest()
|
||||
self.key = key[:8]
|
||||
self.iv = key[8:16]
|
||||
|
||||
def _get_cipher(self):
|
||||
try :
|
||||
from Crypto.Cipher import DES
|
||||
return DES.new(self.key, DES.MODE_CBC, self.iv)
|
||||
except ImportError:
|
||||
from python_des import Des, CBC
|
||||
return Des(self.key, CBC, self.iv)
|
||||
|
||||
def parse_preference(path):
|
||||
''' parse android's shared preference xml '''
|
||||
storage = {}
|
||||
read = open(path)
|
||||
for line in read:
|
||||
line = line.strip()
|
||||
# <string name="key">value</string>
|
||||
if line.startswith('<string name="'):
|
||||
index = line.find('"', 14)
|
||||
key = line[14:index]
|
||||
value = line[index+2:-9]
|
||||
storage[key] = value
|
||||
read.close()
|
||||
return storage
|
||||
|
||||
def get_serials1(path=STORAGE1):
|
||||
''' get serials from android's shared preference xml '''
|
||||
|
||||
if not os.path.isfile(path):
|
||||
return []
|
||||
|
||||
storage = parse_preference(path)
|
||||
salt = storage.get('AmazonSaltKey')
|
||||
if salt and len(salt) == 16:
|
||||
obfuscation = AndroidObfuscationV2(a2b_hex(salt))
|
||||
else:
|
||||
obfuscation = AndroidObfuscation()
|
||||
|
||||
def get_value(key):
|
||||
encrypted_key = obfuscation.encrypt(key)
|
||||
encrypted_value = storage.get(encrypted_key)
|
||||
if encrypted_value:
|
||||
return obfuscation.decrypt(encrypted_value)
|
||||
return ''
|
||||
|
||||
# also see getK4Pids in kgenpids.py
|
||||
try:
|
||||
dsnid = get_value('DsnId')
|
||||
except:
|
||||
sys.stderr.write('cannot get DsnId\n')
|
||||
return []
|
||||
|
||||
try:
|
||||
tokens = set(get_value('kindle.account.tokens').split(','))
|
||||
except:
|
||||
return []
|
||||
|
||||
serials = []
|
||||
if dsnid:
|
||||
serials.append(dsnid)
|
||||
for token in tokens:
|
||||
if token:
|
||||
serials.append('%s%s' % (dsnid, token))
|
||||
serials.append(token)
|
||||
return serials
|
||||
|
||||
def get_serials2(path=STORAGE2):
|
||||
''' get serials from android's sql database '''
|
||||
if not os.path.isfile(path):
|
||||
return []
|
||||
|
||||
import sqlite3
|
||||
connection = sqlite3.connect(path)
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%token.device.deviceserialname%' ''')
|
||||
userdata_keys = cursor.fetchall()
|
||||
dsns = []
|
||||
for userdata_row in userdata_keys:
|
||||
try:
|
||||
if userdata_row and userdata_row[0]:
|
||||
userdata_utf8 = userdata_row[0].encode('utf8')
|
||||
if len(userdata_utf8) > 0:
|
||||
dsns.append(userdata_utf8)
|
||||
except:
|
||||
print "Error getting one of the device serial name keys"
|
||||
traceback.print_exc()
|
||||
pass
|
||||
dsns = list(set(dsns))
|
||||
|
||||
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
|
||||
userdata_keys = cursor.fetchall()
|
||||
tokens = []
|
||||
for userdata_row in userdata_keys:
|
||||
try:
|
||||
if userdata_row and userdata_row[0]:
|
||||
userdata_utf8 = userdata_row[0].encode('utf8')
|
||||
if len(userdata_utf8) > 0:
|
||||
tokens.append(userdata_utf8)
|
||||
except:
|
||||
print "Error getting one of the account token keys"
|
||||
traceback.print_exc()
|
||||
pass
|
||||
tokens = list(set(tokens))
|
||||
|
||||
serials = []
|
||||
for x in dsns:
|
||||
serials.append(x)
|
||||
for y in tokens:
|
||||
serials.append('%s%s' % (x, y))
|
||||
for y in tokens:
|
||||
serials.append(y)
|
||||
return serials
|
||||
|
||||
def get_serials(path=STORAGE):
|
||||
'''get serials from files in from android backup.ab
|
||||
backup.ab can be get using adb command:
|
||||
shell> adb backup com.amazon.kindle
|
||||
or from individual files if they're passed.
|
||||
'''
|
||||
if not os.path.isfile(path):
|
||||
return []
|
||||
|
||||
basename = os.path.basename(path)
|
||||
if basename == STORAGE1:
|
||||
return get_serials1(path)
|
||||
elif basename == STORAGE2:
|
||||
return get_serials2(path)
|
||||
|
||||
output = None
|
||||
try :
|
||||
read = open(path, 'rb')
|
||||
head = read.read(24)
|
||||
if head[:14] == 'ANDROID BACKUP':
|
||||
output = StringIO(zlib.decompress(read.read()))
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
read.close()
|
||||
|
||||
if not output:
|
||||
return []
|
||||
|
||||
serials = []
|
||||
tar = tarfile.open(fileobj=output)
|
||||
for member in tar.getmembers():
|
||||
if member.name.strip().endswith(STORAGE1):
|
||||
write = tempfile.NamedTemporaryFile(mode='wb', delete=False)
|
||||
write.write(tar.extractfile(member).read())
|
||||
write.close()
|
||||
write_path = os.path.abspath(write.name)
|
||||
serials.extend(get_serials1(write_path))
|
||||
os.remove(write_path)
|
||||
elif member.name.strip().endswith(STORAGE2):
|
||||
write = tempfile.NamedTemporaryFile(mode='wb', delete=False)
|
||||
write.write(tar.extractfile(member).read())
|
||||
write.close()
|
||||
write_path = os.path.abspath(write.name)
|
||||
serials.extend(get_serials2(write_path))
|
||||
os.remove(write_path)
|
||||
return list(set(serials))
|
||||
|
||||
__all__ = [ 'get_serials', 'getkey']
|
||||
|
||||
# procedure for CLI and GUI interfaces
|
||||
# returns single or multiple keys (one per line) in the specified file
|
||||
def getkey(outfile, inpath):
|
||||
keys = get_serials(inpath)
|
||||
if len(keys) > 0:
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
for key in keys:
|
||||
keyfileout.write(key)
|
||||
keyfileout.write("\n")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def usage(progname):
|
||||
print u"Decrypts the serial number(s) of Kindle For Android from Android backup or file"
|
||||
print u"Get backup.ab file using adb backup com.amazon.kindle for Android 4.0+."
|
||||
print u"Otherwise extract AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml"
|
||||
print u"Or map_data_storage.db from /data/data/com.amazon.kindle/databases/map_data_storage.db"
|
||||
print u""
|
||||
print u"Usage:"
|
||||
print u" {0:s} [-h] [-b <backup.ab>] [<outfile.k4a>]".format(progname)
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2010-2015 Thom, some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "hb:")
|
||||
except getopt.GetoptError, err:
|
||||
usage(progname)
|
||||
print u"\nError in options or arguments: {0}".format(err.args[0])
|
||||
return 2
|
||||
|
||||
inpath = ""
|
||||
for o, a in opts:
|
||||
if o == "-h":
|
||||
usage(progname)
|
||||
return 0
|
||||
if o == "-b":
|
||||
inpath = a
|
||||
|
||||
if len(args) > 1:
|
||||
usage(progname)
|
||||
return 2
|
||||
|
||||
if len(args) == 1:
|
||||
# save to the specified file or directory
|
||||
outfile = args[0]
|
||||
if not os.path.isabs(outfile):
|
||||
outfile = os.path.join(os.path.dirname(argv[0]),outfile)
|
||||
outfile = os.path.abspath(outfile)
|
||||
if os.path.isdir(outfile):
|
||||
outfile = os.path.join(os.path.dirname(argv[0]),"androidkindlekey.k4a")
|
||||
else:
|
||||
# save to the same directory as the script
|
||||
outfile = os.path.join(os.path.dirname(argv[0]),"androidkindlekey.k4a")
|
||||
|
||||
# make sure the outpath is OK
|
||||
outfile = os.path.realpath(os.path.normpath(outfile))
|
||||
|
||||
if not os.path.isfile(inpath):
|
||||
usage(progname)
|
||||
print u"\n{0:s} file not found".format(inpath)
|
||||
return 2
|
||||
|
||||
if getkey(outfile, inpath):
|
||||
print u"\nSaved Kindle for Android key to {0}".format(outfile)
|
||||
else:
|
||||
print u"\nCould not retrieve Kindle for Android key."
|
||||
return 0
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import tkFileDialog
|
||||
except:
|
||||
print "Tkinter not installed"
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"Select backup.ab file")
|
||||
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||
body = Tkinter.Frame(self)
|
||||
body.pack(fill=Tkconstants.X, expand=1)
|
||||
sticky = Tkconstants.E + Tkconstants.W
|
||||
body.grid_columnconfigure(1, weight=2)
|
||||
Tkinter.Label(body, text=u"Backup file").grid(row=0, column=0)
|
||||
self.keypath = Tkinter.Entry(body, width=40)
|
||||
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||
self.keypath.insert(2, u"backup.ab")
|
||||
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||
button.grid(row=0, column=2)
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
button2 = Tkinter.Button(
|
||||
buttons, text=u"Extract", width=10, command=self.generate)
|
||||
button2.pack(side=Tkconstants.LEFT)
|
||||
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||
button3 = Tkinter.Button(
|
||||
buttons, text=u"Quit", width=10, command=self.quit)
|
||||
button3.pack(side=Tkconstants.RIGHT)
|
||||
|
||||
def get_keypath(self):
|
||||
keypath = tkFileDialog.askopenfilename(
|
||||
parent=None, title=u"Select backup.ab file",
|
||||
defaultextension=u".ab",
|
||||
filetypes=[('adb backup com.amazon.kindle', '.ab'),
|
||||
('All Files', '.*')])
|
||||
if keypath:
|
||||
keypath = os.path.normpath(keypath)
|
||||
self.keypath.delete(0, Tkconstants.END)
|
||||
self.keypath.insert(0, keypath)
|
||||
return
|
||||
|
||||
def generate(self):
|
||||
inpath = self.keypath.get()
|
||||
self.status['text'] = u"Getting key..."
|
||||
try:
|
||||
keys = get_serials(inpath)
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(progpath,u"kindlekey{0:d}.k4a".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
success = True
|
||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||
return
|
||||
self.status['text'] = u"Select backup.ab file"
|
||||
|
||||
argv=unicode_argv()
|
||||
progpath, progname = os.path.split(argv[0])
|
||||
root = Tkinter.Tk()
|
||||
root.title(u"Kindle for Android Key Extraction v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(300, 0)
|
||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
sys.exit(cli_main())
|
||||
sys.exit(gui_main())
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,275 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# kindleforios4key.py
|
||||
# Copyright © 2013 by Apprentice Alf
|
||||
# Portions Copyright © 2007, 2009 Igor Skochinsky <skochinsky@mail.ru>
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Generates fixed PID for Kindle for iOS 3.1.1 running on iOS 4.x
|
||||
|
||||
|
||||
"""
|
||||
Generate fixed PID for Kindle for iOS 3.1.1
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '1.0'
|
||||
|
||||
import sys, os
|
||||
import getopt
|
||||
import binascii
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
# encoded using "replace" before writing them.
|
||||
class SafeUnbuffered:
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
self.encoding = stream.encoding
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
iswindows = sys.platform.startswith('win')
|
||||
isosx = sys.platform.startswith('darwin')
|
||||
|
||||
def unicode_argv():
|
||||
if iswindows:
|
||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
||||
# strings.
|
||||
|
||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
||||
# characters with '?'.
|
||||
|
||||
|
||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
||||
|
||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
||||
GetCommandLineW.argtypes = []
|
||||
GetCommandLineW.restype = LPCWSTR
|
||||
|
||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
||||
|
||||
cmd = GetCommandLineW()
|
||||
argc = c_int(0)
|
||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
||||
if argc.value > 0:
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"mobidedrm.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
|
||||
import hashlib
|
||||
|
||||
def SHA256(message):
|
||||
ctx = hashlib.sha256()
|
||||
ctx.update(message)
|
||||
return ctx.digest()
|
||||
|
||||
def crc32(s):
|
||||
return (~binascii.crc32(s,-1))&0xFFFFFFFF
|
||||
|
||||
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
||||
|
||||
def checksumPid(s):
|
||||
crc = crc32(s)
|
||||
crc = crc ^ (crc >> 16)
|
||||
res = s
|
||||
l = len(letters)
|
||||
for i in (0,1):
|
||||
b = crc & 0xff
|
||||
pos = (b // l) ^ (b % l)
|
||||
res += letters[pos%l]
|
||||
crc >>= 8
|
||||
|
||||
return res
|
||||
|
||||
def pidFromSerial(s, l):
|
||||
crc = crc32(s)
|
||||
|
||||
arr1 = [0]*l
|
||||
for i in xrange(len(s)):
|
||||
arr1[i%l] ^= ord(s[i])
|
||||
|
||||
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
|
||||
for i in xrange(l):
|
||||
arr1[i] ^= crc_bytes[i&3]
|
||||
|
||||
pid = ''
|
||||
for i in xrange(l):
|
||||
b = arr1[i] & 0xff
|
||||
pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]
|
||||
|
||||
return pid
|
||||
|
||||
def generatekeys(email, mac):
|
||||
keys = []
|
||||
email = email.encode('utf-8').lower()
|
||||
mac = mac.encode('utf-8').lower()
|
||||
cleanmac = "".join(c if (c in "0123456789abcdef") else "" for c in mac)
|
||||
lowermac = cleanmac.lower()
|
||||
#print lowermac
|
||||
keyseed = lowermac + email.encode('utf-8')
|
||||
#print keyseed
|
||||
keysha256 = SHA256(keyseed)
|
||||
keybase64 = keysha256.encode('base64')
|
||||
#print keybase64
|
||||
cleankeybase64 = "".join(c if (c in "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") else "0" for c in keybase64)
|
||||
#print cleankeybase64
|
||||
pseudoudid = cleankeybase64[:40]
|
||||
#print pseudoudid
|
||||
keys.append(pidFromSerial(pseudoudid.encode("utf-8"),8))
|
||||
return keys
|
||||
|
||||
# interface for Python DeDRM
|
||||
# returns single key or multiple keys, depending on path or file passed in
|
||||
def getkey(email, mac, outpath):
|
||||
keys = generatekeys(email,mac)
|
||||
if len(keys) > 0:
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(keys[0])
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
else:
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(outpath,u"kindleios{0:d}.pid".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
return True
|
||||
return False
|
||||
|
||||
def usage(progname):
|
||||
print u"Generates the key for Kindle for iOS 3.1.1"
|
||||
print u"Requires email address of Amazon acccount"
|
||||
print u"And MAC address for iOS device’s wifi"
|
||||
print u"Outputs to a file or to stdout"
|
||||
print u"Usage:"
|
||||
print u" {0:s} [-h] <email address> <MAC address> [<outfile>]".format(progname)
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2013 Apprentice Alf".format(progname,__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "h")
|
||||
except getopt.GetoptError, err:
|
||||
print u"Error in options or arguments: {0}".format(err.args[0])
|
||||
usage(progname)
|
||||
sys.exit(2)
|
||||
|
||||
for o, a in opts:
|
||||
if o == "-h":
|
||||
usage(progname)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if len(args) < 2 or len(args) > 3:
|
||||
usage(progname)
|
||||
sys.exit(2)
|
||||
|
||||
if len(args) == 3:
|
||||
# save to the specified file or folder
|
||||
getkey(args[0],args[1],args[2])
|
||||
else:
|
||||
keys = generatekeys(args[0],args[1])
|
||||
for key in keys:
|
||||
print key
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
except:
|
||||
print "Tkinter not installed"
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"Enter parameters")
|
||||
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||
body = Tkinter.Frame(self)
|
||||
body.pack(fill=Tkconstants.X, expand=1)
|
||||
sticky = Tkconstants.E + Tkconstants.W
|
||||
body.grid_columnconfigure(1, weight=2)
|
||||
Tkinter.Label(body, text=u"Amazon email address").grid(row=0)
|
||||
self.email = Tkinter.Entry(body, width=40)
|
||||
self.email.grid(row=0, column=1, sticky=sticky)
|
||||
Tkinter.Label(body, text=u"iOS MAC address").grid(row=1)
|
||||
self.mac = Tkinter.Entry(body, width=40)
|
||||
self.mac.grid(row=1, column=1, sticky=sticky)
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
button = Tkinter.Button(
|
||||
buttons, text=u"Generate", width=10, command=self.generate)
|
||||
button.pack(side=Tkconstants.LEFT)
|
||||
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||
button = Tkinter.Button(
|
||||
buttons, text=u"Quit", width=10, command=self.quit)
|
||||
button.pack(side=Tkconstants.RIGHT)
|
||||
|
||||
def generate(self):
|
||||
email = self.email.get()
|
||||
mac = self.mac.get()
|
||||
if not email:
|
||||
self.status['text'] = u"Email not specified"
|
||||
return
|
||||
if not mac:
|
||||
self.status['text'] = u"MAC not specified"
|
||||
return
|
||||
self.status['text'] = u"Generating..."
|
||||
try:
|
||||
keys = generatekeys(email, mac)
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: (0}".format(e.args[0])
|
||||
return
|
||||
self.status['text'] = ", ".join(key for key in keys)
|
||||
|
||||
root = Tkinter.Tk()
|
||||
root.title(u"Kindle for iOS PID Generator v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(300, 0)
|
||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
sys.exit(cli_main())
|
||||
sys.exit(gui_main())
|
||||
@@ -1,50 +0,0 @@
|
||||
Of Historical Interest Only
|
||||
===========================
|
||||
|
||||
It is now much simpler and easier to get a backup.ab file from your Android device and import that into the tools.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Comment at Apprentice Alf's Blog by cestmoicestmoi, 21st December, 2012.
|
||||
========================================================================
|
||||
|
||||
just to share my experience in patching the kindle.apk
|
||||
|
||||
first thanks to all of you guys for this great site & tools, keep going
|
||||
|
||||
following instructions in latest readme.txt
|
||||
successfully patched Android Kindle 3.7.0.108, Android 2.3.4 Xperia Arc unrooted;
|
||||
key painful snags encountered in my case (+ had never coded java/built any apk! .apk = app on Android) (just sums up here the tip of the iceberg):
|
||||
on phone :
|
||||
to get the app kindle.apk (invisible in unrooted phones) :
|
||||
use MyAppShare (free), but this required old Kindle.apk version (for patching) won’t probably be in your phone anymore (latest today 3.8.1.11, with new updates almost everyday !)
|
||||
anyway in any-case turnoff the Play-Store auto update
|
||||
otherwise/then look for a 3.7.0.108 on the web (or try patch a newer one ?)
|
||||
reminder: .apk are just containers = zip files… to check just add an extra .zip and unzip kindle.apk, but don’t play with that… patching wouldn’t work; however all names of kindle.apk can be completely freely changed : kindle2patchedJojo.apk, com.amazon.kindle.apk, etc.
|
||||
on your PC (XP):
|
||||
rather long & tricky expert process for any non-android-app maker
|
||||
(basics is to use the cmd.exe very old DOS style command line with the tricky path and directories changes, forward and back slash etc etc !)
|
||||
pb to obtain all the necessary .exe tools (then you call them as a ‘command’ in cdm.exe)
|
||||
to make things easier, put copy of everything in same folder anywhere:
|
||||
kindle.apk, cmd.exe (from XP), apktool.jar+apktool.bat+aapt.exe (from http://code.google.com/p/android-apktool/), patch.exe (from http://gnuwin32.sourceforge.net/packages/patch.htm), keytool.exe+jarsign.exe(from http://www.oracle.com/technetwork/java/javase/downloads/ download JDK+JRE same recent version, in my case v6038; start with keytool then jarsign; keep these .exe in their java/…/bin files, make shortcuts, copy them in your “work” folder, then do the path thing in cmd.exe in just copying the paths in the properties of the shortcuts)
|
||||
then, key points:
|
||||
download/use apktool.jar v143 not v150 (version seen in calling from cdm.exe)
|
||||
do not “apktool if…” (frameworks)
|
||||
besides all cmd.exe commands given in the readme.txt (read both .txt the old and the newer, even if same name) are perfectly ok (except in keytool: a typo: -valkeidity is -validity; and -genkey preferably -genkeypair)
|
||||
(for the tricky keytool then jarsign stuff better read for ex. https://www.owasp.org/index.php/Signing_jar_files_with_jarsigner)
|
||||
(almost1) finally move your patched & signed new kindle.apk anywhere to phone sd card, and then from within android just like possibly for any .apk move to it with any file manager and click it, it installs by itself (mine didn’t want to install over the non-patched kindle app, so I had to desinstall the latter 1st from within the settings, but it’s probably because my phone ram was full !)
|
||||
(almost2) finally you get your famous PIDs at the bottom of the info in the kindle.apk: don’t worry, in my test case (just 1 book yet), there were 11 PIDs, but 5 were redundant, and the last is a weird large neg number -obviously not a 10 max char. PID, so forget it-).
|
||||
then thanks to Android 2.3.4 now you can screenshot (press power button then back button), then from my PC i got this screen pict with all these PIDs, which I even OCRised (stupidly without checking… so I mixed up some “O” and “0″ etc !),
|
||||
(almost3) finally then I implemented these PIDs in deDRM v541, …and it didn’t work, long error message, but, finally, at last, worked in deDRM v531 !
|
||||
|
||||
conclusion:
|
||||
the idea and work behind this patch is brilliant… but I have the bad feeling that most of this/my rather crazy work above to do myself the whole patching thing was more for glory, like climbing some kind of Everest without any training, than anything else…
|
||||
must precise that I have only an old mac-ppc + this Android phone; ppc has no kindle.app available*; ppc has no java 1.6 so no possible apktool etc. (nor eclipse Android plugin etc.) (possible with very old versions ?), luckily I had an old version of VPC(virtualpc)/XP (extremely slow!), so I could try all this java stuff at least in a normal XP window/virtual machine, on my mac-ppc; i didn’t try any XP-Android link through USB or else, had only my usual Mac-Android wifi ftp (great direct drag’n drop with cyberduck).
|
||||
But when i see all these/my efforts described above for a non-specialist (like 1 week work vs. 1-2 hours for you apk developers !), I wonder if this patched apk shouldn’t/couldn’t be made directly available for most other average joes, no ? just like deDRM ? (or maybe i don’t see the problems here ?);
|
||||
besides, i didn’t check, but i have the strong feeling that in using kindle directly on XP to buy my books, this great app deDRM would have found directly these PIDs or other keys automatically (as it does for me on my mac-ppc for some ebook purchased and downloaded directly from other vendors; i.e. “stupid me”… on XP, the deDRM app would have worked in 1 sec… vs. 1 week work above for patching Android, + the crazy procedure just to get these pids, screenshot + ocr + etc. ! well, it should be much faster now, of course !).
|
||||
|
||||
*reminder: there is a solution for a mac-ppc only guy who want to buy & read Kindle books for very cheap, and accept to read them online in a browser : little advertised “Kindle cloud reader” which used to work even with rather old Safari versions (didn’t check recently if it still works)… (looks a lot like the ‘Google play’ book reader) texts are very little protected/not encrypted there apparently (journalists even said at launch 2-3 years ago that Amazon was abandoning DRMs, just like Apple did with music a while ago) : texts can more or less easily be copied piece by piece/ by page with a few astute clicks (same for Google; but formatting is gone; and who want to recopy a 300 p. book page by page !); tried to reverse engineer a bit for fun this browser reader to see where and how the text was stored, etc. but (as mentioned I think by another in another comment) the text is downloaded only by pieces, not all at once (don’t remember what if browser turned “offline” ?), and stored here and there, in caches… maybe there are tools for capturing auto these books sent to browsers ? didn’t check ? (not for my mac-ppc at the time in any case; but possibly Windows).
|
||||
@@ -1,50 +0,0 @@
|
||||
Kindle for Android
|
||||
------------------
|
||||
|
||||
Kindle for Android uses a different scheme to generate its books specific PIDs than Kindle for PC, Kindle for iPhone/iPad, and standalone Kindles.
|
||||
|
||||
Unfortunately, K4Android uses an "account secrets" file that would only be available if the device were jail-broken and even then someone would have to figure out how to decode this secret information in order to reverse the process.
|
||||
|
||||
Instead of trying to calculate the correct PIDs for each book from this primary data, "Me" (a commenter who posted to the ApprenticeAlf site) came up with a wonderful idea to simply modify the Kindle 3 for Android application to store the PIDs it uses to decode each book in its "about activity" window. This list of PIDS can then be provided to MobiDeDRM.py, which in its latest incarnations allows a comma separated list of pids to be passed in, to successfully remove the DRM from that book. Effectively "Me" has created an "Unswindle" for the Kindle for Android 3 application!
|
||||
|
||||
Obviously, to use "Me"'s approach, requires an Android Developer's Certificate (to sign the modified application) and access to and knowledge of the developer tools, but does not require anything to be jail-broken.
|
||||
|
||||
This is a copy the detailed instructions supplied by "Me" to the ApprenticeAlf blog in the comments. The kindle3.patch described below is included in this folder in the tools:
|
||||
|
||||
From the ApprenticeAlf Comments:
|
||||
|
||||
"Me" writes:
|
||||
|
||||
A better solution seems to create a patched version of the Kindle apk which either logs or displays it’s PID. I created a patch to both log the pid list and show it in the Kindle application in the about activity screen. The pid list isn’t available until the DRMed book has been opened (and the list seem to differ for different books).
|
||||
|
||||
To create the patched kindle apk a certificate must be created (http://developer.android.com/guide/publishing/app-signing.html#cert) and the apktool must be build from source (all subprojects) as long as version 1.4.2 isn’t released (http://code.google.com/p/android-apktool/wiki/BuildApktool).
|
||||
|
||||
These are the steps to pull the original apk from the Android device, uninstall it, create a patched apk and install that (tested on a rooted device, but I think all steps should also work on non-rooted devices):
|
||||
|
||||
adb pull /data/app/com.amazon.kindle-1.apk kindle3.apk
|
||||
adb uninstall com.amazon.kindle
|
||||
apktool d kindle3.apk kindle3
|
||||
cd kindle3
|
||||
patch -p1 < ../kindle3.patch
|
||||
cd ..
|
||||
apktool b kindle3 kindle3_patched.apk
|
||||
jarsigner -verbose -keystore kindle.keystore kindle3_patched.apk kindle
|
||||
zipalign -v 4 kindle3_patched.apk kindle3_signed.apk
|
||||
adb install kindle3_signed.apk
|
||||
|
||||
kindle3.patch (based on kindle version 3.0.1.70) is available on pastebin:
|
||||
http://pastebin.com/LNpgkcpP
|
||||
|
||||
Have fun!
|
||||
|
||||
Comment by me — June 9, 2011 @ 9:01 pm | Reply
|
||||
|
||||
Hi me,
|
||||
Wow! Great work!!!!
|
||||
|
||||
With your patch, you have created the equivalent of Unswindle for the Kindle for Android app and it does not even require jailbreaking!
|
||||
|
||||
Very nice work indeed!
|
||||
|
||||
Comment by some_updates — June 10, 2011 @ 4:28 am | Reply
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
diff -ru kindle3_orig/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali kindle3/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali
|
||||
--- kindle3_orig/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali
|
||||
+++ kindle3/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
.field private security:Lcom/mobipocket/android/library/reader/AndroidSecurity;
|
||||
|
||||
+.field private pidList:Ljava/lang/String;
|
||||
+
|
||||
|
||||
# direct methods
|
||||
.method public constructor <init>(Lcom/mobipocket/android/library/reader/AndroidSecurity;Lcom/amazon/kcp/application/AndroidDeviceType;)V
|
||||
@@ -28,6 +30,10 @@
|
||||
.line 26
|
||||
iput-object p2, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->deviceType:Lcom/amazon/kcp/application/AndroidDeviceType;
|
||||
|
||||
+ const-string v0, "Open DRMed book to show PID list."
|
||||
+
|
||||
+ iput-object v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
|
||||
+
|
||||
.line 27
|
||||
new-instance v0, Ljava/lang/StringBuilder;
|
||||
|
||||
@@ -175,4 +181,26 @@
|
||||
move-result-object v0
|
||||
|
||||
return-object v0
|
||||
+.end method
|
||||
+
|
||||
+.method public getPidList()Ljava/lang/String;
|
||||
+ .locals 1
|
||||
+
|
||||
+ .prologue
|
||||
+ .line 15
|
||||
+ iget-object v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
|
||||
+
|
||||
+ return-object v0
|
||||
+.end method
|
||||
+
|
||||
+.method public setPidList(Ljava/lang/String;)V
|
||||
+ .locals 0
|
||||
+ .parameter "value"
|
||||
+
|
||||
+ .prologue
|
||||
+ .line 11
|
||||
+ iput-object p1, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
|
||||
+
|
||||
+ .line 12
|
||||
+ return-void
|
||||
.end method
|
||||
diff -ru kindle3_orig/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali kindle3/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali
|
||||
--- kindle3_orig/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali
|
||||
+++ kindle3/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali
|
||||
@@ -27,3 +27,9 @@
|
||||
|
||||
.method public abstract getPid()Ljava/lang/String;
|
||||
.end method
|
||||
+
|
||||
+.method public abstract getPidList()Ljava/lang/String;
|
||||
+.end method
|
||||
+
|
||||
+.method public abstract setPidList(Ljava/lang/String;)V
|
||||
+.end method
|
||||
\ No newline at end of file
|
||||
diff -ru kindle3_orig/smali/com/amazon/kcp/info/AboutActivity.smali kindle3/smali/com/amazon/kcp/info/AboutActivity.smali
|
||||
--- kindle3_orig/smali/com/amazon/kcp/info/AboutActivity.smali
|
||||
+++ kindle3/smali/com/amazon/kcp/info/AboutActivity.smali
|
||||
@@ -32,9 +32,11 @@
|
||||
invoke-direct {v6, v1}, Ljava/util/ArrayList;-><init>(I)V
|
||||
|
||||
.line 36
|
||||
- const v1, 0x7f0b0005
|
||||
+ invoke-static {}, Lcom/amazon/kcp/application/DeviceInformationProviderFactory;->getProvider()Lcom/amazon/kcp/application/IDeviceInformationProvider;
|
||||
|
||||
- invoke-virtual {p0, v1}, Lcom/amazon/kcp/info/AboutActivity;->getString(I)Ljava/lang/String;
|
||||
+ move-result-object v0
|
||||
+
|
||||
+ invoke-interface {v0}, Lcom/amazon/kcp/application/IDeviceInformationProvider;->getPidList()Ljava/lang/String;
|
||||
|
||||
move-result-object v1
|
||||
|
||||
diff -ru kindle3_orig/smali/com/amazon/system/security/Security.smali kindle3/smali/com/amazon/system/security/Security.smali
|
||||
--- kindle3_orig/smali/com/amazon/system/security/Security.smali
|
||||
+++ kindle3/smali/com/amazon/system/security/Security.smali
|
||||
@@ -884,6 +884,15 @@
|
||||
|
||||
.line 332
|
||||
:cond_1
|
||||
+
|
||||
+ const-string v1, "PID list"
|
||||
+ invoke-static {}, Lcom/amazon/kcp/application/DeviceInformationProviderFactory;->getProvider()Lcom/amazon/kcp/application/IDeviceInformationProvider;
|
||||
+ move-result-object v0
|
||||
+ invoke-static {v7}, Ljava/util/Arrays;->toString([Ljava/lang/Object;)Ljava/lang/String;
|
||||
+ move-result-object v2
|
||||
+ invoke-interface {v0, v2}, Lcom/amazon/kcp/application/IDeviceInformationProvider;->setPidList(Ljava/lang/String;)V
|
||||
+ invoke-static {v1, v2}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
|
||||
+
|
||||
return-object v7
|
||||
|
||||
:cond_2
|
||||
@@ -1,74 +0,0 @@
|
||||
Kindle for Android
|
||||
------------------
|
||||
|
||||
Kindle for Android uses a different scheme to generate its books specific PIDs than Kindle for PC, Kindle for iPhone/iPad, and standalone Kindles.
|
||||
|
||||
Unfortunately, K4Android uses an "account secrets" file that would only be available if the device were jail-broken and even then someone would have to figure out how to decode this secret information in order to reverse the process.
|
||||
|
||||
Instead of trying to calculate the correct PIDs for each book from this primary data, "Me" (a commenter who posted to the ApprenticeAlf site) came up with a wonderful idea to simply modify the Kindle 3 for Android application to store the PIDs it uses to decode each book in its "about activity" window. This list of PIDS can then be provided to MobiDeDRM.py, which in its latest incarnations allows a comma separated list of pids to be passed in, to successfully remove the DRM from that book. Effectively "Me" has created an "Unswindle" for the Kindle for Android 3 application!
|
||||
|
||||
"Me"'s original patch was for Kindle for Android version 3.0.1.70. Now "Me II" has created a patch for Kindle for Android version 3.7.0.108 and new instructions, since some of the previous steps are no longer necessary.
|
||||
|
||||
From the ApprenticeAlf Comments:
|
||||
|
||||
|
||||
"Me II" writes:
|
||||
|
||||
Since “Me”‘s old method for getting PIDs from Kindle for Android is outdated and no longer works with newer versions of the app, I decided I’d take a stab at bringing it up to date. It took a little fiddling to get everything working, considering how much has changed since the last patch, but I managed to do it. The process is pretty much identical to “Me”‘s original instructions, with a few minor changes.
|
||||
|
||||
1) You don’t need to build apktool from source. You can just grab the binaries from here for whatever OS you’re running: http://code.google.com/p/android-apktool/
|
||||
2) When you sign the rebuilt APK, use the following command instead of the one in the instructions:
|
||||
jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore kindle.keystore kindle3_patched.apk kindle
|
||||
3) It no longer logs the PIDs, only displays them within the app.
|
||||
|
||||
You can get the new patch, for version 3.7.0.108, here: http://pastebin.com/6FN2cTSN
|
||||
|
||||
And here’s a screenshot of the updated menu: http://imgur.com/BbFVF (sorry for the Japanese, I was too lazy to change my phone’s language).
|
||||
|
||||
Subsequently, "s" wrote:
|
||||
|
||||
For others it would be useful to add the keystore generation command into the help file:
|
||||
keytool -genkey -v -keystore kindle.keystore -alias kindle -keyalg RSA -keysize 2048 -validity 10000
|
||||
As well as location of prc’s on android being (with sdcard):
|
||||
/mnt/sdcard/Android/data/com.amazon.kindle/files/
|
||||
|
||||
"s" also reported success with using the patch on version 3.7.1.8, although I recommend using the 3.7.0.108 version just in case.
|
||||
|
||||
|
||||
"Me"'s original instructions, from the ApprenticeAlf Comments:
|
||||
|
||||
"Me" writes:
|
||||
|
||||
A better solution seems to create a patched version of the Kindle apk which either logs or displays it’s PID. I created a patch to both log the pid list and show it in the Kindle application in the about activity screen. The pid list isn’t available until the DRMed book has been opened (and the list seem to differ for different books).
|
||||
|
||||
To create the patched kindle apk a certificate must be created (http://developer.android.com/guide/publishing/app-signing.html#cert) and the apktool must be build from source (all subprojects) as long as version 1.4.2 isn’t released (http://code.google.com/p/android-apktool/wiki/BuildApktool).
|
||||
|
||||
These are the steps to pull the original apk from the Android device, uninstall it, create a patched apk and install that (tested on a rooted device, but I think all steps should also work on non-rooted devices):
|
||||
|
||||
adb pull /data/app/com.amazon.kindle-1.apk kindle3.apk
|
||||
adb uninstall com.amazon.kindle
|
||||
apktool d kindle3.apk kindle3
|
||||
cd kindle3
|
||||
patch -p1 < ../kindle3.patch
|
||||
cd ..
|
||||
apktool b kindle3 kindle3_patched.apk
|
||||
jarsigner -verbose -keystore kindle.keystore kindle3_patched.apk kindle
|
||||
zipalign -v 4 kindle3_patched.apk kindle3_signed.apk
|
||||
adb install kindle3_signed.apk
|
||||
|
||||
kindle3.patch (based on kindle version 3.0.1.70) is available on pastebin:
|
||||
http://pastebin.com/LNpgkcpP
|
||||
|
||||
Have fun!
|
||||
|
||||
Comment by me — June 9, 2011 @ 9:01 pm | Reply
|
||||
|
||||
Hi me,
|
||||
Wow! Great work!!!!
|
||||
|
||||
With your patch, you have created the equivalent of Unswindle for the Kindle for Android app and it does not even require jailbreaking!
|
||||
|
||||
Very nice work indeed!
|
||||
|
||||
Comment by some_updates — June 10, 2011 @ 4:28 am | Reply
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
diff -ru kindle3.7.0.108_orig/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali kindle3.7.0.108/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali
|
||||
--- kindle3.7.0.108_orig/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali
|
||||
+++ kindle3.7.0.108/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali
|
||||
@@ -43,6 +43,8 @@
|
||||
|
||||
.field private security:Lcom/mobipocket/android/library/reader/AndroidSecurity;
|
||||
|
||||
+.field private pidList:Ljava/lang/String;
|
||||
+
|
||||
.field private totalMemory:J
|
||||
|
||||
|
||||
@@ -78,6 +80,10 @@
|
||||
|
||||
.line 132
|
||||
iput-object p2, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->deviceType:Lcom/amazon/kcp/application/AmazonDeviceType;
|
||||
+
|
||||
+ const-string v0, "Open DRMed book to show PID list."
|
||||
+
|
||||
+ iput-object v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
|
||||
|
||||
.line 133
|
||||
sget-object v0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->TAG:Ljava/lang/String;
|
||||
@@ -1242,3 +1248,25 @@
|
||||
|
||||
return-wide v0
|
||||
.end method
|
||||
+
|
||||
+.method public getPidList()Ljava/lang/String;
|
||||
+ .locals 1
|
||||
+
|
||||
+ .prologue
|
||||
+ .line 15
|
||||
+ iget-object v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
|
||||
+
|
||||
+ return-object v0
|
||||
+.end method
|
||||
+
|
||||
+.method public setPidList(Ljava/lang/String;)V
|
||||
+ .locals 0
|
||||
+ .parameter "value"
|
||||
+
|
||||
+ .prologue
|
||||
+ .line 11
|
||||
+ iput-object p1, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
|
||||
+
|
||||
+ .line 12
|
||||
+ return-void
|
||||
+.end method
|
||||
\ No newline at end of file
|
||||
diff -ru kindle3.7.0.108_orig/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali kindle3.7.0.108/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali
|
||||
--- kindle3.7.0.108_orig/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali
|
||||
+++ kindle3.7.0.108/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali
|
||||
@@ -30,3 +30,9 @@
|
||||
|
||||
.method public abstract getPid()Ljava/lang/String;
|
||||
.end method
|
||||
+
|
||||
+.method public abstract getPidList()Ljava/lang/String;
|
||||
+.end method
|
||||
+
|
||||
+.method public abstract setPidList(Ljava/lang/String;)V
|
||||
+.end method
|
||||
\ No newline at end of file
|
||||
diff -ru kindle3.7.0.108_orig/smali/com/amazon/kcp/info/AboutActivity.smali kindle3.7.0.108/smali/com/amazon/kcp/info/AboutActivity.smali
|
||||
--- kindle3.7.0.108_orig/smali/com/amazon/kcp/info/AboutActivity.smali
|
||||
+++ kindle3.7.0.108/smali/com/amazon/kcp/info/AboutActivity.smali
|
||||
@@ -493,6 +493,57 @@
|
||||
return-void
|
||||
.end method
|
||||
|
||||
+.method private populatePIDList()V
|
||||
+ .locals 7
|
||||
+
|
||||
+ .prologue
|
||||
+ .line 313
|
||||
+ invoke-static {}, Lcom/amazon/kcp/application/DeviceInformationProviderFactory;->getProvider()Lcom/amazon/kcp/application/IDeviceInformationProvider;
|
||||
+
|
||||
+ move-result-object v0
|
||||
+
|
||||
+ invoke-interface {v0}, Lcom/amazon/kcp/application/IDeviceInformationProvider;->getPidList()Ljava/lang/String;
|
||||
+
|
||||
+ move-result-object v1
|
||||
+
|
||||
+ .line 314
|
||||
+ .local v1, PidList:Ljava/lang/String;
|
||||
+ iget-object v3, p0, Lcom/amazon/kcp/info/AboutActivity;->groupItemList:Ljava/util/List;
|
||||
+
|
||||
+ new-instance v4, Lcom/amazon/kcp/info/AboutActivity$GroupItem;
|
||||
+
|
||||
+ const-string v5, "PID List"
|
||||
+
|
||||
+ const v6, 0x1
|
||||
+
|
||||
+ invoke-direct {v4, p0, v5, v6}, Lcom/amazon/kcp/info/AboutActivity$GroupItem;-><init>(Lcom/amazon/kcp/info/AboutActivity;Ljava/lang/String;Z)V
|
||||
+
|
||||
+ invoke-interface {v3, v4}, Ljava/util/List;->add(Ljava/lang/Object;)Z
|
||||
+
|
||||
+ .line 315
|
||||
+ new-instance v2, Ljava/util/ArrayList;
|
||||
+
|
||||
+ invoke-direct {v2}, Ljava/util/ArrayList;-><init>()V
|
||||
+
|
||||
+ .line 316
|
||||
+ .local v2, children:Ljava/util/List;,"Ljava/util/List<Lcom/amazon/kcp/info/AboutActivity$DetailItem;>;"
|
||||
+ new-instance v3, Lcom/amazon/kcp/info/AboutActivity$DetailItem;
|
||||
+
|
||||
+ const-string v4, "PIDs"
|
||||
+
|
||||
+ invoke-direct {v3, p0, v4, v1}, Lcom/amazon/kcp/info/AboutActivity$DetailItem;-><init>(Lcom/amazon/kcp/info/AboutActivity;Ljava/lang/String;Ljava/lang/String;)V
|
||||
+
|
||||
+ invoke-interface {v2, v3}, Ljava/util/List;->add(Ljava/lang/Object;)Z
|
||||
+
|
||||
+ .line 317
|
||||
+ iget-object v3, p0, Lcom/amazon/kcp/info/AboutActivity;->detailItemList:Ljava/util/List;
|
||||
+
|
||||
+ invoke-interface {v3, v2}, Ljava/util/List;->add(Ljava/lang/Object;)Z
|
||||
+
|
||||
+ .line 318
|
||||
+ return-void
|
||||
+.end method
|
||||
+
|
||||
.method private populateDisplayItems()V
|
||||
.locals 1
|
||||
|
||||
@@ -539,6 +590,9 @@
|
||||
invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populateDisplayInformation()V
|
||||
|
||||
.line 190
|
||||
+ invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populatePIDList()V
|
||||
+
|
||||
+ .line 191
|
||||
return-void
|
||||
|
||||
.line 172
|
||||
diff -ru kindle3.7.0.108_orig/smali/com/amazon/system/security/Security.smali kindle3.7.0.108/smali/com/amazon/system/security/Security.smali
|
||||
--- kindle3.7.0.108_orig/smali/com/amazon/system/security/Security.smali
|
||||
+++ kindle3.7.0.108/smali/com/amazon/system/security/Security.smali
|
||||
@@ -926,6 +926,16 @@
|
||||
sget-object v0, Lcom/amazon/system/security/Security;->CUSTOM_PID_FOR_BUNDLED_DICTIONARY_DRM:Ljava/lang/String;
|
||||
|
||||
aput-object v0, v6, v8
|
||||
+
|
||||
+ invoke-static {}, Lcom/amazon/kcp/application/DeviceInformationProviderFactory;->getProvider()Lcom/amazon/kcp/application/IDeviceInformationProvider;
|
||||
+
|
||||
+ move-result-object v5
|
||||
+
|
||||
+ invoke-static {v6}, Ljava/util/Arrays;->toString([Ljava/lang/Object;)Ljava/lang/String;
|
||||
+
|
||||
+ move-result-object v2
|
||||
+
|
||||
+ invoke-interface {v5, v2}, Lcom/amazon/kcp/application/IDeviceInformationProvider;->setPidList(Ljava/lang/String;)V
|
||||
|
||||
.line 353
|
||||
return-object v6
|
||||
@@ -1,238 +0,0 @@
|
||||
Only in kindle4.0.2.1: build
|
||||
diff -r -u10 kindle4.0.2.1_orig/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali kindle4.0.2.1/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali
|
||||
--- kindle4.0.2.1_orig/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali 2013-05-22 18:39:03.000000000 -0500
|
||||
+++ kindle4.0.2.1/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali 2013-05-23 16:54:53.000000000 -0500
|
||||
@@ -36,20 +36,22 @@
|
||||
.field private maxCpuSpeed:J
|
||||
|
||||
.field private maxMemory:J
|
||||
|
||||
.field private minCpuSpeed:J
|
||||
|
||||
.field private resources:Landroid/content/res/Resources;
|
||||
|
||||
.field private security:Lcom/mobipocket/android/library/reader/AndroidSecurity;
|
||||
|
||||
+.field private pidList:Ljava/lang/String;
|
||||
+
|
||||
.field private totalMemory:J
|
||||
|
||||
|
||||
# direct methods
|
||||
.method static constructor <clinit>()V
|
||||
.locals 1
|
||||
|
||||
.prologue
|
||||
.line 30
|
||||
const-class v0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;
|
||||
@@ -72,20 +74,24 @@
|
||||
.prologue
|
||||
.line 130
|
||||
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||
|
||||
.line 131
|
||||
iput-object p1, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->security:Lcom/mobipocket/android/library/reader/AndroidSecurity;
|
||||
|
||||
.line 132
|
||||
iput-object p2, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->deviceType:Lcom/amazon/kcp/application/AmazonDeviceType;
|
||||
|
||||
+ const-string v0, "Open DRMed book to show PID list."
|
||||
+
|
||||
+ iput-object v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
|
||||
+
|
||||
.line 133
|
||||
sget-object v0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->TAG:Ljava/lang/String;
|
||||
|
||||
new-instance v0, Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V
|
||||
|
||||
const-string v1, "Device Type is set to \""
|
||||
|
||||
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
@@ -1235,10 +1241,33 @@
|
||||
move-result-wide v0
|
||||
|
||||
iput-wide v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->totalMemory:J
|
||||
|
||||
.line 308
|
||||
:cond_0
|
||||
iget-wide v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->totalMemory:J
|
||||
|
||||
return-wide v0
|
||||
.end method
|
||||
+
|
||||
+.method public getPidList()Ljava/lang/String;
|
||||
+ .locals 1
|
||||
+
|
||||
+ .prologue
|
||||
+ .line 15
|
||||
+ iget-object v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
|
||||
+
|
||||
+ return-object v0
|
||||
+.end method
|
||||
+
|
||||
+.method public setPidList(Ljava/lang/String;)V
|
||||
+ .locals 0
|
||||
+ .parameter "value"
|
||||
+
|
||||
+ .prologue
|
||||
+ .line 11
|
||||
+ iput-object p1, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
|
||||
+
|
||||
+ .line 12
|
||||
+ return-void
|
||||
+.end method
|
||||
+
|
||||
diff -r -u10 kindle4.0.2.1_orig/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali kindle4.0.2.1/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali
|
||||
--- kindle4.0.2.1_orig/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali 2013-05-22 18:39:03.000000000 -0500
|
||||
+++ kindle4.0.2.1/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali 2013-05-23 16:55:58.000000000 -0500
|
||||
@@ -23,10 +23,16 @@
|
||||
.end method
|
||||
|
||||
.method public abstract getDeviceTypeId()Ljava/lang/String;
|
||||
.end method
|
||||
|
||||
.method public abstract getOsVersion()Ljava/lang/String;
|
||||
.end method
|
||||
|
||||
.method public abstract getPid()Ljava/lang/String;
|
||||
.end method
|
||||
+
|
||||
+.method public abstract getPidList()Ljava/lang/String;
|
||||
+.end method
|
||||
+
|
||||
+.method public abstract setPidList(Ljava/lang/String;)V
|
||||
+.end method
|
||||
diff -r -u10 kindle4.0.2.1_orig/smali/com/amazon/kcp/info/AboutActivity.smali kindle4.0.2.1/smali/com/amazon/kcp/info/AboutActivity.smali
|
||||
--- kindle4.0.2.1_orig/smali/com/amazon/kcp/info/AboutActivity.smali 2013-05-22 18:39:03.000000000 -0500
|
||||
+++ kindle4.0.2.1/smali/com/amazon/kcp/info/AboutActivity.smali 2013-05-23 17:18:14.000000000 -0500
|
||||
@@ -486,20 +486,71 @@
|
||||
.end local v2 #screenDpi:Ljava/lang/String;
|
||||
:cond_0
|
||||
iget-object v5, p0, Lcom/amazon/kcp/info/AboutActivity;->detailItemList:Ljava/util/List;
|
||||
|
||||
invoke-interface {v5, v0}, Ljava/util/List;->add(Ljava/lang/Object;)Z
|
||||
|
||||
.line 317
|
||||
return-void
|
||||
.end method
|
||||
|
||||
+.method private populatePIDList()V
|
||||
+ .locals 7
|
||||
+
|
||||
+ .prologue
|
||||
+ .line 313
|
||||
+ invoke-static {}, Lcom/amazon/kcp/application/DeviceInformationProviderFactory;->getProvider()Lcom/amazon/kcp/application/IDeviceInformationProvider;
|
||||
+
|
||||
+ move-result-object v0
|
||||
+
|
||||
+ invoke-interface {v0}, Lcom/amazon/kcp/application/IDeviceInformationProvider;->getPidList()Ljava/lang/String;
|
||||
+
|
||||
+ move-result-object v1
|
||||
+
|
||||
+ .line 314
|
||||
+ .local v1, PidList:Ljava/lang/String;
|
||||
+ iget-object v3, p0, Lcom/amazon/kcp/info/AboutActivity;->groupItemList:Ljava/util/List;
|
||||
+
|
||||
+ new-instance v4, Lcom/amazon/kcp/info/AboutActivity$GroupItem;
|
||||
+
|
||||
+ const-string v5, "PID List"
|
||||
+
|
||||
+ const v6, 0x1
|
||||
+
|
||||
+ invoke-direct {v4, p0, v5, v6}, Lcom/amazon/kcp/info/AboutActivity$GroupItem;-><init>(Lcom/amazon/kcp/info/AboutActivity;Ljava/lang/String;Z)V
|
||||
+
|
||||
+ invoke-interface {v3, v4}, Ljava/util/List;->add(Ljava/lang/Object;)Z
|
||||
+
|
||||
+ .line 315
|
||||
+ new-instance v2, Ljava/util/ArrayList;
|
||||
+
|
||||
+ invoke-direct {v2}, Ljava/util/ArrayList;-><init>()V
|
||||
+
|
||||
+ .line 316
|
||||
+ .local v2, children:Ljava/util/List;,"Ljava/util/List<Lcom/amazon/kcp/info/AboutActivity$DetailItem;>;"
|
||||
+ new-instance v3, Lcom/amazon/kcp/info/AboutActivity$DetailItem;
|
||||
+
|
||||
+ const-string v4, "PIDs"
|
||||
+
|
||||
+ invoke-direct {v3, p0, v4, v1}, Lcom/amazon/kcp/info/AboutActivity$DetailItem;-><init>(Lcom/amazon/kcp/info/AboutActivity;Ljava/lang/String;Ljava/lang/String;)V
|
||||
+
|
||||
+ invoke-interface {v2, v3}, Ljava/util/List;->add(Ljava/lang/Object;)Z
|
||||
+
|
||||
+ .line 317
|
||||
+ iget-object v3, p0, Lcom/amazon/kcp/info/AboutActivity;->detailItemList:Ljava/util/List;
|
||||
+
|
||||
+ invoke-interface {v3, v2}, Ljava/util/List;->add(Ljava/lang/Object;)Z
|
||||
+
|
||||
+ .line 318
|
||||
+ return-void
|
||||
+.end method
|
||||
+
|
||||
.method private populateDisplayItems()V
|
||||
.locals 1
|
||||
|
||||
.prologue
|
||||
.line 171
|
||||
iget-object v0, p0, Lcom/amazon/kcp/info/AboutActivity;->groupItemList:Ljava/util/List;
|
||||
|
||||
if-nez v0, :cond_0
|
||||
|
||||
.line 173
|
||||
@@ -531,20 +582,22 @@
|
||||
|
||||
.line 192
|
||||
invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populateRamInformation()V
|
||||
|
||||
.line 193
|
||||
invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populateStorageInformation()V
|
||||
|
||||
.line 194
|
||||
invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populateDisplayInformation()V
|
||||
|
||||
+ invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populatePIDList()V
|
||||
+
|
||||
.line 195
|
||||
return-void
|
||||
|
||||
.line 177
|
||||
:cond_0
|
||||
iget-object v0, p0, Lcom/amazon/kcp/info/AboutActivity;->groupItemList:Ljava/util/List;
|
||||
|
||||
invoke-interface {v0}, Ljava/util/List;->clear()V
|
||||
|
||||
goto :goto_0
|
||||
diff -r -u10 kindle4.0.2.1_orig/smali/com/amazon/system/security/Security.smali kindle4.0.2.1/smali/com/amazon/system/security/Security.smali
|
||||
--- kindle4.0.2.1_orig/smali/com/amazon/system/security/Security.smali 2013-05-22 18:39:04.000000000 -0500
|
||||
+++ kindle4.0.2.1/smali/com/amazon/system/security/Security.smali 2013-05-23 17:19:05.000000000 -0500
|
||||
@@ -920,20 +920,30 @@
|
||||
|
||||
.line 350
|
||||
:cond_2
|
||||
add-int/lit8 v8, v8, 0x1
|
||||
|
||||
.line 351
|
||||
sget-object v0, Lcom/amazon/system/security/Security;->CUSTOM_PID_FOR_BUNDLED_DICTIONARY_DRM:Ljava/lang/String;
|
||||
|
||||
aput-object v0, v6, v8
|
||||
|
||||
+ invoke-static {}, Lcom/amazon/kcp/application/DeviceInformationProviderFactory;->getProvider()Lcom/amazon/kcp/application/IDeviceInformationProvider;
|
||||
+
|
||||
+ move-result-object v5
|
||||
+
|
||||
+ invoke-static {v6}, Ljava/util/Arrays;->toString([Ljava/lang/Object;)Ljava/lang/String;
|
||||
+
|
||||
+ move-result-object v2
|
||||
+
|
||||
+ invoke-interface {v5, v2}, Lcom/amazon/kcp/application/IDeviceInformationProvider;->setPidList(Ljava/lang/String;)V
|
||||
+
|
||||
.line 353
|
||||
return-object v6
|
||||
.end method
|
||||
|
||||
|
||||
# virtual methods
|
||||
.method public customDrmOnly()I
|
||||
.locals 1
|
||||
|
||||
.prologue
|
||||
@@ -1,11 +0,0 @@
|
||||
Notes from UE01 about this patch:
|
||||
|
||||
1. Revised the “Other_Tools/Kindle_for_Android_Patches/” for Kindle 4.8.1.10
|
||||
2. Built Kindle 4.8.1.10 with the PID List added to the About activity
|
||||
3. Uninstalled the Amazon/Play-store version and installed the patched version
|
||||
4. Signed in to Amazon
|
||||
5. Opened the book
|
||||
6. Did Info > About > PID List and copied the PIDs to Calibre’s Plugins>File type > DeDRM > Mobipocket dialog
|
||||
7. **Crucial** copied the PRC file to the PC (because the file’s checksum has changed since it was last copied)
|
||||
8. In Calibre, Add Books (from a single directory)
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
diff --git a/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali b/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali
|
||||
index 8ea400e..3aefad2 100644
|
||||
--- a/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali
|
||||
+++ b/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali
|
||||
@@ -41,6 +41,8 @@
|
||||
|
||||
.field private security:Lcom/mobipocket/android/library/reader/AndroidSecurity;
|
||||
|
||||
+.field private pidList:Ljava/lang/String;
|
||||
+
|
||||
.field private totalMemory:J
|
||||
|
||||
|
||||
@@ -74,6 +76,10 @@
|
||||
.line 133
|
||||
iput-object p1, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->security:Lcom/mobipocket/android/library/reader/AndroidSecurity;
|
||||
|
||||
+ const-string v0, "Open DRMed book to show PID list."
|
||||
+
|
||||
+ iput-object v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
|
||||
+
|
||||
.line 134
|
||||
sget-object v0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->TAG:Ljava/lang/String;
|
||||
|
||||
@@ -1339,3 +1345,26 @@
|
||||
|
||||
return-wide v0
|
||||
.end method
|
||||
+
|
||||
+.method public getPidList()Ljava/lang/String;
|
||||
+ .locals 1
|
||||
+
|
||||
+ .prologue
|
||||
+ .line 15
|
||||
+ iget-object v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
|
||||
+
|
||||
+ return-object v0
|
||||
+.end method
|
||||
+
|
||||
+.method public setPidList(Ljava/lang/String;)V
|
||||
+ .locals 0
|
||||
+ .param p1, "value"
|
||||
+
|
||||
+ .prologue
|
||||
+ .line 11
|
||||
+ iput-object p1, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
|
||||
+
|
||||
+ .line 12
|
||||
+ return-void
|
||||
+.end method
|
||||
+
|
||||
diff --git a/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali b/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali
|
||||
index e4a3523..2269fab 100644
|
||||
--- a/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali
|
||||
+++ b/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali
|
||||
@@ -30,3 +30,9 @@
|
||||
|
||||
.method public abstract getPid()Ljava/lang/String;
|
||||
.end method
|
||||
+
|
||||
+.method public abstract getPidList()Ljava/lang/String;
|
||||
+.end method
|
||||
+
|
||||
+.method public abstract setPidList(Ljava/lang/String;)V
|
||||
+.end method
|
||||
diff --git a/smali/com/amazon/kcp/info/AboutActivity.smali b/smali/com/amazon/kcp/info/AboutActivity.smali
|
||||
index 5640e9e..e298341 100644
|
||||
--- a/smali/com/amazon/kcp/info/AboutActivity.smali
|
||||
+++ b/smali/com/amazon/kcp/info/AboutActivity.smali
|
||||
@@ -493,6 +493,57 @@
|
||||
return-void
|
||||
.end method
|
||||
|
||||
+.method private populatePIDList()V
|
||||
+ .locals 7
|
||||
+
|
||||
+ .prologue
|
||||
+ .line 313
|
||||
+ invoke-static {}, Lcom/amazon/kcp/application/DeviceInformationProviderFactory;->getProvider()Lcom/amazon/kcp/application/IDeviceInformationProvider;
|
||||
+
|
||||
+ move-result-object v0
|
||||
+
|
||||
+ invoke-interface {v0}, Lcom/amazon/kcp/application/IDeviceInformationProvider;->getPidList()Ljava/lang/String;
|
||||
+
|
||||
+ move-result-object v1
|
||||
+
|
||||
+ .line 314
|
||||
+ .local v1, "PidList":Ljava/lang/String;
|
||||
+ iget-object v3, p0, Lcom/amazon/kcp/info/AboutActivity;->groupItemList:Ljava/util/List;
|
||||
+
|
||||
+ new-instance v4, Lcom/amazon/kcp/info/AboutActivity$GroupItem;
|
||||
+
|
||||
+ const-string v5, "PID List"
|
||||
+
|
||||
+ const v6, 0x1
|
||||
+
|
||||
+ invoke-direct {v4, p0, v5, v6}, Lcom/amazon/kcp/info/AboutActivity$GroupItem;-><init>(Lcom/amazon/kcp/info/AboutActivity;Ljava/lang/String;Z)V
|
||||
+
|
||||
+ invoke-interface {v3, v4}, Ljava/util/List;->add(Ljava/lang/Object;)Z
|
||||
+
|
||||
+ .line 315
|
||||
+ new-instance v2, Ljava/util/ArrayList;
|
||||
+
|
||||
+ invoke-direct {v2}, Ljava/util/ArrayList;-><init>()V
|
||||
+
|
||||
+ .line 316
|
||||
+ .local v2, "children":Ljava/util/List;,"Ljava/util/List<Lcom/amazon/kcp/info/AboutActivity$DetailItem;>;"
|
||||
+ new-instance v3, Lcom/amazon/kcp/info/AboutActivity$DetailItem;
|
||||
+
|
||||
+ const-string v4, "PIDs"
|
||||
+
|
||||
+ invoke-direct {v3, p0, v4, v1}, Lcom/amazon/kcp/info/AboutActivity$DetailItem;-><init>(Lcom/amazon/kcp/info/AboutActivity;Ljava/lang/String;Ljava/lang/String;)V
|
||||
+
|
||||
+ invoke-interface {v2, v3}, Ljava/util/List;->add(Ljava/lang/Object;)Z
|
||||
+
|
||||
+ .line 317
|
||||
+ iget-object v3, p0, Lcom/amazon/kcp/info/AboutActivity;->detailItemList:Ljava/util/List;
|
||||
+
|
||||
+ invoke-interface {v3, v2}, Ljava/util/List;->add(Ljava/lang/Object;)Z
|
||||
+
|
||||
+ .line 318
|
||||
+ return-void
|
||||
+.end method
|
||||
+
|
||||
.method private populateDisplayItems()V
|
||||
.locals 1
|
||||
|
||||
@@ -538,6 +589,8 @@
|
||||
.line 173
|
||||
invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populateDisplayInformation()V
|
||||
|
||||
+ invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populatePIDList()V
|
||||
+
|
||||
.line 174
|
||||
return-void
|
||||
|
||||
diff --git a/smali/com/amazon/system/security/Security.smali b/smali/com/amazon/system/security/Security.smali
|
||||
index 04ea997..e88fe08 100644
|
||||
--- a/smali/com/amazon/system/security/Security.smali
|
||||
+++ b/smali/com/amazon/system/security/Security.smali
|
||||
@@ -940,6 +940,16 @@
|
||||
|
||||
aput-object v0, v6, v8
|
||||
|
||||
+ invoke-static {}, Lcom/amazon/kcp/application/DeviceInformationProviderFactory;->getProvider()Lcom/amazon/kcp/application/IDeviceInformationProvider;
|
||||
+
|
||||
+ move-result-object v5
|
||||
+
|
||||
+ invoke-static {v6}, Ljava/util/Arrays;->toString([Ljava/lang/Object;)Ljava/lang/String;
|
||||
+
|
||||
+ move-result-object v2
|
||||
+
|
||||
+ invoke-interface {v5, v2}, Lcom/amazon/kcp/application/IDeviceInformationProvider;->setPidList(Ljava/lang/String;)V
|
||||
+
|
||||
.line 347
|
||||
return-object v6
|
||||
.end method
|
||||
@@ -1,751 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Version 3.2.5 December 2016
|
||||
# Improve detection of good text decryption.
|
||||
#
|
||||
# Version 3.2.4 December 2016
|
||||
# Remove incorrect support for Kobo Desktop under Wine
|
||||
#
|
||||
# Version 3.2.3 October 2016
|
||||
# Fix for windows network user and more xml fixes
|
||||
#
|
||||
# Version 3.2.2 October 2016
|
||||
# Change to the way the new database version is handled.
|
||||
#
|
||||
# Version 3.2.1 September 2016
|
||||
# Update for v4.0 of Windows Desktop app.
|
||||
#
|
||||
# Version 3.2.0 January 2016
|
||||
# Update for latest version of Windows Desktop app.
|
||||
# Support Kobo devices in the command line version.
|
||||
#
|
||||
# Version 3.1.9 November 2015
|
||||
# Handle Kobo Desktop under wine on Linux
|
||||
#
|
||||
# Version 3.1.8 November 2015
|
||||
# Handle the case of Kobo Arc or Vox device (i.e. don't crash).
|
||||
#
|
||||
# Version 3.1.7 October 2015
|
||||
# Handle the case of no device or database more gracefully.
|
||||
#
|
||||
# Version 3.1.6 September 2015
|
||||
# Enable support for Kobo devices
|
||||
# More character encoding fixes (unicode strings)
|
||||
#
|
||||
# Version 3.1.5 September 2015
|
||||
# Removed requirement that a purchase has been made.
|
||||
# Also add in character encoding fixes
|
||||
#
|
||||
# Version 3.1.4 September 2015
|
||||
# Updated for version 3.17 of the Windows Desktop app.
|
||||
#
|
||||
# Version 3.1.3 August 2015
|
||||
# Add translations for Portuguese and Arabic
|
||||
#
|
||||
# Version 3.1.2 January 2015
|
||||
# Add coding, version number and version announcement
|
||||
#
|
||||
# Version 3.05 October 2014
|
||||
# Identifies DRM-free books in the dialog
|
||||
#
|
||||
# Version 3.04 September 2014
|
||||
# Handles DRM-free books as well (sometimes Kobo Library doesn't
|
||||
# show download link for DRM-free books)
|
||||
#
|
||||
# Version 3.03 August 2014
|
||||
# If PyCrypto is unavailable try to use libcrypto for AES_ECB.
|
||||
#
|
||||
# Version 3.02 August 2014
|
||||
# Relax checking of application/xhtml+xml and image/jpeg content.
|
||||
#
|
||||
# Version 3.01 June 2014
|
||||
# Check image/jpeg as well as application/xhtml+xml content. Fix typo
|
||||
# in Windows ipconfig parsing.
|
||||
#
|
||||
# Version 3.0 June 2014
|
||||
# Made portable for Mac and Windows, and the only module dependency
|
||||
# not part of python core is PyCrypto. Major code cleanup/rewrite.
|
||||
# No longer tries the first MAC address; tries them all if it detects
|
||||
# the decryption failed.
|
||||
#
|
||||
# Updated September 2013 by Anon
|
||||
# Version 2.02
|
||||
# Incorporated minor fixes posted at Apprentice Alf's.
|
||||
#
|
||||
# Updates July 2012 by Michael Newton
|
||||
# PWSD ID is no longer a MAC address, but should always
|
||||
# be stored in the registry. Script now works with OS X
|
||||
# and checks plist for values instead of registry. Must
|
||||
# have biplist installed for OS X support.
|
||||
#
|
||||
# Original comments left below; note the "AUTOPSY" is inaccurate. See
|
||||
# KoboLibrary.userkeys and KoboFile.decrypt()
|
||||
#
|
||||
##########################################################
|
||||
# KOBO DRM CRACK BY #
|
||||
# PHYSISTICATED #
|
||||
##########################################################
|
||||
# This app was made for Python 2.7 on Windows 32-bit
|
||||
#
|
||||
# This app needs pycrypto - get from here:
|
||||
# http://www.voidspace.org.uk/python/modules.shtml
|
||||
#
|
||||
# Usage: obok.py
|
||||
# Choose the book you want to decrypt
|
||||
#
|
||||
# Shouts to my krew - you know who you are - and one in
|
||||
# particular who gave me a lot of help with this - thank
|
||||
# you so much!
|
||||
#
|
||||
# Kopimi /K\
|
||||
# Keep sharing, keep copying, but remember that nothing is
|
||||
# for free - make sure you compensate your favorite
|
||||
# authors - and cut out the middle man whenever possible
|
||||
# ;) ;) ;)
|
||||
#
|
||||
# DRM AUTOPSY
|
||||
# The Kobo DRM was incredibly easy to crack, but it took
|
||||
# me months to get around to making this. Here's the
|
||||
# basics of how it works:
|
||||
# 1: Get MAC address of first NIC in ipconfig (sometimes
|
||||
# stored in registry as pwsdid)
|
||||
# 2: Get user ID (stored in tons of places, this gets it
|
||||
# from HKEY_CURRENT_USER\Software\Kobo\Kobo Desktop
|
||||
# Edition\Browser\cookies)
|
||||
# 3: Concatenate and SHA256, take the second half - this
|
||||
# is your master key
|
||||
# 4: Open %LOCALAPPDATA%\Kobo Desktop Editions\Kobo.sqlite
|
||||
# and dump content_keys
|
||||
# 5: Unbase64 the keys, then decode these with the master
|
||||
# key - these are your page keys
|
||||
# 6: Unzip EPUB of your choice, decrypt each page with its
|
||||
# page key, then zip back up again
|
||||
#
|
||||
# WHY USE THIS WHEN INEPT WORKS FINE? (adobe DRM stripper)
|
||||
# Inept works very well, but authors on Kobo can choose
|
||||
# what DRM they want to use - and some have chosen not to
|
||||
# let people download them with Adobe Digital Editions -
|
||||
# they would rather lock you into a single platform.
|
||||
#
|
||||
# With Obok, you can sync Kobo Desktop, decrypt all your
|
||||
# ebooks, and then use them on whatever device you want
|
||||
# - you bought them, you own them, you can do what you
|
||||
# like with them.
|
||||
#
|
||||
# Obok is Kobo backwards, but it is also means "next to"
|
||||
# in Polish.
|
||||
# When you buy a real book, it is right next to you. You
|
||||
# can read it at home, at work, on a train, you can lend
|
||||
# it to a friend, you can scribble on it, and add your own
|
||||
# explanations/translations.
|
||||
#
|
||||
# Obok gives you this power over your ebooks - no longer
|
||||
# are you restricted to one device. This allows you to
|
||||
# embed foreign fonts into your books, as older Kobo's
|
||||
# can't display them properly. You can read your books
|
||||
# on your phones, in different PC readers, and different
|
||||
# ereader devices. You can share them with your friends
|
||||
# too, if you like - you can do that with a real book
|
||||
# after all.
|
||||
#
|
||||
"""Manage all Kobo books, either encrypted or DRM-free."""
|
||||
|
||||
__version__ = '3.2.4'
|
||||
__about__ = u"Obok v{0}\nCopyright © 2012-2016 Physisticated et al.".format(__version__)
|
||||
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import sqlite3
|
||||
import base64
|
||||
import binascii
|
||||
import re
|
||||
import zipfile
|
||||
import hashlib
|
||||
import xml.etree.ElementTree as ET
|
||||
import string
|
||||
import shutil
|
||||
import argparse
|
||||
import tempfile
|
||||
|
||||
can_parse_xml = True
|
||||
try:
|
||||
from xml.etree import ElementTree as ET
|
||||
# print u"using xml.etree for xml parsing"
|
||||
except ImportError:
|
||||
can_parse_xml = False
|
||||
# print u"Cannot find xml.etree, disabling extraction of serial numbers"
|
||||
|
||||
# List of all known hash keys
|
||||
KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook','QJhwzAtXL']
|
||||
|
||||
class ENCRYPTIONError(Exception):
|
||||
pass
|
||||
|
||||
def _load_crypto_libcrypto():
|
||||
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
|
||||
Structure, c_ulong, create_string_buffer, cast
|
||||
from ctypes.util import find_library
|
||||
|
||||
if sys.platform.startswith('win'):
|
||||
libcrypto = find_library('libeay32')
|
||||
else:
|
||||
libcrypto = find_library('crypto')
|
||||
|
||||
if libcrypto is None:
|
||||
raise ENCRYPTIONError('libcrypto not found')
|
||||
libcrypto = CDLL(libcrypto)
|
||||
|
||||
AES_MAXNR = 14
|
||||
|
||||
c_char_pp = POINTER(c_char_p)
|
||||
c_int_p = POINTER(c_int)
|
||||
|
||||
class AES_KEY(Structure):
|
||||
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
|
||||
('rounds', c_int)]
|
||||
AES_KEY_p = POINTER(AES_KEY)
|
||||
|
||||
def F(restype, name, argtypes):
|
||||
func = getattr(libcrypto, name)
|
||||
func.restype = restype
|
||||
func.argtypes = argtypes
|
||||
return func
|
||||
|
||||
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
|
||||
[c_char_p, c_int, AES_KEY_p])
|
||||
AES_ecb_encrypt = F(None, 'AES_ecb_encrypt',
|
||||
[c_char_p, c_char_p, AES_KEY_p, c_int])
|
||||
|
||||
class AES(object):
|
||||
def __init__(self, userkey):
|
||||
self._blocksize = len(userkey)
|
||||
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
||||
raise ENCRYPTIONError(_('AES improper key used'))
|
||||
return
|
||||
key = self._key = AES_KEY()
|
||||
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
|
||||
if rv < 0:
|
||||
raise ENCRYPTIONError(_('Failed to initialize AES key'))
|
||||
|
||||
def decrypt(self, data):
|
||||
clear = ''
|
||||
for i in range(0, len(data), 16):
|
||||
out = create_string_buffer(16)
|
||||
rv = AES_ecb_encrypt(data[i:i+16], out, self._key, 0)
|
||||
if rv == 0:
|
||||
raise ENCRYPTIONError(_('AES decryption failed'))
|
||||
clear += out.raw
|
||||
return clear
|
||||
|
||||
return AES
|
||||
|
||||
def _load_crypto_pycrypto():
|
||||
from Crypto.Cipher import AES as _AES
|
||||
class AES(object):
|
||||
def __init__(self, key):
|
||||
self._aes = _AES.new(key, _AES.MODE_ECB)
|
||||
|
||||
def decrypt(self, data):
|
||||
return self._aes.decrypt(data)
|
||||
|
||||
return AES
|
||||
|
||||
def _load_crypto():
|
||||
AES = None
|
||||
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
|
||||
for loader in cryptolist:
|
||||
try:
|
||||
AES = loader()
|
||||
break
|
||||
except (ImportError, ENCRYPTIONError):
|
||||
pass
|
||||
return AES
|
||||
|
||||
AES = _load_crypto()
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
# encoded using "replace" before writing them.
|
||||
class SafeUnbuffered:
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
self.encoding = stream.encoding
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
|
||||
class KoboLibrary(object):
|
||||
"""The Kobo library.
|
||||
|
||||
This class represents all the information available from the data
|
||||
written by the Kobo Desktop Edition application, including the list
|
||||
of books, their titles, and the user's encryption key(s)."""
|
||||
|
||||
def __init__ (self, serials = [], device_path = None):
|
||||
print __about__
|
||||
self.kobodir = u""
|
||||
kobodb = u""
|
||||
|
||||
# Order of checks
|
||||
# 1. first check if a device_path has been passed in, and whether
|
||||
# we can find the sqlite db in the respective place
|
||||
# 2. if 1., and we got some serials passed in (from saved
|
||||
# settings in calibre), just use it
|
||||
# 3. if 1. worked, but we didn't get serials, try to parse them
|
||||
# from the device, if this didn't work, unset everything
|
||||
# 4. if by now we don't have kobodir set, give up on device and
|
||||
# try to use the Desktop app.
|
||||
|
||||
# step 1. check whether this looks like a real device
|
||||
if (device_path):
|
||||
# we got a device path
|
||||
self.kobodir = os.path.join(device_path, u".kobo")
|
||||
# devices use KoboReader.sqlite
|
||||
kobodb = os.path.join(self.kobodir, u"KoboReader.sqlite")
|
||||
if (not(os.path.isfile(kobodb))):
|
||||
# device path seems to be wrong, unset it
|
||||
device_path = u""
|
||||
self.kobodir = u""
|
||||
kobodb = u""
|
||||
|
||||
if (self.kobodir):
|
||||
# step 3. we found a device but didn't get serials, try to get them
|
||||
if (len(serials) == 0):
|
||||
# we got a device path but no saved serial
|
||||
# try to get the serial from the device
|
||||
# print u"get_device_settings - device_path = {0}".format(device_path)
|
||||
# get serial from device_path/.adobe-digital-editions/device.xml
|
||||
if can_parse_xml:
|
||||
devicexml = os.path.join(device_path, '.adobe-digital-editions', 'device.xml')
|
||||
# print u"trying to load {0}".format(devicexml)
|
||||
if (os.path.exists(devicexml)):
|
||||
# print u"trying to parse {0}".format(devicexml)
|
||||
xmltree = ET.parse(devicexml)
|
||||
for node in xmltree.iter():
|
||||
if "deviceSerial" in node.tag:
|
||||
serial = node.text
|
||||
# print u"found serial {0}".format(serial)
|
||||
serials.append(serial)
|
||||
break
|
||||
else:
|
||||
# print u"cannot get serials from device."
|
||||
device_path = u""
|
||||
self.kobodir = u""
|
||||
kobodb = u""
|
||||
|
||||
if (self.kobodir == u""):
|
||||
# step 4. we haven't found a device with serials, so try desktop apps
|
||||
if sys.platform.startswith('win'):
|
||||
import _winreg as winreg
|
||||
if sys.getwindowsversion().major > 5:
|
||||
if 'LOCALAPPDATA' in os.environ.keys():
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
self.kobodir = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||
if (self.kobodir == u""):
|
||||
if 'USERPROFILE' in os.environ.keys():
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings(u"%USERPROFILE%"), u"Local Settings", u"Application Data")
|
||||
self.kobodir = os.path.join(self.kobodir, u"Kobo", u"Kobo Desktop Edition")
|
||||
elif sys.platform.startswith('darwin'):
|
||||
self.kobodir = os.path.join(os.environ['HOME'], u"Library", u"Application Support", u"Kobo", u"Kobo Desktop Edition")
|
||||
#elif linux_path != None:
|
||||
# Probably Linux, let's get the wine prefix and path to Kobo.
|
||||
# self.kobodir = os.path.join(linux_path, u"Local Settings", u"Application Data", u"Kobo", u"Kobo Desktop Edition")
|
||||
# desktop versions use Kobo.sqlite
|
||||
kobodb = os.path.join(self.kobodir, u"Kobo.sqlite")
|
||||
# check for existence of file
|
||||
if (not(os.path.isfile(kobodb))):
|
||||
# give up here, we haven't found anything useful
|
||||
self.kobodir = u""
|
||||
kobodb = u""
|
||||
|
||||
|
||||
if (self.kobodir != u""):
|
||||
self.bookdir = os.path.join(self.kobodir, u"kepub")
|
||||
# make a copy of the database in a temporary file
|
||||
# so we can ensure it's not using WAL logging which sqlite3 can't do.
|
||||
self.newdb = tempfile.NamedTemporaryFile(mode='wb', delete=False)
|
||||
print self.newdb.name
|
||||
olddb = open(kobodb, 'rb')
|
||||
self.newdb.write(olddb.read(18))
|
||||
self.newdb.write('\x01\x01')
|
||||
olddb.read(2)
|
||||
self.newdb.write(olddb.read())
|
||||
olddb.close()
|
||||
self.newdb.close()
|
||||
self.__sqlite = sqlite3.connect(self.newdb.name)
|
||||
self.__cursor = self.__sqlite.cursor()
|
||||
self._userkeys = []
|
||||
self._books = []
|
||||
self._volumeID = []
|
||||
self._serials = serials
|
||||
|
||||
def close (self):
|
||||
"""Closes the database used by the library."""
|
||||
self.__cursor.close()
|
||||
self.__sqlite.close()
|
||||
# delete the temporary copy of the database
|
||||
os.remove(self.newdb.name)
|
||||
|
||||
@property
|
||||
def userkeys (self):
|
||||
"""The list of potential userkeys being used by this library.
|
||||
Only one of these will be valid.
|
||||
"""
|
||||
if len(self._userkeys) != 0:
|
||||
return self._userkeys
|
||||
for macaddr in self.__getmacaddrs():
|
||||
self._userkeys.extend(self.__getuserkeys(macaddr))
|
||||
return self._userkeys
|
||||
|
||||
@property
|
||||
def books (self):
|
||||
"""The list of KoboBook objects in the library."""
|
||||
if len(self._books) != 0:
|
||||
return self._books
|
||||
"""Drm-ed kepub"""
|
||||
for row in self.__cursor.execute('SELECT DISTINCT volumeid, Title, Attribution, Series FROM content_keys, content WHERE contentid = volumeid'):
|
||||
self._books.append(KoboBook(row[0], row[1], self.__bookfile(row[0]), 'kepub', self.__cursor, author=row[2], series=row[3]))
|
||||
self._volumeID.append(row[0])
|
||||
"""Drm-free"""
|
||||
for f in os.listdir(self.bookdir):
|
||||
if(f not in self._volumeID):
|
||||
row = self.__cursor.execute("SELECT Title, Attribution, Series FROM content WHERE ContentID = '" + f + "'").fetchone()
|
||||
if row is not None:
|
||||
fTitle = row[0]
|
||||
self._books.append(KoboBook(f, fTitle, self.__bookfile(f), 'drm-free', self.__cursor, author=row[1], series=row[2]))
|
||||
self._volumeID.append(f)
|
||||
"""Sort"""
|
||||
self._books.sort(key=lambda x: x.title)
|
||||
return self._books
|
||||
|
||||
def __bookfile (self, volumeid):
|
||||
"""The filename needed to open a given book."""
|
||||
return os.path.join(self.kobodir, u"kepub", volumeid)
|
||||
|
||||
def __getmacaddrs (self):
|
||||
"""The list of all MAC addresses on this machine."""
|
||||
macaddrs = []
|
||||
if sys.platform.startswith('win'):
|
||||
c = re.compile('\s(' + '[0-9a-f]{2}-' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||
(p_in, p_out, p_err) = os.popen3('ipconfig /all')
|
||||
for line in p_out:
|
||||
m = c.search(line)
|
||||
if m:
|
||||
macaddrs.append(re.sub("-", ":", m.group(1)).upper())
|
||||
elif sys.platform.startswith('darwin'):
|
||||
c = re.compile('\s(' + '[0-9a-f]{2}:' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||
output = subprocess.check_output('/sbin/ifconfig -a', shell=True)
|
||||
matches = c.findall(output)
|
||||
for m in matches:
|
||||
# print u"m:{0}".format(m[0])
|
||||
macaddrs.append(m[0].upper())
|
||||
elif sys.platform.startswith('linux'):
|
||||
p_out = subprocess.check_output("ip -br link show | awk '{print $3}'", shell=True)
|
||||
for line in p_out:
|
||||
macaddrs.append(line.upper())
|
||||
else:
|
||||
# probably linux, let's try ipconfig under wine
|
||||
c = re.compile('\s(' + '[0-9a-f]{2}-' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||
for line in os.popen('ipconfig /all'):
|
||||
m = c.search(line)
|
||||
if m:
|
||||
macaddrs.append(re.sub("-", ":", m.group(1)).upper())
|
||||
|
||||
# extend the list of macaddrs in any case with the serials
|
||||
# cannot hurt ;-)
|
||||
macaddrs.extend(self._serials)
|
||||
|
||||
return macaddrs
|
||||
|
||||
def __getuserids (self):
|
||||
userids = []
|
||||
cursor = self.__cursor.execute('SELECT UserID FROM user')
|
||||
row = cursor.fetchone()
|
||||
while row is not None:
|
||||
try:
|
||||
userid = row[0]
|
||||
userids.append(userid)
|
||||
except:
|
||||
pass
|
||||
row = cursor.fetchone()
|
||||
return userids
|
||||
|
||||
def __getuserkeys (self, macaddr):
|
||||
userids = self.__getuserids()
|
||||
userkeys = []
|
||||
for hash in KOBO_HASH_KEYS:
|
||||
deviceid = hashlib.sha256(hash + macaddr).hexdigest()
|
||||
for userid in userids:
|
||||
userkey = hashlib.sha256(deviceid + userid).hexdigest()
|
||||
userkeys.append(binascii.a2b_hex(userkey[32:]))
|
||||
return userkeys
|
||||
|
||||
class KoboBook(object):
|
||||
"""A Kobo book.
|
||||
|
||||
A Kobo book contains a number of unencrypted and encrypted files.
|
||||
This class provides a list of the encrypted files.
|
||||
|
||||
Each book has the following instance variables:
|
||||
volumeid - a UUID which uniquely refers to the book in this library.
|
||||
title - the human-readable book title.
|
||||
filename - the complete path and filename of the book.
|
||||
type - either kepub or drm-free"""
|
||||
def __init__ (self, volumeid, title, filename, type, cursor, author=None, series=None):
|
||||
self.volumeid = volumeid
|
||||
self.title = title
|
||||
self.author = author
|
||||
self.series = series
|
||||
self.series_index = None
|
||||
self.filename = filename
|
||||
self.type = type
|
||||
self.__cursor = cursor
|
||||
self._encryptedfiles = {}
|
||||
|
||||
@property
|
||||
def encryptedfiles (self):
|
||||
"""A dictionary of KoboFiles inside the book.
|
||||
|
||||
The dictionary keys are the relative pathnames, which are
|
||||
the same as the pathnames inside the book 'zip' file."""
|
||||
if (self.type == 'drm-free'):
|
||||
return self._encryptedfiles
|
||||
if len(self._encryptedfiles) != 0:
|
||||
return self._encryptedfiles
|
||||
# Read the list of encrypted files from the DB
|
||||
for row in self.__cursor.execute('SELECT elementid,elementkey FROM content_keys,content WHERE volumeid = ? AND volumeid = contentid',(self.volumeid,)):
|
||||
self._encryptedfiles[row[0]] = KoboFile(row[0], None, base64.b64decode(row[1]))
|
||||
|
||||
# Read the list of files from the kepub OPF manifest so that
|
||||
# we can get their proper MIME type.
|
||||
# NOTE: this requires that the OPF file is unencrypted!
|
||||
zin = zipfile.ZipFile(self.filename, "r")
|
||||
xmlns = {
|
||||
'ocf': 'urn:oasis:names:tc:opendocument:xmlns:container',
|
||||
'opf': 'http://www.idpf.org/2007/opf'
|
||||
}
|
||||
ocf = ET.fromstring(zin.read('META-INF/container.xml'))
|
||||
opffile = ocf.find('.//ocf:rootfile', xmlns).attrib['full-path']
|
||||
basedir = re.sub('[^/]+$', '', opffile)
|
||||
opf = ET.fromstring(zin.read(opffile))
|
||||
zin.close()
|
||||
|
||||
c = re.compile('/')
|
||||
for item in opf.findall('.//opf:item', xmlns):
|
||||
mimetype = item.attrib['media-type']
|
||||
|
||||
# Convert relative URIs
|
||||
href = item.attrib['href']
|
||||
if not c.match(href):
|
||||
href = string.join((basedir, href), '')
|
||||
|
||||
# Update books we've found from the DB.
|
||||
if href in self._encryptedfiles:
|
||||
self._encryptedfiles[href].mimetype = mimetype
|
||||
return self._encryptedfiles
|
||||
|
||||
@property
|
||||
def has_drm (self):
|
||||
return not self.type == 'drm-free'
|
||||
|
||||
|
||||
class KoboFile(object):
|
||||
"""An encrypted file in a KoboBook.
|
||||
|
||||
Each file has the following instance variables:
|
||||
filename - the relative pathname inside the book zip file.
|
||||
mimetype - the file's MIME type, e.g. 'image/jpeg'
|
||||
key - the encrypted page key."""
|
||||
|
||||
def __init__ (self, filename, mimetype, key):
|
||||
self.filename = filename
|
||||
self.mimetype = mimetype
|
||||
self.key = key
|
||||
def decrypt (self, userkey, contents):
|
||||
"""
|
||||
Decrypt the contents using the provided user key and the
|
||||
file page key. The caller must determine if the decrypted
|
||||
data is correct."""
|
||||
# The userkey decrypts the page key (self.key)
|
||||
keyenc = AES(userkey)
|
||||
decryptedkey = keyenc.decrypt(self.key)
|
||||
# The decrypted page key decrypts the content
|
||||
pageenc = AES(decryptedkey)
|
||||
return self.__removeaespadding(pageenc.decrypt(contents))
|
||||
|
||||
def check (self, contents):
|
||||
"""
|
||||
If the contents uses some known MIME types, check if it
|
||||
conforms to the type. Throw a ValueError exception if not.
|
||||
If the contents uses an uncheckable MIME type, don't check
|
||||
it and don't throw an exception.
|
||||
Returns True if the content was checked, False if it was not
|
||||
checked."""
|
||||
if self.mimetype == 'application/xhtml+xml':
|
||||
# assume utf-8 with no BOM
|
||||
textoffset = 0
|
||||
stride = 1
|
||||
print u"Checking text:{0}:".format(contents[:10])
|
||||
# check for byte order mark
|
||||
if contents[:3]=="\xef\xbb\xbf":
|
||||
# seems to be utf-8 with BOM
|
||||
print u"Could be utf-8 with BOM"
|
||||
textoffset = 3
|
||||
elif contents[:2]=="\xfe\xff":
|
||||
# seems to be utf-16BE
|
||||
print u"Could be utf-16BE"
|
||||
textoffset = 3
|
||||
stride = 2
|
||||
elif contents[:2]=="\xff\xfe":
|
||||
# seems to be utf-16LE
|
||||
print u"Could be utf-16LE"
|
||||
textoffset = 2
|
||||
stride = 2
|
||||
else:
|
||||
print u"Perhaps utf-8 without BOM"
|
||||
|
||||
# now check that the first few characters are in the ASCII range
|
||||
for i in xrange(textoffset,textoffset+5*stride,stride):
|
||||
if ord(contents[i])<32 or ord(contents[i])>127:
|
||||
# Non-ascii, so decryption probably failed
|
||||
print u"Bad character at {0}, value {1}".format(i,ord(contents[i]))
|
||||
raise ValueError
|
||||
print u"Seems to be good text"
|
||||
return True
|
||||
if contents[:5]=="<?xml" or contents[:8]=="\xef\xbb\xbf<?xml":
|
||||
# utf-8
|
||||
return True
|
||||
elif contents[:14]=="\xfe\xff\x00<\x00?\x00x\x00m\x00l":
|
||||
# utf-16BE
|
||||
return True
|
||||
elif contents[:14]=="\xff\xfe<\x00?\x00x\x00m\x00l\x00":
|
||||
# utf-16LE
|
||||
return True
|
||||
elif contents[:9]=="<!DOCTYPE" or contents[:12]=="\xef\xbb\xbf<!DOCTYPE":
|
||||
# utf-8 of weird <!DOCTYPE start
|
||||
return True
|
||||
elif contents[:22]=="\xfe\xff\x00<\x00!\x00D\x00O\x00C\x00T\x00Y\x00P\x00E":
|
||||
# utf-16BE of weird <!DOCTYPE start
|
||||
return True
|
||||
elif contents[:22]=="\xff\xfe<\x00!\x00D\x00O\x00C\x00T\x00Y\x00P\x00E\x00":
|
||||
# utf-16LE of weird <!DOCTYPE start
|
||||
return True
|
||||
else:
|
||||
print u"Bad XML: {0}".format(contents[:8])
|
||||
raise ValueError
|
||||
elif self.mimetype == 'image/jpeg':
|
||||
if contents[:3] == '\xff\xd8\xff':
|
||||
return True
|
||||
else:
|
||||
print u"Bad JPEG: {0}".format(contents[:3].encode('hex'))
|
||||
raise ValueError()
|
||||
return False
|
||||
|
||||
def __removeaespadding (self, contents):
|
||||
"""
|
||||
Remove the trailing padding, using what appears to be the CMS
|
||||
algorithm from RFC 5652 6.3"""
|
||||
lastchar = binascii.b2a_hex(contents[-1:])
|
||||
strlen = int(lastchar, 16)
|
||||
padding = strlen
|
||||
if strlen == 1:
|
||||
return contents[:-1]
|
||||
if strlen < 16:
|
||||
for i in range(strlen):
|
||||
testchar = binascii.b2a_hex(contents[-strlen:-(strlen-1)])
|
||||
if testchar != lastchar:
|
||||
padding = 0
|
||||
if padding > 0:
|
||||
contents = contents[:-padding]
|
||||
return contents
|
||||
|
||||
def decrypt_book(book, lib):
|
||||
print u"Converting {0}".format(book.title)
|
||||
zin = zipfile.ZipFile(book.filename, "r")
|
||||
# make filename out of Unicode alphanumeric and whitespace equivalents from title
|
||||
outname = u"{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE))
|
||||
if (book.type == 'drm-free'):
|
||||
print u"DRM-free book, conversion is not needed"
|
||||
shutil.copyfile(book.filename, outname)
|
||||
print u"Book saved as {0}".format(os.path.join(os.getcwd(), outname))
|
||||
return 0
|
||||
result = 1
|
||||
for userkey in lib.userkeys:
|
||||
print u"Trying key: {0}".format(userkey.encode('hex_codec'))
|
||||
try:
|
||||
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
|
||||
for filename in zin.namelist():
|
||||
contents = zin.read(filename)
|
||||
if filename in book.encryptedfiles:
|
||||
file = book.encryptedfiles[filename]
|
||||
contents = file.decrypt(userkey, contents)
|
||||
# Parse failures mean the key is probably wrong.
|
||||
file.check(contents)
|
||||
zout.writestr(filename, contents)
|
||||
zout.close()
|
||||
print u"Decryption succeeded."
|
||||
print u"Book saved as {0}".format(os.path.join(os.getcwd(), outname))
|
||||
result = 0
|
||||
break
|
||||
except ValueError:
|
||||
print u"Decryption failed."
|
||||
zout.close()
|
||||
os.remove(outname)
|
||||
zin.close()
|
||||
return result
|
||||
|
||||
|
||||
def cli_main():
|
||||
description = __about__
|
||||
epilog = u"Parsing of arguments failed."
|
||||
parser = argparse.ArgumentParser(prog=sys.argv[0], description=description, epilog=epilog)
|
||||
parser.add_argument('--devicedir', default='/media/KOBOeReader', help="directory of connected Kobo device")
|
||||
parser.add_argument('--all', action='store_true', help="flag for converting all books on device")
|
||||
args = vars(parser.parse_args())
|
||||
serials = []
|
||||
devicedir = u""
|
||||
if args['devicedir']:
|
||||
devicedir = args['devicedir']
|
||||
|
||||
lib = KoboLibrary(serials, devicedir)
|
||||
|
||||
if args['all']:
|
||||
books = lib.books
|
||||
else:
|
||||
for i, book in enumerate(lib.books):
|
||||
print u"{0}: {1}".format(i + 1, book.title)
|
||||
print u"Or 'all'"
|
||||
|
||||
choice = raw_input(u"Convert book number... ")
|
||||
if choice == u'all':
|
||||
books = list(lib.books)
|
||||
else:
|
||||
try:
|
||||
num = int(choice)
|
||||
books = [lib.books[num - 1]]
|
||||
except (ValueError, IndexError):
|
||||
print u"Invalid choice. Exiting..."
|
||||
exit()
|
||||
|
||||
results = [decrypt_book(book, lib) for book in books]
|
||||
lib.close()
|
||||
overall_result = all(result != 0 for result in results)
|
||||
if overall_result != 0:
|
||||
print u"Could not decrypt book with any of the keys found."
|
||||
return overall_result
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
sys.exit(cli_main())
|
||||
Binary file not shown.
@@ -1,8 +0,0 @@
|
||||
Rocket eBooks
|
||||
=============
|
||||
|
||||
Rocket ebooks (.rb) are no longer sold.
|
||||
|
||||
This is the only archive of tools for decrypting Rocket ebooks that I have been able to find. It is included here without further comment or support.
|
||||
|
||||
— Alf.
|
||||
@@ -1,4 +0,0 @@
|
||||
The latest Scuolabook tool can be found at Hex's own blog:
|
||||
https://thisishex.wordpress.com/scuolabook-drm-remover/
|
||||
|
||||
Harper.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
||||
ineptpdf 8.4.51
|
||||
---------------
|
||||
|
||||
This is a version of the ineptpdf script produced by TetraChroma that can remove, on Windows, "FileOpen" DRM.
|
||||
|
||||
No support for this script is offered at Apprentice Alf's blog.
|
||||
|
||||
Trtrachroma's blog is http://tetrachroma.wordpress.com/
|
||||
Reference in New Issue
Block a user