]> xmof Git - DeDRM.git/commitdiff
unswindle from i♥cabbages
authori♥cabbages <i♥cabbages@blogspot.co.uk>
Thu, 24 Dec 2009 09:57:39 +0000 (09:57 +0000)
committerApprentice Alf <apprenticealf@gmail.com>
Sat, 28 Feb 2015 09:59:10 +0000 (09:59 +0000)
Kindle_Mobi_Tools/unswindle.pyw [new file with mode: 0644]

diff --git a/Kindle_Mobi_Tools/unswindle.pyw b/Kindle_Mobi_Tools/unswindle.pyw
new file mode 100644 (file)
index 0000000..6cb6aab
--- /dev/null
@@ -0,0 +1,828 @@
+#! /usr/bin/python
+
+# unswindle.pyw, version 5
+
+# To run this program install a 32-bit version of Python 2.6 from
+# <http://www.python.org/download/>.  Save this script file as unswindle.pyw.
+# Find and save in the same directory a copy of mobidedrm.py.  Double-click on
+# unswindle.pyw.  It will run Kindle For PC.  Open the book you want to
+# decrypt.  Close Kindle For PC.  A dialog will open allowing you to select the
+# output file.  And you're done!
+
+# Revision history:
+#   1 - Initial release
+#   2 - Fixes to work properly on Windows versions >XP
+#   3 - Fix minor bug in path extraction
+#   4 - Fix error opening threads; detect Topaz books;
+#       detect unsupported versions of K4PC
+#   5 - Work with new (20091222) version of K4PC
+
+"""
+Decrypt Kindle For PC encrypted Mobipocket books.
+"""
+
+__license__ = 'GPL v3'
+
+import sys
+import os
+import re
+import tempfile
+import shutil
+import subprocess
+import struct
+import hashlib
+import ctypes
+from ctypes import *
+from ctypes.wintypes import *
+import binascii
+import _winreg as winreg
+import Tkinter
+import Tkconstants
+import tkMessageBox
+import tkFileDialog
+import traceback
+
+#
+# _extrawintypes.py
+
+UBYTE = c_ubyte
+ULONG_PTR = POINTER(ULONG)
+PULONG = ULONG_PTR
+PVOID = LPVOID
+LPCTSTR = LPTSTR = c_wchar_p
+LPBYTE = c_char_p
+SIZE_T = c_uint
+SIZE_T_p = POINTER(SIZE_T)
+
+#
+# _ntdll.py
+
+NTSTATUS = DWORD
+
+ntdll = windll.ntdll
+
+class PROCESS_BASIC_INFORMATION(Structure):
+    _fields_ = [('Reserved1', PVOID),
+                ('PebBaseAddress', PVOID),
+                ('Reserved2', PVOID * 2),
+                ('UniqueProcessId', ULONG_PTR),
+                ('Reserved3', PVOID)]
+
+# NTSTATUS WINAPI NtQueryInformationProcess(
+#   __in       HANDLE ProcessHandle,
+#   __in       PROCESSINFOCLASS ProcessInformationClass,
+#   __out      PVOID ProcessInformation,
+#   __in       ULONG ProcessInformationLength,
+#   __out_opt  PULONG ReturnLength
+# );
+NtQueryInformationProcess = ntdll.NtQueryInformationProcess
+NtQueryInformationProcess.argtypes = [HANDLE, DWORD, PVOID, ULONG, PULONG]
+NtQueryInformationProcess.restype = NTSTATUS
+
+#
+# _kernel32.py
+
+INFINITE = 0xffffffff
+
+CREATE_UNICODE_ENVIRONMENT = 0x00000400
+DEBUG_ONLY_THIS_PROCESS = 0x00000002
+DEBUG_PROCESS = 0x00000001
+
+THREAD_GET_CONTEXT = 0x0008
+THREAD_QUERY_INFORMATION = 0x0040
+THREAD_SET_CONTEXT = 0x0010
+THREAD_SET_INFORMATION = 0x0020
+
+EXCEPTION_BREAKPOINT = 0x80000003
+EXCEPTION_SINGLE_STEP = 0x80000004
+EXCEPTION_ACCESS_VIOLATION = 0xC0000005
+
+DBG_CONTINUE = 0x00010002L
+DBG_EXCEPTION_NOT_HANDLED = 0x80010001L
+
+EXCEPTION_DEBUG_EVENT = 1
+CREATE_THREAD_DEBUG_EVENT = 2
+CREATE_PROCESS_DEBUG_EVENT = 3
+EXIT_THREAD_DEBUG_EVENT = 4
+EXIT_PROCESS_DEBUG_EVENT = 5
+LOAD_DLL_DEBUG_EVENT = 6
+UNLOAD_DLL_DEBUG_EVENT = 7
+OUTPUT_DEBUG_STRING_EVENT = 8
+RIP_EVENT = 9
+
+class DataBlob(Structure):
+    _fields_ = [('cbData', c_uint),
+                ('pbData', c_void_p)]
+DataBlob_p = POINTER(DataBlob)
+
+class SECURITY_ATTRIBUTES(Structure):
+    _fields_ = [('nLength', DWORD),
+                ('lpSecurityDescriptor', LPVOID),
+                ('bInheritHandle', BOOL)]
+LPSECURITY_ATTRIBUTES = POINTER(SECURITY_ATTRIBUTES)
+
+class STARTUPINFO(Structure):
+    _fields_ = [('cb', DWORD),
+                ('lpReserved', LPTSTR),
+                ('lpDesktop', LPTSTR),
+                ('lpTitle', LPTSTR),
+                ('dwX', DWORD),
+                ('dwY', DWORD),
+                ('dwXSize', DWORD),
+                ('dwYSize', DWORD),
+                ('dwXCountChars', DWORD),
+                ('dwYCountChars', DWORD),
+                ('dwFillAttribute', DWORD),
+                ('dwFlags', DWORD),
+                ('wShowWindow', WORD),
+                ('cbReserved2', WORD),
+                ('lpReserved2', LPBYTE),
+                ('hStdInput', HANDLE),
+                ('hStdOutput', HANDLE),
+                ('hStdError', HANDLE)]
+LPSTARTUPINFO = POINTER(STARTUPINFO)
+
+class PROCESS_INFORMATION(Structure):
+    _fields_ = [('hProcess', HANDLE),
+                ('hThread', HANDLE),
+                ('dwProcessId', DWORD),
+                ('dwThreadId', DWORD)]
+LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION)
+
+EXCEPTION_MAXIMUM_PARAMETERS = 15
+class EXCEPTION_RECORD(Structure):
+    pass
+EXCEPTION_RECORD._fields_ = [
+    ('ExceptionCode', DWORD),
+    ('ExceptionFlags', DWORD),
+    ('ExceptionRecord', POINTER(EXCEPTION_RECORD)),
+    ('ExceptionAddress', LPVOID),
+    ('NumberParameters', DWORD),
+    ('ExceptionInformation', ULONG_PTR * EXCEPTION_MAXIMUM_PARAMETERS)]
+
+class EXCEPTION_DEBUG_INFO(Structure):
+    _fields_ = [('ExceptionRecord', EXCEPTION_RECORD),
+                ('dwFirstChance', DWORD)]
+
+class CREATE_THREAD_DEBUG_INFO(Structure):
+    _fields_ = [('hThread', HANDLE),
+                ('lpThreadLocalBase', LPVOID),
+                ('lpStartAddress', LPVOID)]
+
+class CREATE_PROCESS_DEBUG_INFO(Structure):
+    _fields_ = [('hFile', HANDLE),
+                ('hProcess', HANDLE),
+                ('hThread', HANDLE),
+                ('dwDebugInfoFileOffset', DWORD),
+                ('nDebugInfoSize', DWORD),
+                ('lpThreadLocalBase', LPVOID),
+                ('lpStartAddress', LPVOID),
+                ('lpImageName', LPVOID),
+                ('fUnicode', WORD)]
+
+class EXIT_THREAD_DEBUG_INFO(Structure):
+    _fields_ = [('dwExitCode', DWORD)]
+
+class EXIT_PROCESS_DEBUG_INFO(Structure):
+    _fields_ = [('dwExitCode', DWORD)]
+
+class LOAD_DLL_DEBUG_INFO(Structure):
+    _fields_ = [('hFile', HANDLE),
+                ('lpBaseOfDll', LPVOID),
+                ('dwDebugInfoFileOffset', DWORD),
+                ('nDebugInfoSize', DWORD),
+                ('lpImageName', LPVOID),
+                ('fUnicode', WORD)]
+
+class UNLOAD_DLL_DEBUG_INFO(Structure):
+    _fields_ = [('lpBaseOfDll', LPVOID)]
+
+class OUTPUT_DEBUG_STRING_INFO(Structure):
+    _fields_ = [('lpDebugStringData', LPSTR),
+                ('fUnicode', WORD),
+                ('nDebugStringLength', WORD)]
+
+class RIP_INFO(Structure):
+    _fields_ = [('dwError', DWORD),
+                ('dwType', DWORD)]
+
+class _U(Union):
+    _fields_ = [('Exception', EXCEPTION_DEBUG_INFO),
+                ('CreateThread', CREATE_THREAD_DEBUG_INFO),
+                ('CreateProcessInfo', CREATE_PROCESS_DEBUG_INFO),
+                ('ExitThread', EXIT_THREAD_DEBUG_INFO),
+                ('ExitProcess', EXIT_PROCESS_DEBUG_INFO),
+                ('LoadDll', LOAD_DLL_DEBUG_INFO),
+                ('UnloadDll', UNLOAD_DLL_DEBUG_INFO),
+                ('DebugString', OUTPUT_DEBUG_STRING_INFO),
+                ('RipInfo', RIP_INFO)]
+
+class DEBUG_EVENT(Structure):
+    _anonymous_ = ('u',)
+    _fields_ = [('dwDebugEventCode', DWORD),
+                ('dwProcessId', DWORD),
+                ('dwThreadId', DWORD),
+                ('u', _U)]
+LPDEBUG_EVENT = POINTER(DEBUG_EVENT)
+
+CONTEXT_X86 = 0x00010000
+CONTEXT_i386 = CONTEXT_X86
+CONTEXT_i486 = CONTEXT_X86
+
+CONTEXT_CONTROL = (CONTEXT_i386 | 0x0001) # SS:SP, CS:IP, FLAGS, BP
+CONTEXT_INTEGER = (CONTEXT_i386 | 0x0002) # AX, BX, CX, DX, SI, DI
+CONTEXT_SEGMENTS = (CONTEXT_i386 | 0x0004) # DS, ES, FS, GS
+CONTEXT_FLOATING_POINT = (CONTEXT_i386 | 0x0008L) # 387 state
+CONTEXT_DEBUG_REGISTERS =  (CONTEXT_i386 | 0x0010L) # DB 0-3,6,7
+CONTEXT_EXTENDED_REGISTERS =  (CONTEXT_i386 | 0x0020L)
+CONTEXT_FULL = (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS)
+CONTEXT_ALL = (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS |
+               CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS |
+               CONTEXT_EXTENDED_REGISTERS)
+
+SIZE_OF_80387_REGISTERS = 80
+class FLOATING_SAVE_AREA(Structure):
+    _fields_ = [('ControlWord', DWORD),
+                ('StatusWord', DWORD),
+                ('TagWord', DWORD),
+                ('ErrorOffset', DWORD),
+                ('ErrorSelector', DWORD),
+                ('DataOffset', DWORD),
+                ('DataSelector', DWORD),
+                ('RegisterArea', BYTE * SIZE_OF_80387_REGISTERS),
+                ('Cr0NpxState', DWORD)]
+
+MAXIMUM_SUPPORTED_EXTENSION = 512
+class CONTEXT(Structure):
+    _fields_ = [('ContextFlags', DWORD),
+                ('Dr0', DWORD),
+                ('Dr1', DWORD),
+                ('Dr2', DWORD),
+                ('Dr3', DWORD),
+                ('Dr6', DWORD),
+                ('Dr7', DWORD),
+                ('FloatSave', FLOATING_SAVE_AREA),
+                ('SegGs', DWORD),
+                ('SegFs', DWORD),
+                ('SegEs', DWORD),
+                ('SegDs', DWORD),
+                ('Edi', DWORD),
+                ('Esi', DWORD),
+                ('Ebx', DWORD),
+                ('Edx', DWORD),
+                ('Ecx', DWORD),
+                ('Eax', DWORD),
+                ('Ebp', DWORD),
+                ('Eip', DWORD),
+                ('SegCs', DWORD),
+                ('EFlags', DWORD),
+                ('Esp', DWORD),
+                ('SegSs', DWORD),
+                ('ExtendedRegisters', BYTE * MAXIMUM_SUPPORTED_EXTENSION)]
+LPCONTEXT = POINTER(CONTEXT)
+
+class LDT_ENTRY(Structure):
+    _fields_ = [('LimitLow', WORD),
+                ('BaseLow',  WORD),
+                ('BaseMid', UBYTE),
+                ('Flags1', UBYTE),
+                ('Flags2', UBYTE),
+                ('BaseHi', UBYTE)]
+LPLDT_ENTRY = POINTER(LDT_ENTRY)
+
+kernel32 = windll.kernel32
+
+# BOOL WINAPI CloseHandle(
+#   __in  HANDLE hObject
+# );
+CloseHandle = kernel32.CloseHandle
+CloseHandle.argtypes = [HANDLE]
+CloseHandle.restype = BOOL
+
+# BOOL WINAPI CreateProcess(
+#   __in_opt     LPCTSTR lpApplicationName,
+#   __inout_opt  LPTSTR lpCommandLine,
+#   __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
+#   __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
+#   __in         BOOL bInheritHandles,
+#   __in         DWORD dwCreationFlags,
+#   __in_opt     LPVOID lpEnvironment,
+#   __in_opt     LPCTSTR lpCurrentDirectory,
+#   __in         LPSTARTUPINFO lpStartupInfo,
+#   __out        LPPROCESS_INFORMATION lpProcessInformation
+# );
+CreateProcess = kernel32.CreateProcessW
+CreateProcess.argtypes = [LPCTSTR, LPTSTR, LPSECURITY_ATTRIBUTES,
+                          LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCTSTR,
+                          LPSTARTUPINFO, LPPROCESS_INFORMATION]
+CreateProcess.restype = BOOL
+
+# HANDLE WINAPI OpenThread(
+#   __in  DWORD dwDesiredAccess,
+#   __in  BOOL bInheritHandle,
+#   __in  DWORD dwThreadId
+# );
+OpenThread = kernel32.OpenThread
+OpenThread.argtypes = [DWORD, BOOL, DWORD]
+OpenThread.restype = HANDLE
+
+# BOOL WINAPI ContinueDebugEvent(
+#   __in  DWORD dwProcessId,
+#   __in  DWORD dwThreadId,
+#   __in  DWORD dwContinueStatus
+# );
+ContinueDebugEvent = kernel32.ContinueDebugEvent
+ContinueDebugEvent.argtypes = [DWORD, DWORD, DWORD]
+ContinueDebugEvent.restype = BOOL
+
+# BOOL WINAPI DebugActiveProcess(
+#   __in  DWORD dwProcessId
+# );
+DebugActiveProcess = kernel32.DebugActiveProcess
+DebugActiveProcess.argtypes = [DWORD]
+DebugActiveProcess.restype = BOOL
+
+# BOOL WINAPI GetThreadContext(
+#   __in     HANDLE hThread,
+#   __inout  LPCONTEXT lpContext
+# );
+GetThreadContext = kernel32.GetThreadContext
+GetThreadContext.argtypes = [HANDLE, LPCONTEXT]
+GetThreadContext.restype = BOOL
+
+# BOOL WINAPI GetThreadSelectorEntry(
+#   __in   HANDLE hThread,
+#   __in   DWORD dwSelector,
+#   __out  LPLDT_ENTRY lpSelectorEntry
+# );
+GetThreadSelectorEntry = kernel32.GetThreadSelectorEntry
+GetThreadSelectorEntry.argtypes = [HANDLE, DWORD, LPLDT_ENTRY]
+GetThreadSelectorEntry.restype = BOOL
+
+# BOOL WINAPI ReadProcessMemory(
+#   __in   HANDLE hProcess,
+#   __in   LPCVOID lpBaseAddress,
+#   __out  LPVOID lpBuffer,
+#   __in   SIZE_T nSize,
+#   __out  SIZE_T *lpNumberOfBytesRead
+# );
+ReadProcessMemory = kernel32.ReadProcessMemory
+ReadProcessMemory.argtypes = [HANDLE, LPCVOID, LPVOID, SIZE_T, SIZE_T_p]
+ReadProcessMemory.restype = BOOL
+
+# BOOL WINAPI SetThreadContext(
+#   __in  HANDLE hThread,
+#   __in  const CONTEXT *lpContext
+# );
+SetThreadContext = kernel32.SetThreadContext
+SetThreadContext.argtypes = [HANDLE, LPCONTEXT]
+SetThreadContext.restype = BOOL
+
+# BOOL WINAPI WaitForDebugEvent(
+#   __out  LPDEBUG_EVENT lpDebugEvent,
+#   __in   DWORD dwMilliseconds
+# );
+WaitForDebugEvent = kernel32.WaitForDebugEvent
+WaitForDebugEvent.argtypes = [LPDEBUG_EVENT, DWORD]
+WaitForDebugEvent.restype = BOOL
+
+# BOOL WINAPI WriteProcessMemory(
+#   __in   HANDLE hProcess,
+#   __in   LPVOID lpBaseAddress,
+#   __in   LPCVOID lpBuffer,
+#   __in   SIZE_T nSize,
+#   __out  SIZE_T *lpNumberOfBytesWritten
+# );
+WriteProcessMemory = kernel32.WriteProcessMemory
+WriteProcessMemory.argtypes = [HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T_p]
+WriteProcessMemory.restype = BOOL
+
+# BOOL WINAPI FlushInstructionCache(
+#   __in  HANDLE hProcess,
+#   __in  LPCVOID lpBaseAddress,
+#   __in  SIZE_T dwSize
+# );
+FlushInstructionCache = kernel32.FlushInstructionCache
+FlushInstructionCache.argtypes = [HANDLE, LPCVOID, SIZE_T]
+FlushInstructionCache.restype = BOOL
+
+
+#
+# debugger.py
+
+FLAG_TRACE_BIT = 0x100
+
+class DebuggerError(Exception):
+    pass
+
+class Debugger(object):
+    def __init__(self, process_info):
+        self.process_info = process_info
+        self.pid = process_info.dwProcessId
+        self.tid = process_info.dwThreadId
+        self.hprocess = process_info.hProcess
+        self.hthread = process_info.hThread
+        self._threads = {self.tid: self.hthread}
+        self._processes = {self.pid: self.hprocess}
+        self._bps = {}
+        self._inactive = {}
+
+    def read_process_memory(self, addr, size=None, type=str):
+        if issubclass(type, basestring):
+            buf = ctypes.create_string_buffer(size)
+            ref = buf
+        else:
+            size = ctypes.sizeof(type)
+            buf = type()
+            ref = byref(buf)
+        copied = SIZE_T(0)
+        rv = ReadProcessMemory(self.hprocess, addr, ref, size, byref(copied))
+        if not rv:
+            addr = getattr(addr, 'value', addr)
+            raise DebuggerError("could not read memory @ 0x%08x" % (addr,))
+        if copied.value != size:
+            raise DebuggerError("insufficient memory read")
+        if issubclass(type, basestring):
+            return buf.raw
+        return buf
+
+    def set_bp(self, addr, callback, bytev=None):
+        hprocess = self.hprocess
+        if bytev is None:
+            byte = self.read_process_memory(addr, type=ctypes.c_byte)
+            bytev = byte.value
+        else:
+            byte = ctypes.c_byte(0)
+        self._bps[addr] = (bytev, callback)
+        byte.value = 0xcc
+        copied = SIZE_T(0)
+        rv = WriteProcessMemory(hprocess, addr, byref(byte), 1, byref(copied))
+        if not rv:
+            addr = getattr(addr, 'value', addr)
+            raise DebuggerError("could not write memory @ 0x%08x" % (addr,))
+        if copied.value != 1:
+            raise DebuggerError("insufficient memory written")
+        rv = FlushInstructionCache(hprocess, None, 0)
+        if not rv:
+            raise DebuggerError("could not flush instruction cache")
+        return
+
+    def _restore_bps(self):
+        for addr, (bytev, callback) in self._inactive.items():
+            self.set_bp(addr, callback, bytev=bytev)
+        self._inactive.clear()
+
+    def _handle_bp(self, addr):
+        hprocess = self.hprocess
+        hthread = self.hthread
+        bytev, callback = self._inactive[addr] = self._bps.pop(addr)
+        byte = ctypes.c_byte(bytev)
+        copied = SIZE_T(0)
+        rv = WriteProcessMemory(hprocess, addr, byref(byte), 1, byref(copied))
+        if not rv:
+            raise DebuggerError("could not write memory")
+        if copied.value != 1:
+            raise DebuggerError("insufficient memory written")
+        rv = FlushInstructionCache(hprocess, None, 0)
+        if not rv:
+            raise DebuggerError("could not flush instruction cache")
+        context = CONTEXT(ContextFlags=CONTEXT_FULL)
+        rv = GetThreadContext(hthread, byref(context))
+        if not rv:
+            raise DebuggerError("could not get thread context")
+        context.Eip = addr
+        callback(self, context)
+        context.EFlags |= FLAG_TRACE_BIT
+        rv = SetThreadContext(hthread, byref(context))
+        if not rv:
+            raise DebuggerError("could not set thread context")
+        return
+
+    def _get_peb_address(self):
+        hthread = self.hthread
+        hprocess = self.hprocess
+        try:
+            pbi = PROCESS_BASIC_INFORMATION()
+            rv = NtQueryInformationProcess(hprocess, 0, byref(pbi),
+                                           sizeof(pbi), None)
+            if rv != 0:
+                raise DebuggerError("could not query process information")
+            return pbi.PebBaseAddress
+        except DebuggerError:
+            pass
+        try:
+            context = CONTEXT(ContextFlags=CONTEXT_FULL)
+            rv = GetThreadContext(hthread, byref(context))
+            if not rv:
+                raise DebuggerError("could not get thread context")
+            entry = LDT_ENTRY()
+            rv = GetThreadSelectorEntry(hthread, context.SegFs, byref(entry))
+            if not rv:
+                raise DebuggerError("could not get selector entry")
+            low, mid, high = entry.BaseLow, entry.BaseMid, entry.BaseHi
+            fsbase = low | (mid << 16) | (high << 24)
+            pebaddr = self.read_process_memory(fsbase + 0x30, type=c_voidp)
+            return pebaddr.value
+        except DebuggerError:
+            pass
+        return 0x7ffdf000
+
+    def get_base_address(self):
+        addr = self._get_peb_address() + (2 * 4)
+        baseaddr = self.read_process_memory(addr, type=c_voidp)
+        return baseaddr.value
+
+    def main_loop(self):
+        event = DEBUG_EVENT()
+        finished = False
+        while not finished:
+            rv = WaitForDebugEvent(byref(event), INFINITE)
+            if not rv:
+                raise DebuggerError("could not get debug event")
+            self.pid = pid = event.dwProcessId
+            self.tid = tid = event.dwThreadId
+            self.hprocess = self._processes.get(pid, None)
+            self.hthread = self._threads.get(tid, None)
+            status = DBG_CONTINUE
+            evid = event.dwDebugEventCode
+            if evid == EXCEPTION_DEBUG_EVENT:
+                first = event.Exception.dwFirstChance
+                record = event.Exception.ExceptionRecord
+                exid = record.ExceptionCode
+                flags = record.ExceptionFlags
+                addr = record.ExceptionAddress
+                if exid == EXCEPTION_BREAKPOINT:
+                    if addr in self._bps:
+                        self._handle_bp(addr)
+                elif exid == EXCEPTION_SINGLE_STEP:
+                    self._restore_bps()
+                else:
+                    status = DBG_EXCEPTION_NOT_HANDLED
+            elif evid == LOAD_DLL_DEBUG_EVENT:
+                hfile = event.LoadDll.hFile
+                if hfile is not None:
+                    rv = CloseHandle(hfile)
+                    if not rv:
+                        raise DebuggerError("error closing file handle")
+            elif evid == CREATE_THREAD_DEBUG_EVENT:
+                info = event.CreateThread
+                self.hthread = info.hThread
+                self._threads[tid] = self.hthread
+            elif evid == EXIT_THREAD_DEBUG_EVENT:
+                hthread = self._threads.pop(tid, None)
+                if hthread is not None:
+                    rv = CloseHandle(hthread)
+                    if not rv:
+                        raise DebuggerError("error closing thread handle")
+            elif evid == CREATE_PROCESS_DEBUG_EVENT:
+                info = event.CreateProcessInfo
+                self.hprocess = info.hProcess
+                self._processes[pid] = self.hprocess
+            elif evid == EXIT_PROCESS_DEBUG_EVENT:
+                hprocess = self._processes.pop(pid, None)
+                if hprocess is not None:
+                    rv = CloseHandle(hprocess)
+                    if not rv:
+                        raise DebuggerError("error closing process handle")
+                if pid == self.process_info.dwProcessId:
+                    finished = True
+            rv = ContinueDebugEvent(pid, tid, status)
+            if not rv:
+                raise DebuggerError("could not continue debug")
+        return True
+
+
+#
+# unswindle.py
+
+KINDLE_REG_KEY = \
+    r'Software\Classes\Amazon.KindleForPC.content\shell\open\command'
+
+class UnswindleError(Exception):
+    pass
+
+class PC1KeyGrabber(object):
+    HOOKS = {
+        'b9f7e422094b8c8966a0e881e6358116e03e5b7b': {
+            0x004a719d: '_no_debugger_here',
+            0x005a795b: '_no_debugger_here',
+            0x0054f7e0: '_get_pc1_pid',
+            0x004f9c79: '_get_book_path',
+        },
+        'd5124ee20dab10e44b41a039363f6143725a5417': {
+            0x0041150d: '_i_like_wine',
+            0x004a681d: '_no_debugger_here',
+            0x005a438b: '_no_debugger_here',
+            0x0054c9e0: '_get_pc1_pid',
+            0x004f8ac9: '_get_book_path',
+        },
+    }
+
+    @classmethod
+    def supported_version(cls, hexdigest):
+        return (hexdigest in cls.HOOKS)
+
+    def _taddr(self, addr):
+        return (addr - 0x00400000) + self.baseaddr
+
+    def __init__(self, debugger, hexdigest):
+        self.book_path = None
+        self.book_pid = None
+        self.baseaddr = debugger.get_base_address()
+        hooks = self.HOOKS[hexdigest]
+        for addr, mname in hooks.items():
+            debugger.set_bp(self._taddr(addr), getattr(self, mname))
+
+    def _i_like_wine(self, debugger, context):
+        context.Eax = 1
+        return
+        
+    def _no_debugger_here(self, debugger, context):
+        context.Eip += 2
+        context.Eax = 0
+        return
+
+    def _get_book_path(self, debugger, context):
+        addr = debugger.read_process_memory(context.Esp, type=ctypes.c_voidp)
+        try:
+            path = debugger.read_process_memory(addr, 4096)
+        except DebuggerError:
+            pgrest = 0x1000 - (addr.value & 0xfff)
+            path = debugger.read_process_memory(addr, pgrest)
+        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'):
+            return
+        self.book_path = path
+
+    def _get_pc1_pid(self, debugger, context):
+        addr = context.Esp + ctypes.sizeof(ctypes.c_voidp)
+        addr = debugger.read_process_memory(addr, type=ctypes.c_char_p)
+        pid = debugger.read_process_memory(addr, 8)
+        pid = self._checksum_pid(pid)
+        self.book_pid = pid
+
+    def _checksum_pid(self, s):
+        letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
+        crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
+        crc = crc ^ (crc >> 16)
+        res = s
+        l = len(letters)
+        for i in (0,1):
+            b = crc & 0xff
+            pos = (b // l) ^ (b % l)
+            res += letters[pos%l]
+            crc >>= 8
+        return res
+
+class Unswindler(object):
+    def __init__(self):
+        self._exepath = self._get_exe_path()
+        self._hexdigest = self._get_hexdigest()
+        self._exedir = os.path.dirname(self._exepath)
+        self._mobidedrmpath = self._get_mobidedrm_path()
+
+    def _get_mobidedrm_path(self):
+        basedir = sys.modules[self.__module__].__file__
+        basedir = os.path.dirname(basedir)
+        for basename in ('mobidedrm', 'mobidedrm.py', 'mobidedrm.pyw'):
+            path = os.path.join(basedir, basename)
+            if os.path.isfile(path):
+                return path
+        raise UnswindleError("could not locate MobiDeDRM script")
+
+    def _get_exe_path(self):
+        path = None
+        for root in (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE):
+            try:
+                regkey = winreg.OpenKey(root, KINDLE_REG_KEY)
+                path = winreg.QueryValue(regkey, None)
+                break
+            except WindowsError:
+                pass
+        else:
+            raise UnswindleError("Kindle For PC installation not found")
+        if '"' in path:
+            path = re.search(r'"(.*?)"', path).group(1)
+        return path
+
+    def _get_hexdigest(self):
+        path = self._exepath
+        sha1 = hashlib.sha1()
+        with open(path, 'rb') as f:
+            data = f.read(4096)
+            while data:
+                sha1.update(data)
+                data = f.read(4096)
+        hexdigest = sha1.hexdigest()
+        if not PC1KeyGrabber.supported_version(hexdigest):
+            raise UnswindleError("Unsupported version of Kindle For PC")
+        return hexdigest
+    
+    def _is_topaz(self, path):
+        with open(path, 'rb') as f:
+            magic = f.read(4)
+        if magic == 'TPZ0':
+            return True
+        return False
+
+    def get_book(self):
+        creation_flags = (CREATE_UNICODE_ENVIRONMENT |
+                          DEBUG_PROCESS |
+                          DEBUG_ONLY_THIS_PROCESS)
+        startup_info = STARTUPINFO()
+        process_info = PROCESS_INFORMATION()
+        path = pid = None
+        try:
+            rv = CreateProcess(self._exepath, None, None, None, False,
+                               creation_flags, None, self._exedir,
+                               byref(startup_info), byref(process_info))
+            if not rv:
+                raise UnswindleError("failed to launch Kindle For PC")
+            debugger = Debugger(process_info)
+            grabber = PC1KeyGrabber(debugger, self._hexdigest)
+            debugger.main_loop()
+            path = grabber.book_path
+            pid = grabber.book_pid
+        finally:
+            if process_info.hThread is not None:
+                CloseHandle(process_info.hThread)
+            if process_info.hProcess is not None:
+                CloseHandle(process_info.hProcess)
+        if path is None:
+            raise UnswindleError("failed to determine book path")
+        if self._is_topaz(path):
+            raise UnswindleError("cannot decrypt Topaz format book")
+        if pid is None:
+            raise UnswindleError("failed to determine book PID")
+        return (path, pid)
+
+    def decrypt_book(self, inpath, outpath, pid):
+        # darkreverser didn't protect mobidedrm's script execution to allow
+        # importing, so we have to just run it in a subprocess
+        with tempfile.NamedTemporaryFile(delete=False) as tmpf:
+            tmppath = tmpf.name
+        args = [sys.executable, self._mobidedrmpath, inpath, tmppath, pid]
+        mobidedrm = subprocess.Popen(args, stderr=subprocess.STDOUT,
+                                     stdout=subprocess.PIPE,
+                                     universal_newlines=True)
+        output = mobidedrm.communicate()[0]
+        if not output.endswith("done\n"):
+            try:
+                os.remove(tmppath)
+            except OSError:
+                pass
+            raise UnswindleError("problem running MobiDeDRM:\n" + output)
+        shutil.move(tmppath, outpath)
+        return
+
+class ExceptionDialog(Tkinter.Frame):
+    def __init__(self, root, text):
+        Tkinter.Frame.__init__(self, root, border=5)
+        label = Tkinter.Label(self, text="Unexpected error:",
+                              anchor=Tkconstants.W, justify=Tkconstants.LEFT)
+        label.pack(fill=Tkconstants.X, expand=0)
+        self.text = Tkinter.Text(self)
+        self.text.pack(fill=Tkconstants.BOTH, expand=1)
+        self.text.insert(Tkconstants.END, text)
+
+def gui_main(argv=sys.argv):
+    root = Tkinter.Tk()
+    root.withdraw()
+    progname = os.path.basename(argv[0])
+    try:
+        unswindler = Unswindler()
+        inpath, pid = unswindler.get_book()
+        outpath = tkFileDialog.asksaveasfilename(
+            parent=None, title='Select unencrypted Mobipocket file to produce',
+            defaultextension='.mobi', filetypes=[('MOBI files', '.mobi'),
+                                                 ('All files', '.*')])
+        if not outpath:
+            return 0
+        unswindler.decrypt_book(inpath, outpath, pid)
+    except UnswindleError, e:
+        tkMessageBox.showerror("Unswindle For PC", "Error: " + str(e))
+        return 1
+    except Exception:
+        root.wm_state('normal')
+        root.title('Unswindle For PC')
+        text = traceback.format_exc()
+        ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
+        root.mainloop()
+        return 1
+
+def cli_main(argv=sys.argv):
+    progname = os.path.basename(argv[0])
+    args = argv[1:]
+    if len(args) != 1:
+        sys.stderr.write("usage: %s OUTFILE\n" % (progname,))
+        return 1
+    outpath = args[0]
+    unswindler = Unswindler()
+    inpath, pid = unswindler.get_book()
+    unswindler.decrypt_book(inpath, outpath, pid)
+    return 0
+
+if __name__ == '__main__':
+    sys.exit(gui_main())