mirror of
https://github.com/hakril/PythonForWindows
synced 2026-06-08 14:31:45 +00:00
add current word on event logs
This commit is contained in:
@@ -0,0 +1,503 @@
|
||||
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
|
||||
|
||||
|
||||
# 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 high-level API
|
||||
class EvtQuery(gdef.EVT_HANDLE):
|
||||
TIMEOUT = 0x1000
|
||||
|
||||
def __init__(self, handle=0, channel=None):
|
||||
super(EvtQuery, self).__init__(handle)
|
||||
self.channel = channel
|
||||
|
||||
def __next__(self):
|
||||
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
|
||||
|
||||
next = __next__ # Yep.. real name is 'next' in Py2 :D
|
||||
|
||||
def all(self): # SqlAlchemy like :)
|
||||
return list(self)
|
||||
|
||||
|
||||
class EvtEvent(gdef.EVT_HANDLE):
|
||||
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):
|
||||
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)
|
||||
return [r.value for r in result]
|
||||
|
||||
def system(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)
|
||||
return [r.value for r in result]
|
||||
|
||||
def user(self):
|
||||
ctx = windows.winproxy.EvtCreateRenderContext(0, None, gdef.EvtRenderContextUser)
|
||||
result = self.render(ctx, gdef.EvtRenderEventValues)
|
||||
return [r.value for r in result]
|
||||
|
||||
# Properties arround common Event/System values
|
||||
@property
|
||||
def id(self):
|
||||
return self.value("Event/System/EventID")
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self.value("Event/System/Version")
|
||||
|
||||
@property
|
||||
def level(self):
|
||||
return self.value("Event/System/Level")
|
||||
|
||||
@property
|
||||
def opcode(self):
|
||||
return self.value("Event/System/Opcode")
|
||||
|
||||
@property
|
||||
def time_created(self):
|
||||
return self.value("Event/System/TimeCreated/@SystemTime")
|
||||
|
||||
@property
|
||||
def metadata(self):
|
||||
"HEAVY CALL"
|
||||
return self.channel.get_event_metadata(self.id)
|
||||
|
||||
# Test
|
||||
@property
|
||||
def data(self): # user/event specifique data
|
||||
return {k:v for k,v in zip(self.metadata.user_data, self.user())}
|
||||
|
||||
|
||||
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 : 'BinaryVal',
|
||||
gdef.EvtVarTypeHexInt64 : 'BinaryVal',
|
||||
gdef.EvtVarTypeEvtHandle : 'EvtHandleVal',
|
||||
gdef.EvtVarTypeEvtXml : 'XmlVal',
|
||||
}
|
||||
NoneValue = None
|
||||
@property
|
||||
def Type(self):
|
||||
raw_type = super(ImprovedEVT_VARIANT, self).Type
|
||||
return gdef.EVT_VARIANT_TYPE.mapper[raw_type]
|
||||
|
||||
@property
|
||||
def value(self): # Prototype !!
|
||||
attrname = self.VALUE_MAPPER[self.Type]
|
||||
# print("Resolve type <{0}> -> {1}".format(self.Type, attrname))
|
||||
return getattr(self, attrname)
|
||||
|
||||
def __repr__(self):
|
||||
return "<{0} of type={1}>".format(type(self).__name__, self.Type)
|
||||
|
||||
|
||||
def channels():
|
||||
h = windows.winproxy.EvtOpenChannelEnum(None, 0)
|
||||
size = 0x1000
|
||||
buffer = ctypes.create_unicode_buffer(size)
|
||||
ressize = gdef.DWORD()
|
||||
try:
|
||||
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"
|
||||
yield buffer[:ressize.value - 1]
|
||||
finally: # TODO: ctx manager
|
||||
windows.winproxy.EvtClose(h)
|
||||
|
||||
|
||||
def publishers():
|
||||
h = windows.winproxy.EvtOpenPublisherEnum(None, 0)
|
||||
size = 0x1000
|
||||
buffer = ctypes.create_unicode_buffer(size)
|
||||
ressize = gdef.DWORD()
|
||||
try:
|
||||
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"
|
||||
yield buffer[:ressize.value - 1]
|
||||
finally: # TODO: ctx manager
|
||||
windows.winproxy.EvtClose(h)
|
||||
|
||||
|
||||
|
||||
# x = windows.winproxy.EvtQuery(None,
|
||||
# "Microsoft-Windows-Windows Firewall With Advanced Security/Firewall",
|
||||
# "Event/System[EventID=2004 or EventID=2006]",
|
||||
# gdef.EvtQueryChannelPath + gdef.EvtQueryForwardDirection)
|
||||
# print(x)
|
||||
# eq = EvtQuery(x)
|
||||
# event = next(eq)
|
||||
|
||||
# xx = gdef.LPWSTR("ModifyingApplication")
|
||||
# xx = gdef.LPWSTR("Event/System/Channel")
|
||||
# xx = gdef.LPWSTR('Event/EventData/Data[@Name="ModifyingApplication"]')
|
||||
# xx = gdef.LPWSTR('Event/System/EventID')
|
||||
|
||||
# event = next(eq)
|
||||
|
||||
# list(channels())
|
||||
|
||||
class EvtChannel(object):
|
||||
DEFAULT_QUERY_FLAGS = gdef.EvtQueryChannelPath + gdef.EvtQueryForwardDirection
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.event_metadata_by_id = {}
|
||||
|
||||
def query(self, ids=None, filter=None):
|
||||
if ids is not None:
|
||||
if isinstance(ids, (long, int)):
|
||||
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)
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
return ChannelConfig.from_channel_name(self.name)
|
||||
|
||||
def get_event_metadata(self, id):
|
||||
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 __repr__(self):
|
||||
return '<{0} "{1}">'.format(type(self).__name__, self.name)
|
||||
|
||||
|
||||
class EvtFile(EvtChannel):
|
||||
DEFAULT_QUERY_FLAGS = gdef.EvtQueryFilePath + gdef.EvtQueryForwardDirection
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
raise NotImplementedError("Cannot retrieve the configuration of an EvtFile")
|
||||
|
||||
|
||||
class ChannelConfig(gdef.EVT_HANDLE):
|
||||
def __init__(self, handle, name=None):
|
||||
super(ChannelConfig, self).__init__(handle)
|
||||
self.name = name
|
||||
|
||||
@classmethod
|
||||
def from_channel_name(cls, channel_name):
|
||||
return cls(winproxy.EvtOpenChannelConfig(None, channel_name, 0), channel_name)
|
||||
|
||||
@property
|
||||
def publisher(self):
|
||||
return EvtPublisher(chaninfo(self, gdef.EvtChannelConfigOwningPublisher).value)
|
||||
|
||||
def __repr__(self):
|
||||
return '<{0} "{1}">'.format(type(self).__name__, self.name)
|
||||
|
||||
|
||||
class EvtPublisher(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
@property
|
||||
def metadata(self):
|
||||
return PublisherMetadata.from_publisher_name(self.name)
|
||||
|
||||
def __repr__(self):
|
||||
return '<{0} "{1}">'.format(type(self).__name__, self.name)
|
||||
|
||||
|
||||
class PublisherMetadata(gdef.EVT_HANDLE):
|
||||
def __init__(self, handle, name=None):
|
||||
super(PublisherMetadata, self).__init__(handle)
|
||||
self.name = name
|
||||
|
||||
@classmethod
|
||||
def from_publisher_name(cls, publisher_name):
|
||||
return cls(winproxy.EvtOpenPublisherMetadata(None, publisher_name, None, 0, 0), publisher_name)
|
||||
|
||||
@property
|
||||
def chanrefs(self):
|
||||
return PropertyArray(publishinfo(self, gdef.EvtPublisherMetadataChannelReferences).value)
|
||||
|
||||
@property
|
||||
def events_metadata(self):
|
||||
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
|
||||
|
||||
def channel_name_by_id(self):
|
||||
chansref = self.chanrefs
|
||||
channame_by_value_id = {}
|
||||
for i in range(chansref.size):
|
||||
value = chansref.propery(gdef.EvtPublisherMetadataChannelReferenceID, i)
|
||||
name = chansref.propery(gdef.EvtPublisherMetadataChannelReferencePath, i)
|
||||
channame_by_value_id[value] = name
|
||||
return channame_by_value_id
|
||||
|
||||
def message(self, msgid):
|
||||
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
|
||||
|
||||
def __repr__(self):
|
||||
return '<{0} "{1}">'.format(type(self).__name__, self.name)
|
||||
|
||||
|
||||
class PropertyArray(gdef.EVT_OBJECT_ARRAY_PROPERTY_HANDLE):
|
||||
@property
|
||||
def size(self):
|
||||
array_size = gdef.DWORD()
|
||||
windows.winproxy.EvtGetObjectArraySize(self, array_size)
|
||||
return array_size.value
|
||||
|
||||
def propery(self, type, index):
|
||||
return arrayproperty(self, type, index).value
|
||||
|
||||
class EventMetadata(gdef.EVT_HANDLE):
|
||||
@property
|
||||
def id(self):
|
||||
return eventinfo(self, gdef.EventMetadataEventID).value
|
||||
|
||||
@property
|
||||
def channel_id(self):
|
||||
return eventinfo(self, gdef.EventMetadataEventChannel).value
|
||||
|
||||
@property
|
||||
def message_id(self):
|
||||
return eventinfo(self, gdef.EventMetadataEventMessageID).value
|
||||
|
||||
@property
|
||||
def template(self):
|
||||
return eventinfo(self, gdef.EventMetadataEventTemplate).value
|
||||
|
||||
@property
|
||||
def user_data(self):
|
||||
result = []
|
||||
xmltemplate = xml.dom.minidom.parseString(self.template)
|
||||
for data in xmltemplate.getElementsByTagName("data"):
|
||||
result.append(data.attributes["name"].value)
|
||||
return result
|
||||
|
||||
|
||||
class EvtlogManager(object):
|
||||
"TODO: doc -> the frontend API object"
|
||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa385784(v=vs.85).aspx
|
||||
|
||||
def open_channel(self, channel_name):
|
||||
chan = EvtChannel(channel_name)
|
||||
chan.config # Force to retrieve a handle (check channel exists)
|
||||
return chan
|
||||
|
||||
def open_evtx_file(self, filename):
|
||||
# We don't check if file exists because of 32->64 redirection
|
||||
# Do a check with FSRedirection disabled ?
|
||||
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, publisher_name):
|
||||
publisher = EvtPublisher(publisher_name)
|
||||
publisher.metadata # Force to retrieve a handle (check channel exists)
|
||||
return publisher
|
||||
|
||||
def __getitem__(self, name):
|
||||
try:
|
||||
return self.open_channel(name)
|
||||
except WindowsError as e:
|
||||
pass
|
||||
|
||||
try:
|
||||
return self.open_publisher(name)
|
||||
except WindowsError as e:
|
||||
pass
|
||||
# Raise FILE_NOT_FOUND if not found (last chance)
|
||||
return self.open_evtx_file(name)
|
||||
|
||||
|
||||
|
||||
|
||||
# CHANNAME = "Microsoft-Windows-Windows Firewall With Advanced Security/Firewall"
|
||||
# CHANNAME = r"Microsoft-Windows-Windows Defender/Operational"
|
||||
|
||||
# print("Working of channel: <{0}>".format(CHANNAME))
|
||||
|
||||
# channel = EvtChannel(CHANNAME)
|
||||
# chanconf = channel.config
|
||||
# publisher = chanconf.publisher
|
||||
|
||||
# open publisher metadata
|
||||
|
||||
# print("Provider is <{0}>".format(publisher.name))
|
||||
|
||||
# pmd = publisher.metadata
|
||||
# chansref = pmd.chanrefs
|
||||
|
||||
# channame_by_value_id = pmd.channel_name_by_id()
|
||||
|
||||
# for event_metadata in pmd.events_metadata:
|
||||
# id = event_metadata.id
|
||||
# if id == 2004:
|
||||
# print("LOL")
|
||||
# chan = event_metadata.channel_id
|
||||
# channame = channame_by_value_id[chan]
|
||||
# print(" * {0}) {1}".format(id, channame))
|
||||
# if "unexpected" in pmd.message(event_metadata.message_id).lower():
|
||||
# print("UNEXPECTED in message :D")
|
||||
|
||||
|
||||
# query = channel.query(ids=5008)
|
||||
# evts = list(query)
|
||||
Reference in New Issue
Block a user