Source code for pyintrovirt.event
"""Event handling helpers and classes."""
import traceback
from typing import TYPE_CHECKING, Optional, Protocol, Union, cast
import introvirt # pylint: disable=import-error
if TYPE_CHECKING:
from .vmi import VMI
[docs]
class EventCallback(Protocol):
# pylint: disable=too-few-public-methods
"""The event callback function signature."""
def __call__(self, vmi: "VMI", event: "Event") -> None: ...
[docs]
class Event:
"""A more helpful wrapper around introvirt.Event that exposes more functionality from one object."""
def __init__(self, iv_event: introvirt.Event):
self._iv_event = iv_event
self._syscall: Optional[introvirt.SystemCallEvent] = None
self._handler: Optional[introvirt.SystemCall] = None
self._vcpu: introvirt.Vcpu = self._iv_event.vcpu()
self._task: introvirt.EventTaskInformation = self._iv_event.task()
if introvirt.EventType(self._iv_event.type()) in (introvirt.EventType.EVENT_FAST_SYSCALL, introvirt.EventType.EVENT_FAST_SYSCALL_RET):
self._syscall = iv_event.syscall()
self._handler = self._syscall.handler()
def __str__(self) -> str:
supp = ""
if self.supported is False:
supp = " - unsupported"
if self.has_result():
return f"Vcpu {self.vcpu.id()}: [{self.pid}:{self.tid}] {self.process_name}\n\t{self.syscall_name} - {self.get_result_str()}{supp}\n"
return f"Vcpu {self.vcpu.id()}: [{self.pid}:{self.tid}] {self.process_name}\n\t{self.syscall_name}{supp}\n"
[docs]
def is_syscall(self) -> bool:
"""Is it a system call event."""
return self._syscall is not None
[docs]
def to_dict(self) -> dict:
"""Convert the event to a dictionary."""
return self._iv_event.to_dict()
@property
def supported(self) -> Union[None, bool]:
"""Checks supported. Only valid for system calls with a handler."""
if self.is_syscall() and self._handler:
return self._handler.supported()
return None
@property
def pid(self) -> int:
"""Get the process ID for the task associated with the event."""
return self._task.pid()
@property
def tid(self) -> int:
"""Get the thread ID for the task associated with the event."""
return self._task.tid()
@property
def kpcr(self) -> introvirt.KPCR:
"""Get the KPCR from the task."""
return cast(introvirt.WindowsEventTaskInformation, self._task).pcr()
@property
def vcpu(self) -> introvirt.Vcpu:
"""Get access to the VCPU object."""
return self._vcpu
@property
def process_name(self) -> str:
"""Get the task info process name."""
return self._task.process_name()
@property
def syscall_name(self) -> Union[None, str]:
"""Get the system call name if it's a system call."""
if self._syscall is None:
return None
return self._syscall.name()
@property
def syscall_index(self) -> Union[None, introvirt.SystemCallIndex]:
"""Get the system call index if it's a system call."""
if not self.is_syscall() or self._syscall is None:
return None
return introvirt.SystemCallIndex(cast(introvirt.WindowsSystemCallEvent, self._syscall).index())
[docs]
def event_type(self) -> introvirt.EventType:
"""Wrap in an introvirt.EventType object."""
return introvirt.EventType(self._iv_event.type())
[docs]
def will_return(self) -> Union[None, bool]:
"""This is valid for system calls. Everything else will always return False."""
if not self.is_syscall():
return None # Not a system call
if not self._handler:
return None # No supported handler
if self.event_type() != introvirt.EventType.EVENT_FAST_SYSCALL:
return None # Not a system call call
return self._handler.will_return()
[docs]
def hook_return(self, enabled: bool):
"""Set whether or not we'll hook the return. Only valid on system calls."""
if self._syscall is None:
return # Not a system call
if self.event_type() != introvirt.EventType.EVENT_FAST_SYSCALL:
return # Not a system call call
self._syscall.hook_return(enabled)
[docs]
def has_result(self) -> bool:
"""Return True if this event includes a syscall result."""
return self.is_syscall() and self.event_type() == introvirt.EventType.EVENT_FAST_SYSCALL_RET and isinstance(self._iv_event, introvirt.WindowsEvent)
[docs]
def get_result(self) -> Union[None, int]:
"""Get the result value if there is one. Only valid for system calls right now."""
if not self.is_syscall():
return None # Not a system call
if self.event_type() != introvirt.EventType.EVENT_FAST_SYSCALL_RET:
return None # Not a system call return
if not isinstance(self._iv_event, introvirt.WindowsEvent):
return None # Not supported
ok, value = introvirt.get_windows_syscall_result_value(self._iv_event)
if not ok:
return None # Not ok
return value
[docs]
def get_result_str(self) -> Union[None, str]:
"""Get the result value if there is one as a string. Only valid for system calls right now."""
value = self.get_result()
if value is not None and isinstance(self._iv_event, introvirt.WindowsEvent):
return introvirt.ntstatus_to_string(value)
return None
[docs]
def get_syscall_handler(self) -> Union[None, introvirt.WindowsSystemCall]:
"""Get the lowest level OS-specific system call handler for the system call if there is one."""
if not self.is_syscall():
return None # Not a system call
if not isinstance(self._iv_event, introvirt.WindowsEvent):
return None # Not supported
return introvirt.get_concrete_handler(self._iv_event)
[docs]
class CallbackEventHandler(introvirt.EventCallback):
"""Event callback handler."""
def __init__(self, vmi: "VMI"):
super().__init__() # required so SWIG director wrapper is created for poll()
self.event_callbacks = {}
self.global_event_callback = None
self.vmi = vmi
[docs]
def set_global_event_callback(self, callback: EventCallback):
"""Set a callback to be called with every event type."""
self.global_event_callback = callback
[docs]
def register_event_callback(self, event_type: introvirt.EventType, callback: EventCallback):
"""Set a callback to be called for a specific event type."""
self.event_callbacks[event_type] = callback
[docs]
def process_event(self, event: introvirt.Event):
"""Callback from the introvirt.EventCallback"""
try:
self._process_event(event)
except Exception as exc: # pylint: disable=broad-exception-caught
print(f"Unhandled exception processing event: {exc}")
traceback.print_exc()
def _process_event(self, event: introvirt.Event):
"""Called internally for each event received."""
if not self.event_callbacks and not self.global_event_callback:
return # no callbacks to call
os_event = None
match introvirt.OS(event.os_type()):
case introvirt.OS.Windows:
os_event = introvirt.WindowsEvent_from_event(event)
case introvirt.OS.Linux:
raise NotImplementedError("Linux guest introspection is not implemented yet")
# Get the event to use (OS-specific or generic)
iv_event = os_event if os_event else event
send_event = Event(iv_event)
# Send it to the global callback if it's set
if self.global_event_callback:
self.global_event_callback(self.vmi, send_event)
# Find any specific event handling callback to send it to next.
callback = self.event_callbacks.get(introvirt.EventType(send_event.event_type()))
if callback:
callback(self.vmi, send_event)