diff --git a/acquire/acquire.py b/acquire/acquire.py index 6b281689..aa998507 100644 --- a/acquire/acquire.py +++ b/acquire/acquire.py @@ -1813,6 +1813,21 @@ def acquire_target_targetd(target: Target, args: argparse.Namespace, output_ts: return files +def _add_modules_for_profile(choice: str, operating_system: str, profile: dict, msg: str) -> Optional[dict]: + modules_selected = dict() + + if choice and choice != "none": + profile_dict = profile[choice] + if operating_system not in profile_dict: + log.error(msg, operating_system, choice) + return None + + for mod in profile_dict[operating_system]: + modules_selected[mod.__modname__] = mod + + return modules_selected + + def acquire_target_regular(target: Target, args: argparse.Namespace, output_ts: Optional[str] = None) -> list[str]: files = [] output_ts = output_ts or get_utc_now_str() @@ -1880,13 +1895,18 @@ def acquire_target_regular(target: Target, args: argparse.Namespace, output_ts: profile = "default" log.info("") - if profile and profile != "none": - if target.os not in PROFILES[profile]: - log.error("No collection set for OS %s with profile %s", target.os, profile) - return files + profile_modules = _add_modules_for_profile( + profile, target.os, PROFILES, "No collection set for OS %s with profile %s" + ) + volatile_modules = _add_modules_for_profile( + args.volatile_profile, target.os, VOLATILE, "No collection set for OS %s with volatile profile %s" + ) - for mod in PROFILES[profile][target.os]: - modules_selected[mod.__modname__] = mod + if (profile_modules or volatile_modules) is None: + return files + + modules_selected.update(profile_modules) + modules_selected.update(volatile_modules) log.info("Modules selected: %s", ", ".join(sorted(modules_selected))) @@ -2021,171 +2041,165 @@ def upload_files(paths: list[Path], upload_plugin: UploaderPlugin, no_proxy: boo log.exception("") +class WindowsProfile: + MINIMAL = [ + NTFS, + EventLogs, + Registry, + Tasks, + PowerShell, + Prefetch, + Appcompat, + PCA, + Misc, + ] + DEFAULT = [ + *MINIMAL, + ETL, + Recents, + RecycleBin, + Drivers, + Syscache, + WBEM, + AV, + BITS, + DHCP, + DNS, + ActiveDirectory, + RemoteAccess, + ActivitiesCache, + ] + FULL = [ + *DEFAULT, + History, + NTDS, + QuarantinedFiles, + WindowsNotifications, + SSH, + IIS, + ] + + +class LinuxProfile: + MINIMAL = [ + Etc, + Boot, + Home, + SSH, + Var, + ] + DEFAULT = MINIMAL + FULL = [ + *DEFAULT, + History, + WebHosting, + ] + + +class BsdProfile: + MINIMAL = [ + Etc, + Boot, + Home, + SSH, + Var, + BSD, + ] + DEFAULT = MINIMAL + FULL = MINIMAL + + +class ESXiProfile: + MINIMAL = [ + Bootbanks, + ESXi, + SSH, + ] + DEFAULT = [ + *MINIMAL, + VMFS, + ] + FULL = DEFAULT + + +class OSXProfile: + MINIMAL = [ + Etc, + Home, + Var, + OSX, + OSXApplicationsInfo, + ] + DEFAULT = MINIMAL + FULL = [ + *DEFAULT, + History, + SSH, + ] + + PROFILES = { "full": { - "windows": [ - NTFS, - EventLogs, - Registry, - Tasks, - ETL, - Recents, - RecycleBin, - Drivers, - PowerShell, - Prefetch, - Appcompat, - PCA, - Syscache, - WBEM, - AV, - ActivitiesCache, - BITS, - DHCP, - DNS, - History, - Misc, - NTDS, - ActiveDirectory, - QuarantinedFiles, - RemoteAccess, - WindowsNotifications, - SSH, - IIS, - ], - "linux": [ - Etc, - Boot, - Home, - History, - SSH, - Var, - WebHosting, - ], - "bsd": [ - Etc, - Boot, - SSH, - Home, - Var, - BSD, - ], - "esxi": [ - Bootbanks, - ESXi, - VMFS, - SSH, - ], - "osx": [ - Etc, - Home, - Var, - OSX, - OSXApplicationsInfo, - History, - SSH, - ], + "windows": WindowsProfile.FULL, + "linux": LinuxProfile.FULL, + "bsd": BsdProfile.FULL, + "esxi": ESXiProfile.FULL, + "osx": OSXProfile.FULL, }, "default": { - "windows": [ - NTFS, - EventLogs, - Registry, - Tasks, - ETL, - Recents, - RecycleBin, - Drivers, - PowerShell, - Prefetch, - Appcompat, - PCA, - Syscache, - WBEM, - AV, - BITS, - DHCP, - DNS, - Misc, - ActiveDirectory, - RemoteAccess, - ActivitiesCache, - ], - "linux": [ - Etc, - Boot, - Home, - SSH, - Var, - ], - "bsd": [ - Etc, - Boot, - Home, - SSH, - Var, - BSD, - ], - "esxi": [ - Bootbanks, - ESXi, - VMFS, - SSH, - ], - "osx": [ - Etc, - Home, - Var, - OSX, - OSXApplicationsInfo, - ], + "windows": WindowsProfile.DEFAULT, + "linux": LinuxProfile.DEFAULT, + "bsd": BsdProfile.DEFAULT, + "esxi": ESXiProfile.DEFAULT, + "osx": OSXProfile.DEFAULT, }, "minimal": { - "windows": [ - NTFS, - EventLogs, - Registry, - Tasks, - PowerShell, - Prefetch, - Appcompat, - PCA, - Misc, - ], - "linux": [ - Etc, - Boot, - Home, - SSH, - Var, - ], - "bsd": [ - Etc, - Boot, - Home, - SSH, - Var, - BSD, - ], - "esxi": [ - Bootbanks, - ESXi, - SSH, - ], - "osx": [ - Etc, - Home, - Var, - OSX, - OSXApplicationsInfo, - ], + "windows": WindowsProfile.MINIMAL, + "linux": LinuxProfile.MINIMAL, + "bsd": BsdProfile.MINIMAL, + "esxi": ESXiProfile.MINIMAL, + "osx": OSXProfile.MINIMAL, + }, + "none": None, +} + + +class VolatileProfile: + DEFAULT = [ + Netstat, + WinProcesses, + WinProcEnv, + WinArpCache, + WinRDPSessions, + WinDnsClientCache, + ] + EXTENSIVE = [ + Proc, + Sys, + ] + + +VOLATILE = { + "default": { + "windows": VolatileProfile.DEFAULT, + "linux": [], + "bsd": [], + "esxi": [], + "osx": [], + }, + "extensive": { + "windows": VolatileProfile.DEFAULT, + "linux": VolatileProfile.EXTENSIVE, + "bsd": VolatileProfile.EXTENSIVE, + "esxi": VolatileProfile.EXTENSIVE, + "osx": [], }, "none": None, } def main() -> None: - parser = create_argument_parser(PROFILES, MODULES) + parser = create_argument_parser(PROFILES, VOLATILE, MODULES) args = parse_acquire_args(parser, config=CONFIG) try: diff --git a/acquire/utils.py b/acquire/utils.py index 673cc2c0..10cf5c0e 100644 --- a/acquire/utils.py +++ b/acquire/utils.py @@ -36,26 +36,37 @@ class StrEnum(str, Enum): """Sortable and serializible string-based enum""" -def create_argument_parser(profiles: dict, modules: dict) -> argparse.ArgumentParser: +def _create_profile_information(profiles: dict) -> str: desc = "" profile_names = (name for name in profiles.keys() if name != "none") - for name in profile_names: + profile_dict = profiles[name] desc += f"{name} profile:\n" - minindent = max([len(os_) for os_ in profiles[name].keys()]) + + minindent = max([len(os_) for os_ in profile_dict.keys()]) descfmt = f" {{:{minindent}s}}: {{}}\n" - for os_ in profiles[name].keys(): - indent = 4 + len(os_) - modlist = textwrap.wrap(", ".join([mod.__modname__ for mod in profiles[name][os_]]), 50) + for os_, modlist in profile_dict.items(): + if not modlist: + continue + indent = 4 + len(os_) + modlist = textwrap.wrap(", ".join([mod.__modname__ for mod in modlist]), 50) moddesc = modlist.pop(0) for ml in modlist: moddesc += "\n" + (" " * indent) + ml - desc += descfmt.format(os_, moddesc) desc += "\n" + return desc + + +def create_argument_parser(profiles: dict, volatile: dict, modules: dict) -> argparse.ArgumentParser: + module_profiles = "Module:\n" + textwrap.indent(_create_profile_information(profiles), " ") + volatile_profiles = "Volatile:\n" + textwrap.indent(_create_profile_information(volatile), " ") + + desc = module_profiles + volatile_profiles + parser = argparse.ArgumentParser( prog="acquire", description=desc, @@ -101,6 +112,7 @@ def create_argument_parser(profiles: dict, modules: dict) -> argparse.ArgumentPa parser.add_argument("-l", "--log", type=Path, help="log directory location") parser.add_argument("--no-log", action="store_true", help=argparse.SUPPRESS) parser.add_argument("-p", "--profile", choices=profiles.keys(), help="collection profile") + parser.add_argument("--volatile-profile", choices=volatile.keys(), default="none", help="volatile profile") parser.add_argument("-f", "--file", action="append", help="acquire file") parser.add_argument("-d", "--directory", action="append", help="acquire directory recursively") diff --git a/tests/test_acquire_command.py b/tests/test_acquire_command.py index 9eb7bcee..46c505e0 100644 --- a/tests/test_acquire_command.py +++ b/tests/test_acquire_command.py @@ -8,6 +8,7 @@ CONFIG, MODULES, PROFILES, + VOLATILE, create_argument_parser, parse_acquire_args, ) @@ -17,7 +18,7 @@ def acquire_parser_args(config: List, argument_list: List) -> Namespace: CONFIG["arguments"] = config with patch("argparse._sys.argv", [""] + argument_list): - return parse_acquire_args(create_argument_parser(PROFILES, MODULES), config=CONFIG) + return parse_acquire_args(create_argument_parser(PROFILES, VOLATILE, MODULES), config=CONFIG) @pytest.mark.parametrize("config, argument_list", [([], [])]) diff --git a/tests/test_utils.py b/tests/test_utils.py index 69007f92..9321c119 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -6,7 +6,7 @@ import pytest from dissect.target import Target -from acquire.acquire import MODULES, PROFILES +from acquire.acquire import MODULES, PROFILES, VOLATILE from acquire.utils import ( check_and_set_acquire_args, check_and_set_log_args, @@ -26,7 +26,7 @@ def get_args(**kwargs): "public_key": None, } - parser = create_argument_parser(PROFILES, MODULES) + parser = create_argument_parser(PROFILES, VOLATILE, MODULES) default_args = dict(parser.parse_args(args=[])._get_kwargs()) default_args.update(kwargs)