mirror of
				https://github.com/noDRM/DeDRM_tools.git
				synced 2025-10-23 23:07:47 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			871 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			871 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #! /usr/bin/python
 | |
| # -*- coding: utf-8 -*-
 | |
| 
 | |
| # unswindle.pyw, version 6-rc1
 | |
| # Copyright © 2009 i♥cabbages
 | |
| 
 | |
| # Released under the terms of the GNU General Public Licence, version 3 or
 | |
| # later.  <http://www.gnu.org/licenses/>
 | |
| 
 | |
| # To run this program install a 32-bit version of Python 2.6 from
 | |
| # <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
 | |
| #   6 - Detect and just copy DRM-free books
 | |
| 
 | |
| """
 | |
| 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)
 | |
|         print 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 MobiParser(object):
 | |
|     def __init__(self, data):
 | |
|         self.data = data
 | |
|         header = data[0:72]
 | |
|         if header[0x3C:0x3C+8] != 'BOOKMOBI':
 | |
|             raise UnswindleError("invalid file format")
 | |
|         self.nsections = nsections = struct.unpack('>H', data[76:78])[0]
 | |
|         self.sections = sections = []
 | |
|         for i in xrange(nsections):
 | |
|             offset, a1, a2, a3, a4 = \
 | |
|                 struct.unpack('>LBBBB', data[78+i*8:78+i*8+8])
 | |
|             flags, val = a1, ((a2 << 16) | (a3 << 8) | a4)
 | |
|             sections.append((offset, flags, val))
 | |
|         sect = self.load_section(0)
 | |
|         self.crypto_type = struct.unpack('>H', sect[0x0c:0x0c+2])[0]
 | |
| 
 | |
|     def load_section(self, snum):
 | |
|         if (snum + 1) == self.nsections:
 | |
|             endoff = len(self.data)
 | |
|         else:
 | |
|             endoff = self.sections[snum + 1][0]
 | |
|         off = self.sections[snum][0]
 | |
|         return self.data[off:endoff]
 | |
| 
 | |
| 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 _check_topaz(self, path):
 | |
|         with open(path, 'rb') as f:
 | |
|             magic = f.read(4)
 | |
|         if magic == 'TPZ0':
 | |
|             return True
 | |
|         return False
 | |
| 
 | |
|     def _check_drm_free(self, path):
 | |
|         with open(path, 'rb') as f:
 | |
|             crypto = MobiParser(f.read()).crypto_type
 | |
|         return (crypto == 0)
 | |
| 
 | |
|     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._check_topaz(path):
 | |
|             raise UnswindleError("cannot decrypt Topaz format book")
 | |
|         return (path, pid)
 | |
| 
 | |
|     def decrypt_book(self, inpath, outpath, pid):
 | |
|         if self._check_drm_free(inpath):
 | |
|             shutil.copy(inpath, outpath)
 | |
|         else:
 | |
|             self._mobidedrm(inpath, outpath, pid)
 | |
|         return
 | |
| 
 | |
|     def _mobidedrm(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
 | |
|         if pid is None:
 | |
|             raise UnswindleError("failed to determine book PID")
 | |
|         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())
 | 
