killchain-compendium/post_exploitation/Seatbelt/Seatbelt/Runtime.cs

397 lines
14 KiB
C#

#nullable disable
using Seatbelt.Commands;
using Seatbelt.Interop;
using Seatbelt.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Seatbelt.Output.Sinks;
using System.Management;
using Microsoft.Win32;
using System.Diagnostics.Eventing.Reader;
namespace Seatbelt
{
internal class Runtime
{
public List<CommandBase> AllCommands { get; private set; } = new List<CommandBase>();
public readonly IOutputSink OutputSink;
private IEnumerable<string> Commands { get; set; }
private IEnumerable<string> CommandGroups { get; set; }
public bool FilterResults { get; }
public string ComputerName { get; } // for remote connections
private string UserName { get; } // for remote connections
private string Password { get; } // for remote connections
private ManagementClass wmiRegProv { get; }
public Runtime(IOutputSink outputSink, IEnumerable<string> commands, IEnumerable<string> commandGroups, bool filter)
: this(outputSink, commands, commandGroups, filter, "", "", "")
{
}
public Runtime(IOutputSink outputSink, IEnumerable<string> commands, IEnumerable<string> commandGroups, bool filter, string computerName)
: this(outputSink, commands, commandGroups, filter, computerName, "", "")
{
}
public Runtime(IOutputSink outputSink, IEnumerable<string> commands, IEnumerable<string> commandGroups, bool filter, string computerName, string userName, string password)
{
OutputSink = outputSink;
Commands = commands;
CommandGroups = commandGroups;
FilterResults = filter;
ComputerName = computerName;
UserName = userName;
Password = password;
// test a remote connection first if a remote system is specified
if (!string.IsNullOrEmpty(computerName))
{
try
{
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
{
OutputSink.WriteHost($"[*] Running commands remotely against the host '{computerName}' with credentials -> user:{UserName} , password:{Password}\r\n");
var options = new ConnectionOptions();
options.Username = UserName;
options.Password = Password;
options.Impersonation = ImpersonationLevel.Impersonate;
options.EnablePrivileges = true;
var scope = new ManagementScope($"\\\\{computerName}\\root\\cimv2", options);
scope.Connect();
}
else
{
OutputSink.WriteHost($"[*] Running commands remotely against the host '{computerName}' with current user credentials\r\n");
var scope = new ManagementScope($"\\\\{computerName}\\root\\cimv2");
scope.Connect();
}
InitializeCommands();
}
catch (Exception e)
{
OutputSink.WriteError($"Error connecting to \"{computerName}\" : {e.Message}");
throw e;
}
wmiRegProv = WMIUtil.WMIRegConnection(computerName, userName, password);
}
else
{
InitializeCommands();
}
}
public ManagementObjectSearcher GetManagementObjectSearcher(string nameSpace, string query)
{
// helper that takes the current configuration for a remote management and builds the proper ManagementObjectSearcher object
// used for WMI searching
if (string.IsNullOrEmpty(ComputerName))
return new ManagementObjectSearcher(nameSpace, query);
try
{
if (!string.IsNullOrEmpty(UserName) && !string.IsNullOrEmpty(Password))
{
var options = new ConnectionOptions
{
Username = UserName,
Password = Password,
Impersonation = ImpersonationLevel.Impersonate,
EnablePrivileges = true
};
var scope = new ManagementScope($"\\\\{ComputerName}\\{nameSpace}", options);
scope.Connect();
var queryObj = new ObjectQuery(query);
return new ManagementObjectSearcher(scope, queryObj);
}
else
{
var scope = new ManagementScope($"\\\\{ComputerName}\\{nameSpace}");
scope.Connect();
var queryObj = new ObjectQuery(query);
return new ManagementObjectSearcher(scope, queryObj);
}
}
catch (Exception e)
{
throw new Exception($"Error connecting to \"{ComputerName}\" : {e.Message}");
}
}
public string[]? GetSubkeyNames(RegistryHive hive, string path)
{
if (!string.IsNullOrEmpty(ComputerName))
{
return RegistryUtil.GetSubkeyNames(hive, path, wmiRegProv);
}
return RegistryUtil.GetSubkeyNames(hive, path);
}
public string? GetStringValue(RegistryHive hive, string path, string value)
{
if (!string.IsNullOrEmpty(ComputerName))
{
return RegistryUtil.GetStringValue(hive, path, value, wmiRegProv);
}
return RegistryUtil.GetStringValue(hive, path, value);
}
public uint? GetDwordValue(RegistryHive hive, string path, string value)
{
if (!string.IsNullOrEmpty(ComputerName))
{
return RegistryUtil.GetDwordValue(hive, path, value, wmiRegProv);
}
return RegistryUtil.GetDwordValue(hive, path, value);
}
public byte[]? GetBinaryValue(RegistryHive hive, string path, string value)
{
if (!string.IsNullOrEmpty(ComputerName))
{
return RegistryUtil.GetBinaryValue(hive, path, value, wmiRegProv);
}
return RegistryUtil.GetBinaryValue(hive, path, value);
}
public Dictionary<string, object> GetValues(RegistryHive hive, string path)
{
if (!string.IsNullOrEmpty(ComputerName))
{
return RegistryUtil.GetValues(hive, path, wmiRegProv);
}
return RegistryUtil.GetValues(hive, path);
}
public string[] GetUserSIDs()
{
if (!string.IsNullOrEmpty(ComputerName))
{
return RegistryUtil.GetUserSIDs(wmiRegProv);
}
return RegistryUtil.GetUserSIDs();
}
public string[] GetDirectories(string relPath)
{
relPath = relPath.Trim('\\');
if (!string.IsNullOrEmpty(ComputerName))
{
return System.IO.Directory.GetDirectories($"\\\\{ComputerName}\\C$\\{relPath}\\");
}
else
{
return System.IO.Directory.GetDirectories($"{Environment.GetEnvironmentVariable("SystemDrive")}\\{relPath}\\");
}
}
public EventLogReader GetEventLogReader(string path, string query)
{
// TODO: investigate https://docs.microsoft.com/en-us/previous-versions/windows/desktop/eventlogprov/win32-ntlogevent
var eventsQuery = new EventLogQuery(path, PathType.LogName, query) { ReverseDirection = true };
if (!string.IsNullOrEmpty(ComputerName))
{
//EventLogSession session = new EventLogSession(
// ComputerName,
// "Domain", // Domain
// "Username", // Username
// pw,
// SessionAuthentication.Default); // TODO password specification! https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.eventing.reader.eventlogsession.-ctor?view=dotnet-plat-ext-3.1#System_Diagnostics_Eventing_Reader_EventLogSession__ctor_System_String_System_String_System_String_System_Security_SecureString_System_Diagnostics_Eventing_Reader_SessionAuthentication_
var session = new EventLogSession(ComputerName);
eventsQuery.Session = session;
}
var logReader = new EventLogReader(eventsQuery);
return logReader;
}
public string GetEnvironmentVariable(string variableName)
{
if (!string.IsNullOrEmpty(ComputerName))
{
var result = "";
var wmiData = this.GetManagementObjectSearcher(@"root\cimv2", $"SELECT VariableValue from win32_environment WHERE name='{variableName}' AND UserName='<SYSTEM>'");
foreach (var wmiResult in wmiData.Get())
{
result = wmiResult["VariableValue"].ToString();
}
return result;
}
else
{
return Environment.GetEnvironmentVariable(variableName);
}
}
public bool ISRemote()
{
return !string.IsNullOrEmpty(ComputerName);
}
private void InitializeCommands()
{
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
{
if (!type.IsSubclassOf(typeof(CommandBase)) || type.IsAbstract)
{
continue;
}
var instance = (CommandBase)Activator.CreateInstance(type, new object[] { this });
#if DEBUG
if (instance.Command != "TEMPLATE")
{
AllCommands.Add(instance);
}
#else
AllCommands.Add(instance);
#endif
}
AllCommands = AllCommands.OrderBy(c => c.Command).ToList();
}
public void Execute()
{
foreach (var group in CommandGroups)
{
if (!ProcessGroup(group))
{
OutputSink.WriteError($"Invalid command group \"{group}\"");
}
}
foreach (var command in Commands)
{
try
{
if (!ProcessCommand(command))
{
OutputSink.WriteError($"Error running command \"{command}\"");
}
}
catch (Exception e)
{
OutputSink.WriteError($"Error running {command}: {e}");
}
}
}
private bool ProcessGroup(string command)
{
var commandGroupStrings = Enum.GetNames(typeof(CommandGroup)).ToList().Select(g=> g.ToLower());
if (!commandGroupStrings.Contains(command.ToLower()))
return false;
List<CommandBase> toExecute;
switch (command.ToLower())
{
case "all":
toExecute = AllCommands.ToList();
break;
default:
CommandGroup commandGroup;
try
{
var groupName = Enum.GetNames(typeof(CommandGroup)).FirstOrDefault(c => c.ToLower() == command.ToLower());
commandGroup = (CommandGroup)Enum.Parse(typeof(CommandGroup), groupName);
}
catch (ArgumentException)
{
return false;
}
toExecute = AllCommands.Where(g => g.Group.Contains(commandGroup)).ToList();
break;
}
toExecute.ForEach(c =>
{
ExecuteCommand(c, new string[] { });
});
return true;
}
private bool ProcessCommand(string commandLine)
{
var args = Shell32.CommandLineToArgs(commandLine);
var commandName = args[0];
var command = AllCommands.FirstOrDefault(c => c.Command.Equals(commandName, StringComparison.InvariantCultureIgnoreCase));
if (command == null)
{
return false;
}
var commandArgs = new string[] { };
if (args.Length > 1)
{
commandArgs = args.SubArray(1, args.Length - 1);
}
ExecuteCommand(command, commandArgs);
return true;
}
private void ExecuteCommand(CommandBase? command, string[] commandArgs)
{
try
{
OutputSink.WriteOutput(new HostDTO($"====== {command.Command} ======\n"));
var results = command.Execute(commandArgs);
if (results != null)
{
// OutputSink.BeginOutput();
foreach (var result in results)
{
// pass the command version from the command module to the DTO
result.SetCommandVersion(command.CommandVersion);
OutputSink.WriteOutput(result);
}
// OutputSink.EndOutput();
}
}
catch (Exception e)
{
// TODO: Return an error DTO
OutputSink.WriteError($" [!] Terminating exception running command '{command.Command}': " + e);
}
}
}
}
#nullable enable