tools v1.8

pull/6/head v1.8
Apprentice Alf 14 years ago
parent 92dafd94b2
commit d427f758f6

@ -1,18 +1,29 @@
#! /usr/bin/python
# -*- coding: utf-8 -*-
# ineptepub.pyw, version 4.1
# ineptepub.pyw, version 5.2
# Copyright © 2009-2010 i♥cabbages
# To run this program install Python 2.6 from http://www.python.org/download/
# and PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
# (make sure to install the version for Python 2.6). Save this script file as
# ineptepub.pyw and double-click on it to run it.
# Released under the terms of the GNU General Public Licence, version 3 or
# later. <http://www.gnu.org/licenses/>
# Windows users: Before running this program, you must first install Python 2.6
# from <http://www.python.org/download/> and PyCrypto from
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
# install the version for Python 2.6). Save this script file as
# ineptepub.pyw and double-click on it to run it.
#
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
# program from the command line (pythonw ineptepub.pyw) or by double-clicking
# it when it has been associated with PythonLauncher.
# Revision history:
# 1 - Initial release
# 2 - Rename to INEPT, fix exit code
# 3 - Add cmd or gui choosing
# 4 - support for 1.7.2 support (anon)
# 4.1 - backward compatibility for 1.7.1 and old adeptkey.der
# 5 - Version bump to avoid (?) confusion;
# Improve OS X support by using OpenSSL when available
# 5.1 - Improve OpenSSL error checking
# 5.2 - Fix ctypes error causing segfaults on some systems
"""
Decrypt Adobe ADEPT-encrypted EPUB books.
@ -34,116 +45,223 @@ import Tkconstants
import tkFileDialog
import tkMessageBox
try:
from Crypto.Cipher import AES
from Crypto.PublicKey import RSA
except ImportError:
AES = None
RSA = None
META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
NSMAP = {'adept': 'http://ns.adobe.com/adept',
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
class ADEPTError(Exception):
pass
# ASN.1 parsing code from tlslite
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
def bytesToNumber(bytes):
total = 0L
multiplier = 1L
for count in range(len(bytes)-1, -1, -1):
byte = bytes[count]
total += multiplier * byte
multiplier *= 256
return total
libcrypto = find_library('crypto')
if libcrypto is None:
raise ADEPTError('libcrypto not found')
libcrypto = CDLL(libcrypto)
class ASN1Error(Exception):
pass
RSA_NO_PADDING = 3
AES_MAXNR = 14
c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int)
class ASN1Parser(object):
class Parser(object):
def __init__(self, bytes):
self.bytes = bytes
self.index = 0
class RSA(Structure):
pass
RSA_p = POINTER(RSA)
def get(self, length):
if self.index + length > len(self.bytes):
raise ASN1Error("Error decoding ASN.1")
x = 0
for count in range(length):
x <<= 8
x |= self.bytes[self.index]
self.index += 1
return x
class AES_KEY(Structure):
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
def getFixBytes(self, lengthBytes):
bytes = self.bytes[self.index : self.index+lengthBytes]
self.index += lengthBytes
return bytes
def F(restype, name, argtypes):
func = getattr(libcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
def getVarBytes(self, lengthLength):
lengthBytes = self.get(lengthLength)
return self.getFixBytes(lengthBytes)
d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey',
[RSA_p, c_char_pp, c_long])
RSA_size = F(c_int, 'RSA_size', [RSA_p])
RSA_private_decrypt = F(c_int, 'RSA_private_decrypt',
[c_int, c_char_p, c_char_p, RSA_p, c_int])
RSA_free = F(None, 'RSA_free', [RSA_p])
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])
def getFixList(self, length, lengthList):
l = [0] * lengthList
for x in range(lengthList):
l[x] = self.get(length)
return l
class RSA(object):
def __init__(self, der):
buf = create_string_buffer(der)
pp = c_char_pp(cast(buf, c_char_p))
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
if rsa is None:
raise ADEPTError('Error parsing ADEPT user key DER')
def decrypt(self, from_):
rsa = self._rsa
to = create_string_buffer(RSA_size(rsa))
dlen = RSA_private_decrypt(len(from_), from_, to, rsa,
RSA_NO_PADDING)
if dlen < 0:
raise ADEPTError('RSA decryption failed')
return to[:dlen]
def getVarList(self, length, lengthLength):
lengthList = self.get(lengthLength)
if lengthList % length != 0:
raise ASN1Error("Error decoding ASN.1")
lengthList = int(lengthList/length)
l = [0] * lengthList
for x in range(lengthList):
l[x] = self.get(length)
return l
def __del__(self):
if self._rsa is not None:
RSA_free(self._rsa)
self._rsa = None
class AES(object):
def __init__(self, userkey):
self._blocksize = len(userkey)
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 startLengthCheck(self, lengthLength):
self.lengthCheck = self.get(lengthLength)
self.indexCheck = self.index
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, RSA)
def _load_crypto_pycrypto():
from Crypto.Cipher import AES as _AES
from Crypto.PublicKey import RSA as _RSA
# ASN.1 parsing code from tlslite
class ASN1Error(Exception):
pass
def setLengthCheck(self, length):
self.lengthCheck = length
self.indexCheck = self.index
class ASN1Parser(object):
class Parser(object):
def __init__(self, bytes):
self.bytes = bytes
self.index = 0
def stopLengthCheck(self):
if (self.index - self.indexCheck) != self.lengthCheck:
raise ASN1Error("Error decoding ASN.1")
def get(self, length):
if self.index + length > len(self.bytes):
raise ASN1Error("Error decoding ASN.1")
x = 0
for count in range(length):
x <<= 8
x |= self.bytes[self.index]
self.index += 1
return x
def atLengthCheck(self):
if (self.index - self.indexCheck) < self.lengthCheck:
return False
elif (self.index - self.indexCheck) == self.lengthCheck:
return True
else:
raise ASN1Error("Error decoding ASN.1")
def __init__(self, bytes):
p = self.Parser(bytes)
p.get(1)
self.length = self._getASN1Length(p)
self.value = p.getFixBytes(self.length)
def getChild(self, which):
p = self.Parser(self.value)
for x in range(which+1):
markIndex = p.index
def getFixBytes(self, lengthBytes):
bytes = self.bytes[self.index : self.index+lengthBytes]
self.index += lengthBytes
return bytes
def getVarBytes(self, lengthLength):
lengthBytes = self.get(lengthLength)
return self.getFixBytes(lengthBytes)
def getFixList(self, length, lengthList):
l = [0] * lengthList
for x in range(lengthList):
l[x] = self.get(length)
return l
def getVarList(self, length, lengthLength):
lengthList = self.get(lengthLength)
if lengthList % length != 0:
raise ASN1Error("Error decoding ASN.1")
lengthList = int(lengthList/length)
l = [0] * lengthList
for x in range(lengthList):
l[x] = self.get(length)
return l
def startLengthCheck(self, lengthLength):
self.lengthCheck = self.get(lengthLength)
self.indexCheck = self.index
def setLengthCheck(self, length):
self.lengthCheck = length
self.indexCheck = self.index
def stopLengthCheck(self):
if (self.index - self.indexCheck) != self.lengthCheck:
raise ASN1Error("Error decoding ASN.1")
def atLengthCheck(self):
if (self.index - self.indexCheck) < self.lengthCheck:
return False
elif (self.index - self.indexCheck) == self.lengthCheck:
return True
else:
raise ASN1Error("Error decoding ASN.1")
def __init__(self, bytes):
p = self.Parser(bytes)
p.get(1)
length = self._getASN1Length(p)
p.getFixBytes(length)
return ASN1Parser(p.bytes[markIndex:p.index])
self.length = self._getASN1Length(p)
self.value = p.getFixBytes(self.length)
def getChild(self, which):
p = self.Parser(self.value)
for x in range(which+1):
markIndex = p.index
p.get(1)
length = self._getASN1Length(p)
p.getFixBytes(length)
return ASN1Parser(p.bytes[markIndex:p.index])
def _getASN1Length(self, p):
firstLength = p.get(1)
if firstLength<=127:
return firstLength
else:
lengthLength = firstLength & 0x7F
return p.get(lengthLength)
class AES(object):
def __init__(self, key):
self._aes = _AES.new(key, _AES.MODE_CBC)
def decrypt(self, data):
return self._aes.decrypt(data)
class RSA(object):
def __init__(self, der):
key = ASN1Parser([ord(x) for x in der])
key = [key.getChild(x).value for x in xrange(1, 4)]
key = [self.bytesToNumber(v) for v in key]
self._rsa = _RSA.construct(key)
def bytesToNumber(self, bytes):
total = 0L
for byte in bytes:
total = (total << 8) + byte
return total
def decrypt(self, data):
return self._rsa.decrypt(data)
def _getASN1Length(self, p):
firstLength = p.get(1)
if firstLength<=127:
return firstLength
else:
lengthLength = firstLength & 0x7F
return p.get(lengthLength)
return (AES, RSA)
def _load_crypto():
AES = RSA = None
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto):
try:
AES, RSA = loader()
break
except (ImportError, ADEPTError):
pass
return (AES, RSA)
AES, RSA = _load_crypto()
META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
NSMAP = {'adept': 'http://ns.adobe.com/adept',
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
class ZipInfo(zipfile.ZipInfo):
def __init__(self, *args, **kwargs):
@ -152,11 +270,10 @@ class ZipInfo(zipfile.ZipInfo):
super(ZipInfo, self).__init__(*args, **kwargs)
self.compress_type = compress_type
class Decryptor(object):
def __init__(self, bookkey, encryption):
enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
self._aes = AES.new(bookkey, AES.MODE_CBC)
self._aes = AES(bookkey)
encryption = etree.fromstring(encryption)
self._encrypted = encrypted = set()
expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
@ -165,7 +282,7 @@ class Decryptor(object):
path = elem.get('URI', None)
if path is not None:
encrypted.add(path)
def decompress(self, bytes):
dc = zlib.decompressobj(-15)
bytes = dc.decompress(bytes)
@ -173,7 +290,7 @@ class Decryptor(object):
if ex:
bytes = bytes + ex
return bytes
def decrypt(self, path, data):
if path in self._encrypted:
data = self._aes.decrypt(data)[16:]
@ -181,16 +298,12 @@ class Decryptor(object):
data = self.decompress(data)
return data
class ADEPTError(Exception):
pass
def cli_main(argv=sys.argv):
progname = os.path.basename(argv[0])
if AES is None:
print "%s: This script requires PyCrypto, which must be installed " \
"separately. Read the top-of-script comment for details." % \
(progname,)
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 "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
@ -198,9 +311,7 @@ def cli_main(argv=sys.argv):
keypath, inpath, outpath = argv[1:]
with open(keypath, 'rb') as f:
keyder = f.read()
key = ASN1Parser([ord(x) for x in keyder])
key = [bytesToNumber(key.getChild(x).value) for x in xrange(1, 4)]
rsa = RSA.construct(key)
rsa = RSA(keyder)
with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \
@ -227,7 +338,6 @@ def cli_main(argv=sys.argv):
outf.writestr(path, decryptor.decrypt(path, data))
return 0
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
@ -328,8 +438,9 @@ def gui_main():
root.withdraw()
tkMessageBox.showerror(
"INEPT EPUB Decrypter",
"This script requires PyCrypto, which must be installed "
"separately. Read the top-of-script comment for details.")
"This script requires OpenSSL or PyCrypto, which must be"
" installed separately. Read the top-of-script comment for"
" details.")
return 1
root.title('INEPT EPUB Decrypter')
root.resizable(True, False)

@ -1,25 +1,38 @@
#! /usr/bin/python
# ineptkey.pyw, version 4.4
# ineptkeyv44
# To run this program install Python 2.6 from http://www.python.org/download/
# and PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
# (make sure to install the version for Python 2.6). Save this script file as
# ineptkey.pyw and double-click on it to run it. It will create a file named
# adeptkey.der in the same directory. These are your ADEPT user keys.
# -*- coding: utf-8 -*-
# ineptkey.pyw, version 5
# Copyright © 2009-2010 i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3 or
# later. <http://www.gnu.org/licenses/>
# Windows users: Before running this program, you must first install Python 2.6
# from <http://www.python.org/download/> and PyCrypto from
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make certain
# to install the version for Python 2.6). Then save this script file as
# ineptkey.pyw and double-click on it to run it. It will create a file named
# adeptkey.der in the same directory. This is your ADEPT user key.
#
# Mac OS X users: Save this script file as ineptkey.pyw. You can run this
# program from the command line (pythonw ineptkey.pyw) or by double-clicking
# it when it has been associated with PythonLauncher. It will create a file
# named adeptkey.der in the same directory. This is your ADEPT 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
"""
Retrieve Adobe ADEPT user key under Windows.
Retrieve Adobe ADEPT user key.
"""
from __future__ import with_statement
@ -28,246 +41,305 @@ __license__ = 'GPL v3'
import sys
import os
from struct import pack
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_ulonglong, \
sizeof, c_void_p, c_size_t
import _winreg as winreg
import struct
import Tkinter
import Tkconstants
import tkMessageBox
import traceback
import hashlib
import pickle
try:
from Crypto.Cipher import AES
except ImportError:
AES = None
class ADEPTError(Exception):
pass
DEVICE_KEY = 'Software\\Adobe\\Adept\\Device'
PRIVATE_LICENCE_KEY = 'Software\\Adobe\\Adept\\Activation\\%04d'
PRIVATE_LICENCE_KEY_KEY = 'Software\\Adobe\\Adept\\Activation\\%04d\\%04d'
ACTIVATION = 'Software\\Adobe\\Adept\\Activation\\'
MAX_PATH = 255
if sys.platform.startswith('win'):
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
from ctypes.wintypes import LPVOID, DWORD, BOOL
import _winreg as winreg
kernel32 = windll.kernel32
advapi32 = windll.advapi32
crypt32 = windll.crypt32
try:
from Crypto.Cipher import AES
except ImportError:
AES = None
DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
class ADEPTError(Exception):
pass
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
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():
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()
if sizeof(c_void_p) == 4:
## 32-bit Python
CPUID0_INSNS = create_string_buffer("\x53\x31\xc0\x0f\xa2\x8b\x44\x24\x08\x89"
"\x18\x89\x50\x04\x89\x48\x08\x5b\xc3")
def cpuid0():
buffer = create_string_buffer(12)
cpuid0__ = CFUNCTYPE(c_char_p)(addressof(CPUID0_INSNS))
def cpuid0():
cpuid0__(buffer)
return buffer.raw
return cpuid0
cpuid0 = cpuid0()
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()
CPUID1_INSNS = create_string_buffer("\x53\x31\xc0\x40\x0f\xa2\x5b\xc3")
cpuid1 = CFUNCTYPE(c_uint)(addressof(CPUID1_INSNS))
else:
## 64 bit Python
# In 64-bit we cannot execute instructions stored in a string because
# the O.S. prevents that to defend against buffer overrun attacks.
# Therefore we have to allocate a block of memory with the execute
# permission and copy our code into it.
NULL = c_void_p(0)
PAGE_EXECUTE_READWRITE = 0x40
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
VirtualAlloc = windll.kernel32.VirtualAlloc
VirtualAlloc.restype = c_void_p
VirtualAlloc.argtypes = (c_void_p, c_size_t, c_uint, c_uint)
from ctypes import memmove
memmove.restype = c_void_p
memmove.argtypes = (c_void_p, c_void_p, c_size_t)
CPUID0_INSNS = (b"\x55" # push %rbp
"\x48\x89\xe5" # mov %rsp,%rbp
"\x48\x89\x4d\xf8" # mov %rcx,-0x8(%rbp)
"\x31\xc0" # xor %eax,%eax
"\x0f\xa2" # cpuid
"\x48\x8b\x45\xf8" # mov -0x8(%rbp),%rax
"\x89\x18" # mov %ebx,(%rax)
"\x89\x50\x04" # mov %edx,0x4(%rax)
"\x89\x48\x08" # mov %ecx,0x8(%rax)
"\x48\x8b\x45\xf8" # mov -0x8(%rbp),%rax
"\xc9" # leave
"\xc3" # ret
)
CPUID1_INSNS = (b"\x31\xc0" # xor %eax,%eax
"\xff\xc0" # inc %eax
"\x0f\xa2" # cpuid
"\xc3" # ret
)
insnlen0 = len(CPUID0_INSNS)
insnlen1 = len(CPUID1_INSNS)
insnlen = insnlen0 + insnlen1
code_addr = (VirtualAlloc(NULL, insnlen,
MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE))
if code_addr is None:
raise ADEPTError("Failed to allocate memory")
memmove(code_addr, CPUID0_INSNS + CPUID1_INSNS, insnlen)
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():
buffer = create_string_buffer(12)
cpuid0__ = CFUNCTYPE(c_ulonglong, c_char_p)(code_addr)
_cpuid0 = NativeFunction(None, [c_char_p], CPUID0_INSNS)
buf = create_string_buffer(12)
def cpuid0():
cpuid0__(buffer)
return buffer.raw
_cpuid0(buf)
return buf.raw
return cpuid0
cpuid0 = cpuid0()
cpuid1 = CFUNCTYPE(c_ulonglong)(code_addr + insnlen0)
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 retrieve_key(keypath):
root = GetSystemDirectory().split('\\')[0] + '\\'
serial = GetVolumeSerialNumber(root)
vendor = cpuid0()
signature = pack('>I', cpuid1())[1:]
user = GetUserName()
entropy = pack('>I12s3s13s', serial, vendor, signature, user)
cuser = winreg.HKEY_CURRENT_USER
try:
regkey = winreg.OpenKey(cuser, DEVICE_KEY)
except WindowsError:
raise ADEPTError("Adobe Digital Editions not activated")
device = winreg.QueryValueEx(regkey, 'key')[0]
keykey = CryptUnprotectData(device, entropy)
userkey = None
pkcs = None
keys = {}
counter = 0
for i in xrange(0, 16):
skey = PRIVATE_LICENCE_KEY % i
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 retrieve_key(keypath):
if AES is None:
tkMessageBox.showerror(
"ADEPT Key",
"This script requires PyCrypto, which must be installed "
"separately. Read the top-of-script comment for details.")
return False
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, skey)
regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH)
except WindowsError:
break
type = winreg.QueryValueEx(regkey, None)[0]
# obfuscation technique
if type != 'credentials':
continue
for j in xrange(0, 16):
plkkey = PRIVATE_LICENCE_KEY_KEY % (i, j)
raise ADEPTError("Adobe Digital Editions not activated")
device = winreg.QueryValueEx(regkey, 'key')[0]
keykey = CryptUnprotectData(device, entropy)
userkey = None
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:
regkey = winreg.OpenKey(cuser, plkkey)
plkparent = winreg.OpenKey(plkroot, "%04d" % (i,))
except WindowsError:
break
type = winreg.QueryValueEx(regkey, None)[0]
if type != 'privateLicenseKey':
ktype = winreg.QueryValueEx(plkparent, None)[0]
if ktype != 'credentials':
continue
userkey = winreg.QueryValueEx(regkey, 'value')[0]
break
for j in xrange(0, 16):
plkkey = PRIVATE_LICENCE_KEY_KEY % (i, j)
try:
pkcs = winreg.OpenKey(cuser, plkkey)
except WindowsError:
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]
break
if userkey is not None:
break
type = winreg.QueryValueEx(pkcs, None)[0]
if type != 'pkcs12':
if userkey is None:
raise ADEPTError('Could not locate privateLicenseKey')
userkey = userkey.decode('base64')
userkey = AES.new(keykey, AES.MODE_CBC).decrypt(userkey)
userkey = userkey[26:-ord(userkey[-1])]
with open(keypath, 'wb') as f:
f.write(userkey)
return True
elif sys.platform.startswith('darwin'):
import xml.etree.ElementTree as etree
import Carbon.File
import Carbon.Folder
import Carbon.Folders
import MacOS
ACTIVATION_PATH = 'Adobe/Digital Editions/activation.dat'
NSMAP = {'adept': 'http://ns.adobe.com/adept',
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
def find_folder(domain, dtype):
try:
fsref = Carbon.Folder.FSFindFolder(domain, dtype, False)
return Carbon.File.pathname(fsref)
except MacOS.Error:
return None
def find_app_support_file(subpath):
dtype = Carbon.Folders.kApplicationSupportFolderType
for domain in Carbon.Folders.kUserDomain, Carbon.Folders.kLocalDomain:
path = find_folder(domain, dtype)
if path is None:
continue
pkcs = winreg.QueryValueEx(pkcs, 'value')[0]
break
if pkcs is None:
raise ADEPTError('Could not locate PKCS specification')
if userkey is None:
raise ADEPTError('Could not locate privateLicenseKey')
userkey = userkey.decode('base64')
userkey = AES.new(keykey, AES.MODE_CBC).decrypt(userkey)
userkey = userkey[26:-ord(userkey[-1])]
with open(keypath, 'wb') as f:
f.write(userkey)
return
path = os.path.join(path, subpath)
if os.path.isfile(path):
return path
return None
def retrieve_key(keypath):
actpath = find_app_support_file(ACTIVATION_PATH)
if actpath is None:
raise ADEPTError("Could not locate ADE activation")
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:]
with open(keypath, 'wb') as f:
f.write(userkey)
return True
elif sys.platform.startswith('cygwin'):
def retrieve_key(keypath):
tkMessageBox.showerror(
"ADEPT Key",
"This script requires a Windows-native Python, and cannot be run "
"under Cygwin. Please install a Windows-native Python and/or "
"check your file associations.")
return False
else:
def retrieve_key(keypath):
tkMessageBox.showerror(
"ADEPT Key",
"This script only supports Windows and Mac OS X. For Linux "
"you should be able to run ADE and this script under Wine (with "
"an appropriate version of Windows Python installed).")
return False
class ExceptionDialog(Tkinter.Frame):
def __init__(self, root, text):
@ -279,33 +351,27 @@ class ExceptionDialog(Tkinter.Frame):
self.text.pack(fill=Tkconstants.BOTH, expand=1)
self.text.insert(Tkconstants.END, text)
def main(argv=sys.argv):
root = Tkinter.Tk()
root.withdraw()
progname = os.path.basename(argv[0])
if AES is None:
tkMessageBox.showerror(
"ADEPT Key",
"This script requires PyCrypto, which must be installed "
"separately. Read the top-of-script comment for details.")
return 1
keypath = 'adeptkey.der'
success = False
try:
retrieve_key(keypath)
success = retrieve_key(keypath)
except ADEPTError, e:
tkMessageBox.showerror("ADEPT Key", "Error: " + str(e))
return 1
except Exception:
root.wm_state('normal')
root.title('ADEPT Key')
text = traceback.format_exc()
ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
root.mainloop()
if not success:
return 1
tkMessageBox.showinfo(
"ADEPT Key", "Key successfully retrieved to %s" % (keypath))
return 0
if __name__ == '__main__':
sys.exit(main())
sys.exit(main())

@ -1,123 +0,0 @@
#! /usr/bin/env python
# ineptkeymac.py, version 1
# This program runs on Mac OS X, version 10.6.2 and probably several other
# versions. It uses Python 2.6, but it probably also runs on all versions
# 2.x with x >= 5.
# This program extracts the private RSA key for your ADE account in a
# standard binary form (DER format) in a file of your choosing. Its purpose
# is to make a backup of that key so that your legally bought ADE encoded
# ebooks can be salvaged in case they would no longer be supported by ADE
# software. No other usages are intended.
# It has been tested with the key storage structure of ADE 1.7.1 and 1.7.2
# and Sony Reader Library.
# This software does not contain any encryption code. Its only use of
# external encryption software is the use of openssl for the conversion of
# the private key from pem to der format. It doesn't use encryption or
# decryption, however.
# You can run this program from the command line (python ineptkeymac.py
# filename), or by doubleclicking when it has been associated with
# Pythonlauncher. When no filename is given it will show a dialog to obtain one.
from __future__ import with_statement
__license__ = 'GPL v3'
import sys
import os
import xml.etree.ElementTree as etree
from contextlib import closing
import Tkinter
import Tkconstants
import tkFileDialog
from tkMessageBox import showerror
from subprocess import Popen, PIPE
import textwrap
NS = 'http://ns.adobe.com/adept'
ACTFILE = '~/Library/Application Support/Adobe/Digital Editions/activation.dat'
HEADER = '-----BEGIN PRIVATE KEY-----\n'
FOOTER = '\n-----END PRIVATE KEY-----\n'
Gui = False
def get_key():
'''Returns the private key as a binary string (DER format)'''
try:
filename = os.path.expanduser(ACTFILE)
tree = etree.parse(filename)
xpath = '//{%s}credentials/{%s}privateLicenseKey' % (NS, NS)
b64key = tree.findtext(xpath)
pemkey = HEADER + textwrap.fill(b64key, 64) + FOOTER
cmd = ['openssl', 'rsa', '-outform', 'der']
proc = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
stdout, stderr = proc.communicate(pemkey)
if proc.returncode != 0:
error("openssl error: " + stderr)
return None
return stdout
except IOError:
error("Can find keyfile. Maybe you should activate your Adobe ID.")
sys.exit(1)
def store_key(key, keypath):
'''Store the key in the file given as keypath. If no keypath is given a
dialog will ask for one.'''
try:
if keypath is None:
keypath = get_keypath()
if not keypath: # Cancelled
return
with closing(open(keypath, 'wb')) as outf:
outf.write(key)
except IOError, e:
error("Can write keyfile: " + str(e))
def get_keypath():
keypath = tkFileDialog.asksaveasfilename(
parent = None, title = 'Select file to store ADEPT key',
initialdir = os.path.expanduser('~/Desktop'),
initialfile = 'adeptkey.der',
defaultextension = '.der', filetypes = [('DER-encoded files', '.der'),
('All Files', '.*')])
if keypath:
keypath = os.path.normpath(keypath)
return keypath
def error(text):
print text
if Gui: showerror('Error!', text)
def gui_main():
root = Tkinter.Tk()
root.iconify()
global Gui
Gui = True
store_key(get_key(), None)
return 0
def main(argv=sys.argv):
progname = os.path.basename(argv[0])
if len(argv) == 1: # assume GUI if no argument given
return gui_main()
if len(argv) != 2:
print "usage: %s KEYFILE" % (progname,)
return 1
store_key(get_key(), argv[1])
if __name__ == '__main__':
sys.exit(main())

@ -0,0 +1,88 @@
#include <cstdlib>
#include <iostream>
#include <conio.h>
#include <fstream>
using namespace std;
int main(int argc, char *argv[])
{
// Variables
int TopazTrue = 0;
int strlength = 0;
char uinfile[80];
char outfile[80];
char command[80];
char buffer[80];
// String initialization
strcpy(uinfile,"");
strcpy(outfile,"");
strcpy(buffer,"");
strcpy(command,"skindle "); // string preloaded with "skindle "
cout << "\n\n\n Please enter the name of the book to be converted:\n\n ";
cout << " Don't forget the prc file extension!\n\n ";
cout << " Watch out for zeros and Os. Zeros are skinny and Os are fat.\n\n\n ";
cin >> uinfile; // get file name of the book to be converted from user
ifstream infile(uinfile);
infile.getline(buffer,4);
if (strncmp (buffer,"TPZ",3)==0) // open file and test first 3 char if TPZ then book is topaz
{
TopazTrue = 1; // This is a Topaz file
}
strlength = strlen(uinfile);
if(strlength > 13)
{
strncat(outfile,uinfile,10); // Create output file name using first 10 char of input file name
}
else
{
strncat(outfile,uinfile, (strlength - 4)); // If file name is less than 10 characters
}
if(TopazTrue == 1) // This is Topaz Book
{
strcat(command,"-d "); // Add the topaz switch to the command line
strcat(outfile,".tpz"); // give tpz file extension to topaz output file
} // end of TopazTrue
else
{
strcat(outfile,".azw");
} // if not Topaz make it azw
strcat(command,"-i "); // Add the input switch to the command line
strcat(command,uinfile); // add the input file name to the command line
strcat(command," -o "); // add the output switch to the command line
strcat(command,outfile); // Add the output file name to the command line
cout << "\n\n The skindle program is called here.\n";
cout << " Any errors reported between here and \"The command line used was:\"\n";
cout << " Are errors from the skindle program. Not EZskindle4PC.\n\n";
system(command); // call skindle program to convert the book
cout << "\n\n The command line used was:\n\n";
cout << " " << command << "\n";
cout << "\n\n\n Please note the output file is created from the input";
cout << "\n file name. The file extension is changed to tpz for Topaz";
cout << "\n files and to azw for non-Topaz files. Also, _EBOK is removed ";
cout << "\n from the file name. This is to make it eaiser to identify ";
cout << "\n the file with no DRM.";
system("PAUSE");
return EXIT_SUCCESS;
}

@ -0,0 +1,44 @@
LZskindle4PCv1_1 The Lazy skindle program for those who are typing impared
To setup:
1. Create a new folder: example C:\skindle
2. Place LZskindle4PCv1_1.exe and skindle.exe in this folder.
3. Create 2 subfolders: C:\skindle\input
and C:\skindle\output
To run:
1. Copy the book(s) you wish to remove DRM from into the input directory
(leave the originals in my kindle folder).
2. Double click on LZskindle4PCv1_0.exe
3. A DOS window will open and will show skindle being called for
each book in the input directory.
4. The books with the DRM removed will now be in the output directory.
Rev1_1
fixed program to allow any file extension. My testing indicates that
skindle does not care what file extension a file has. If it is a file type
that it can convert it will. If the file is not compatible it will close
and give an unknown file type message.
Rev1_0
If the program is run with no files in the input directory you will get a
“File not Found” error and the program will terminate.
PLEASE USE ONLY FOR YOUR PERSONAL USE ON BOOKS YOU PAID FOR!!
This program is provided to allow you to archive your library in a format that
will outlast the kindle. Technology moves on and you should not have to reinvest
in an entire new library when the Kindle is obsolete. Please do not use this program
for piracy.

@ -0,0 +1,150 @@
#include <cstdlib>
#include <iostream>
#include <fstream>
//#include <conio.h>
using namespace std;
int main(int argc, char *argv[])
{
// Variable Declarations ??
char buffer[80];
int error = 0;
// int YesNo = 0;
// int exit = 0;
// Variables EZskindle4PC
int TopazTrue = 0;
int strlength = 0;
char uinfile[80];
char outfile[80];
char command[80];
char buffer2[20];
char tempfile[80];
// Initialize strings
strcpy(uinfile,"");
strcpy(outfile,"");
strcpy(buffer,"");
strcpy(buffer2,"");
strcpy(command,"skindle "); // string preloaded with "skindle "
//// Beginning of program code ////////////////////////////////////////////////////////////
system("dir /b .\\input\\*.* > books.txt"); // Create txt file with list of books
// No testing of file type being done
// I am letting skindle determing if valid
// file type
// Read in the list of book file names
ifstream infile("books.txt");
do // while not end of file
{
infile.getline(buffer,50); // load the first 50 characters of the line to buffer
if(strcmp(buffer, "")!= 0) // If there is file name in the buffer do this on last loop buffer will be empty
{
strcpy(uinfile,buffer); // load file name from buffer
strcpy(tempfile,".\\input\\"); // load directory name for input files
strcat(tempfile,buffer); // load the file name
ifstream infile2(tempfile); // open the book file for reading
infile2.getline(buffer2,4); // load first 4 char from file
infile2.close(); // close the book file
if (strncmp (buffer2,"TPZ",3)==0) // open file and test first 3 char if TPZ then book is topaz
{
TopazTrue = 1; // This is a Topaz file
}
strlength = strlen(uinfile);
if(strlength > 13)
{
strncat(outfile,uinfile,10); // Create output file name using first 10 char of input file name
}
else
{
strncat(outfile,uinfile, (strlength - 4)); // If file name is less than 10 characters
}
if(TopazTrue == 1) // This is Topaz Book
{
strcat(command,"-d "); // Add the topaz switch to the command line
strcat(outfile,".tpz"); // give tpz file extension to topaz output file
} // end of TopazTrue
else
{
strcat(outfile,".azw");
} // if not Topaz make it azw
strcat(command,"-i "); // Add the input switch to the command line
strcat(command,".\\input\\"); // Add the input directory to the command line
strcat(command,uinfile); // add the input file name to the command line
strcat(command," -o "); // add the output switch to the command line
strcat(command,".\\output\\"); // Add directory for out files
strcat(command,outfile); // Add the output file name to the command line
cout << "\n\n The skindle program is called here.\n";
cout << " Any errors reported between here and \"The command line used was:\"\n";
cout << " Are errors from the skindle program. Not EZskindle4PC.\n\n";
system(command); // call skindle program to convert the book
cout << "\n\n The command line used was:\n\n";
cout << " " << command << "\n\n\n\n";
}// end of file name in the buffer required to prevent execution on EOF
strcpy(command,"skindle "); // reset strings and variables for next book
strcpy(outfile,"");
strcpy(uinfile,"");
strcpy(buffer,"");
strcpy(buffer2,"");
TopazTrue = 0;
strlength = 0;
}while (! infile.eof() ); // no more books in the file
infile.close(); // close books.txt
// cout << "\n\n\n Do you want to delete all of the books from the input directory?\n\n";
// cout << " DO NOT DELETE IF THESE ARE ONLY COPY OF YOUR BOOKS!!!!\n\n";
// cout << " Y or N: ";
// do { // while not yes or no
// YesNo = getch(); // This is a DOS/Windows console command not standard C may not be
// // Usable under Unix or Mac implementations
//
// if((YesNo == 121)||(YesNo == 89)) // y or Y is true
// {
// exit = 1; // valid input exit do while loop
// cout << "\n\n";
// system("del .\\input\\*.*"); // delete everything in the input directory
// cout << "\n\n";
// }
// if((YesNo == 110)||(YesNo == 78)) // n or N is true
// {
// exit = 1; // valid input exit do while loop
// }
//
// }while (exit != 1);
// cout << "\n\nYesNo = " << YesNo << "\n\n";
system("PAUSE");
system("del books.txt"); // Delete txt file with list of books
return EXIT_SUCCESS;
}

@ -0,0 +1,27 @@
Ezskindle4PC.exe
This executable program makes using skindle easier for people using Windows PCs.
I do not know if it will work under any other operating system, however, I have included
the source code should anyone want to port it into other operating systems.
To use this program:
1. Copy the ezskindle4PC.exe into the same directory with the skindle files.
2. Copy the kindle book into the same directory.
3. double click the EZskindle4PCv1_0.exe file.
a. A DOS window will open and you will be asked for the name of the file you want to work with.
4. Type in the books file name. (it will look something like B000WCTBTA_EBOK.prc)
5. The program will then check if it is a Topaz file and then create the output file name using the
first part of the input file name. It will use “tpz” file extension for Topaz books and will use “azw”
for non topaz books. The files with the “azw” format can be converted to other ebook formats using
Calibre. If you want to convert Topaz books to other formats you need to use Topaz tools not skindle.
6. The program will then create a command line and call the skindle program to process the book and
remove the DRM.
7. The program will pause and allow you to see the result of the skindle process.
8. Press any key to close the program.
version 1.1
Ok
Found a new 32 bit compiler and I think I have worked out the kinks.

@ -114,7 +114,7 @@ class MainDialog(Tkinter.Frame):
def get_mobipath(self):
mobipath = tkFileDialog.askopenfilename(
parent=None, title='Select Mobi eBook File',
defaultextension='.prc', filetypes=[('Mobi eBook File', '.prc'), ('Mobi eBook File', '.mobi'),
defaultextension='.prc', filetypes=[('Mobi eBook File', '.prc'), ('Mobi eBook File', '.azw'),('Mobi eBook File', '.mobi'),
('All Files', '.*')])
if mobipath:
mobipath = os.path.normpath(mobipath)

@ -76,6 +76,8 @@ def main(argv=sys.argv):
print "Kindle 2 Global serial number detected"
elif serial.startswith("B004"):
print "Kindle DX serial number detected"
elif serial.startswith("B005"):
print "Kindle DX International serial number detected"
else:
print "Warning: unrecognized serial number. Please recheck input."
return 1

@ -38,8 +38,9 @@
# This knowledge leads to a simplification of the test for the
# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
# 0.15 - Now outputs 'hearbeat', and is also quicker for long files.
# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
__version__ = '0.15'
__version__ = '0.16'
import sys
import struct
@ -242,7 +243,7 @@ class DrmStripper:
if self.num_sections > records+1:
new_data += self.data_file[self.sections[records+1][0]:]
self.data_file = new_data
print "done."
print "done"
def getResult(self):
return self.data_file
@ -255,7 +256,7 @@ if not __name__ == "__main__":
description = 'Removes DRM from secure Mobi files'
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
author = 'The Dark Reverser' # The author of this plugin
version = (0, 1, 5) # The version number of this plugin
version = (0, 1, 6) # The version number of this plugin
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import

@ -38,8 +38,9 @@
# This knowledge leads to a simplification of the test for the
# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
# 0.15 - Now outputs 'hearbeat', and is also quicker for long files.
# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
__version__ = '0.15'
__version__ = '0.16'
import sys
import struct
@ -242,7 +243,7 @@ class DrmStripper:
if self.num_sections > records+1:
new_data += self.data_file[self.sections[records+1][0]:]
self.data_file = new_data
print "done."
print "done"
def getResult(self):
return self.data_file
@ -255,7 +256,7 @@ if not __name__ == "__main__":
description = 'Removes DRM from secure Mobi files'
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
author = 'The Dark Reverser' # The author of this plugin
version = (0, 1, 5) # The version number of this plugin
version = (0, 1, 6) # The version number of this plugin
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import

@ -1,13 +1,13 @@
#! /usr/bin/python
# -*- coding: utf-8 -*-
# unswindle.pyw, version 6-rc1
# Copyright © 2009 i♥cabbages
# unswindle.pyw, version 7
# Copyright © 2009-2010 i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3 or
# later. <http://www.gnu.org/licenses/>
# To run this program install a 32-bit version of Python 2.6 from
# Before running this program, you must first install Python 2.6 from
# <http://www.python.org/download/>. Save this script file as unswindle.pyw.
# Find and save in the same directory a copy of mobidedrm.py. Double-click on
# unswindle.pyw. It will run Kindle For PC. Open the book you want to
@ -22,11 +22,14 @@
# detect unsupported versions of K4PC
# 5 - Work with new (20091222) version of K4PC
# 6 - Detect and just copy DRM-free books
# 7 - Work with new (20100629) version of K4PC
"""
Decrypt Kindle For PC encrypted Mobipocket books.
"""
from __future__ import with_statement
__license__ = 'GPL v3'
import sys
@ -622,8 +625,17 @@ class PC1KeyGrabber(object):
0x0054c9e0: '_get_pc1_pid',
0x004f8ac9: '_get_book_path',
},
'd791f52dd2ecc68722212d801ad52cb79d1b6fc9': {
0x0041724d: '_i_like_wine',
0x004bfe3d: '_no_debugger_here',
0x005bd9db: '_no_debugger_here',
0x00565920: '_get_pc1_pid',
0x0050fde9: '_get_book_path',
},
}
MOBI_EXTENSIONS = set(['.prc', '.pdb', '.mobi', '.azw', '.az1', '.azw1'])
@classmethod
def supported_version(cls, hexdigest):
return (hexdigest in cls.HOOKS)
@ -658,7 +670,8 @@ class PC1KeyGrabber(object):
path = path.decode('utf-16', 'ignore')
if u'\0' in path:
path = path[:path.index(u'\0')]
if path[-4:].lower() not in ('.prc', '.pdb', '.mobi'):
root, ext = os.path.splitext(path)
if ext.lower() not in self.MOBI_EXTENSIONS:
return
self.book_path = path
@ -667,7 +680,6 @@ class PC1KeyGrabber(object):
addr = debugger.read_process_memory(addr, type=ctypes.c_char_p)
pid = debugger.read_process_memory(addr, 8)
pid = self._checksum_pid(pid)
print pid
self.book_pid = pid
def _checksum_pid(self, s):

@ -38,8 +38,9 @@
# This knowledge leads to a simplification of the test for the
# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
# 0.15 - Now outputs 'hearbeat', and is also quicker for long files.
# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
__version__ = '0.15'
__version__ = '0.16'
import sys
import struct
@ -242,7 +243,7 @@ class DrmStripper:
if self.num_sections > records+1:
new_data += self.data_file[self.sections[records+1][0]:]
self.data_file = new_data
print "done."
print "done"
def getResult(self):
return self.data_file
@ -255,7 +256,7 @@ if not __name__ == "__main__":
description = 'Removes DRM from secure Mobi files'
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
author = 'The Dark Reverser' # The author of this plugin
version = (0, 1, 5) # The version number of this plugin
version = (0, 1, 6) # The version number of this plugin
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import

@ -121,7 +121,7 @@ class MainDialog(Tkinter.Frame):
def get_tpzpath(self):
tpzpath = tkFileDialog.askopenfilename(
parent=None, title='Select Topaz File',
defaultextension='.prc', filetypes=[('Topaz azw1', '.azw1'), ('Topaz prc', '.prc'),
defaultextension='.prc', filetypes=[('Topaz azw', '.azw'),('Topaz azw1', '.azw1'), ('Topaz prc', '.prc'),
('All Files', '.*')])
if tpzpath:
tpzpath = os.path.normpath(tpzpath)

@ -0,0 +1,200 @@
#!/usr/bin/env python
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
import sys
sys.path.append('lib')
import os, os.path, urllib
import subprocess
from subprocess import Popen, PIPE, STDOUT
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
import subasyncio
from subasyncio import Process
from scrolltextwidget import ScrolledText
class MainDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.root = root
self.interval = 2000
self.p2 = None
self.status = Tkinter.Label(self, text='Extract Contents of Topaz eBook to a Directory')
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='Topaz eBook input file').grid(row=0, sticky=Tkconstants.E)
self.tpzpath = Tkinter.Entry(body, width=50)
self.tpzpath.grid(row=0, column=1, sticky=sticky)
cwd = os.getcwdu()
cwd = cwd.encode('utf-8')
self.tpzpath.insert(0, cwd)
button = Tkinter.Button(body, text="...", command=self.get_tpzpath)
button.grid(row=0, column=2)
Tkinter.Label(body, text='Output Directory').grid(row=1, sticky=Tkconstants.E)
self.outpath = Tkinter.Entry(body, width=50)
self.outpath.grid(row=1, column=1, sticky=sticky)
cwd = os.getcwdu()
cwd = cwd.encode('utf-8')
self.outpath.insert(0, cwd)
button = Tkinter.Button(body, text="...", command=self.get_outpath)
button.grid(row=1, column=2)
Tkinter.Label(body, text='First 8 characters of PID').grid(row=3, sticky=Tkconstants.E)
self.pidnum = Tkinter.StringVar()
self.ccinfo = Tkinter.Entry(body, width=10, textvariable=self.pidnum)
self.ccinfo.grid(row=3, column=1, sticky=sticky)
msg1 = 'Conversion Log \n\n'
self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE, height=15, width=60, wrap=Tkconstants.WORD)
self.stext.grid(row=4, column=0, columnspan=2,sticky=sticky)
self.stext.insert(Tkconstants.END,msg1)
buttons = Tkinter.Frame(self)
buttons.pack()
self.sbotton = Tkinter.Button(
buttons, text="Start", width=10, command=self.convertit)
self.sbotton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
self.qbutton = Tkinter.Button(
buttons, text="Quit", width=10, command=self.quitting)
self.qbutton.pack(side=Tkconstants.RIGHT)
# read from subprocess pipe without blocking
# invoked every interval via the widget "after"
# option being used, so need to reset it for the next time
def processPipe(self):
poll = self.p2.wait('nowait')
if poll != None:
text = self.p2.readerr()
text += self.p2.read()
msg = text + '\n\n' + 'Files successfully extracted\n'
if poll != 0:
msg = text + '\n\n' + 'Error: File Extraction Failed\n'
self.showCmdOutput(msg)
self.p2 = None
self.sbotton.configure(state='normal')
return
text = self.p2.readerr()
text += self.p2.read()
self.showCmdOutput(text)
# make sure we get invoked again by event loop after interval
self.stext.after(self.interval,self.processPipe)
return
# post output from subprocess in scrolled text widget
def showCmdOutput(self, msg):
if msg and msg !='':
msg = msg.encode('utf-8')
self.stext.insert(Tkconstants.END,msg)
self.stext.yview_pickplace(Tkconstants.END)
return
# run as a subprocess via pipes and collect stdout
def topazrdr(self, infile, outdir, pidnum):
# os.putenv('PYTHONUNBUFFERED', '1')
pidoption = ' -p "' + pidnum + '" '
outoption = ' -o "' + outdir + '" '
cmdline = 'python ./lib/cmbtc_dump_nonK4PC.py -v -d ' + pidoption + outoption + '"' + infile + '"'
if sys.platform[0:3] == 'win':
search_path = os.environ['PATH']
search_path = search_path.lower()
if search_path.find('python') >= 0:
cmdline = 'python lib\cmbtc_dump_nonK4PC.py -v -d ' + pidoption + outoption + '"' + infile + '"'
else :
cmdline = 'lib\cmbtc_dump_nonK4PC.py -v -d ' + pidoption + outoption + '"' + infile + '"'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
return p2
def get_tpzpath(self):
tpzpath = tkFileDialog.askopenfilename(
parent=None, title='Select Topaz File',
defaultextension='.prc', filetypes=[('Topaz azw1', '.azw1'), ('Topaz prc', '.prc'),('Topaz azw', '.azw'),
('All Files', '.*')])
if tpzpath:
tpzpath = os.path.normpath(tpzpath)
self.tpzpath.delete(0, Tkconstants.END)
self.tpzpath.insert(0, tpzpath)
return
def get_outpath(self):
cwd = os.getcwdu()
cwd = cwd.encode('utf-8')
outpath = tkFileDialog.askdirectory(
parent=None, title='Directory to Extract Files into',
initialdir=cwd, initialfile=None)
if outpath:
outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END)
self.outpath.insert(0, outpath)
return
def quitting(self):
# kill any still running subprocess
if self.p2 != None:
if (self.p2.wait('nowait') == None):
self.p2.terminate()
self.root.destroy()
# actually ready to run the subprocess and get its output
def convertit(self):
# now disable the button to prevent multiple launches
self.sbotton.configure(state='disabled')
tpzpath = self.tpzpath.get()
outpath = self.outpath.get()
if not tpzpath or not os.path.exists(tpzpath):
self.status['text'] = 'Specified Topaz eBook file does not exist'
self.sbotton.configure(state='normal')
return
if not outpath:
self.status['text'] = 'No output directory specified'
self.sbotton.configure(state='normal')
return
if not os.path.exists(outpath):
os.makedirs(outpath)
pidnum = self.pidnum.get()
if not pidnum or pidnum == '':
self.status['text'] = 'You have not entered a PID '
self.sbotton.configure(state='normal')
return
log = 'Command = "python cmbtc_dump_nonK4PC.py"\n'
log += 'Topaz Path Path = "'+ tpzpath + '"\n'
log += 'Output Directory = "' + outpath + '"\n'
log += 'First 8 chars of PID = "' + pidnum + '"\n'
log += '\n\n'
log += 'Please Wait ...\n'
log = log.encode('utf-8')
self.stext.insert(Tkconstants.END,log)
self.p2 = self.topazrdr(tpzpath, outpath, pidnum)
# python does not seem to allow you to create
# your own eventloop which every other gui does - strange
# so need to use the widget "after" command to force
# event loop to run non-gui events every interval
self.stext.after(self.interval,self.processPipe)
return
def main(argv=None):
root = Tkinter.Tk()
root.title('Topaz eBook File Extraction')
root.resizable(True, False)
root.minsize(300, 0)
MainDialog(root).pack(fill=Tkconstants.X, expand=1)
root.mainloop()
return 0
if __name__ == "__main__":
sys.exit(main())

@ -245,12 +245,13 @@ class PageParser(object):
'empty_text_region' : (1, 'snippets', 1, 0),
'img' : (1, 'snippets', 1, 0),
'img.x' : (1, 'scalar_number', 0, 0),
'img.y' : (1, 'scalar_number', 0, 0),
'img.h' : (1, 'scalar_number', 0, 0),
'img.w' : (1, 'scalar_number', 0, 0),
'img.src' : (1, 'scalar_number', 0, 0),
'img' : (1, 'snippets', 1, 0),
'img.x' : (1, 'scalar_number', 0, 0),
'img.y' : (1, 'scalar_number', 0, 0),
'img.h' : (1, 'scalar_number', 0, 0),
'img.w' : (1, 'scalar_number', 0, 0),
'img.src' : (1, 'scalar_number', 0, 0),
'img.color_src' : (1, 'scalar_number', 0, 0),
'paragraph' : (1, 'snippets', 1, 0),
'paragraph.class' : (1, 'scalar_text', 0, 0),
@ -674,6 +675,8 @@ class PageParser(object):
elif (magic[0:1] == 'p') and (magic[2:9] == '__PAGE_'):
skip = self.fo.read(2)
first_token = 'info'
elif (magic[0:1] == 'p') and (magic[2:8] == '_PAGE_'):
first_token = 'info'
elif (magic[0:1] == 'g') and (magic[2:9] == '__GLYPH'):
skip = self.fo.read(3)
first_token = 'info'
@ -706,7 +709,10 @@ class PageParser(object):
else:
if self.debug:
print "Main Loop: Unknown value: %x" % v
if (v == 0):
if (self.peek(1) == 0x5f):
skip = self.fo.read(1)
first_token = 'info'
# now do snippet injection
if len(self.snippetList) > 0 :
@ -795,4 +801,4 @@ def main(argv):
return xmlpage
if __name__ == '__main__':
sys.exit(main(''))
sys.exit(main(''))
Loading…
Cancel
Save