WinThread is not a THREADENTRY32 anymore + add Winprocess.peb.exe

This commit is contained in:
Clement Rouault
2017-04-27 12:06:04 +02:00
parent c0d03a2f58
commit f9a650fcf3
4 changed files with 185 additions and 39 deletions
+42
View File
@@ -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("<Q", calc.read_memory(data, 8))[0]
self.assertEqual(dword, get_current_proc_id)
@check_for_gc_garbage
def test_remote_peb_exe_32(self):
with Calc32() as calc:
exe = calc.peb.exe
exe_by_module = calc.peb.modules[0].pe
self.assertEqual(exe.baseaddr, exe_by_module.baseaddr)
self.assertEqual(exe.bitness, exe_by_module.bitness)
@windows_64bit_only
@check_for_gc_garbage
def test_remote_peb_exe_64(self):
with Calc64() as calc:
exe = calc.peb.exe
exe_by_module = calc.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_thread_exit_value_32(self):
with Calc32() as calc:
@@ -575,6 +600,23 @@ class WindowsTestCase(unittest.TestCase):
t = calc.threads[0]
self.assertNotEqual(t.teb_base, 0)
@check_for_gc_garbage
def test_thread_owner_from_tid_32(self):
with Calc32() as calc:
thread = calc.threads[0]
tst_thread = windows.winobject.process.WinThread(tid=thread.tid)
self.assertEqual(thread.owner_pid, tst_thread.owner_pid)
self.assertEqual(thread.owner.name, tst_thread.owner.name)
@windows_64bit_only
@check_for_gc_garbage
def test_thread_owner_from_tid_64(self):
with Calc64() as calc:
thread = calc.threads[0]
tst_thread = windows.winobject.process.WinThread(tid=thread.tid)
self.assertEqual(thread.owner_pid, tst_thread.owner_pid)
self.assertEqual(thread.owner.name, tst_thread.owner.name)
class WindowsAPITestCase(unittest.TestCase):
def test_createfileA_fail(self):
+6 -3
View File
@@ -104,12 +104,15 @@ 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)
t.wait()
return t.exit_code
result = t.exit_code
if result > 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)
+86 -30
View File
@@ -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 <pid> or <handle> 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 = "<Dead process with pid {0}>".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 <pid> or <handle> to create a {0}".format(type(self).__name))
raise ValueError("Need at least <pid> or <handle> 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
+51 -6
View File
@@ -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 <generator>", "SLOW")
thread_entry = THREADENTRY32()
thread_entry.dwSize = ctypes.sizeof(thread_entry)
snap = winproxy.CreateToolhelp32Snapshot(windef.TH32CS_SNAPTHREAD, 0)
dbgprint("New handle CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD) <generator> | {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) <generator> | {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