[go: nahoru, domu]

Skip to content

Commit

Permalink
Quality of life improvements (fortra#1439)
Browse files Browse the repository at this point in the history
* Added ability to specify an output file for smbclient.py to log commands / output while using the smbclient shell

* Improved logging to output file for smbclient

* Added increased control over execution of secretsdump.py, including ability to skip specific users when dumping NTDS.dit or skip SAM hive when dumping remote machine

* Improved relaying to ADCS endpoints

* Improved relaying to ADCS endpoints

* Requested ADCS certificates are now saved to lootdir specified in command line

* Improved writing certificate to file

* Improved log file for smbclient.py

* Bug fix with smbclient output file after rebase

* Bug fix with outputfile logic
  • Loading branch information
RazzburyPi committed May 3, 2024
1 parent 66050dd commit cb8467c
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 47 deletions.
76 changes: 43 additions & 33 deletions examples/secretsdump.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ def __init__(self, remoteName, username='', password='', domain='', options=None
self.__securityHive = options.security
self.__samHive = options.sam
self.__ntdsFile = options.ntds
self.__skipSam = options.skip_sam
self.__skipSecurity = options.skip_security
self.__history = options.history
self.__noLMHash = True
self.__isRemote = True
Expand All @@ -105,6 +107,7 @@ def __init__(self, remoteName, username='', password='', domain='', options=None
self.__justDCNTLM = options.just_dc_ntlm
self.__justUser = options.just_dc_user
self.__ldapFilter = options.ldapfilter
self.__skipUser = options.skip_user
self.__pwdLastSet = options.pwd_last_set
self.__printUserStatus= options.user_status
self.__resumeFileName = options.resumefile
Expand Down Expand Up @@ -227,38 +230,40 @@ def dump(self):
else:
# 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)
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,
if not self.__skipSam:
try:
if self.__isRemote is True:
SAMFileName = self.__remoteOps.saveSAM()
else:
SAMFileName = self.__samHive

self.__SAMHashes = SAMHashes(SAMFileName, bootKey, isRemote = self.__isRemote)
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))

if not self.__skipSecurity:
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)
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:
if logging.getLogger().level == logging.DEBUG:
import traceback
traceback.print_exc()
logging.error('LSA hashes extraction failed: %s' % str(e))
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:
if logging.getLogger().level == logging.DEBUG:
import traceback
traceback.print_exc()
logging.error('LSA hashes extraction failed: %s' % str(e))

