tools v1.8

This commit is contained in:
Apprentice Alf 2010-07-24 12:55:24 +01:00
parent 92dafd94b2
commit d427f758f6
18 changed files with 1081 additions and 495 deletions

View File

@ -1,18 +1,29 @@
#! /usr/bin/python #! /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/ # Released under the terms of the GNU General Public Licence, version 3 or
# and PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto # later. <http://www.gnu.org/licenses/>
# (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. # 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: # Revision history:
# 1 - Initial release # 1 - Initial release
# 2 - Rename to INEPT, fix exit code # 2 - Rename to INEPT, fix exit code
# 3 - Add cmd or gui choosing # 5 - Version bump to avoid (?) confusion;
# 4 - support for 1.7.2 support (anon) # Improve OS X support by using OpenSSL when available
# 4.1 - backward compatibility for 1.7.1 and old adeptkey.der # 5.1 - Improve OpenSSL error checking
# 5.2 - Fix ctypes error causing segfaults on some systems
""" """
Decrypt Adobe ADEPT-encrypted EPUB books. Decrypt Adobe ADEPT-encrypted EPUB books.
@ -34,117 +45,224 @@ import Tkconstants
import tkFileDialog import tkFileDialog
import tkMessageBox import tkMessageBox
try: class ADEPTError(Exception):
from Crypto.Cipher import AES pass
from Crypto.PublicKey import RSA
except ImportError: def _load_crypto_libcrypto():
AES = None from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
RSA = None Structure, c_ulong, create_string_buffer, cast
from ctypes.util import find_library
libcrypto = find_library('crypto')
if libcrypto is None:
raise ADEPTError('libcrypto not found')
libcrypto = CDLL(libcrypto)
RSA_NO_PADDING = 3
AES_MAXNR = 14
c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int)
class RSA(Structure):
pass
RSA_p = POINTER(RSA)
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
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])
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 __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 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
class ASN1Parser(object):
class Parser(object):
def __init__(self, bytes):
self.bytes = bytes
self.index = 0
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 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)
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)
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') META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
NSMAP = {'adept': 'http://ns.adobe.com/adept', NSMAP = {'adept': 'http://ns.adobe.com/adept',
'enc': 'http://www.w3.org/2001/04/xmlenc#'} 'enc': 'http://www.w3.org/2001/04/xmlenc#'}
# ASN.1 parsing code from tlslite
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
class ASN1Error(Exception):
pass
class ASN1Parser(object):
class Parser(object):
def __init__(self, bytes):
self.bytes = bytes
self.index = 0
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 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)
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 ZipInfo(zipfile.ZipInfo): class ZipInfo(zipfile.ZipInfo):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if 'compress_type' in kwargs: if 'compress_type' in kwargs:
@ -152,11 +270,10 @@ class ZipInfo(zipfile.ZipInfo):
super(ZipInfo, self).__init__(*args, **kwargs) super(ZipInfo, self).__init__(*args, **kwargs)
self.compress_type = compress_type self.compress_type = compress_type
class Decryptor(object): class Decryptor(object):
def __init__(self, bookkey, encryption): def __init__(self, bookkey, encryption):
enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag) enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
self._aes = AES.new(bookkey, AES.MODE_CBC) self._aes = AES(bookkey)
encryption = etree.fromstring(encryption) encryption = etree.fromstring(encryption)
self._encrypted = encrypted = set() self._encrypted = encrypted = set()
expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'), expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
@ -165,7 +282,7 @@ class Decryptor(object):
path = elem.get('URI', None) path = elem.get('URI', None)
if path is not None: if path is not None:
encrypted.add(path) encrypted.add(path)
def decompress(self, bytes): def decompress(self, bytes):
dc = zlib.decompressobj(-15) dc = zlib.decompressobj(-15)
bytes = dc.decompress(bytes) bytes = dc.decompress(bytes)
@ -173,7 +290,7 @@ class Decryptor(object):
if ex: if ex:
bytes = bytes + ex bytes = bytes + ex
return bytes return bytes
def decrypt(self, path, data): def decrypt(self, path, data):
if path in self._encrypted: if path in self._encrypted:
data = self._aes.decrypt(data)[16:] data = self._aes.decrypt(data)[16:]
@ -181,16 +298,12 @@ class Decryptor(object):
data = self.decompress(data) data = self.decompress(data)
return data return data
class ADEPTError(Exception):
pass
def cli_main(argv=sys.argv): def cli_main(argv=sys.argv):
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
if AES is None: if AES is None:
print "%s: This script requires PyCrypto, which must be installed " \ print "%s: This script requires OpenSSL or PyCrypto, which must be" \
"separately. Read the top-of-script comment for details." % \ " installed separately. Read the top-of-script comment for" \
(progname,) " details." % (progname,)
return 1 return 1
if len(argv) != 4: if len(argv) != 4:
print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,) print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
@ -198,9 +311,7 @@ def cli_main(argv=sys.argv):
keypath, inpath, outpath = argv[1:] keypath, inpath, outpath = argv[1:]
with open(keypath, 'rb') as f: with open(keypath, 'rb') as f:
keyder = f.read() keyder = f.read()
key = ASN1Parser([ord(x) for x in keyder]) rsa = RSA(keyder)
key = [bytesToNumber(key.getChild(x).value) for x in xrange(1, 4)]
rsa = RSA.construct(key)
with closing(ZipFile(open(inpath, 'rb'))) as inf: with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist()) namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \ 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)) outf.writestr(path, decryptor.decrypt(path, data))
return 0 return 0
class DecryptionDialog(Tkinter.Frame): class DecryptionDialog(Tkinter.Frame):
def __init__(self, root): def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5) Tkinter.Frame.__init__(self, root, border=5)
@ -328,8 +438,9 @@ def gui_main():
root.withdraw() root.withdraw()
tkMessageBox.showerror( tkMessageBox.showerror(
"INEPT EPUB Decrypter", "INEPT EPUB Decrypter",
"This script requires PyCrypto, which must be installed " "This script requires OpenSSL or PyCrypto, which must be"
"separately. Read the top-of-script comment for details.") " installed separately. Read the top-of-script comment for"
" details.")
return 1 return 1
root.title('INEPT EPUB Decrypter') root.title('INEPT EPUB Decrypter')
root.resizable(True, False) root.resizable(True, False)

