diff --git a/README.md b/README.md index 8bf6cab..f2c7c15 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ or (to dev) ```text git clone git+https://github.com/login-securite/DonPAPI.git cd DonPAPI -poetry update +poetry install poetry run DonPAPI ``` @@ -129,7 +129,7 @@ authentication: attacks: -c COLLECTORS, --collectors COLLECTORS - Chromium, Certificates, CredMan, Files, Firefox, MobaXterm, MRemoteNG, RDCMan, SCCM, Vaults, VNC, Wifi, All (all + Chromium, Certificates, CredMan, Files, Firefox, MobaXterm, MRemoteNG, RDCMan, SCCM, Vaults, VNC, Wifi, Custom (as specified in config), All (all previous) (default: All) -nr, --no-remoteops Disable Remote Ops operations (basically no Remote Registry operations, no DPAPI System Credentials) --fetch-pvk Will automatically use domain backup key from database, and if not already dumped, will dump it on a domain controller @@ -181,7 +181,16 @@ share = C$ remote_filepath = \Users\Default\AppData\Local\Temp filename_regex = \d{4}-\d{4}-\d{4}-[0-9]{4} file_extension = .log -``` +``` + +#### Collectors config + +The collectors section in the config offers the option to define a custom set of collectors to run, as well as to specify collectors that will always be excluded (for example when using 'All' as collector). The config file is located at ~/.donpapi/donpapi.conf. +```toml +[collectors] +collectors_list = Chromium,Firefox,CredMan,MobaXterm,MRemoteNG,RDCMan,SCCM,Vaults,VNC,Wifi +always_exclude = +``` #### Recover diff --git a/donpapi/entry.py b/donpapi/entry.py index 1f36707..9515430 100644 --- a/donpapi/entry.py +++ b/donpapi/entry.py @@ -47,16 +47,24 @@ def set_main_logger(logger , host = "\U0001F480"): "hostname": "", } -def load_collectors(root, collectors_list) -> Tuple[List, List] : +def load_collectors(root, collectors_list, config) -> Tuple[List, List] : loaded_collectors = [] available_collectors = [] for _, collector_name, _ in iter_modules(path=[f"{root}/collectors/"]): available_collectors.append(collector_name) if "All" in collectors_list: loaded_collectors.append(getattr(import_module(f"donpapi.collectors.{collector_name}"), collector_name)) + elif "Custom" in collectors_list: + if collector_name in config.custom_collectors_list: + loaded_collectors.append(getattr(import_module(f"donpapi.collectors.{collector_name}"), collector_name)) else: if collector_name in collectors_list: loaded_collectors.append(getattr(import_module(f"donpapi.collectors.{collector_name}"), collector_name)) + + if config.always_exclude_collectors: + for collector in loaded_collectors: + if collector.__name__ in config.always_exclude_collectors: + loaded_collectors.remove(collector) return available_collectors, loaded_collectors def fetch_all_computers(options): @@ -215,7 +223,7 @@ def main(): group_authent.add_argument("-r", "--recover-file", metavar="/home/user/.donpapi/recover/recover_1718281433", type=str, help="The recover file path. If used, the other parameters will be ignored") group_attacks = collect_subparser.add_argument_group('attacks') - group_attacks.add_argument('-c','--collectors', action="store", default="All", help= ", ".join(load_collectors(root, [])[0])+", All (all previous) (default: All). Possible to chain multiple collectors comma separated") + group_attacks.add_argument('-c','--collectors', action="store", default="All", help= ", ".join(load_collectors(root, [], DonPAPIConfig())[0])+", Custom (as specified in config), All (all previous) (default: All). Possible to chain multiple collectors comma separated") group_attacks.add_argument("-nr","--no-remoteops", action="store_true", help="Disable Remote Ops operations (basically no Remote Registry operations, no DPAPI System Credentials)") group_attacks.add_argument("--fetch-pvk", action="store_true", help=("Will automatically use domain backup key from database, and if not already dumped, will dump it on a domain controller")) group_attacks.add_argument("--pvkfile", action="store", help=("Pvk file with domain backup key")) @@ -281,6 +289,10 @@ def main(): db = Database(db_engine) if options.action == "collect": + # Parse config file ? + donpapi_config = DonPAPIConfig() + if not options.no_config: + donpapi_config = parse_config_file() # Handle recover file current_target_recovered = [] @@ -330,7 +342,8 @@ def main(): pvkbytes = fetch_domain_backupkey(options, db) # Handling collectors - _, collectors = load_collectors(root, options.collectors.split(",")) + _, collectors = load_collectors(root, options.collectors.split(","), donpapi_config) + donpapi_logger.display(f"Loaded the following collectors: {','.join(collector.__name__ for collector in collectors)}") # Target selection targets = [] @@ -358,11 +371,6 @@ def main(): donpapi_logger.display("Loaded {i} targets".format(i=len(targets))) donpapi_logger.debug(f"Targets :{targets}") - # Parse config file ? - donpapi_config = DonPAPIConfig() - if not options.no_config: - donpapi_config = parse_config_file() - # Let's rock try: asyncio.run(start_dpp( diff --git a/donpapi/lib/config.py b/donpapi/lib/config.py index 4e02b26..176f7e4 100644 --- a/donpapi/lib/config.py +++ b/donpapi/lib/config.py @@ -8,6 +8,8 @@ DEFAULT_REMOTE_FILEPATH = "\\Users\\Default\\AppData\\Local\\Temp" DEFAULT_FILENAME_REGEX = r"\d{4}-\d{4}-\d{4}-[0-9]{4}" DEFAULT_FILE_EXTENSION = ".log" +DEFAULT_CUSTOM_COLLECTORS_LIST = "Chromium,Firefox,CredMan,MobaXterm,MRemoteNG,RDCMan,SCCM,Vaults,VNC,Wifi" +DEFAULT_ALWAYS_EXCLUDE_COLLECTORS = "" @dataclass class DonPAPIConfig: @@ -15,12 +17,14 @@ class DonPAPIConfig: custom_remote_filepath: str = DEFAULT_REMOTE_FILEPATH custom_filename_regex: str = DEFAULT_FILENAME_REGEX custom_file_extension: str = DEFAULT_FILE_EXTENSION + custom_collectors_list: str = DEFAULT_CUSTOM_COLLECTORS_LIST + always_exclude_collectors: str = DEFAULT_ALWAYS_EXCLUDE_COLLECTORS def parse_config_file(): donpapi_config = configparser.ConfigParser() donpapi_config.read(DPP_CONFIG_FILE_PATH) - if "secretsdump" not in donpapi_config.sections(): + if "secretsdump" not in donpapi_config.sections() or "collectors" not in donpapi_config.sections(): first_run() donpapi_config.read(DPP_CONFIG_FILE_PATH) @@ -29,4 +33,6 @@ def parse_config_file(): custom_remote_filepath = donpapi_config.get("secretsdump", "remote_filepath", fallback=DEFAULT_REMOTE_FILEPATH), custom_filename_regex = donpapi_config.get("secretsdump", "filename_regex", fallback=DEFAULT_FILENAME_REGEX), custom_file_extension = donpapi_config.get("secretsdump", "file_extension", fallback=DEFAULT_FILE_EXTENSION), + custom_collectors_list = donpapi_config.get("collectors", "collectors_list", fallback=DEFAULT_CUSTOM_COLLECTORS_LIST), + always_exclude_collectors = donpapi_config.get("collectors", "always_exclude", fallback=DEFAULT_ALWAYS_EXCLUDE_COLLECTORS), ) \ No newline at end of file diff --git a/donpapi/lib/utils.py b/donpapi/lib/utils.py index 6e54d40..25cdfd9 100644 --- a/donpapi/lib/utils.py +++ b/donpapi/lib/utils.py @@ -12,6 +12,9 @@ def create_recover_file(dirpath, targets, options): timestamp = int(time.time()) filepath = os.path.join(dirpath, DPP_RECOVER_DIR_NAME, f"recover_{timestamp}") + recover_dirpath = os.path.join(dirpath, DPP_RECOVER_DIR_NAME) + if not os.path.exists(recover_dirpath): + os.makedirs(recover_dirpath) with open(filepath, "w") as f: write_recover_file(f, [f"{json.dumps(vars(options))}\n", ",".join(targets)]) diff --git a/donpapi/res/donpapi.conf b/donpapi/res/donpapi.conf index 84106d7..a201617 100644 --- a/donpapi/res/donpapi.conf +++ b/donpapi/res/donpapi.conf @@ -2,4 +2,7 @@ share = C$ remote_filepath = \Users\Default\AppData\Local\Temp filename_regex = \d{4}-\d{4}-\d{4}-[0-9]{4} -file_extension = .log \ No newline at end of file +file_extension = .log +[collectors] +collectors_list = Certificates,Chromium,CloudCredentials,CredMan,Firefox,IDEProjects,MRemoteNG,MobaXTerm,NotepadPP,PasswordManagers,PowerShellHistory,RDCMan,RecentFiles,RecycleBin,SCCM,SSHSecrets,VNC,Vaults,VersionControlSystems,Wam,Wifi +always_exclude = \ No newline at end of file