# NTDS Extraction we can try regardless of RemoteOperations failing. It might still work
if self.__isRemote is True:
Expand All @@ -273,8 +278,9 @@ def dump(self):
noLMHash=self.__noLMHash, remoteOps=self.__remoteOps,
useVSSMethod=self.__useVSSMethod, justNTLM=self.__justDCNTLM,
pwdLastSet=self.__pwdLastSet, resumeSession=self.__resumeFileName,
outputFileName=self.__outputFileName, justUser=self.__justUser,
ldapFilter=self.__ldapFilter, printUserStatus=self.__printUserStatus)
outputFileName=self.__outputFileName, justUser=self.__justUser,
skipUser=self.__skipUser, ldapFilter=self.__ldapFilter,
printUserStatus=self.__printUserStatus)
try:
self.__NTDSHashes.dump()
except Exception as e:
Expand Down Expand Up @@ -360,6 +366,8 @@ def cleanup(self):
parser.add_argument('-resumefile', action='store', help='resume file name to resume NTDS.DIT session dump (only '
'available to DRSUAPI approach). This file will also be used to keep updating the session\'s '
'state')
parser.add_argument('-skip-sam', action='store_true', help='Do NOT parse the SAM hive on remote system')
parser.add_argument('-skip-security', action='store_true', help='Do NOT parse the SECURITY hive on remote system')
parser.add_argument('-outputfile', action='store',
help='base output filename. Extensions will be added for sam, secrets, cached and ntds')
parser.add_argument('-use-vss', action='store_true', default=False,
Expand All @@ -382,6 +390,8 @@ def cleanup(self):
help='Extract only NTDS.DIT data (NTLM hashes and Kerberos keys)')
group.add_argument('-just-dc-ntlm', action='store_true', default=False,
help='Extract only NTDS.DIT data (NTLM hashes only)')
group.add_argument('-skip-user', action='store', help='Do NOT extract NTDS.DIT data for the user specified. '
'Can provide comma-separated list of users to skip, or text file with one user per line')
group.add_argument('-pwd-last-set', action='store_true', default=False,
help='Shows pwdLastSet attribute for each NTDS.DIT account. Doesn\'t apply to -outputfile data')
group.add_argument('-user-status', action='store_true', default=False,
Expand Down
17 changes: 12 additions & 5 deletions examples/smbclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ def main():
parser = argparse.ArgumentParser(add_help = True, description = "SMB client implementation.")

parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
parser.add_argument('-file', type=argparse.FileType('r'), help='input file with commands to execute in the mini shell')
parser.add_argument('-inputfile', type=argparse.FileType('r'), help='input file with commands to execute in the mini shell')
parser.add_argument('-outputfile', action='store', help='Output file to log smbclient actions in')
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')

group = parser.add_argument_group('authentication')
Expand Down Expand Up @@ -101,18 +102,24 @@ def main():
else:
smbClient.login(username, password, domain, lmhash, nthash)

shell = MiniImpacketShell(smbClient)
shell = MiniImpacketShell(smbClient, None, options.outputfile)

if options.file is not None:
logging.info("Executing commands from %s" % options.file.name)
for line in options.file.readlines():
if options.outputfile is not None:
f = open(options.outputfile, 'a')
f.write('=' * 20 + '\n' + options.target_ip + '\n' + '=' * 20 + '\n')
f.close()

if options.inputfile is not None:
logging.info("Executing commands from %s" % options.inputfile.name)
for line in options.inputfile.readlines():
if line[0] != '#':
print("# %s" % line, end=' ')
shell.onecmd(line)
else:
print(line, end=' ')
else:
shell.cmdloop()

except Exception as e:
if logging.getLogger().level == logging.DEBUG:
import traceback
Expand Down
15 changes: 13 additions & 2 deletions impacket/examples/ntlmrelayx/attacks/httpattacks/adcsattack.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import re
import base64
import os
from OpenSSL import crypto

from impacket import LOG
Expand Down Expand Up @@ -58,7 +59,7 @@ def _run(self):
response = self.client.getresponse()

if response.status != 200:
LOG.error("Error getting certificate! Make sure you have entered valid certiface template.")
LOG.error("Error getting certificate! Make sure you have entered valid certificate template.")
return

content = response.read()
Expand All @@ -76,7 +77,17 @@ def _run(self):
certificate = response.read().decode()

certificate_store = self.generate_pfx(key, certificate)
LOG.info("Base64 certificate of user %s: \n%s" % (self.username, base64.b64encode(certificate_store).decode()))
LOG.info("Writing certificate to %s/%s.pfx" % (self.config.lootdir, self.username))
try:
if not os.path.isdir(self.config.lootdir):
os.mkdir(self.config.lootdir)
with open("%s/%s.pfx" % (self.config.lootdir, self.username), 'wb') as f:
f.write(certificate_store)
LOG.info("Certificate successfully written to file")
except Exception as e:
LOG.info("Unable to write certificate to file, printing B64 of certificate to console instead")
LOG.info("Base64 certificate of user %s: \n%s" % (self.username, base64.b64encode(certificate_store).decode()))
pass

if self.config.altName:
LOG.info("This certificate can also be used for user : {}".format(self.config.altName))
Expand Down
5 changes: 3 additions & 2 deletions impacket/examples/ntlmrelayx/servers/httprelayserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,8 @@ def do_relay(self, messageType, token, proxy, content = None):
writeJohnOutputToFile(ntlm_hash_data['hash_string'], ntlm_hash_data['hash_version'],
self.server.config.outputFile)

self.server.config.target.logTarget(self.target, True, self.authUser)
if not self.server.config.isADCSAttack:
self.server.config.target.logTarget(self.target, True, self.authUser)
self.do_attack()
if self.server.config.disableMulti:
# We won't use the redirect trick, closing connection...
Expand Down Expand Up @@ -543,4 +544,4 @@ def run(self):
except KeyboardInterrupt:
pass
LOG.info('Shutting down HTTP Server')
self.server.server_close()
self.server.server_close()
3 changes: 2 additions & 1 deletion impacket/examples/ntlmrelayx/servers/smbrelayserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,8 @@ def SmbSessionSetup(self, connId, smbServer, recvPacket):
# We have a session, create a thread and do whatever we want
LOG.info("Authenticating against %s://%s as %s SUCCEED" % (self.target.scheme, self.target.netloc, self.authUser))
# Log this target as processed for this client
self.targetprocessor.logTarget(self.target, True, self.authUser)
if not self.config.isADCSAttack:
self.targetprocessor.logTarget(self.target, True, self.authUser)

ntlm_hash_data = outputToJohnFormat(connData['CHALLENGE_MESSAGE']['challenge'],
authenticateMessage['user_name'],
Expand Down
15 changes: 13 additions & 2 deletions impacket/examples/secretsdump.py
Original file line number Diff line number Diff line change
Expand Up @@ -1996,7 +1996,7 @@ class CRYPTED_BLOB(Structure):

def __init__(self, ntdsFile, bootKey, isRemote=False, history=False, noLMHash=True, remoteOps=None,
useVSSMethod=False, justNTLM=False, pwdLastSet=False, resumeSession=None, outputFileName=None,
justUser=None, ldapFilter=None, printUserStatus=False,
justUser=None, skipUser=None,ldapFilter=None, printUserStatus=False,
perSecretCallback = lambda secretType, secret : _print_helper(secret),
resumeSessionMgr=ResumeSessionMgrInFile):
self.__bootKey = bootKey
Expand All @@ -2020,6 +2020,7 @@ def __init__(self, ntdsFile, bootKey, isRemote=False, history=False, noLMHash=Tr
self.__outputFileName = outputFileName
self.__justUser = justUser
self.__ldapFilter = ldapFilter
self.__skipUser = skipUser
self.__perSecretCallback = perSecretCallback

# these are all the columns that we need to get the secrets.
Expand Down Expand Up @@ -2499,7 +2500,16 @@ def dump(self):
hashesOutputFile = None
keysOutputFile = None
clearTextOutputFile = None
skipUsers = []

if self.__skipUser:
if os.path.isfile(self.__skipUser):
f = open(self.__skipUser, 'r')
skipUsers = [ line.strip() for line in f ]
f.close()
else:
skipUsers = self.__skipUser.split(',')

if self.__useVSSMethod is True:
if self.__NTDS is None:
# No NTDS.dit file provided and were asked to use VSS
Expand Down Expand Up @@ -2702,7 +2712,8 @@ def dump(self):

for user in resp['Buffer']['Buffer']:
userName = user['Name']

if userName in skipUsers:
continue
userSid = "%s-%i" % (self.__remoteOps.getDomainSid(), user['RelativeId'])
if resumeSid is not None:
# Means we're looking for a SID before start processing back again
Expand Down
39 changes: 37 additions & 2 deletions impacket/examples/smbclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
import readline

class MiniImpacketShell(cmd.Cmd):
def __init__(self, smbClient, tcpShell=None):
def __init__(self, smbClient, tcpShell=None, outputfile=None):
#If the tcpShell parameter is passed (used in ntlmrelayx),
# all input and output is redirected to a tcp socket
# instead of to stdin / stdout
Expand All @@ -67,12 +67,17 @@ def __init__(self, smbClient, tcpShell=None):
self.loggedIn = True
self.last_output = None
self.completion = []
self.outputfile = outputfile

def emptyline(self):
pass

def precmd(self,line):
# switch to unicode
if self.outputfile is not None:
f = open(self.outputfile, 'a')
f.write('> ' + line + "\n")
f.close()
if PY2:
return line.decode('utf-8')
return line
Expand Down Expand Up @@ -325,8 +330,14 @@ def do_shares(self, line):
LOG.error("Not logged in")
return
resp = self.smb.listShares()
if self.outputfile is not None:
f = open(self.outputfile, 'a')
for i in range(len(resp)):
if self.outputfile:
f.write(resp[i]['shi1_netname'][:-1] + '\n')
print(resp[i]['shi1_netname'][:-1])
if self.outputfile:
f.close()

def do_use(self,line):
if self.loggedIn is False:
Expand Down Expand Up @@ -372,6 +383,10 @@ def do_pwd(self,line):
LOG.error("Not logged in")
return
print(self.pwd.replace("\\","/"))
if self.outputfile is not None:
f = open(self.outputfile, 'a')
f.write(self.pwd.replace("\\","/"))
f.close()

def do_ls(self, wildcard, display = True):
if self.loggedIn is False:
Expand All @@ -387,12 +402,22 @@ def do_ls(self, wildcard, display = True):
self.completion = []
pwd = pwd.replace('/','\\')
pwd = ntpath.normpath(pwd)
if self.outputfile is not None:
of = open(self.outputfile, 'a')
for f in self.smb.listPath(self.share, pwd):
if display is True:
if self.outputfile:
of.write("%crw-rw-rw- %10d %s %s" % (
'd' if f.is_directory() > 0 else '-', f.get_filesize(), time.ctime(float(f.get_mtime_epoch())),
f.get_longname()) + "\n")

print("%crw-rw-rw- %10d %s %s" % (
'd' if f.is_directory() > 0 else '-', f.get_filesize(), time.ctime(float(f.get_mtime_epoch())),
f.get_longname()))
self.completion.append((f.get_longname(), f.is_directory()))
if self.outputfile:
of.close()

def do_lls(self, currentDir):
if currentDir == "":
currentDir = "./"
Expand Down Expand Up @@ -460,7 +485,6 @@ def do_tree(self, filepath):
pass
print("Finished - " + str(totalFilesRead) + " files and folders")


def do_rm(self, filename):
if self.tid is None:
LOG.error("No share selected")
Expand Down Expand Up @@ -575,14 +599,25 @@ def do_cat(self, filename):
output = fh.getvalue()
encoding = chardet.detect(output)["encoding"]
error_msg = "[-] Output cannot be correctly decoded, are you sure the text is readable ?"
if self.outputfile is not None:
f = open(self.outputfile, 'a')
if encoding:
try:
if self.outputfile:
f.write(output.decode(encoding) + '\n')
f.close()
print(output.decode(encoding))
except:
if self.outputfile:
f.write(error_msg + '\n')
f.close()
print(error_msg)
finally:
fh.close()
else:
if self.outpufile:
f.write(error_msg + '\n')
f.close()
print(error_msg)
fh.close()

Expand Down

0 comments on commit cb8467c

Please sign in to comment.