diff --git a/Adobe_EPUB_Tools/ineptepub.pyw b/Adobe_EPUB_Tools/ineptepub.pyw index 35e18af..d6c5f7d 100644 --- a/Adobe_EPUB_Tools/ineptepub.pyw +++ b/Adobe_EPUB_Tools/ineptepub.pyw @@ -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. + +# Windows users: Before running this program, you must first install Python 2.6 +# from and PyCrypto from +# (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,117 +45,224 @@ import Tkconstants import tkFileDialog import tkMessageBox -try: - from Crypto.Cipher import AES - from Crypto.PublicKey import RSA -except ImportError: - AES = None - RSA = None +class ADEPTError(Exception): + pass + +def _load_crypto_libcrypto(): + from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ + Structure, c_ulong, create_string_buffer, cast + from ctypes.util import find_library + + 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') NSMAP = {'adept': 'http://ns.adobe.com/adept', '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): def __init__(self, *args, **kwargs): if 'compress_type' in 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) diff --git a/Adobe_EPUB_Tools/ineptkey.pyw b/Adobe_EPUB_Tools/ineptkey.pyw index 4a5d868..3756ae3 100644 --- a/Adobe_EPUB_Tools/ineptkey.pyw +++ b/Adobe_EPUB_Tools/ineptkey.pyw @@ -1,25 +1,38 @@ #! /usr/bin/python +# -*- coding: utf-8 -*- -# ineptkey.pyw, version 4.4 -# ineptkeyv44 +# ineptkey.pyw, version 5 +# 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 -# 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. +# Released under the terms of the GNU General Public Licence, version 3 or +# later. + +# Windows users: Before running this program, you must first install Python 2.6 +# from and PyCrypto from +# (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 - - -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): 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(): - buffer = create_unicode_buffer(MAX_PATH + 1) - GetSystemDirectoryW(buffer, len(buffer)) - return buffer.value - return GetSystemDirectory -GetSystemDirectory = 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 GetVolumeSerialNumber(): - GetVolumeInformationW = kernel32.GetVolumeInformationW - GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint, - POINTER(c_uint), POINTER(c_uint), - POINTER(c_uint), c_wchar_p, c_uint] - GetVolumeInformationW.restype = c_uint - def GetVolumeSerialNumber(path): - vsn = c_uint(0) - GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0) - return vsn.value - return GetVolumeSerialNumber -GetVolumeSerialNumber = GetVolumeSerialNumber() - - -def GetUserName(): - GetUserNameW = advapi32.GetUserNameW - GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)] - GetUserNameW.restype = c_uint def GetUserName(): - buffer = create_unicode_buffer(32) - size = c_uint(len(buffer)) - while not GetUserNameW(buffer, byref(size)): - buffer = create_unicode_buffer(len(buffer) * 2) - size.value = len(buffer) - return buffer.value.encode('utf-16-le')[::2] - return GetUserName -GetUserName = GetUserName() + 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() -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 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) + 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() - from ctypes import memmove - memmove.restype = c_void_p - memmove.argtypes = (c_void_p, c_void_p, c_size_t) + MEM_RELEASE = 0x8000 - 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 - ) + 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() - CPUID1_INSNS = (b"\x31\xc0" # xor %eax,%eax - "\xff\xc0" # inc %eax - "\x0f\xa2" # cpuid - "\xc3" # ret - ) + 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) - insnlen0 = len(CPUID0_INSNS) - insnlen1 = len(CPUID1_INSNS) - insnlen = insnlen0 + insnlen1 + def __call__(self, *args): + return self._native(*args) - code_addr = (VirtualAlloc(NULL, insnlen, - MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE)) + def __del__(self): + if self._buf is not None: + VirtualFree(self._buf) + self._buf = None - if code_addr is None: - raise ADEPTError("Failed to allocate memory") - - memmove(code_addr, CPUID0_INSNS + CPUID1_INSNS, insnlen) + 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) + cpuid1 = NativeFunction(c_uint, [], CPUID1_INSNS) -class DataBlob(Structure): - _fields_ = [('cbData', c_uint), - ('pbData', c_void_p)] -DataBlob_p = POINTER(DataBlob) + 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 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 + 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 - type = winreg.QueryValueEx(pkcs, None)[0] - if type != 'pkcs12': + if userkey is not None: + 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 - 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()) \ No newline at end of file + sys.exit(main()) diff --git a/Adobe_EPUB_Tools/ineptkeymac.pyw b/Adobe_EPUB_Tools/ineptkeymac.pyw deleted file mode 100644 index 0dd1307..0000000 --- a/Adobe_EPUB_Tools/ineptkeymac.pyw +++ /dev/null @@ -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()) diff --git a/Kindle_Mobi_Tools/LZskindle4PCv1_1/EZskindle4PCv1_1_1.cpp b/Kindle_Mobi_Tools/LZskindle4PCv1_1/EZskindle4PCv1_1_1.cpp new file mode 100644 index 0000000..535df90 --- /dev/null +++ b/Kindle_Mobi_Tools/LZskindle4PCv1_1/EZskindle4PCv1_1_1.cpp @@ -0,0 +1,88 @@ +#include +#include +#include +#include + +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; +} diff --git a/Kindle_Mobi_Tools/LZskindle4PCv1_1/EZskindle4PCv1_1_1.exe b/Kindle_Mobi_Tools/LZskindle4PCv1_1/EZskindle4PCv1_1_1.exe new file mode 100644 index 0000000..b40343c Binary files /dev/null and b/Kindle_Mobi_Tools/LZskindle4PCv1_1/EZskindle4PCv1_1_1.exe differ diff --git a/Kindle_Mobi_Tools/LZskindle4PCv1_1/LZskindle Read Me.txt b/Kindle_Mobi_Tools/LZskindle4PCv1_1/LZskindle Read Me.txt new file mode 100644 index 0000000..59d97e0 --- /dev/null +++ b/Kindle_Mobi_Tools/LZskindle4PCv1_1/LZskindle Read Me.txt @@ -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. + + diff --git a/Kindle_Mobi_Tools/LZskindle4PCv1_1/LZskindle4PCv1_1.cpp b/Kindle_Mobi_Tools/LZskindle4PCv1_1/LZskindle4PCv1_1.cpp new file mode 100644 index 0000000..1adaa93 --- /dev/null +++ b/Kindle_Mobi_Tools/LZskindle4PCv1_1/LZskindle4PCv1_1.cpp @@ -0,0 +1,150 @@ +#include +#include +#include +//#include + +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; +} diff --git a/Kindle_Mobi_Tools/LZskindle4PCv1_1/LZskindle4PCv1_1.exe b/Kindle_Mobi_Tools/LZskindle4PCv1_1/LZskindle4PCv1_1.exe new file mode 100644 index 0000000..d6e3a8f Binary files /dev/null and b/Kindle_Mobi_Tools/LZskindle4PCv1_1/LZskindle4PCv1_1.exe differ diff --git a/Kindle_Mobi_Tools/LZskindle4PCv1_1/ReadMe.txt b/Kindle_Mobi_Tools/LZskindle4PCv1_1/ReadMe.txt new file mode 100644 index 0000000..40989fd --- /dev/null +++ b/Kindle_Mobi_Tools/LZskindle4PCv1_1/ReadMe.txt @@ -0,0 +1,27 @@ +Ezskindle4PC.exe + +This executable program makes using skindle easier for people using Windows PC’s. + +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 book’s 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. diff --git a/Kindle_Mobi_Tools/MobiDeDRM.pyw b/Kindle_Mobi_Tools/MobiDeDRM.pyw index 7e96bef..c63ed9c 100644 --- a/Kindle_Mobi_Tools/MobiDeDRM.pyw +++ b/Kindle_Mobi_Tools/MobiDeDRM.pyw @@ -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) diff --git a/Kindle_Mobi_Tools/lib/kindlepid.py b/Kindle_Mobi_Tools/lib/kindlepid.py index 29e4b30..e6076b5 100644 --- a/Kindle_Mobi_Tools/lib/kindlepid.py +++ b/Kindle_Mobi_Tools/lib/kindlepid.py @@ -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 diff --git a/Kindle_Mobi_Tools/lib/mobidedrm.py b/Kindle_Mobi_Tools/lib/mobidedrm.py index 0565356..07d5f6f 100644 --- a/Kindle_Mobi_Tools/lib/mobidedrm.py +++ b/Kindle_Mobi_Tools/lib/mobidedrm.py @@ -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 diff --git a/Kindle_Mobi_Tools/unswindle/mobidedrm.py b/Kindle_Mobi_Tools/unswindle/mobidedrm.py index 0565356..07d5f6f 100644 --- a/Kindle_Mobi_Tools/unswindle/mobidedrm.py +++ b/Kindle_Mobi_Tools/unswindle/mobidedrm.py @@ -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 diff --git a/Kindle_Mobi_Tools/unswindle/unswindle.pyw b/Kindle_Mobi_Tools/unswindle/unswindle.pyw index f04b1b6..8eb0771 100644 --- a/Kindle_Mobi_Tools/unswindle/unswindle.pyw +++ b/Kindle_Mobi_Tools/unswindle/unswindle.pyw @@ -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. -# 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 # . 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): diff --git a/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/MobiDeDRM.py b/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/MobiDeDRM.py index 0565356..07d5f6f 100644 --- a/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/MobiDeDRM.py +++ b/Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/MobiDeDRM.py @@ -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 diff --git a/Topaz_Tools/TopazExtract_Kindle4PC.pyw b/Topaz_Tools/TopazExtract_Kindle4PC.pyw index 48b7f81..924d4c9 100644 --- a/Topaz_Tools/TopazExtract_Kindle4PC.pyw +++ b/Topaz_Tools/TopazExtract_Kindle4PC.pyw @@ -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) diff --git a/Topaz_Tools/TopazExtract_KindleV1_iPhone_iPad.pyw b/Topaz_Tools/TopazExtract_KindleV1_iPhone_iPad.pyw new file mode 100644 index 0000000..83cb79c --- /dev/null +++ b/Topaz_Tools/TopazExtract_KindleV1_iPhone_iPad.pyw @@ -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()) diff --git a/Topaz_Tools/lib/convert2xml.py b/Topaz_Tools/lib/convert2xml.py index e3f0fe2..d3ccd48 100644 --- a/Topaz_Tools/lib/convert2xml.py +++ b/Topaz_Tools/lib/convert2xml.py @@ -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('')) \ No newline at end of file