From f9a650fcf3bc7e76d9fdf9b9f8ea7c05250d5228 Mon Sep 17 00:00:00 2001 From: Clement Rouault Date: Thu, 27 Apr 2017 12:06:04 +0200 Subject: [PATCH] WinThread is not a THREADENTRY32 anymore + add Winprocess.peb.exe --- windows/test/mytest.py | 42 +++++++++++++ windows/test/test_hooks.py | 9 ++- windows/winobject/process.py | 116 ++++++++++++++++++++++++++--------- windows/winobject/system.py | 57 +++++++++++++++-- 4 files changed, 185 insertions(+), 39 deletions(-) diff --git a/windows/test/mytest.py b/windows/test/mytest.py index fe16d3e..4cff65a 100644 --- a/windows/test/mytest.py +++ b/windows/test/mytest.py @@ -69,6 +69,14 @@ class WindowsTestCase(unittest.TestCase): def test_get_current_process_modules(self): self.assertIn("python", windows.current_process.peb.modules[0].name) + @check_for_gc_garbage + def test_get_current_process_exe(self): + exe = windows.current_process.peb.exe + exe_by_module = windows.current_process.peb.modules[0].pe + self.assertEqual(exe.baseaddr, exe_by_module.baseaddr) + self.assertEqual(exe.bitness, exe_by_module.bitness) + + @check_for_gc_garbage def test_local_process_pe_imports(self): python_module = windows.current_process.peb.modules[0] @@ -312,6 +320,23 @@ class WindowsTestCase(unittest.TestCase): dword = struct.unpack(" 100: + import pdb;pdb.set_trace() + return result self.assertEqual(remote_ask("windows.current_thread.exit(len(calling_thread))"), 1) self.assertEqual(remote_ask("windows.current_thread.exit(calling_thread == set([hooking_thread]))"), 1) @@ -156,7 +159,7 @@ class HookTestCase(unittest.TestCase): except WindowsError as e: pass """ - calc.execute_python_unsafe(textwrap.dedent(code)) + calc.execute_python(textwrap.dedent(code)) # Tricky part: we use an injected thread exit_value to ask stuff about the remote python def remote_ask(request): t = calc.execute_python_unsafe(request) diff --git a/windows/winobject/process.py b/windows/winobject/process.py index ad4ac91..4acd54d 100644 --- a/windows/winobject/process.py +++ b/windows/winobject/process.py @@ -64,14 +64,41 @@ class AutoHandle(object): self._close_function(self._handle) -class WinThread(THREADENTRY32, AutoHandle): +class WinThread(AutoHandle): """Represent a thread """ + + def __init__(self, tid=None, handle=None, owner_pid=None, owner=None): + if tid is None and handle is None: + raise ValueError("Need at least or to create a {0}".format(type(self).__name__)) + + if tid is not None: self._tid = tid + if handle is not None: self._handle = handle + if owner is not None: self._owner = owner + if owner_pid is not None: self._owner_pid = owner_pid + if owner_pid is None and owner: + self._owner_pid = owner.pid + + @classmethod + def _from_THREADENTRY32(cls, entry, owner=None): + tid = entry.th32ThreadID + owner_pid = entry.th32OwnerProcessID + return cls(tid=tid, owner_pid=owner_pid, owner=owner) + + @classmethod + def _from_handle(cls, handle): + # Create a DeadThread if thread is already dead ? + return WinThread(handle=handle) + @utils.fixedpropety def tid(self): """Thread ID :type: :class:`int`""" - return self.th32ThreadID + return self._get_thread_id(self.handle) + + @utils.fixedpropety + def owner_pid(self): + return self._get_thread_owner_pid(self.handle) @utils.fixedpropety def owner(self): @@ -79,13 +106,7 @@ class WinThread(THREADENTRY32, AutoHandle): :type: :class:`WinProcess` """ - if hasattr(self, "_owner"): - return self._owner - try: - self._owner = [process for process in windows.system.processes if process.pid == self.th32OwnerProcessID][0] - except IndexError: - return None - return self._owner + return WinProcess(pid=self.owner_pid) @property def context(self): @@ -227,23 +248,12 @@ class WinThread(THREADENTRY32, AutoHandle): if owner is None: owner_name = "".format(hex(self.th32OwnerProcessID)) else: - owner_name = owner.name + try: + owner_name = owner.name + except EnvironmentError: + owner_name = "!cannot-retrieve-owner-name" return '<{0} {1} owner "{2}" at {3}>'.format(self.__class__.__name__, self.tid, owner_name, hex(id(self))) - @staticmethod - def _from_handle(handle): - tid = WinThread._get_thread_id(handle) - try: - # Really useful ? - thread = [t for t in windows.winobject.system.System().threads if t.tid == tid][0] - # set AutoHandle _handle - thread._handle = handle - dbgprint("Thread {0} from handle {1}".format(thread, hex(handle)), "HANDLE") - return thread - except IndexError: - dbgprint("DeadThread from handle {0}".format(hex(handle)), "HANDLE") - return DeadThread(handle, tid) - @staticmethod def _get_thread_id_by_api(handle): return winproxy.GetThreadId(handle) @@ -253,10 +263,17 @@ class WinThread(THREADENTRY32, AutoHandle): if windows.current_process.bitness == 32 and self.owner.bitness == 64: raise NotImplementedError("[_get_thread_id_manual] 32 -> 64 (XP64 bits + Syswow process ?)") res = THREAD_BASIC_INFORMATION() - windows.winproxy.NtQueryInformationThread(hand, ThreadBasicInformation, byref(res), ctypes.sizeof(res)) + windows.winproxy.NtQueryInformationThread(handle, ThreadBasicInformation, byref(res), ctypes.sizeof(res)) id2 = res.ClientId.UniqueThread return id2 + def _get_thread_owner_pid(self, handle): + res = THREAD_BASIC_INFORMATION() + windows.winproxy.NtQueryInformationThread(handle, ThreadBasicInformation, byref(res), ctypes.sizeof(res)) + id2 = res.ClientId.UniqueProcess + return id2 + + if winproxy.is_implemented(winproxy.GetThreadId): _get_thread_id = _get_thread_id_by_api else: @@ -329,7 +346,8 @@ class Process(AutoHandle): :type: [:class:`WinThread`] -- A list of Thread """ - return [thread for thread in windows.system.threads if thread.th32OwnerProcessID == self.pid] + owner_pid = self.pid + return [WinThread._from_THREADENTRY32(th, owner=self) for th in windows.system.enumerate_threads_generator() if th.th32OwnerProcessID == owner_pid] def virtual_alloc(self, size): raise NotImplementedError("virtual_alloc") @@ -821,7 +839,7 @@ class WinProcess(Process): """A Process on the system""" def __init__(self, pid=None, handle=None, name=None, ppid=None): if pid is None and handle is None: - raise ValueError("Need at least or to create a {0}".format(type(self).__name)) + raise ValueError("Need at least or to create a {0}".format(type(self).__name__)) if pid is not None: self._pid = pid if handle is not None: self._handle = handle @@ -843,7 +861,7 @@ class WinProcess(Process): name = entry.szExeFile.decode() pid = entry.th32ProcessID ppid = entry.th32ParentProcessID - return WinProcess(pid=pid, name=name, ppid=ppid) + return cls(pid=pid, name=name, ppid=ppid) @utils.fixedpropety @@ -879,12 +897,16 @@ class WinProcess(Process): return winproxy.OpenProcess(dwProcessId=self.pid) def __repr__(self): + try: + exe_name = self.name + except WindowsError as e: + exe_name = "!cannot-retrieve-name" try: if self.is_exit: - return '<{0} "{1}" pid {2} (DEAD) at {3}>'.format(self.__class__.__name__, self.name, self.pid, hex(id(self))) + return '<{0} "{1}" pid {2} (DEAD) at {3}>'.format(self.__class__.__name__, exe_name, self.pid, hex(id(self))) except WindowsError: # Cannot open process pass - return '<{0} "{1}" pid {2} at {3}>'.format(self.__class__.__name__, self.name, self.pid, hex(id(self))) + return '<{0} "{1}" pid {2} at {3}>'.format(self.__class__.__name__, exe_name, self.pid, hex(id(self))) def virtual_alloc(self, size, prot=PAGE_EXECUTE_READWRITE): """Allocate memory in the process @@ -1231,6 +1253,14 @@ class PEB(Structure): {"ProcessParameters": POINTER(RTL_USER_PROCESS_PARAMETERS)} ) + @property + def exe(self): + """The executable of the process, as pointed by PEB.ImageBaseAddress + + :type: :class:`windows.pe_parse.PEFile` + """ + return windows.pe_parse.GetPEFile(self.ImageBaseAddress) + @property def imagepath(self): """The ImagePathName of the PEB @@ -1343,6 +1373,14 @@ class RemotePEB(rctypes.RemoteStructure.from_structure(PEB)): def ptr_flink_to_remote_module(self, ptr_value): return RemoteLoadedModule(ptr_value - ctypes.sizeof(ctypes.c_void_p) * 2, self._target) + @property + def exe(self): + """The executable of the process, as pointed by PEB.ImageBaseAddress + + :type: :class:`windows.pe_parse.PEFile` + """ + return pe_parse.GetPEFile(self.ImageBaseAddress, target=self._target) + @property def modules(self): """The loaded modules present in the PEB @@ -1376,6 +1414,15 @@ if CurrentProcess().bitness == 32: def ptr_flink_to_remote_module(self, ptr_value): return RemoteLoadedModule64(ptr_value - ctypes.sizeof(rctypes.c_void_p64) * 2, self._target) + + @property + def exe(self): + """The executable of the process, as pointed by PEB.ImageBaseAddress + + :type: :class:`windows.pe_parse.PEFile` + """ + return pe_parse.GetPEFile(self.ImageBaseAddress, target=self._target) + @property def modules(self): """The loaded modules present in the PEB @@ -1408,6 +1455,15 @@ if CurrentProcess().bitness == 64: def ptr_flink_to_remote_module(self, ptr_value): return RemoteLoadedModule32(ptr_value - ctypes.sizeof(rctypes.c_void_p32) * 2, self._target) + @property + def exe(self): + """The executable of the process, as pointed by PEB.ImageBaseAddress + + :type: :class:`windows.pe_parse.PEFile` + """ + return pe_parse.GetPEFile(self.ImageBaseAddress, target=self._target) + + @property def modules(self): """The loaded modules present in the PEB diff --git a/windows/winobject/system.py b/windows/winobject/system.py index b81b981..1b845f0 100644 --- a/windows/winobject/system.py +++ b/windows/winobject/system.py @@ -19,6 +19,7 @@ from windows.winobject import kernobj from windows.winobject import handle from windows.generated_def.winstructs import * +from windows.dbgprint import dbgprint class System(object): """The state of the current ``Windows`` system ``Python`` is running on""" @@ -42,7 +43,7 @@ class System(object): :type: [:class:`process.WinThread`] -- A list of Thread """ - return self.enumerate_threads() + return self.enumerate_threads_setup_owners() @property def logicaldrives(self): @@ -202,6 +203,7 @@ class System(object): @staticmethod def enumerate_processes(): + dbgprint("Enumerating processes with CreateToolhelp32Snapshot", "SLOW") process_entry = PROCESSENTRY32() process_entry.dwSize = ctypes.sizeof(process_entry) snap = winproxy.CreateToolhelp32Snapshot(windef.TH32CS_SNAPPROCESS, 0) @@ -214,14 +216,57 @@ class System(object): return res @staticmethod - def enumerate_threads(): - thread_entry = process.WinThread() + def enumerate_threads_generator(): + # Ptet dangereux, parce que on yield la meme THREADENTRY32 a chaque fois + dbgprint("Enumerating threads with CreateToolhelp32Snapshot ", "SLOW") + thread_entry = THREADENTRY32() thread_entry.dwSize = ctypes.sizeof(thread_entry) snap = winproxy.CreateToolhelp32Snapshot(windef.TH32CS_SNAPTHREAD, 0) + dbgprint("New handle CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD) | {0:#x}".format(snap), "HANDLE") + try: + winproxy.Thread32First(snap, thread_entry) + yield thread_entry + while winproxy.Thread32Next(snap, thread_entry): + yield thread_entry + finally: + winproxy.CloseHandle(snap) + dbgprint("CLOSE CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD) | {0:#x}".format(snap), "HANDLE") + + + @staticmethod + def enumerate_threads(): + return [WinThread._from_THREADENTRY32(th) for th in System.enumerate_threads_generator()] + + + def enumerate_threads_setup_owners(self): + # Enumerating threads is a special operation concerning the owner process. + # We may not be able to retrieve the name of the owning process by normal way + # (as we need to get a handle on the process) + # So, this implementation of enumerate_thread also setup the owner with the result of enumerate_processes + dbgprint("Enumerating threads with CreateToolhelp32Snapshot and setup owner", "SLOW") + + # One snap for both enum to be prevent race + snap = winproxy.CreateToolhelp32Snapshot(windef.TH32CS_SNAPTHREAD | windef.TH32CS_SNAPPROCESS, 0) + + process_entry = PROCESSENTRY32() + process_entry.dwSize = ctypes.sizeof(process_entry) + winproxy.Process32First(snap, process_entry) + processes = [] + processes.append(process.WinProcess._from_PROCESSENTRY32(process_entry)) + while winproxy.Process32Next(snap, process_entry): + processes.append(process.WinProcess._from_PROCESSENTRY32(process_entry)) + + # Forge a dict pid -> process + proc_dict = {proc.pid: proc for proc in processes} + + thread_entry = THREADENTRY32() + thread_entry.dwSize = ctypes.sizeof(thread_entry) threads = [] winproxy.Thread32First(snap, thread_entry) - threads.append(copy.copy(thread_entry)) + parent = proc_dict[thread_entry.th32OwnerProcessID] + threads.append(process.WinThread._from_THREADENTRY32(thread_entry, owner=parent)) while winproxy.Thread32Next(snap, thread_entry): - threads.append(copy.copy(thread_entry)) + parent = proc_dict[thread_entry.th32OwnerProcessID] + threads.append(process.WinThread._from_THREADENTRY32(thread_entry, owner=parent)) winproxy.CloseHandle(snap) - return threads + return threads \ No newline at end of file