diff --git a/windows/__init__.py b/windows/__init__.py index 5c60545..615ae80 100644 --- a/windows/__init__.py +++ b/windows/__init__.py @@ -19,8 +19,9 @@ if sys.platform != "win32": from windows import winproxy from windows import winobject -from winobject.system import System -from winobject.process import CurrentProcess, CurrentThread, WinProcess, WinThread +from .winobject.system import System +from .winobject.process import CurrentProcess, CurrentThread, WinProcess, WinThread +from .winobject.file import WinFile system = System() diff --git a/windows/hooks.py b/windows/hooks.py index 04e1cf4..fccdf8e 100644 --- a/windows/hooks.py +++ b/windows/hooks.py @@ -51,7 +51,6 @@ for func in windows.generated_def.meta.functions: class IATHook(object): """Look at my hook <3""" - yolo = [] def __init__(self, IAT_entry, callback, types=None): if types is None: @@ -62,11 +61,14 @@ class IATHook(object): self.callback_types = self.transform_arguments(self.original_types) self.entry = IAT_entry self.callback = callback + ## No more circular ref -> but stub is destroyed -> segv :( self.stub = ctypes.WINFUNCTYPE(*self.callback_types)(self.hook_callback) - self.stub_addr = ctypes.cast(self.stub, PVOID).value + # stub = ctypes.WINFUNCTYPE(*self.callback_types)(self.hook_callback) + # self.stub_addr = ctypes.cast(stub, PVOID) # Same problem as keep stub... (GC..) + self.stub_addr = ctypes.cast(self.stub, PVOID).value # Same problem as keep stub... (GC..) + # IAT_entry.stub = stub self.realfunction = ctypes.WINFUNCTYPE(*types)(IAT_entry.nonhookvalue) self.is_enable = False - #IATHook.yolo.append(self) def transform_arguments(self, types): res = [] @@ -82,12 +84,14 @@ class IATHook(object): with utils.VirtualProtected(self.entry.addr, ctypes.sizeof(PVOID), PAGE_EXECUTE_READWRITE): self.entry.value = self.stub_addr self.is_enable = True + self.entry.enabled = True def disable(self): """Disable the IAT hook""" with utils.VirtualProtected(self.entry.addr, ctypes.sizeof(PVOID), PAGE_EXECUTE_READWRITE): self.entry.value = self.entry.nonhookvalue self.is_enable = False + self.entry.enabled = True def hook_callback(self, *args): adapted_args = [] @@ -105,11 +109,6 @@ class IATHook(object): return self.realfunction(*args) return self.callback(*adapted_args, real_function=real_function) - # Use this tricks to prevent garbage collection of hook ? - #def __del__(self): - # pass - - ## New simple hook API based on winproxy def setup_hook(target, hook, dll_to_hook): "TODO: Test and doc :D" diff --git a/windows/winobject/event_log.py b/windows/winobject/event_log.py index 65fa831..79c9e4c 100644 --- a/windows/winobject/event_log.py +++ b/windows/winobject/event_log.py @@ -6,6 +6,7 @@ from contextlib import contextmanager import windows import windows.generated_def as gdef from windows import winproxy +from windows.pycompat import int_types # Helpers @@ -244,7 +245,8 @@ class EvtEvent(EvtHandle): # What about classic channels where there is no event_metadata ? # Return a dict with [0-1-2-3-4] as key ? raise ? # Juste use the render_xml ? - return {k:v for k,v in zip(self.metadata.event_data, self.event_values())} + event_data_name = (i["name"] for i in self.metadata.event_data if i["type"] == "data") + return {k:v for k,v in zip(event_data_name, self.event_values())} def __repr__(self): @@ -305,6 +307,27 @@ class ImprovedEVT_VARIANT(gdef.EVT_VARIANT): v = v[:self.Count] return v + @classmethod + def from_value(cls, value, vtype=None): + if vtype is None: + # Guess type + if isinstance(value, int_types): + vtype = gdef.EvtVarTypeUInt64 + elif isinstance(value, basestring): + vtype = gdef.EvtVarTypeString + else: + raise NotImplementedError("LATER") + self = cls() + # import pdb;pdb.set_trace() + # Yolo test :) + super(ImprovedEVT_VARIANT, ImprovedEVT_VARIANT).Type.__set__(self, vtype) + # super(ImprovedEVT_VARIANT, self).Type = vtype + attrname = self.VALUE_MAPPER[self.Type] + setattr(self, attrname, value) + if self.Type in (gdef.EvtVarTypeBinary, gdef.EvtVarTypeString): + self.Count = len(value) + return self + def __repr__(self): return "<{0} of type={1}>".format(type(self).__name__, self.Type) @@ -360,7 +383,7 @@ class EvtChannel(object): if ids and filter: raise ValueError(" and are mutually exclusive") if ids is not None: - if isinstance(ids, (long, int)): + if isinstance(ids, int_types): ids = (ids,) ids_filter = " or ".join("EventID={0}".format(id) for id in ids) filter = "Event/System[{0}]".format(ids_filter) @@ -854,19 +877,45 @@ class EventMetadata(EvtHandle): """Identifies the template attribute of the event definition which is an XML string""" return eventinfo(self, gdef.EventMetadataEventTemplate).value + def _parse_event_template_data_element(self, element): + res = {"type": "data"} + res["name"] = element.attributes["name"].value + res["inType"] = element.attributes["inType"].value + res["outType"] = element.attributes["outType"].value + count = element.attributes.get("count", None) + if count: + res["count"] = count.value + length = element.attributes.get("length", None) + if length: + res["length"] = length.value + return res + + def _parse_event_template_struct_element(self, element): + res = {"type": "struct"} + res["name"] = element.attributes["name"].value + res["fields"] = [self._parse_event_template_data_element(elt) for elt in element.childNodes if elt.nodeType == elt.ELEMENT_NODE] + return res + + def _event_data_generator(self, template): + xmldoc = xml.dom.minidom.parseString(template) + xmltemplate = xmldoc.getElementsByTagName("template")[0] + for element in (n for n in xmltemplate.childNodes if n.nodeType == n.ELEMENT_NODE): + if element.tagName == "data": + yield self._parse_event_template_data_element(element) + elif element.tagName == "struct": + yield self._parse_event_template_struct_element(element) + else: + raise ValueError("Unexpected XML element <{0}> in event template".format(element.tagName)) + @property def event_data(self): """The list of attribute specifique for this event. Retrieved by parsing :data:`EventMetadata.template` """ - result = [] template = self.template if not template: - return {} - xmltemplate = xml.dom.minidom.parseString(template) - for data in xmltemplate.getElementsByTagName("data"): - result.append(data.attributes["name"].value) - return result + return [] + return list(self._event_data_generator(template)) def yolo(self): template = self.template diff --git a/windows/winobject/event_trace.py b/windows/winobject/event_trace.py index e427460..a4c1c45 100644 --- a/windows/winobject/event_trace.py +++ b/windows/winobject/event_trace.py @@ -66,7 +66,7 @@ class EventRecord(gdef.EVENT_RECORD): # def match(self, provider=None, id=None, opcode=None): def __repr__(self): - guid = self.EventHeader.ProviderId.to_string() + guid = self.EventHeader.ProviderId return """<{0} provider="{1}" id={2}>""".format(type(self).__name__, guid, self.id) @@ -218,7 +218,7 @@ class EtwTrace(object): # TODO : maybe raise an Exception instead of silently masking the value ? level = gdef.UCHAR(chr(level & 0xff)) - return windows.winproxy.EnableTraceEx2(self.handle, guid, EVENT_CONTROL_CODE_ENABLE_PROVIDER, level , any_keyword, all_keyword, 0, None) + return windows.winproxy.EnableTraceEx2(self.handle, guid, EVENT_CONTROL_CODE_ENABLE_PROVIDER, level , any_keyword, all_keyword, 0, None) def process(self, callback, begin=None, end=None, context = None): @@ -295,7 +295,7 @@ class TraceProvider(object): return self.infos.instances def __repr__(self): - return """<{0} for "{1}">""".format(type(self).__name__, self.guid.to_string()) + return """<{0} for "{1}">""".format(type(self).__name__, self.guid) class TraceGuidInfo(gdef.TRACE_GUID_INFO): diff --git a/windows/winobject/task_scheduler.py b/windows/winobject/task_scheduler.py index 55ce553..fb9e7f1 100644 --- a/windows/winobject/task_scheduler.py +++ b/windows/winobject/task_scheduler.py @@ -203,6 +203,11 @@ class Task(gdef.IRegisteredTask): self.RunEx(params, flags, sessionid, user, result) return result + def get_security_descriptor(self, secinfo): + res = gdef.BSTR() + self.GetSecurityDescriptor(secinfo, res) + return res.value + def __repr__(self): return """<{0} "{1}" at {2:#x}>""".format(type(self).__name__, self.name, id(self)) diff --git a/windows/winobject/wmi.py b/windows/winobject/wmi.py index 88fb33d..d9fdaa9 100644 --- a/windows/winobject/wmi.py +++ b/windows/winobject/wmi.py @@ -2,6 +2,7 @@ import windows import ctypes import struct import functools +from functools import partial from collections import namedtuple from ctypes.wintypes import * @@ -9,7 +10,8 @@ from ctypes.wintypes import * import windows.com import windows.generated_def as gdef from windows.generated_def.winstructs import * -from functools import partial + +from windows.pycompat import basestring # Common error check for all WMI COM interfaces # This 'just' add the corresponding 'WBEMSTATUS' to the hresult error code @@ -18,9 +20,44 @@ class WmiComInterface(object): def errcheck(self, result, func, args): if result < 0: wmitag = gdef.WBEMSTATUS.mapper[result & 0xffffffff] - raise WindowsError(result, wmitag) + raise ctypes.WinError(result, wmitag) return args +sentinel = object() +# POC +class QualifierSet(gdef.IWbemQualifierSet): + def get_variant(self, name): + """Retrieve the value of property ``name`` as a :class:`~windows.com.Variant` + + :return: :class:`~windows.com.Variant` + """ + if not isinstance(name, basestring): + nametype = type(name).__name__ + raise TypeError("WmiObject attributes name must be str, not <{0}>".format(nametype)) + variant_res = windows.com.Variant() + self.Get(name, 0, variant_res, None) + return variant_res + + def get(self, name, default=sentinel): + """Return the value of the property ``name``. The return value depends of the type of the property and can vary""" + try: + return self.get_variant(name).value + except WindowsError as e: + if (e.winerror & 0xffffffff) != gdef.WBEM_E_NOT_FOUND: + raise + if default is sentinel: + raise + return default + + def names(self): + res = POINTER(windows.com.SafeArray)() + x = ctypes.pointer(res) + self.GetNames(0, cast(x, POINTER(POINTER(gdef.SAFEARRAY)))) + # need to free the safearray / unlock ? + properties = [p for p in res[0].to_list(BSTR)] + return properties + + # https://docs.microsoft.com/en-us/windows/desktop/api/wbemcli/nn-wbemcli-iwbemclassobject WmiMethod = namedtuple("WmiMethod", ["inparam", "outparam"]) @@ -88,7 +125,6 @@ class WmiObject(gdef.IWbemClassObject, WmiComInterface): return gdef.tag_WBEM_GENUS_TYPE.mapper[self.get("__GENUS")] ## Higher level API - def get_properties(self, system_properties=False): """Return the list of properties names available for the current object. If ``system_properties`` is ``False`` property names begining with ``_`` are ignored. @@ -108,6 +144,17 @@ class WmiObject(gdef.IWbemClassObject, WmiComInterface): properties = property(get_properties) #: The properties of the object (exclude system properties) + @property + def qualifier_set(self): # changer de nom ? + res = QualifierSet() + self.GetQualifierSet(res) + return res + + def get_p_set(self, name): # Changer de nom ? + res = QualifierSet() + self.GetPropertyQualifierSet(name, res) + return res + # Make WmiObject a mapping object def keys(self): @@ -133,6 +180,11 @@ class WmiObject(gdef.IWbemClassObject, WmiComInterface): return """<{0} class "{1}">""".format(type(self).__name__, self.get("__Class")) return """<{0} instance of "{1}">""".format(type(self).__name__, self.get("__Class")) + def __sprint__(self): + return """ {0}\n + {1} + """.format(repr(self), "\n".join(": ".join([x[0], str(x[1])]) for x in sorted(self.items()))) + class WmiEnumeration(gdef.IEnumWbemClassObject, WmiComInterface): """Represent an enumeration of object that can be itered""" @@ -150,7 +202,7 @@ class WmiEnumeration(gdef.IEnumWbemClassObject, WmiComInterface): return_count = gdef.ULONG(0) error = self.Next(timeout, 1, obj, return_count) if error == gdef.WBEM_S_TIMEDOUT: - raise WindowsError(gdef.WBEM_S_TIMEDOUT, "Wmi timeout") + raise ctypes.WinError(gdef.WBEM_S_TIMEDOUT, "Wmi timeout") elif error == WBEM_S_FALSE: return None else: @@ -337,6 +389,12 @@ class WmiNamespace(gdef.IWbemServices, WmiComInterface): self.PutInstance(instance, flags, None, res) return res + def delete_instance(self, instance, flags=0): + """TODO: Document""" + if isinstance(instance, gdef.IWbemClassObject): + instance = instance["__Path"] + return self.DeleteInstance(instance, flags, None, None) + def exec_method(self, obj, method, inparam, flags=0): """Exec method named on ``object`` with ``inparam``.