View File

@ -1,25 +1,38 @@
#! /usr/bin/python #! /usr/bin/python
# -*- coding: utf-8 -*-
# ineptkey.pyw, version 4.4 # ineptkey.pyw, version 5
# ineptkeyv44 # Copyright © 2009-2010 i♥cabbages
# To run this program install Python 2.6 from http://www.python.org/download/ # Released under the terms of the GNU General Public Licence, version 3 or
# and PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto # later. <http://www.gnu.org/licenses/>
# (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 # Windows users: Before running this program, you must first install Python 2.6
# adeptkey.der in the same directory. These are your ADEPT user keys. # 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: # Revision history:
# 1 - Initial release, for Adobe Digital Editions 1.7 # 1 - Initial release, for Adobe Digital Editions 1.7
# 2 - Better algorithm for finding pLK; improved error handling # 2 - Better algorithm for finding pLK; improved error handling
# 3 - Rename to INEPT # 3 - Rename to INEPT
# 4 - Series of changes by joblack (and others?) --
# 4.1 - quick beta fix for ADE 1.7.2 (anon) # 4.1 - quick beta fix for ADE 1.7.2 (anon)
# 4.2 - added old 1.7.1 processing # 4.2 - added old 1.7.1 processing
# 4.3 - better key search # 4.3 - better key search
# 4.4 - Make it working on 64-bit Python # 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 from __future__ import with_statement
@ -28,246 +41,305 @@ __license__ = 'GPL v3'
import sys import sys
import os import os
from struct import pack import struct
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 Tkinter import Tkinter
import Tkconstants import Tkconstants
import tkMessageBox import tkMessageBox
import traceback import traceback
import hashlib
import pickle
try:
from Crypto.Cipher import AES
except ImportError:
AES = None
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
kernel32 = windll.kernel32
advapi32 = windll.advapi32
crypt32 = windll.crypt32
class ADEPTError(Exception): class ADEPTError(Exception):
pass pass
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
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'
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(): def GetSystemDirectory():
buffer = create_unicode_buffer(MAX_PATH + 1) GetSystemDirectoryW = kernel32.GetSystemDirectoryW
GetSystemDirectoryW(buffer, len(buffer)) GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
return buffer.value GetSystemDirectoryW.restype = c_uint
return GetSystemDirectory def GetSystemDirectory():
GetSystemDirectory = 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 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(): def GetUserName():
buffer = create_unicode_buffer(32) GetUserNameW = advapi32.GetUserNameW
size = c_uint(len(buffer)) GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
while not GetUserNameW(buffer, byref(size)): GetUserNameW.restype = c_uint
buffer = create_unicode_buffer(len(buffer) * 2) def GetUserName():
size.value = len(buffer) buffer = create_unicode_buffer(32)
return buffer.value.encode('utf-16-le')[::2] size = c_uint(len(buffer))
return GetUserName while not GetUserNameW(buffer, byref(size)):
GetUserName = GetUserName() 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()
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 PAGE_EXECUTE_READWRITE = 0x40
MEM_COMMIT = 0x1000 MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000 MEM_RESERVE = 0x2000
VirtualAlloc = windll.kernel32.VirtualAlloc def VirtualAlloc():
VirtualAlloc.restype = c_void_p _VirtualAlloc = kernel32.VirtualAlloc
VirtualAlloc.argtypes = (c_void_p, c_size_t, c_uint, c_uint) _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()
from ctypes import memmove MEM_RELEASE = 0x8000
memmove.restype = c_void_p
memmove.argtypes = (c_void_p, c_void_p, c_size_t)
CPUID0_INSNS = (b"\x55" # push %rbp def VirtualFree():
"\x48\x89\xe5" # mov %rsp,%rbp _VirtualFree = kernel32.VirtualFree
"\x48\x89\x4d\xf8" # mov %rcx,-0x8(%rbp) _VirtualFree.argtypes = [LPVOID, c_size_t, DWORD]
"\x31\xc0" # xor %eax,%eax _VirtualFree.restype = BOOL
"\x0f\xa2" # cpuid def VirtualFree(addr, size=0, freetype=MEM_RELEASE):
"\x48\x8b\x45\xf8" # mov -0x8(%rbp),%rax return _VirtualFree(addr, size, freetype)
"\x89\x18" # mov %ebx,(%rax) return VirtualFree
"\x89\x50\x04" # mov %edx,0x4(%rax) VirtualFree = VirtualFree()
"\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 class NativeFunction(object):
"\xff\xc0" # inc %eax def __init__(self, restype, argtypes, insns):
"\x0f\xa2" # cpuid self._buf = buf = VirtualAlloc(None, len(insns))
"\xc3" # ret memmove(buf, insns, len(insns))
) ftype = CFUNCTYPE(restype, *argtypes)
self._native = ftype(buf)
insnlen0 = len(CPUID0_INSNS) def __call__(self, *args):
insnlen1 = len(CPUID1_INSNS) return self._native(*args)
insnlen = insnlen0 + insnlen1
code_addr = (VirtualAlloc(NULL, insnlen, def __del__(self):
MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE)) if self._buf is not None:
VirtualFree(self._buf)
self._buf = None
if code_addr is None: if struct.calcsize("P") == 4:
raise ADEPTError("Failed to allocate memory") CPUID0_INSNS = (
"\x53" # push %ebx
memmove(code_addr, CPUID0_INSNS + CPUID1_INSNS, insnlen) "\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(): def cpuid0():
buffer = create_string_buffer(12) _cpuid0 = NativeFunction(None, [c_char_p], CPUID0_INSNS)
cpuid0__ = CFUNCTYPE(c_ulonglong, c_char_p)(code_addr) buf = create_string_buffer(12)
def cpuid0(): def cpuid0():
cpuid0__(buffer) _cpuid0(buf)
return buffer.raw return buf.raw
return cpuid0 return cpuid0
cpuid0 = cpuid0() cpuid0 = cpuid0()
cpuid1 = CFUNCTYPE(c_ulonglong)(code_addr + insnlen0) cpuid1 = NativeFunction(c_uint, [], CPUID1_INSNS)
class DataBlob(Structure): class DataBlob(Structure):
_fields_ = [('cbData', c_uint), _fields_ = [('cbData', c_uint),
('pbData', c_void_p)] ('pbData', c_void_p)]
DataBlob_p = POINTER(DataBlob) DataBlob_p = POINTER(DataBlob)
def CryptUnprotectData(): def CryptUnprotectData():
_CryptUnprotectData = crypt32.CryptUnprotectData _CryptUnprotectData = crypt32.CryptUnprotectData
_CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p, _CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
c_void_p, c_void_p, c_uint, DataBlob_p] c_void_p, c_void_p, c_uint, DataBlob_p]
_CryptUnprotectData.restype = c_uint _CryptUnprotectData.restype = c_uint
def CryptUnprotectData(indata, entropy): def CryptUnprotectData(indata, entropy):
indatab = create_string_buffer(indata) indatab = create_string_buffer(indata)
indata = DataBlob(len(indata), cast(indatab, c_void_p)) indata = DataBlob(len(indata), cast(indatab, c_void_p))
entropyb = create_string_buffer(entropy) entropyb = create_string_buffer(entropy)
entropy = DataBlob(len(entropy), cast(entropyb, c_void_p)) entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
outdata = DataBlob() outdata = DataBlob()
if not _CryptUnprotectData(byref(indata), None, byref(entropy), if not _CryptUnprotectData(byref(indata), None, byref(entropy),
None, None, 0, byref(outdata)): None, None, 0, byref(outdata)):
raise ADEPTError("Failed to decrypt user key key (sic)") raise ADEPTError("Failed to decrypt user key key (sic)")
return string_at(outdata.pbData, outdata.cbData) return string_at(outdata.pbData, outdata.cbData)
return CryptUnprotectData return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData() CryptUnprotectData = CryptUnprotectData()
def retrieve_key(keypath):
def retrieve_key(keypath): if AES is None:
root = GetSystemDirectory().split('\\')[0] + '\\' tkMessageBox.showerror(
serial = GetVolumeSerialNumber(root) "ADEPT Key",
vendor = cpuid0() "This script requires PyCrypto, which must be installed "
signature = pack('>I', cpuid1())[1:] "separately. Read the top-of-script comment for details.")
user = GetUserName() return False
entropy = pack('>I12s3s13s', serial, vendor, signature, user) root = GetSystemDirectory().split('\\')[0] + '\\'
cuser = winreg.HKEY_CURRENT_USER serial = GetVolumeSerialNumber(root)
try: vendor = cpuid0()
regkey = winreg.OpenKey(cuser, DEVICE_KEY) signature = struct.pack('>I', cpuid1())[1:]
except WindowsError: user = GetUserName()
raise ADEPTError("Adobe Digital Editions not activated") entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user)
device = winreg.QueryValueEx(regkey, 'key')[0] cuser = winreg.HKEY_CURRENT_USER
keykey = CryptUnprotectData(device, entropy)
userkey = None
pkcs = None
keys = {}
counter = 0
for i in xrange(0, 16):
skey = PRIVATE_LICENCE_KEY % i
try: try:
regkey = winreg.OpenKey(cuser, skey) regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH)
except WindowsError: except WindowsError:
break raise ADEPTError("Adobe Digital Editions not activated")
type = winreg.QueryValueEx(regkey, None)[0] device = winreg.QueryValueEx(regkey, 'key')[0]
# obfuscation technique keykey = CryptUnprotectData(device, entropy)
if type != 'credentials': userkey = None
continue try:
for j in xrange(0, 16): plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH)
plkkey = PRIVATE_LICENCE_KEY_KEY % (i, j) except WindowsError:
raise ADEPTError("Could not locate ADE activation")
for i in xrange(0, 16):
try: try:
regkey = winreg.OpenKey(cuser, plkkey) plkparent = winreg.OpenKey(plkroot, "%04d" % (i,))
except WindowsError: except WindowsError:
break break
type = winreg.QueryValueEx(regkey, None)[0] ktype = winreg.QueryValueEx(plkparent, None)[0]
if type != 'privateLicenseKey': if ktype != 'credentials':
continue continue
userkey = winreg.QueryValueEx(regkey, 'value')[0] for j in xrange(0, 16):
break try:
for j in xrange(0, 16): plkkey = winreg.OpenKey(plkparent, "%04d" % (j,))
plkkey = PRIVATE_LICENCE_KEY_KEY % (i, j) except WindowsError:
try: break
pkcs = winreg.OpenKey(cuser, plkkey) ktype = winreg.QueryValueEx(plkkey, None)[0]
except WindowsError: if ktype != 'privateLicenseKey':
continue
userkey = winreg.QueryValueEx(plkkey, 'value')[0]
break break
type = winreg.QueryValueEx(pkcs, None)[0] if userkey is not None:
if type != 'pkcs12': break
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 continue
pkcs = winreg.QueryValueEx(pkcs, 'value')[0] path = os.path.join(path, subpath)
break if os.path.isfile(path):
if pkcs is None: return path
raise ADEPTError('Could not locate PKCS specification') return None
if userkey is None:
raise ADEPTError('Could not locate privateLicenseKey') def retrieve_key(keypath):
userkey = userkey.decode('base64') actpath = find_app_support_file(ACTIVATION_PATH)
userkey = AES.new(keykey, AES.MODE_CBC).decrypt(userkey) if actpath is None:
userkey = userkey[26:-ord(userkey[-1])] raise ADEPTError("Could not locate ADE activation")
with open(keypath, 'wb') as f: tree = etree.parse(actpath)
f.write(userkey) adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
return 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): class ExceptionDialog(Tkinter.Frame):
def __init__(self, root, text): def __init__(self, root, text):
@ -279,33 +351,27 @@ class ExceptionDialog(Tkinter.Frame):
self.text.pack(fill=Tkconstants.BOTH, expand=1) self.text.pack(fill=Tkconstants.BOTH, expand=1)
self.text.insert(Tkconstants.END, text) self.text.insert(Tkconstants.END, text)
def main(argv=sys.argv): def main(argv=sys.argv):
root = Tkinter.Tk() root = Tkinter.Tk()
root.withdraw() root.withdraw()
progname = os.path.basename(argv[0]) 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' keypath = 'adeptkey.der'
success = False
try: try:
retrieve_key(keypath) success = retrieve_key(keypath)
except ADEPTError, e: except ADEPTError, e:
tkMessageBox.showerror("ADEPT Key", "Error: " + str(e)) tkMessageBox.showerror("ADEPT Key", "Error: " + str(e))
return 1
except Exception: except Exception:
root.wm_state('normal') root.wm_state('normal')
root.title('ADEPT Key') root.title('ADEPT Key')
text = traceback.format_exc() text = traceback.format_exc()
ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1) ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
root.mainloop() root.mainloop()
if not success:
return 1 return 1
tkMessageBox.showinfo( tkMessageBox.showinfo(
"ADEPT Key", "Key successfully retrieved to %s" % (keypath)) "ADEPT Key", "Key successfully retrieved to %s" % (keypath))
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(main()) sys.exit(main())

