From 0f49dff1288581679bc80e93b1b67be297d50155 Mon Sep 17 00:00:00 2001 From: hakril Date: Thu, 16 Jul 2020 23:33:27 +0200 Subject: [PATCH] Added some docstrings --- windows/security.py | 17 +++++- windows/winobject/event_log.py | 54 ++++++++++++++++++ windows/winobject/event_trace.py | 91 ++++++++++++++++++++++++++++--- windows/winobject/system.py | 8 +++ windows/winobject/token.py | 5 +- windows/winproxy/apis/advapi32.py | 41 +++++++++++++- 6 files changed, 202 insertions(+), 14 deletions(-) diff --git a/windows/security.py b/windows/security.py index a3eaee0..4c6c295 100644 --- a/windows/security.py +++ b/windows/security.py @@ -890,6 +890,17 @@ class SecurityDescriptor(gdef.PSECURITY_DESCRIPTOR): flags |= gdef.SACL_SECURITY_INFORMATION winproxy.SetNamedSecurityInfoW(filename, gdef.SE_FILE_OBJECT, flags, self.owner, self.group, self.dacl, self.sacl) + def to_handle(self, handle, flags=0): + if not flags: + if self.owner: + flags |= gdef.OWNER_SECURITY_INFORMATION + if self.group: + flags |= gdef.GROUP_SECURITY_INFORMATION + if self.dacl: + flags |= gdef.DACL_SECURITY_INFORMATION + if self.sacl: + flags |= gdef.SACL_SECURITY_INFORMATION + self._apply_to_handle_and_type(handle, gdef.SE_FILE_OBJECT, flags) @classmethod def from_service(cls, filename, query_sacl=False, flags=SERVICE_SECURITY_INFORMATION): @@ -978,11 +989,11 @@ def explain_mask(mask, sdtype): def explain_simple_ace(ace, sdtype): yield u"Type:" yield u" " + str(ace.Header.AceType) - yield u"Flags:" - yield u" {0:#x}".format(ace.Header.AceFlags) + yield u"Flags: {0:#x}".format(ace.Header.AceFlags) + yield u" {0}".format(ace.Header.flags) yield u"SID:" yield u" " + explain_sid(ace.sid) - yield u"Mask:" + yield u"Mask: {0:#x}".format(ace.Mask) mapper = windows.security.SPECIFIC_ACCESS_RIGTH_BY_TYPE[sdtype] yield u" " + str(list(mapper[x] for x in ace.mask)) diff --git a/windows/winobject/event_log.py b/windows/winobject/event_log.py index 63bd459..267273d 100644 --- a/windows/winobject/event_log.py +++ b/windows/winobject/event_log.py @@ -214,6 +214,11 @@ class EvtEvent(EvtHandle): """The provider of the event""" return self.system_values()[gdef.EvtSystemProviderName] + @property + def computer(self): + """The computer that generated the event""" + return self.system_values()[gdef.EvtSystemComputer] + @property def id(self): """The ID of the Event""" @@ -249,6 +254,16 @@ class EvtEvent(EvtHandle): """The process ID of the Event""" return self.value("Event/System/Execution/@ThreadID") + @property + def error_payload(self): + raw = self.value("Event/ProcessingErrorData/EventPayload") + return bytearray(raw) if raw is not None else None + + @property + def user(self): + """The User ID associated with the Event""" + return self.system_values()[gdef.EvtSystemUserID] + @property def metadata(self): """The medata for the current Event @@ -276,6 +291,45 @@ class EvtEvent(EvtHandle): 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 xml_data(self): + xmlevt = xml.dom.minidom.parseString(self.render_xml()) + res = {} + + eventdata = xmlevt.getElementsByTagName("EventData") + if eventdata: + # FIELD_VALUE + for i, datanode in enumerate(xmlevt.getElementsByTagName("Data")): + name = datanode.getAttribute("Name") + if not name: + # Some Data in old EVTX have no name (Windows Powershell) + # Do the best we can by using the position of the event + name = str(i) + + if datanode.hasChildNodes(): + value = datanode.firstChild.nodeValue + else: + value = "" + if not (name not in res): + import pdb;pdb.set_trace() + res[name] = value + userdata = xmlevt.getElementsByTagName("UserData") + if userdata: + # + # + # FIELD_VALUE + # + # + for datanode in userdata[0].firstChild.childNodes: + name = datanode.tagName + if datanode.hasChildNodes(): + value = datanode.firstChild.nodeValue + else: + value = "" + assert name not in res + res[name] = value + return res + + @property def date(self): """``Event.time_created`` as a :class:``datetime``""" diff --git a/windows/winobject/event_trace.py b/windows/winobject/event_trace.py index f96af18..6a09090 100644 --- a/windows/winobject/event_trace.py +++ b/windows/winobject/event_trace.py @@ -51,6 +51,8 @@ class EventRecord(gdef.EVENT_RECORD): @property def context(self): + if self.UserContext is None: + return None return ctypes.py_object.from_address(self.UserContext).value @property @@ -74,12 +76,17 @@ class EventRecord(gdef.EVENT_RECORD): PEventRecord = ctypes.POINTER(EventRecord) class EventTraceProperties(gdef.EVENT_TRACE_PROPERTIES): + """Represent an Event Trace session that may exist or now. (https://docs.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-event_trace_properties) + + This class is widly used by :class:`EtwTrace` + """ # Test: ascii / Use Wchar ? FULL_SIZE = ctypes.sizeof(gdef.EVENT_TRACE_PROPERTIES) + MAX_SESSION_NAME_LEN_W + MAX_LOGFILE_PATH_LEN_W # def alloc(cls, size) ? @classmethod def create(cls): + """Initialize a new :class:`EventTraceProperties`""" buff = windows.utils.BUFFER(cls)(size=cls.FULL_SIZE) # ctypes.memset(buff, "\x00", cls.FULL_SIZE) self = buff[0] @@ -92,14 +99,13 @@ class EventTraceProperties(gdef.EVENT_TRACE_PROPERTIES): assert self.LogFileNameOffset return windows.current_process.read_string(ctypes.addressof(self) + self.LogFileNameOffset) - def set_logfilename(self, filename): assert self.LogFileNameOffset if not filename.endswith("\x00"): filename += "\x00" return windows.current_process.write_memory(ctypes.addressof(self) + self.LogFileNameOffset, filename) - logfile = property(get_logfilename, set_logfilename) + logfile = property(get_logfilename, set_logfilename) #: The logfile associated with the session def get_logger_name(self): assert self.LoggerNameOffset @@ -112,17 +118,23 @@ class EventTraceProperties(gdef.EVENT_TRACE_PROPERTIES): filename += "\x00" return windows.current_process.write_memory(ctypes.addressof(self) + self.LoggerNameOffset, filename) - name = property(get_logger_name, set_logfilename) + name = property(get_logger_name, set_logfilename) #: The name of the session @property def guid(self): + """The GUID of the Event Trace session (see ``Wnode.Guid``)""" return self.Wnode.Guid @property def id(self): - """LoggerId""" + """The LoggerId if the session (see ``Wnode.HistoricalContext``)""" return self.Wnode.HistoricalContext + + def __repr__(self): + return """<{0} name="{1}" guid={2}>""".format(type(self).__name__, self.name, self.guid) + + # GUID setter ? class CtxProcess(object): @@ -154,15 +166,17 @@ class CtxProcess(object): class EtwTrace(object): + """Represent an ETW Trace for tracing/processing events""" def __init__(self, name, logfile=None, guid=None): - self.name = windows.pycompat.raw_encode(name) - self.logfile = logfile + self.name = windows.pycompat.raw_encode(name) #: The name of the trace + self.logfile = logfile #: The logging file of the trace (``None`` means real time trace) if guid and isinstance(guid, basestring): guid = gdef.GUID.from_string(guid) - self.guid = guid + self.guid = guid #: The guid of the trace self.handle = 0 def exists(self): + """Return ``True`` if the trace already exist (based on its name)""" prop = EventTraceProperties.create() try: windows.winproxy.ControlTraceA(self.handle, self.name, prop, gdef.EVENT_TRACE_CONTROL_QUERY) @@ -173,6 +187,7 @@ class EtwTrace(object): return True def start(self, flags=0): + """Start the tracing""" prop = EventTraceProperties.create() prop.NumberOfBuffers = 42 prop.EnableFlags = flags @@ -189,6 +204,11 @@ class EtwTrace(object): self.handle = handle def stop(self, soft=False): # Change name + """stop the tracing. + + ``soft`` will allow to stop a non-existing trace that do not exists/run. + This allow for simpler script that stop/start some EtwTrace. + """ prop = EventTraceProperties.create() try: windows.winproxy.ControlTraceA(0, self.name, prop, gdef.EVENT_TRACE_CONTROL_STOP) @@ -199,16 +219,19 @@ class EtwTrace(object): return True def flush(self): + """Flush the trace""" prop = EventTraceProperties.create() windows.winproxy.ControlTraceA(0, self.name, prop, gdef.EVENT_TRACE_CONTROL_FLUSH) def enable(self, guid, flags=0xff, level=0xff): + """Enable the specified event trace provider.""" if isinstance(guid, basestring): guid = gdef.GUID.from_string(guid) return windows.winproxy.EnableTrace(1, flags, level, guid, self.handle) # EnableTraceEx ? def enable_ex(self, guid, flags=0xff, level=0xff, any_keyword = 0xffffffff, all_keyword=0x00): + """Enable the specified event trace provider.""" if isinstance(guid, basestring): guid = gdef.GUID.from_string(guid) @@ -223,6 +246,17 @@ class EtwTrace(object): def process(self, callback, begin=None, end=None, context=None): + """Process the event retrieved by the trace. + This function will call ``callback`` with any :class:`EventRecord` in the trace. + ``begin/end`` allow to filter and only process events in a given timeframe. + + .. warning:: + + If the trace if ``REALTIME`` (no logfile) this function will hang/process new event until the trace is stopped. + + Using ``logman -ets stop TRACE_NAME`` for exemple. + + """ if end == "now": end = gdef.FILETIME() windows.winproxy.GetSystemTimeAsFileTime(end) @@ -272,11 +306,19 @@ class EtwTrace(object): return """<{0} name={1!r} logfile={2!r}>""".format(type(self).__name__, self.name, self.logfile) class TraceProvider(object): + """Represent a ETW provider, which is just a GUID. + Corresponding name for a provider may be available trhought WMI. + """ def __init__(self, guid): self.guid = guid @property def infos(self): + """The :class:`TraceGuidInfo` associated with the provider. + Main use is to retrieve the instances of the provider (directly available with ``instances``) + + :type: :class:`TraceGuidInfo` + """ size = gdef.DWORD() info_buffer = ctypes.c_buffer(0x1000) try: @@ -293,6 +335,10 @@ class TraceProvider(object): # Our trace providers should be able to directly returns its instances @property def instances(self): + """The instances of the provider. + + :type: [:class:`TraceProviderInstanceInfo`] -- A list of :class:`TraceProviderInstanceInfo` + """ return self.infos.instances def __repr__(self): @@ -300,6 +346,10 @@ class TraceProvider(object): class TraceGuidInfo(gdef.TRACE_GUID_INFO): + """Defines the header to the list of sessions that enabled the provider + (see https://docs.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-trace_guid_info) + """ + @classmethod def from_raw_buffer(cls, buffer): self = cls.from_buffer(buffer) @@ -317,6 +367,10 @@ class TraceGuidInfo(gdef.TRACE_GUID_INFO): @property def instances(self): + """The instances of the provider. + + :type: [:class:`TraceProviderInstanceInfo`] -- A list of :class:`TraceProviderInstanceInfo` + """ return [x for x in self._instance_generator()] def __repr__(self): @@ -324,6 +378,9 @@ class TraceGuidInfo(gdef.TRACE_GUID_INFO): class TraceProviderInstanceInfo(gdef.TRACE_PROVIDER_INSTANCE_INFO): + """Defines an instance of the provider + (see https://docs.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-trace_provider_instance_info) + """ @classmethod def from_raw_buffer(cls, buffer, offset): self = cls.from_buffer(buffer, offset) @@ -340,6 +397,10 @@ class TraceProviderInstanceInfo(gdef.TRACE_PROVIDER_INSTANCE_INFO): @property def sessions(self): + """The sessions for the instance + + :type: [:class:`~windows.generated_def.winstructs.TRACE_ENABLE_INFO`] -- A list of session + """ return [x for x in self._instance_generator()] def __repr__(self): @@ -347,8 +408,14 @@ class TraceProviderInstanceInfo(gdef.TRACE_PROVIDER_INSTANCE_INFO): class EtwManager(object): + """An object to query ETW session/providers and open new trace""" + @property def sessions(self): + """The list of currently active ETW session. + + :type: [:class:`EventTraceProperties`] -- A list of :class:`EventTraceProperties` + """ # Create a tuple of MAX_ETW_SESSIONS EventTraceProperties ptr t = [EventTraceProperties.create() for _ in range(MAX_ETW_SESSIONS)] # Put this in a ctypes array @@ -362,12 +429,20 @@ class EtwManager(object): @property def providers(self): + """The list of currently existing ETW providers. + + :type: [:class:`TraceProvider`] -- A list of ETW providers + """ buffer = windows.utils.BUFFER(gdef.GUID, 0x1000)() size = gdef.DWORD() windows.winproxy.EnumerateTraceGuidsEx(gdef.TraceGuidQueryList, None, 0, buffer, buffer.real_size, size) - return [TraceProvider(g) for g in buffer[:size.value / ctypes.sizeof(gdef.GUID)]] + return [TraceProvider(g) for g in buffer[:size.value // ctypes.sizeof(gdef.GUID)]] # Temp name / API ? def open_trace(self, name=None, logfile=None, guid=None): + """Open a new ETW Trace + + :return: :class:`EtwTrace` + """ return EtwTrace(name, logfile, guid) \ No newline at end of file diff --git a/windows/winobject/system.py b/windows/winobject/system.py index 51fc82a..276c455 100644 --- a/windows/winobject/system.py +++ b/windows/winobject/system.py @@ -107,10 +107,18 @@ class System(object): @utils.fixedpropety def event_log(self): + """An object to open Event channel/publisher and evtx file + + :type: :class:`~windows.winobject.event_log.EvtlogManager` + """ return event_log.EvtlogManager() @utils.fixedpropety def etw(self): + """An object to interact with ETW (Event Tracing for Windows) + + :type: :class:`~windows.winobject.event_trace.EtwManager` + """ return event_trace.EtwManager() diff --git a/windows/winobject/token.py b/windows/winobject/token.py index aedb7e7..027525f 100644 --- a/windows/winobject/token.py +++ b/windows/winobject/token.py @@ -5,6 +5,7 @@ import windows from windows import utils from windows import winproxy import windows.generated_def as gdef +# import windows.security # at the end of this file (loop import) bltn_type = type @@ -308,7 +309,6 @@ class Token(utils.AutoHandle): :type: :class:`windows.security.Acl` """ - import window.security # Beuk move token.py & in a security/ directory ? return self.get_token_infomations(gdef.TokenDefaultDacl, windows.security.PAcl)[0] # def source(self): (tok.TokenSource) ?? @@ -618,3 +618,6 @@ class Token(utils.AutoHandle): if toktype == gdef.TokenPrimary: return "<{0} TokenId={1:#x} Type={2}>".format(type(self).__name__, tid_int, flag_repr(toktype)) return "<{0} TokenId={1:#x} Type={2} ImpersonationLevel={3}>".format(type(self).__name__, tid_int, flag_repr(toktype), flag_repr(self.impersonation_level)) + + +import windows.security diff --git a/windows/winproxy/apis/advapi32.py b/windows/winproxy/apis/advapi32.py index c4e8d4a..485bcfb 100644 --- a/windows/winproxy/apis/advapi32.py +++ b/windows/winproxy/apis/advapi32.py @@ -2,7 +2,7 @@ import ctypes import windows.generated_def as gdef from ..apiproxy import ApiProxy, NeededParameter -from ..error import fail_on_zero, succeed_on_zero, result_is_error_code, result_is_handle, no_error_check +from ..error import fail_on_zero, succeed_on_zero, result_is_error_code, result_is_handle, no_error_check, result_is_ntstatus class Advapi32Proxy(ApiProxy): APIDLL = "advapi32" @@ -588,4 +588,41 @@ def TraceEvent(SessionHandle, EventTrace): @Advapi32Proxy(error_check=result_is_handle) def GetTraceLoggerHandle(Buffer): - return GetTraceLoggerHandle.ctypes_function(Buffer) \ No newline at end of file + return GetTraceLoggerHandle.ctypes_function(Buffer) + + +# Lsa APIs +@Advapi32Proxy(error_check=result_is_ntstatus) +def LsaOpenPolicy(SystemName=None, ObjectAttributes=None, DesiredAccess=NeededParameter, PolicyHandle=NeededParameter): + if ObjectAttributes is None: + ObjectAttributes = gdef.LSA_OBJECT_ATTRIBUTES() + return LsaOpenPolicy.ctypes_function(SystemName, ObjectAttributes, DesiredAccess, PolicyHandle) + +@Advapi32Proxy(error_check=result_is_ntstatus) +def LsaQueryInformationPolicy(PolicyHandle, InformationClass, Buffer): + return LsaQueryInformationPolicy.ctypes_function(PolicyHandle, InformationClass, Buffer) + +@Advapi32Proxy(error_check=result_is_ntstatus) +def LsaClose(ObjectHandle): + return LsaClose.ctypes_function(ObjectHandle) + +@Advapi32Proxy(error_check=result_is_ntstatus) +def LsaNtStatusToWinError(Status): + return LsaNtStatusToWinError.ctypes_function(Status) + +@Advapi32Proxy(error_check=result_is_ntstatus) +def LsaLookupNames(PolicyHandle, Count, Names, ReferencedDomains, Sids): + return LsaLookupNames.ctypes_function(PolicyHandle, Count, Names, ReferencedDomains, Sids) + +@Advapi32Proxy(error_check=result_is_ntstatus) +def LsaLookupNames2(PolicyHandle, Flags, Count, Names, ReferencedDomains, Sids): + return LsaLookupNames2.ctypes_function(PolicyHandle, Flags, Count, Names, ReferencedDomains, Sids) + +@Advapi32Proxy(error_check=result_is_ntstatus) +def LsaLookupSids(PolicyHandle, Count, Sids, ReferencedDomains, Names): + return LsaLookupSids.ctypes_function(PolicyHandle, Count, Sids, ReferencedDomains, Names) + +@Advapi32Proxy(error_check=result_is_ntstatus) +def LsaLookupSids2(PolicyHandle, LookupOptions, Count, Sids, ReferencedDomains, Names): + return LsaLookupSids2.ctypes_function(PolicyHandle, LookupOptions, Count, Sids, ReferencedDomains, Names) +