mirror of
https://github.com/apprenticeharper/DeDRM_tools
synced 2024-12-27 09:58:59 +01:00
unswindle from i♥cabbages
This commit is contained in:
parent
049f44231c
commit
ef9119a5eb
1 changed files with 828 additions and 0 deletions
828
Kindle_Mobi_Tools/unswindle.pyw
Normal file
828
Kindle_Mobi_Tools/unswindle.pyw
Normal file
|
@ -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())
|
Loading…
Reference in a new issue