Code: Select all
#! / usr / bin / python
# -*- Coding: utf-8 -*-
# Unswindle.pyw, version 6-rc1
# Copyright © 2009 i ♥ Cabbages
# Released under the terms of the GNU General Public License, 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 (20,091,222) version of K4PC
# 6 - Detect and just copy DRM-free books
' ""
For PC Kindle decrypt 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
#);
Kernel32.GetThreadSelectorEntry 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
DebuggerError class (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:
Getattr addr = (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:
Getattr addr = (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),
buf (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 entry selector")
low, mid, high = Entry.BaseLow, Entry.BaseMid, Entry.BaseHi
Fsbase = low | (upper <<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)
Self.read_process_memory Baseaddr = (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._threads.get Self.hthread = (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:
DebuggerError raise ( "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:
Self._threads.pop Hthread = (tid, None)
if Hthread is not None:
rv = CloseHandle (Hthread)
if not rv:
DebuggerError raise ( "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:
DebuggerError raise ( "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 '
UnswindleError class (Exception):
pass
PC1KeyGrabber class (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)
Debugger.read_process_memory addr = (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
MobiParser class (object):
def __init__ (self, data):
Self.data = data
header = data [0:72]
if header [0x3C: 0x3C +8]! = 'BOOKMOBI:
UnswindleError raise ( "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]
Unswindler class (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):
UnswindleError raise ( "can not 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 did not 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 PID book")
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 ()
TkFileDialog.asksaveasfilename Outpath = (
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 ())