Source code for tuoni.TuoniAgent

from tuoni.TuoniExceptions import *
from tuoni.TuoniCommand import *
from tuoni.TuoniAlias import *
from tuoni.TuoniDefaultCommands import *
import warnings

[docs] class TuoniAgent: """ A class that encapsulates the data and functionality of a connected agent. Attributes: guid (GUID): A unique identifier (GUID) assigned to the agent. first_registration_time (str): The time, in string format, when the agent first connected. last_callback_time (str): The time, in string format, of the agent's most recent connection. metadata (dict): A dictionary containing metadata about the agent. active (bool): A boolean flag indicating whether the agent is currently active. recentListeners (list): A list of listeners that the agent uses to maintain its connection. availableCommands (dict): A dictionary of commands that the agent is capable of executing. """ def __init__(self, conf, c2): """ Constructor. Attributes: conf (dict): Data from server. c2 (TuoniC2): Related server object. """ self.c2 = c2 self._load_conf(conf) def _load_conf(self, conf): self.guid = conf["guid"] self.first_registration_time = conf["firstRegistrationTime"] self.last_callback_time = conf["lastCallbackTime"] self.metadata = conf["metadata"] self.active = conf["active"] self.recentListeners = conf["recentListeners"] self._fill_available_commands(conf["availableCommandTemplates"])
[docs] def send_command(self, command_type, command_conf=None, execution_conf = None, files = None): """ Send command to agent. Args: command_type (str | TuoniAlias | TuoniDefaultCommand): What command to send. command_conf (dict): Command configuration. execution_conf (dict): Execution configuration. files (dict): Files to send with command. Returns: TuoniCommand: An object representing the created command. Examples: >>> command1 = agent.send_command(TuoniCommandLs(".\\subdir", 2)) >>> command2 = agent.send_command( >>> TuoniCommandProcinfo(), >>> execution_conf ={"execType": "NEW"} >>> ) >>> command3 = agent.send_command( >>> TuoniCommandProcinfo(), >>> execution_conf = { >>> "execType": "EXISTING", >>> "pid": 1234 >>> } >>> ) >>> command4 = agent.send_command("ls", {"dir": ".\\subdir", "depth": 2}) >>> command5 = agent.send_command( >>> alias_object, >>> {"conf_value_name": "conf_value"} >>> ) """ if self.guid is None: raise ExceptionTuoniDeleted("") if isinstance(command_type, TuoniDefaultCommand): command_conf = command_type.command_conf execution_conf = command_type.execution_conf files = command_type.files command_type = command_type.command_type if isinstance(command_type, TuoniAlias): command_type = command_type.alias_id if command_conf is None: command_conf = {} data = { "template": command_type, "configuration": command_conf } if execution_conf is not None: data["execConf"] = execution_conf data = self.c2.request_post(f"/api/v1/agents/{self.guid}/commands", data, files) return TuoniCommand(data, self.c2)
[docs] def get_commands(self): """ Retrieves a list of all commands associated with the agent. Returns: list[TuoniCommand]: List of commands sent to the agent. """ if self.guid is None: raise ExceptionTuoniDeleted("") commands_data = self.c2.request_get(f"/api/v1/agents/{self.guid}/commands") commands = [] for command_nr in commands_data: command_data = commands_data[command_nr] command_obj = TuoniCommand(command_data, self.c2) commands.append(command_obj) return commands
[docs] def delete(self): """ Deletes agent (actually makes it inactive but close enough). """ if self.guid is None: raise ExceptionTuoniDeleted("") self.c2.request_put(f"/api/v1/agents/{self.guid}/inactive") self.listener_id = None
def _fill_available_commands(self, command_list): self.availableCommands = {} for cmd in self.c2.request_get("/api/v1/command-templates"): if cmd["id"] in command_list: self.availableCommands[cmd["name"]] = cmd["id"]
[docs] def matches(self, criteria): """ Check if the agent matches the given criteria. Args: criteria (dict): Dictionary containing matching criteria. Keys can use dot notation like 'metadata.os' to access nested attributes. Returns: bool: True if all criteria match, False otherwise. """ for key, expected_value in criteria.items(): # Handle dot notation for nested attributes current_obj = self # Split the key by dots and traverse the object key_parts = key.split('.') try: for part in key_parts: if hasattr(current_obj, part): current_obj = getattr(current_obj, part) elif isinstance(current_obj, dict) and part in current_obj: current_obj = current_obj[part] else: # Debug: print available attributes if hasattr(current_obj, '__dict__'): available_attrs = list(current_obj.__dict__.keys()) elif isinstance(current_obj, dict): available_attrs = list(current_obj.keys()) else: available_attrs = [] return False # Compare the final value (case-insensitive for strings) if isinstance(current_obj, str) and isinstance(expected_value, str): if current_obj.upper() != expected_value.upper(): return False else: if current_obj != expected_value: print(f"{Fore.YELLOW}Debug: Value mismatch for '{key}': got '{current_obj}', expected '{expected_value}'{Style.RESET_ALL}") return False except (AttributeError, TypeError) as e: print(f"{Fore.YELLOW}Debug: Exception accessing '{key}': {e}{Style.RESET_ALL}") return False # All criteria matched return True
[docs] def setCustomProperties(self, name, value): """ .. deprecated Use :func:`set_custom_property` instead. Adds or updates a property in the agents customProperties metadata. Parameters: name (str): The name (key) of the property to add or update. value (Any): The value to assign to the given name. Behavior: - If the property with the given name exists, its value will be updated. - If it does not exist, a new name/value pair will be added. - If value is None the name/value pair will be deleted. Example: myAgent.setCustomProperties("notes", "Domain Controller") """ warnings.warn( f"Function setCustomProperties is deprecated; it will be removed in a future release. Please use `set_custom_property` instead.", category=DeprecationWarning ) self.set_custom_property(name, value)
[docs] def set_custom_property(self, name, value): """ Adds or updates a property in the agents customProperties metadata. Parameters: name (str): The name (key) of the property to add or update. value (Any): The value to assign to the given name. Behavior: - If the property with the given name exists, its value will be updated. - If it does not exist, a new name/value pair will be added. - If value is None the name/value pair will be deleted. Example: myAgent.set_custom_property("notes", "Domain Controller") """ self.metadata['customProperties'][name] = value self.c2.request_put(f"/api/v1/agents/{self.guid}/metadata", self.metadata)