View File

@ -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())

View File

@ -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;
}

View File

@ -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.

View File

@ -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;
}

View File

@ -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.

View File

@ -114,7 +114,7 @@ class MainDialog(Tkinter.Frame):
def get_mobipath(self): def get_mobipath(self):
mobipath = tkFileDialog.askopenfilename( mobipath = tkFileDialog.askopenfilename(
parent=None, title='Select Mobi eBook File', 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', '.*')]) ('All Files', '.*')])
if mobipath: if mobipath:
mobipath = os.path.normpath(mobipath) mobipath = os.path.normpath(mobipath)

View File

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

View File

@ -38,8 +38,9 @@
# This knowledge leads to a simplification of the test for the # This knowledge leads to a simplification of the test for the
# trailing data byte flags - version 5 and higher AND header size >= 0xE4. # 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.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 sys
import struct import struct
@ -242,7 +243,7 @@ class DrmStripper:
if self.num_sections > records+1: if self.num_sections > records+1:
new_data += self.data_file[self.sections[records+1][0]:] new_data += self.data_file[self.sections[records+1][0]:]
self.data_file = new_data self.data_file = new_data
print "done." print "done"
def getResult(self): def getResult(self):
return self.data_file return self.data_file
@ -255,7 +256,7 @@ if not __name__ == "__main__":
description = 'Removes DRM from secure Mobi files' description = 'Removes DRM from secure Mobi files'
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
author = 'The Dark Reverser' # The author of this plugin 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 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 on_import = True # Run this plugin during the import

View File

@ -38,8 +38,9 @@
# This knowledge leads to a simplification of the test for the # This knowledge leads to a simplification of the test for the
# trailing data byte flags - version 5 and higher AND header size >= 0xE4. # 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.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 sys
import struct import struct
@ -242,7 +243,7 @@ class DrmStripper:
if self.num_sections > records+1: if self.num_sections > records+1:
new_data += self.data_file[self.sections[records+1][0]:] new_data += self.data_file[self.sections[records+1][0]:]
self.data_file = new_data self.data_file = new_data
print "done." print "done"
def getResult(self): def getResult(self):
return self.data_file return self.data_file
@ -255,7 +256,7 @@ if not __name__ == "__main__":
description = 'Removes DRM from secure Mobi files' description = 'Removes DRM from secure Mobi files'
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
author = 'The Dark Reverser' # The author of this plugin 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 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 on_import = True # Run this plugin during the import

View File

@ -1,13 +1,13 @@
#! /usr/bin/python #! /usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# unswindle.pyw, version 6-rc1 # unswindle.pyw, version 7
# Copyright © 2009 i♥cabbages # Copyright © 2009-2010 i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3 or # Released under the terms of the GNU General Public Licence, version 3 or
# later. <http://www.gnu.org/licenses/> # 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. # <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 # 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 # unswindle.pyw. It will run Kindle For PC. Open the book you want to
@ -22,11 +22,14 @@
# detect unsupported versions of K4PC # detect unsupported versions of K4PC
# 5 - Work with new (20091222) version of K4PC # 5 - Work with new (20091222) version of K4PC
# 6 - Detect and just copy DRM-free books # 6 - Detect and just copy DRM-free books
# 7 - Work with new (20100629) version of K4PC
""" """
Decrypt Kindle For PC encrypted Mobipocket books. Decrypt Kindle For PC encrypted Mobipocket books.
""" """
from __future__ import with_statement
__license__ = 'GPL v3' __license__ = 'GPL v3'
import sys import sys
@ -622,8 +625,17 @@ class PC1KeyGrabber(object):
0x0054c9e0: '_get_pc1_pid', 0x0054c9e0: '_get_pc1_pid',
0x004f8ac9: '_get_book_path', 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 @classmethod
def supported_version(cls, hexdigest): def supported_version(cls, hexdigest):
return (hexdigest in cls.HOOKS) return (hexdigest in cls.HOOKS)
@ -658,7 +670,8 @@ class PC1KeyGrabber(object):
path = path.decode('utf-16', 'ignore') path = path.decode('utf-16', 'ignore')
if u'\0' in path: if u'\0' in path:
path = path[:path.index(u'\0')] 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 return
self.book_path = path self.book_path = path
@ -667,7 +680,6 @@ class PC1KeyGrabber(object):
addr = debugger.read_process_memory(addr, type=ctypes.c_char_p) addr = debugger.read_process_memory(addr, type=ctypes.c_char_p)
pid = debugger.read_process_memory(addr, 8) pid = debugger.read_process_memory(addr, 8)
pid = self._checksum_pid(pid) pid = self._checksum_pid(pid)
print pid
self.book_pid = pid self.book_pid = pid
def _checksum_pid(self, s): def _checksum_pid(self, s):

View File

@ -38,8 +38,9 @@
# This knowledge leads to a simplification of the test for the # This knowledge leads to a simplification of the test for the
# trailing data byte flags - version 5 and higher AND header size >= 0xE4. # 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.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 sys
import struct import struct
@ -242,7 +243,7 @@ class DrmStripper:
if self.num_sections > records+1: if self.num_sections > records+1:
new_data += self.data_file[self.sections[records+1][0]:] new_data += self.data_file[self.sections[records+1][0]:]
self.data_file = new_data self.data_file = new_data
print "done." print "done"
def getResult(self): def getResult(self):
return self.data_file return self.data_file
@ -255,7 +256,7 @@ if not __name__ == "__main__":
description = 'Removes DRM from secure Mobi files' description = 'Removes DRM from secure Mobi files'
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
author = 'The Dark Reverser' # The author of this plugin 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 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 on_import = True # Run this plugin during the import

View File

@ -121,7 +121,7 @@ class MainDialog(Tkinter.Frame):
def get_tpzpath(self): def get_tpzpath(self):
tpzpath = tkFileDialog.askopenfilename( tpzpath = tkFileDialog.askopenfilename(
parent=None, title='Select Topaz File', 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', '.*')]) ('All Files', '.*')])
if tpzpath: if tpzpath:
tpzpath = os.path.normpath(tpzpath) tpzpath = os.path.normpath(tpzpath)

View File

@ -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())

View File

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