mirror of
https://github.com/naksyn/PythonMemoryModule
synced 2026-06-06 16:24:25 +00:00
db1893910c
This update include support to passing command line parameters to unmanaged exe via PEB stomping. This technique is not working with every executable since it depends on which functions are used to pass arguments. Generally, to get a universally working technique would be required to hook GetCommandlineA GetCommandlineW __getmainargs and __wgetmainargs since PEB stomping won't cover all cases, more details here: https://blog-30cm-tw.translate.goog/2020/08/windows-c-mainargc-argv.html?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=it&_x_tr_pto=wapp However, during my testing I found that mimikatz and several go binaries are working just by doing PEB stomping. On the other hand, cmdline passing via PEB stomping alone to mingw and VS compiled binaries won't likely work.
1124 lines
40 KiB
Python
1124 lines
40 KiB
Python
import ctypes
|
|
import os.path
|
|
import xml.dom.minidom
|
|
from contextlib import contextmanager
|
|
|
|
import windows
|
|
import windows.generated_def as gdef
|
|
from windows import winproxy
|
|
from windows.pycompat import int_types, basestring
|
|
|
|
|
|
# Helpers
|
|
|
|
|
|
@contextmanager
|
|
def ClosingEvtHandle(handle):
|
|
try:
|
|
yield handle
|
|
finally:
|
|
winproxy.EvtClose(handle)
|
|
|
|
|
|
# low-level api helpers
|
|
def queryinfo(handle, propertyid):
|
|
size = 0x1000
|
|
buffer = ctypes.create_string_buffer(size)
|
|
evt = ImprovedEVT_VARIANT.from_buffer(buffer)
|
|
res = gdef.DWORD()
|
|
windows.winproxy.EvtGetLogInfo(handle, propertyid, size, evt, res)
|
|
return evt
|
|
|
|
def arrayproperty(handle, property, index, flags=0):
|
|
size = 0x1000
|
|
buffer = ctypes.create_string_buffer(size)
|
|
evt = ImprovedEVT_VARIANT.from_buffer(buffer)
|
|
res = gdef.DWORD()
|
|
windows.winproxy.EvtGetObjectArrayProperty(handle, property, index, flags, size, evt, res)
|
|
return evt
|
|
|
|
def generate_query_function(query_function):
|
|
def generated_query_function(handle, propertyid, flags=0):
|
|
size = 0x10000
|
|
buffer = ctypes.create_string_buffer(size)
|
|
evt = ImprovedEVT_VARIANT.from_buffer(buffer)
|
|
res = gdef.DWORD()
|
|
query_function(handle, propertyid, flags, size, evt, res)
|
|
return evt
|
|
return generated_query_function
|
|
|
|
chaninfo = generate_query_function(winproxy.EvtGetChannelConfigProperty)
|
|
eventinfo = generate_query_function(winproxy.EvtGetEventMetadataProperty)
|
|
publishinfo = generate_query_function(winproxy.EvtGetPublisherMetadataProperty)
|
|
|
|
|
|
class EvtHandle(gdef.EVT_HANDLE):
|
|
# Class attribute function
|
|
# Will pass (self) as first parameter (binding)
|
|
# No need to pass any param to close ourself :)
|
|
_close_function = windows.winproxy.EvtClose
|
|
|
|
def __del__(self):
|
|
if not bool(self):
|
|
return
|
|
self._close_function()
|
|
|
|
# Class high-level API
|
|
class EvtQuery(EvtHandle):
|
|
"""Represent an Event-log query"""
|
|
DEFAULT_TIMEOUT = 0x1000
|
|
|
|
def __init__(self, handle=0, channel=None, timeout=None):
|
|
super(EvtQuery, self).__init__(handle)
|
|
self.channel = channel
|
|
self.timeout = timeout if timeout is not None else self.DEFAULT_TIMEOUT
|
|
|
|
def __next__(self):
|
|
"""Return the next :class:`EvtEvent` matching the query"""
|
|
try:
|
|
event = EvtEvent(channel=self.channel)
|
|
ret = gdef.DWORD()
|
|
windows.winproxy.EvtNext(self, 1, event, self.timeout, 0, ret)
|
|
except WindowsError as e:
|
|
if e.winerror == gdef.ERROR_NO_MORE_ITEMS:
|
|
raise StopIteration
|
|
raise
|
|
assert ret.value == 1
|
|
return event
|
|
|
|
def __iter__(self):
|
|
return self
|
|
|
|
def seek(self, position, seek_flags=None):
|
|
"""Seek to ``position``.
|
|
``seek_flags`` can be one of:
|
|
|
|
* ``None``
|
|
* ``EvtSeekRelativeToFirst``
|
|
* ``EvtSeekRelativeToLast``
|
|
* ``EvtSeekRelativeToBookmark``
|
|
|
|
If ``seek_flags`` is None:
|
|
|
|
* ``position >= 0`` will use ``EvtSeekRelativeToFirst``
|
|
* ``position < 0`` will use ``EvtSeekRelativeToLast`` and with ``position+1``
|
|
* This allow retrieve the ``position`` lasts events
|
|
"""
|
|
|
|
if seek_flags is None:
|
|
if position >= 0:
|
|
seek_flags = gdef.EvtSeekRelativeToFirst
|
|
else:
|
|
# -1 + EvtSeekRelativeToLast will give us the last 2 events
|
|
# So passing (-1, None) will give us the last event only
|
|
# If user do not want this calcul it can directly pass seek_flags
|
|
seek_flags = gdef.EvtSeekRelativeToLast
|
|
position += 1
|
|
windows.winproxy.EvtSeek(self, position, 0, 0, seek_flags)
|
|
|
|
next = __next__ # Yep.. real name is 'next' in Py2 :D
|
|
|
|
def all(self): # SqlAlchemy like :)
|
|
"""Return a list with all the query results
|
|
|
|
:rtype: [:class:`EvtEvent`] -- A list of Event
|
|
"""
|
|
return list(self)
|
|
|
|
def first(self): # SqlAlchemy like :) -> allow testing in interactive console
|
|
"""Return the first query result
|
|
|
|
:rtype: :class:`EvtEvent` -- An Event
|
|
"""
|
|
return next(iter(self))
|
|
|
|
|
|
|
|
|
|
class EvtEvent(EvtHandle):
|
|
"""An Event log"""
|
|
def __init__(self, handle=0, channel=None):
|
|
super(EvtEvent, self).__init__(handle)
|
|
self.channel = channel
|
|
|
|
def render(self, ctx, rtype):
|
|
size = 0x10000
|
|
buffer = ctypes.c_buffer(size)
|
|
rsize = gdef.DWORD()
|
|
elementnb = gdef.DWORD()
|
|
try:
|
|
windows.winproxy.EvtRender(ctx, self, rtype, size, buffer, rsize, elementnb)
|
|
except WindowsError as e:
|
|
if e.winerror != gdef.ERROR_INSUFFICIENT_BUFFER:
|
|
raise
|
|
size = rsize.value
|
|
buffer = ctypes.c_buffer(size)
|
|
windows.winproxy.EvtRender(ctx, self, rtype, size, buffer, rsize, elementnb)
|
|
# Adapting return value type
|
|
if rtype != gdef.EvtRenderEventValues:
|
|
# import pdb;pdb.set_trace()
|
|
# assert elementnb.value == 1
|
|
return buffer[:rsize.value]
|
|
# print("Got <{0}> elt".format(elementnb.value))
|
|
return list((ImprovedEVT_VARIANT * elementnb.value).from_buffer(buffer))
|
|
|
|
def render_xml(self):
|
|
xml = self.render(None, 1).decode("utf-16")
|
|
assert xml[-1] == "\x00"
|
|
return xml[:-1]
|
|
|
|
def value(self, name, **kwargs):
|
|
"""Retrieve a value from the event.
|
|
``name`` is an XPath expressions that uniquely identify a node or attribute in the event.
|
|
(see https://msdn.microsoft.com/en-us/library/windows/desktop/aa385352(v=vs.85).aspx)
|
|
"""
|
|
values = self.get_values((name,), **kwargs)
|
|
assert len(values) == 1
|
|
return values[0]
|
|
|
|
def get_values(self, values, flags=gdef.EvtRenderContextValues):
|
|
nbelt = len(values)
|
|
pwstr_values = tuple(gdef.LPWSTR(v) for v in values)
|
|
pwstr_rarray = (gdef.LPWSTR * nbelt)(*pwstr_values)
|
|
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa385352(v=vs.85).aspx
|
|
# An array of XPath expressions that uniquely identify a node or attribute in the event that you want to render.
|
|
# Each value wil return 1 node :)
|
|
ctx = windows.winproxy.EvtCreateRenderContext(nbelt, pwstr_rarray, gdef.EvtRenderContextValues)
|
|
result = self.render(ctx, gdef.EvtRenderEventValues)
|
|
windows.winproxy.EvtClose(ctx)
|
|
return [r.value for r in result]
|
|
|
|
def system_values(self): # POC: use this for all @property based on system data ?
|
|
ctx = windows.winproxy.EvtCreateRenderContext(0, None, gdef.EvtRenderContextSystem)
|
|
result = self.render(ctx, gdef.EvtRenderEventValues)
|
|
windows.winproxy.EvtClose(ctx)
|
|
return [r.value for r in result]
|
|
|
|
def event_values(self):
|
|
"""The values of the event in a list"""
|
|
ctx = windows.winproxy.EvtCreateRenderContext(0, None, gdef.EvtRenderContextUser)
|
|
result = self.render(ctx, gdef.EvtRenderEventValues)
|
|
windows.winproxy.EvtClose(ctx)
|
|
return [r.value for r in result]
|
|
|
|
def get_raw_values(self, values, flags=gdef.EvtRenderContextValues):
|
|
nbelt = len(values)
|
|
pwstr_values = tuple(gdef.LPWSTR(v) for v in values)
|
|
pwstr_rarray = (gdef.LPWSTR * nbelt)(*pwstr_values)
|
|
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa385352(v=vs.85).aspx
|
|
# An array of XPath expressions that uniquely identify a node or attribute in the event that you want to render.
|
|
# Each value will return 1 node :)
|
|
ctx = windows.winproxy.EvtCreateRenderContext(nbelt, pwstr_rarray, gdef.EvtRenderContextValues)
|
|
result = self.render(ctx, gdef.EvtRenderEventValues)
|
|
windows.winproxy.EvtClose(ctx)
|
|
return list(result)
|
|
|
|
|
|
# Properties arround common Event/System values
|
|
@property
|
|
def provider(self):
|
|
"""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"""
|
|
return self.value("Event/System/EventID")
|
|
|
|
@property
|
|
def version(self):
|
|
"""The version of the Event"""
|
|
return self.value("Event/System/Version")
|
|
|
|
@property
|
|
def level(self):
|
|
"""The level of the Event"""
|
|
return self.value("Event/System/Level")
|
|
|
|
@property
|
|
def opcode(self):
|
|
"""The opcode of the Event"""
|
|
return self.value("Event/System/Opcode")
|
|
|
|
@property
|
|
def time_created(self):
|
|
"""The creation time of the Event"""
|
|
return self.value("Event/System/TimeCreated/@SystemTime")
|
|
|
|
@property
|
|
def pid(self):
|
|
"""The process ID of the Event"""
|
|
return self.value("Event/System/Execution/@ProcessID")
|
|
|
|
@property
|
|
def tid(self):
|
|
"""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
|
|
|
|
:type: :class:`EventMetadata`
|
|
"""
|
|
try:
|
|
return self.channel.get_event_metadata(self.id)
|
|
except KeyError as e:
|
|
if not self.channel.config.classic:
|
|
raise
|
|
# id not found: try via the Provider in the event (classic channel)
|
|
return self.channel.get_classic_event_metadata(self.id, self.provider)
|
|
|
|
# Test
|
|
@property
|
|
def data(self): # user/event specifique data
|
|
"""A dict of EventData Name:Value for the current dict.
|
|
|
|
:type: :class:`dict`
|
|
"""
|
|
# 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 ?
|
|
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:
|
|
# <Data Name='FIELD_NAME'>FIELD_VALUE</Data>
|
|
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:
|
|
# <UserData>
|
|
# <EventXML xmlns="Event_NS">
|
|
# <FIELD_NAME>FIELD_VALUE</FIELD_NAME>
|
|
# </EventXML>
|
|
# </UserData>
|
|
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``"""
|
|
return windows.utils.datetime_from_filetime(self.time_created)
|
|
|
|
def __repr__(self):
|
|
creation_time = windows.utils.datetime_from_filetime(self.time_created)
|
|
return '<{0} id="{self.id}" time="{creation_time}">'.format(type(self).__name__, self=self, creation_time=creation_time)
|
|
|
|
|
|
class ImprovedEVT_VARIANT(gdef.EVT_VARIANT):
|
|
VALUE_MAPPER = {
|
|
gdef.EvtVarTypeNull : 'NoneValue',
|
|
gdef.EvtVarTypeString : 'StringVal',
|
|
gdef.EvtVarTypeAnsiString : 'AnsiStringVal',
|
|
gdef.EvtVarTypeSByte : 'SByteVal',
|
|
gdef.EvtVarTypeByte : 'ByteVal',
|
|
gdef.EvtVarTypeInt16 : 'Int16Val',
|
|
gdef.EvtVarTypeUInt16 : 'UInt16Val',
|
|
gdef.EvtVarTypeInt32 : 'Int32Val',
|
|
gdef.EvtVarTypeUInt32 : 'UInt32Val',
|
|
gdef.EvtVarTypeInt64 : 'Int64Val',
|
|
gdef.EvtVarTypeUInt64 : 'UInt64Val',
|
|
gdef.EvtVarTypeSingle : 'SingleVal',
|
|
gdef.EvtVarTypeDouble : 'DoubleVal',
|
|
gdef.EvtVarTypeBoolean : 'BooleanVal',
|
|
gdef.EvtVarTypeBinary : 'BinaryVal',
|
|
gdef.EvtVarTypeGuid : 'GuidVal',
|
|
gdef.EvtVarTypeSizeT : 'SizeTVal',
|
|
gdef.EvtVarTypeFileTime : 'FileTimeVal',
|
|
gdef.EvtVarTypeSysTime : 'SysTimeVal',
|
|
gdef.EvtVarTypeSid : 'SidVal',
|
|
gdef.EvtVarTypeHexInt32 : 'UInt32Val',
|
|
gdef.EvtVarTypeHexInt64 : 'UInt64Val',
|
|
gdef.EvtVarTypeEvtHandle : 'EvtHandleVal',
|
|
gdef.EvtVarTypeEvtXml : 'XmlVal',
|
|
# Array types: TODO: generic stuff
|
|
gdef.EvtVarTypeString + gdef.EVT_VARIANT_TYPE_ARRAY : "StringArr",
|
|
gdef.EvtVarTypeUInt16 + gdef.EVT_VARIANT_TYPE_ARRAY : "UInt16Arr",
|
|
gdef.EvtVarTypeUInt32 + gdef.EVT_VARIANT_TYPE_ARRAY : "UInt32Arr",
|
|
gdef.EvtVarTypeUInt64 + gdef.EVT_VARIANT_TYPE_ARRAY : "UInt64Arr",
|
|
}
|
|
NoneValue = None
|
|
|
|
@property
|
|
def Type(self):
|
|
raw_type = super(ImprovedEVT_VARIANT, self).Type
|
|
return gdef.EVT_VARIANT_TYPE.mapper.get(raw_type, raw_type)
|
|
|
|
@property
|
|
def value(self): # Prototype !!
|
|
attrname = self.VALUE_MAPPER[self.Type]
|
|
# print("Resolve type <{0}> -> {1}".format(self.Type, attrname))
|
|
v = getattr(self, attrname)
|
|
if self.Type == gdef.EvtVarTypeBinary:
|
|
v = v[:self.Count] # No need for a raw UBYTE ptr
|
|
elif self.Type == gdef.EvtVarTypeGuid:
|
|
v = v[0] # Deref LP_GUID
|
|
elif self.Type & gdef.EVT_VARIANT_TYPE_ARRAY:
|
|
# TODO: handle all array type
|
|
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
|
|
elif isinstance(value, bytes):
|
|
# not basestring and bytes -> py3 bytes
|
|
vtype = gdef.EvtVarTypeBinary
|
|
value = windows.utils.BUFFER(gdef.BYTE).from_buffer_copy(value)
|
|
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)
|
|
|
|
|
|
|
|
class EvtChannel(object):
|
|
"""An Event Log channel"""
|
|
DEFAULT_QUERY_FLAGS = gdef.EvtQueryChannelPath + gdef.EvtQueryForwardDirection
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.event_metadata_by_id = {}
|
|
self.classic_event_metadata_by_id = {} # For classic only
|
|
|
|
def query(self, filter=None, ids=None, timeout=None):
|
|
"""Query the event with the ``ids`` or perform a query with the raw query ``filter``
|
|
|
|
Both parameters are mutually exclusive.
|
|
|
|
.. note:: Here are some query examples
|
|
|
|
List all events with a event data attribute named 'RuleName':
|
|
``Event/EventData/Data[@Name='RuleName']``
|
|
|
|
List all events with a event data value of 'C:\\\\WINDOWS\\\\System32\\\\svchost.exe':
|
|
``Event/EventData[Data='C:\\WINDOWS\\System32\\svchost.exe']``
|
|
|
|
List all events with an EventID of 2006:
|
|
``Event/System[EventID=2006]``
|
|
|
|
List all event with a given EventID while searching for a specific field value (Sysmon for the test here)
|
|
``Event/System[EventID=3] and Event/EventData/Data[@Name='DestinationIp'] and Event/EventData[Data='10.0.0.2']``
|
|
|
|
:rtype: :class:`EvtQuery`
|
|
"""
|
|
if ids and filter:
|
|
raise ValueError("<ids> and <filter> are mutually exclusive")
|
|
if ids is not None:
|
|
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)
|
|
query_handle = winproxy.EvtQuery(None, self.name, filter, self.DEFAULT_QUERY_FLAGS)
|
|
return EvtQuery(query_handle, self, timeout=timeout)
|
|
|
|
@property
|
|
def events(self):
|
|
"""The list of all events in the channels, an alias for ``channel.query().all()``
|
|
|
|
:type: [:class:`EvtEvent`] -- A list of :class:`EvtEvent`
|
|
"""
|
|
return self.query().all()
|
|
|
|
@property
|
|
def config(self):
|
|
"""The configuration of the channel
|
|
|
|
:type: :class:`ChannelConfig`
|
|
"""
|
|
return ChannelConfig.from_channel_name(self.name)
|
|
|
|
def get_event_metadata(self, id):
|
|
"""Return the metadata for the event ID ``id``
|
|
|
|
:rtype: :class:`EventMetadata`
|
|
"""
|
|
try:
|
|
return self.event_metadata_by_id[id]
|
|
except KeyError as e:
|
|
pass
|
|
|
|
pub_metada = self.config.publisher.metadata
|
|
self.event_metadata_by_id = {evtm.id: evtm for evtm in pub_metada.events_metadata}
|
|
return self.event_metadata_by_id[id]
|
|
|
|
def get_classic_event_metadata(self, id, providername):
|
|
if providername not in self.classic_event_metadata_by_id:
|
|
# print("CALCUL FOR PROVIDER: <{0}> !!!!!!!!!!!!!!".format(providername))
|
|
publisher = EvtPublisher(providername)
|
|
events_metadata = {x.id: x for x in publisher.metadata.events_metadata}
|
|
self.classic_event_metadata_by_id[providername] = events_metadata
|
|
return self.classic_event_metadata_by_id[providername][id]
|
|
|
|
|
|
def __repr__(self):
|
|
return '<{0} "{1}">'.format(type(self).__name__, self.name)
|
|
|
|
|
|
class EvtFile(EvtChannel):
|
|
"""Represent an Evtx file"""
|
|
DEFAULT_QUERY_FLAGS = gdef.EvtQueryFilePath + gdef.EvtQueryForwardDirection
|
|
|
|
@property
|
|
def config(self):
|
|
"""Not implemented for EvtFile
|
|
|
|
:raise: :class:`NotImplementedError`
|
|
"""
|
|
raise NotImplementedError("Cannot retrieve the configuration of an EvtFile")
|
|
|
|
|
|
class ChannelConfig(EvtHandle):
|
|
"""The configuration of a event channel"""
|
|
def __init__(self, handle, name=None):
|
|
super(ChannelConfig, self).__init__(handle)
|
|
self.name = name
|
|
|
|
@classmethod
|
|
def from_channel_name(cls, name):
|
|
"""Return the :class:`ChannelConfig` for the channel ``name``"""
|
|
return cls(winproxy.EvtOpenChannelConfig(None, name, 0), name)
|
|
|
|
@property
|
|
def publisher(self):
|
|
"""The :class:`EvtPublisher` for the channel"""
|
|
return EvtPublisher(chaninfo(self, gdef.EvtChannelConfigOwningPublisher).value)
|
|
|
|
def publishers(self):
|
|
"TEST"
|
|
return [EvtPublisher(pub) for pub in chaninfo(self, gdef.EvtChannelPublisherList).value]
|
|
|
|
@property
|
|
def keywords(self):
|
|
return int(chaninfo(self, gdef.EvtChannelPublishingConfigKeywords).value)
|
|
|
|
@property
|
|
def enabled(self):
|
|
return bool(chaninfo(self, gdef.EvtChannelConfigEnabled).value)
|
|
|
|
|
|
@property
|
|
def classic(self):
|
|
"""``True`` if the channel is a classic event channel (for example the Application or System log)"""
|
|
return bool(chaninfo(self, gdef.EvtChannelConfigClassicEventlog).value)
|
|
|
|
def __repr__(self):
|
|
return '<{0} "{1}">'.format(type(self).__name__, self.name)
|
|
|
|
|
|
class PublisherMetadataChannel(object):
|
|
"""Represent a PublisherMetadataChannel (see https://docs.microsoft.com/en-us/windows/win32/api/winevt/ne-winevt-evt_publisher_metadata_property_id)"""
|
|
|
|
def __init__(self, pub_metadata, channel_id):
|
|
super(PublisherMetadataChannel, self).__init__()
|
|
self.pub_metadata = pub_metadata
|
|
self._id = channel_id
|
|
|
|
def _query_channel_metadata_property(self, propertyid):
|
|
return self.pub_metadata.chanrefs.property(propertyid, self._id)
|
|
|
|
@property
|
|
def flags(self):
|
|
"""The flags of the ``PublisherMetadataChannel``"""
|
|
return int(self._query_channel_metadata_property(gdef.EvtPublisherMetadataChannelReferenceFlags))
|
|
|
|
@property
|
|
def name(self):
|
|
"""The name of the ``PublisherMetadataChannel``"""
|
|
return str(self._query_channel_metadata_property(gdef.EvtPublisherMetadataChannelReferencePath))
|
|
|
|
@property
|
|
def id(self):
|
|
"""The reference id of the ``PublisherMetadataChannel``"""
|
|
return int(self._query_channel_metadata_property(gdef.EvtPublisherMetadataChannelReferenceID))
|
|
|
|
@property
|
|
def index(self):
|
|
"""The reference index of the ``PublisherMetadataChannel``"""
|
|
return int(self._query_channel_metadata_property(gdef.EvtPublisherMetadataChannelReferenceIndex))
|
|
|
|
@property
|
|
def message_id(self):
|
|
"""The message id of the ``PublisherMetadataChannel``"""
|
|
return int(self._query_channel_metadata_property(gdef.EvtPublisherMetadataChannelReferenceMessageID))
|
|
|
|
|
|
class PublisherMetadataLevel(object):
|
|
"""Represent a PublisherMetadataLevel (see https://docs.microsoft.com/en-us/windows/win32/api/winevt/ne-winevt-evt_publisher_metadata_property_id)"""
|
|
def __init__(self, pub_metadata, channel_id):
|
|
super(PublisherMetadataLevel, self).__init__()
|
|
self.pub_metadata = pub_metadata
|
|
self._id = channel_id
|
|
|
|
def _query_level_metadata_property(self, propertyid):
|
|
return self.pub_metadata.levelrefs.property(propertyid, self._id)
|
|
|
|
@property
|
|
def name(self):
|
|
return str(self._query_level_metadata_property(gdef.EvtPublisherMetadataLevelName))
|
|
|
|
@property
|
|
def value(self):
|
|
return int(self._query_level_metadata_property(gdef.EvtPublisherMetadataLevelValue))
|
|
|
|
@property
|
|
def message_id(self):
|
|
return int(self._query_level_metadata_property(gdef.EvtPublisherMetadataLevelMessageID))
|
|
|
|
|
|
class PublisherMetadataOpcode(object):
|
|
"""Represent a PublisherMetadataOpcode (see https://docs.microsoft.com/en-us/windows/win32/api/winevt/ne-winevt-evt_publisher_metadata_property_id)"""
|
|
def __init__(self, pub_metadata, channel_id):
|
|
super(PublisherMetadataOpcode, self).__init__()
|
|
self.pub_metadata = pub_metadata
|
|
self._id = channel_id
|
|
|
|
def _query_opcode_metadata_property(self, propertyid):
|
|
return self.pub_metadata.opcoderefs.property(propertyid, self._id)
|
|
|
|
@property
|
|
def name(self):
|
|
"""The name of the ``PublisherMetadataOpcode``"""
|
|
return str(self._query_opcode_metadata_property(gdef.EvtPublisherMetadataOpcodeName))
|
|
|
|
@property
|
|
def value(self):
|
|
"""The opcode value of the ``PublisherMetadataOpcode``"""
|
|
return int(self._query_opcode_metadata_property(gdef.EvtPublisherMetadataOpcodeValue))
|
|
|
|
@property
|
|
def message_id(self):
|
|
"""The message id of the ``PublisherMetadataOpcode``"""
|
|
return int(self._query_opcode_metadata_property(gdef.EvtPublisherMetadataOpcodeMessageID))
|
|
|
|
|
|
class PublisherMetadataKeyword(object):
|
|
"""Represent a PublisherMetadataKeyword (see https://docs.microsoft.com/en-us/windows/win32/api/winevt/ne-winevt-evt_publisher_metadata_property_id)"""
|
|
|
|
def __init__(self, pub_metadata, channel_id):
|
|
super(PublisherMetadataKeyword, self).__init__()
|
|
self.pub_metadata = pub_metadata
|
|
self._id = channel_id
|
|
|
|
def _query_keyword_metadata_property(self, propertyid):
|
|
return self.pub_metadata.keywordrefs.property(propertyid, self._id)
|
|
|
|
@property
|
|
def name(self):
|
|
"""The name of the ``PublisherMetadataKeyword``"""
|
|
return str(self._query_keyword_metadata_property(gdef.EvtPublisherMetadataKeywordName))
|
|
|
|
@property
|
|
def value(self):
|
|
"""The value of the ``PublisherMetadataKeyword``"""
|
|
return int(self._query_keyword_metadata_property(gdef.EvtPublisherMetadataKeywordValue))
|
|
|
|
@property
|
|
def message_id(self):
|
|
"""The message id of the ``PublisherMetadataKeyword``"""
|
|
return int(self._query_keyword_metadata_property(gdef.EvtPublisherMetadataKeywordMessageID))
|
|
|
|
class PublisherMetadataTask(object):
|
|
"""Represent a PublisherMetadataTask (see https://docs.microsoft.com/en-us/windows/win32/api/winevt/ne-winevt-evt_publisher_metadata_property_id)"""
|
|
def __init__(self, pub_metadata, channel_id):
|
|
super(PublisherMetadataTask, self).__init__()
|
|
self.pub_metadata = pub_metadata
|
|
self._id = channel_id
|
|
|
|
def _query_keyword_metadata_property(self, propertyid):
|
|
return self.pub_metadata.taskrefs.property(propertyid, self._id)
|
|
|
|
@property
|
|
def name(self):
|
|
"""The name of the ``PublisherMetadataTask``"""
|
|
return str(self._query_keyword_metadata_property(gdef.EvtPublisherMetadataTaskName))
|
|
|
|
@property
|
|
def value(self):
|
|
"""The value of the ``PublisherMetadataTask``"""
|
|
return int(self._query_keyword_metadata_property(gdef.EvtPublisherMetadataTaskValue))
|
|
|
|
@property
|
|
def event_guid(self):
|
|
"""The event GUId of the ``PublisherMetadataTask``"""
|
|
return self._query_keyword_metadata_property(gdef.EvtPublisherMetadataTaskEventGuid)
|
|
|
|
@property
|
|
def message_id(self):
|
|
"""The message ID GUId of the ``PublisherMetadataTask``"""
|
|
return int(self._query_keyword_metadata_property(gdef.EvtPublisherMetadataTaskMessageID))
|
|
|
|
class EvtPublisher(object):
|
|
"""An Event provider"""
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
@property
|
|
def metadata(self):
|
|
"""Return the metadata for this publisher
|
|
|
|
:type: :class:`PublisherMetadata`
|
|
"""
|
|
return PublisherMetadata.from_publisher_name(self.name)
|
|
|
|
def __repr__(self):
|
|
return '<{0} "{1}">'.format(type(self).__name__, self.name)
|
|
|
|
|
|
class PublisherMetadata(EvtHandle):
|
|
"""The metadata about an event provider"""
|
|
def __init__(self, handle, name=None):
|
|
super(PublisherMetadata, self).__init__(handle)
|
|
self.name = name
|
|
|
|
@classmethod
|
|
def from_publisher_name(cls, name):
|
|
"""The :class:`PublisherMetadata` for the publisher ``name``"""
|
|
return cls(winproxy.EvtOpenPublisherMetadata(None, name, None, 0, 0), name)
|
|
|
|
@property
|
|
def guid(self):
|
|
"""The GUID associated with this provider
|
|
|
|
:type: [:class:`GUID`] -- the GUID in a XXXXXXXXXX-YYYY-ZZZZ-TTTT-VVVVVVVVVV form
|
|
"""
|
|
return publishinfo(self, gdef.EvtPublisherMetadataPublisherGuid).value
|
|
|
|
@property
|
|
def chanrefs(self):
|
|
"""Identifies the channels child element of the provider.
|
|
|
|
:type: :class:`PropertyArray`
|
|
"""
|
|
return PropertyArray(publishinfo(self, gdef.EvtPublisherMetadataChannelReferences).value)
|
|
|
|
@property
|
|
def levelrefs(self):
|
|
"""Identifies the levels child element of the provider.
|
|
|
|
:type: :class:`PropertyArray`
|
|
"""
|
|
return PropertyArray(publishinfo(self, gdef.EvtPublisherMetadataLevels).value)
|
|
|
|
@property
|
|
def opcoderefs(self):
|
|
"""Identifies the opcodes child element of the provider.
|
|
|
|
:type: :class:`PropertyArray`
|
|
"""
|
|
return PropertyArray(publishinfo(self, gdef.EvtPublisherMetadataOpcodes).value)
|
|
|
|
@property
|
|
def keywordrefs(self):
|
|
"""The list of keywords defined by this provider
|
|
|
|
:type: :class:`PropertyArray`
|
|
"""
|
|
return PropertyArray(publishinfo(self, gdef.EvtPublisherMetadataKeywords).value)
|
|
|
|
@property
|
|
def taskrefs(self):
|
|
"""The list of tasks defined by this provider
|
|
|
|
:type: :class:`PropertyArray`
|
|
"""
|
|
return PropertyArray(publishinfo(self, gdef.EvtPublisherMetadataTasks).value)
|
|
|
|
@property
|
|
def channels_metadata(self):
|
|
"""The :class:`PublisherMetadataChannel` for each channel this provider defines
|
|
|
|
:yield: :class:`PublisherMetadataChannel`
|
|
"""
|
|
return [PublisherMetadataChannel(self, i) for i in range(self.chanrefs.size)]
|
|
|
|
@property
|
|
def levels_metadata(self):
|
|
"""The :class:`PublisherMetadataLevel` for each level this provider defines
|
|
|
|
:yield: :class:`PublisherMetadataLevel`
|
|
"""
|
|
return [PublisherMetadataLevel(self, i) for i in range(self.levelrefs.size)]
|
|
|
|
@property
|
|
def opcodes_metadata(self):
|
|
"""The :class:`PublisherMetadataOpcode` for each opcode this provider defines
|
|
|
|
:yield: :class:`PublisherMetadataOpcode`
|
|
"""
|
|
return [PublisherMetadataOpcode(self, i) for i in range(self.opcoderefs.size)]
|
|
|
|
@property
|
|
def tasks_metadata(self):
|
|
"""The :class:`PublisherMetadataTask` for each opcode this provider defines
|
|
|
|
:yield: :class:`PublisherMetadataTask`
|
|
"""
|
|
return [PublisherMetadataTask(self, i) for i in range(self.taskrefs.size)]
|
|
|
|
@property
|
|
def keywords_metadata(self):
|
|
"""The :class:`PublisherMetadataKeyword` for each opcode this provider defines
|
|
|
|
:yield: :class:`PublisherMetadataKeyword`
|
|
"""
|
|
return [PublisherMetadataKeyword(self, i) for i in range(self.keywordrefs.size)]
|
|
|
|
@property
|
|
def events_metadata(self):
|
|
"""The :class:`EventMetadata` for each event this provider defines
|
|
|
|
:yield: :class:`EventMetadata`
|
|
"""
|
|
eh = winproxy.EvtOpenEventMetadataEnum(self, 0)
|
|
with ClosingEvtHandle(eh):
|
|
while True:
|
|
try:
|
|
nh = windows.winproxy.EvtNextEventMetadata(eh, 0)
|
|
yield EventMetadata(nh)
|
|
except WindowsError as e:
|
|
if e.winerror != gdef.ERROR_NO_MORE_ITEMS:
|
|
raise
|
|
break
|
|
|
|
@property
|
|
def channel_name_by_id(self):
|
|
"""The dict of channel defined by this provider by their id
|
|
|
|
:type: :class:`dict`
|
|
"""
|
|
chansref = self.chanrefs
|
|
channame_by_value_id = {}
|
|
for i in range(chansref.size):
|
|
value = chansref.property(gdef.EvtPublisherMetadataChannelReferenceID, i)
|
|
name = chansref.property(gdef.EvtPublisherMetadataChannelReferencePath, i)
|
|
channame_by_value_id[value] = name
|
|
return channame_by_value_id
|
|
|
|
@property
|
|
def channels(self):
|
|
"""The list of :class:`EvtChannel` defined by this provider
|
|
|
|
:type: [:class:`EvtChannel`] -- A list of :class:`EvtChannel`
|
|
"""
|
|
chansref = self.chanrefs
|
|
propertyid = gdef.EvtPublisherMetadataChannelReferencePath
|
|
return [EvtChannel(chansref.property(propertyid, i)) for i in range(chansref.size)]
|
|
|
|
def message(self, msgid):
|
|
"TODO"
|
|
size = 0x1000
|
|
buffer = ctypes.c_buffer(size)
|
|
sbuff = ctypes.cast(buffer, gdef.LPWSTR)
|
|
outsize = gdef.DWORD()
|
|
try:
|
|
winproxy.EvtFormatMessage(self, None, msgid, 0, None, gdef.EvtFormatMessageId, size, sbuff, outsize)
|
|
except WindowsError as e:
|
|
if e.winerror != gdef.ERROR_EVT_UNRESOLVED_VALUE_INSERT:
|
|
raise
|
|
return sbuff.value
|
|
|
|
@property
|
|
def message_id(self):
|
|
"""
|
|
"""
|
|
return publishinfo(self, gdef.EvtPublisherMetadataPublisherMessageID).value
|
|
|
|
@property
|
|
def message_filepath(self):
|
|
"""
|
|
"""
|
|
return publishinfo(self, gdef.EvtPublisherMetadataMessageFilePath).value
|
|
|
|
@property
|
|
def message_resource_filepath(self):
|
|
"""
|
|
"""
|
|
return publishinfo(self, gdef.EvtPublisherMetadataResourceFilePath).value
|
|
|
|
@property
|
|
def message_parameter_filepath(self):
|
|
"""
|
|
"""
|
|
return publishinfo(self, gdef.EvtPublisherMetadataParameterFilePath).value
|
|
|
|
|
|
def __repr__(self):
|
|
return '<{0} "{1}">'.format(type(self).__name__, self.name)
|
|
|
|
|
|
class PropertyArray(gdef.EVT_OBJECT_ARRAY_PROPERTY_HANDLE):
|
|
"TODO"
|
|
@property
|
|
def size(self):
|
|
array_size = gdef.DWORD()
|
|
windows.winproxy.EvtGetObjectArraySize(self, array_size)
|
|
return array_size.value
|
|
|
|
def property(self, type, index):
|
|
return arrayproperty(self, type, index).value
|
|
|
|
class EventMetadata(EvtHandle):
|
|
"""The Metadata about a given Event type
|
|
|
|
|
|
see: https://msdn.microsoft.com/en-us/library/windows/desktop/aa385517(v=vs.85).aspx
|
|
"""
|
|
|
|
@property
|
|
def id(self):
|
|
"""The ID of the Event"""
|
|
# https://docs.microsoft.com/en-us/windows/desktop/wes/eventschema-systempropertiestype-complextype
|
|
# Qualifiers:
|
|
# A legacy provider uses a 32-bit number to identify its events.
|
|
# If the event is logged by a legacy provider, the value of EventID
|
|
# element contains the low-order 16 bits of the event identifier and the
|
|
# Qualifier attribute contains the high-order 16 bits of the event identifier.
|
|
# [Question] Only true for legacy provider / channels ??
|
|
return eventinfo(self, gdef.EventMetadataEventID).value & 0xffff
|
|
|
|
@property
|
|
def version(self):
|
|
"""The version of the Event"""
|
|
return eventinfo(self, gdef.EventMetadataEventVersion).value
|
|
|
|
@property
|
|
def channel_id(self):
|
|
"""The Channel attribute of the Event definition"""
|
|
return eventinfo(self, gdef.EventMetadataEventChannel).value
|
|
|
|
@property
|
|
def keyword(self):
|
|
"""The keyword attribute of the Event definition"""
|
|
return eventinfo(self, gdef.EventMetadataEventKeyword).value
|
|
|
|
@property
|
|
def opcode(self):
|
|
"""The opcode attribute of the Event definition"""
|
|
return eventinfo(self, gdef.EventMetadataEventOpcode).value
|
|
|
|
@property
|
|
def level(self):
|
|
"""The level attribute of the Event definition"""
|
|
return eventinfo(self, gdef.EventMetadataEventLevel).value
|
|
|
|
@property
|
|
def task(self):
|
|
"""The task attribute of the Event definition"""
|
|
return eventinfo(self, gdef.EventMetadataEventTask).value
|
|
|
|
@property
|
|
def message_id(self):
|
|
"""Identifies the message attribute of the event definition."""
|
|
return eventinfo(self, gdef.EventMetadataEventMessageID).value
|
|
|
|
@property
|
|
def template(self):
|
|
"""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`
|
|
"""
|
|
template = self.template
|
|
if not template:
|
|
return []
|
|
return list(self._event_data_generator(template))
|
|
|
|
def yolo(self):
|
|
template = self.template
|
|
if not template:
|
|
return None
|
|
xmltemplate = xml.dom.minidom.parseString(template)
|
|
return xmltemplate
|
|
|
|
|
|
class EvtlogManager(object):
|
|
"""The main Evt class to open Evt channel/publisher and evtx file"""
|
|
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa385784(v=vs.85).aspx
|
|
|
|
def is_implemented(self):
|
|
"""Return ``True`` if the new Evt-API is implemented on the current computer
|
|
|
|
see: https://msdn.microsoft.com/en-us/library/windows/desktop/aa385784(v=vs.85).aspx
|
|
"""
|
|
return windows.winproxy.is_implemented(windows.winproxy.EvtQuery)
|
|
|
|
@property
|
|
def channels(self):
|
|
h = windows.winproxy.EvtOpenChannelEnum(None, 0)
|
|
size = 0x1000
|
|
buffer = ctypes.create_unicode_buffer(size)
|
|
ressize = gdef.DWORD()
|
|
with ClosingEvtHandle(h):
|
|
while True:
|
|
try:
|
|
windows.winproxy.EvtNextChannelPath(h, size, buffer, ressize)
|
|
except WindowsError as e:
|
|
if e.winerror != gdef.ERROR_NO_MORE_ITEMS:
|
|
raise
|
|
return
|
|
assert buffer[ressize.value - 1] == "\x00"
|
|
name = buffer[:ressize.value - 1]
|
|
chan = EvtChannel(name)
|
|
yield chan
|
|
|
|
@property
|
|
def publishers(self):
|
|
h = windows.winproxy.EvtOpenPublisherEnum(None, 0)
|
|
size = 0x1000
|
|
buffer = ctypes.create_unicode_buffer(size)
|
|
ressize = gdef.DWORD()
|
|
with ClosingEvtHandle(h):
|
|
while True:
|
|
try:
|
|
windows.winproxy.EvtNextPublisherId(h, size, buffer, ressize)
|
|
except WindowsError as e:
|
|
if e.winerror != gdef.ERROR_NO_MORE_ITEMS:
|
|
raise
|
|
return
|
|
assert buffer[ressize.value - 1] == "\x00"
|
|
name = buffer[:ressize.value - 1]
|
|
publisher = EvtPublisher(name)
|
|
yield publisher
|
|
|
|
|
|
|
|
def open_channel(self, name):
|
|
"""Open the Evt channel with ``name``
|
|
|
|
:rtype: :class:`EvtChannel`
|
|
"""
|
|
chan = EvtChannel(name)
|
|
chan.config # Force to retrieve a handle (check channel exists)
|
|
return chan
|
|
|
|
def open_evtx_file(self, filename):
|
|
"""Open the evtx file with ``filename``
|
|
|
|
:rtype: :class:`EvtFile`
|
|
"""
|
|
with windows.utils.DisableWow64FsRedirection():
|
|
if not os.path.exists(filename):
|
|
raise WindowsError(gdef.ERROR_FILE_NOT_FOUND, "Could not find file <{0}>".format(filename))
|
|
file = EvtFile(filename)
|
|
return file
|
|
|
|
def open_publisher(self, name):
|
|
"""Open the Evt publisher with ``name``
|
|
|
|
:rtype: :class:`EvtPublisher`
|
|
"""
|
|
publisher = EvtPublisher(name)
|
|
publisher.metadata # Force to retrieve a handle (check channel exists)
|
|
return publisher
|
|
|
|
def __getitem__(self, name):
|
|
"""Open the Evt Channel/Publisher or Evtx file with ``name``
|
|
|
|
:rtype: :class:`EvtChannel` or :class:`EvtPublisher` or :class:`EvtFile`
|
|
"""
|
|
try:
|
|
return self.open_channel(name)
|
|
except WindowsError as e:
|
|
if e.winerror == gdef.ERROR_ACCESS_DENIED:
|
|
raise
|
|
|
|
try:
|
|
return self.open_publisher(name)
|
|
except WindowsError as e:
|
|
if e.winerror == gdef.ERROR_ACCESS_DENIED:
|
|
raise
|
|
# Raise FILE_NOT_FOUND if not found (last chance)
|
|
return self.open_evtx_file(name)
|
|
|