Skip to content

Commit cb8467c

Browse files
authored
Quality of life improvements (#1439)
* 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
1 parent 66050dd commit cb8467c

File tree

7 files changed

+123
-47
lines changed

7 files changed

+123
-47
lines changed

examples/secretsdump.py

+43-33
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ def __init__(self, remoteName, username='', password='', domain='', options=None
9696
self.__securityHive = options.security
9797
self.__samHive = options.sam
9898
self.__ntdsFile = options.ntds
99+
self.__skipSam = options.skip_sam
100+
self.__skipSecurity = options.skip_security
99101
self.__history = options.history
100102
self.__noLMHash = True
101103
self.__isRemote = True
@@ -105,6 +107,7 @@ def __init__(self, remoteName, username='', password='', domain='', options=None
105107
self.__justDCNTLM = options.just_dc_ntlm
106108
self.__justUser = options.just_dc_user
107109
self.__ldapFilter = options.ldapfilter
110+
self.__skipUser = options.skip_user
108111
self.__pwdLastSet = options.pwd_last_set
109112
self.__printUserStatus= options.user_status
110113
self.__resumeFileName = options.resumefile
@@ -227,38 +230,40 @@ def dump(self):
227230
else:
228231
# If RemoteOperations succeeded, then we can extract SAM and LSA
229232
if self.__justDC is False and self.__justDCNTLM is False and self.__canProcessSAMLSA:
230-
try:
231-
if self.__isRemote is True:
232-
SAMFileName = self.__remoteOps.saveSAM()
233-
else:
234-
SAMFileName = self.__samHive
235-
236-
self.__SAMHashes = SAMHashes(SAMFileName, bootKey, isRemote = self.__isRemote)
237-
self.__SAMHashes.dump()
238-
if self.__outputFileName is not None:
239-
self.__SAMHashes.export(self.__outputFileName)
240-
except Exception as e:
241-
logging.error('SAM hashes extraction failed: %s' % str(e))
242-
243-
try:
244-
if self.__isRemote is True:
245-
SECURITYFileName = self.__remoteOps.saveSECURITY()
246-
else:
247-
SECURITYFileName = self.__securityHive
248-
249-
self.__LSASecrets = LSASecrets(SECURITYFileName, bootKey, self.__remoteOps,
233+
if not self.__skipSam:
234+
try:
235+
if self.__isRemote is True:
236+
SAMFileName = self.__remoteOps.saveSAM()
237+
else:
238+
SAMFileName = self.__samHive
239+
240+
self.__SAMHashes = SAMHashes(SAMFileName, bootKey, isRemote = self.__isRemote)
241+
self.__SAMHashes.dump()
242+
if self.__outputFileName is not None:
243+
self.__SAMHashes.export(self.__outputFileName)
244+
except Exception as e:
245+
logging.error('SAM hashes extraction failed: %s' % str(e))
246+
247+
if not self.__skipSecurity:
248+
try:
249+
if self.__isRemote is True:
250+
SECURITYFileName = self.__remoteOps.saveSECURITY()
251+
else:
252+
SECURITYFileName = self.__securityHive
253+
254+
self.__LSASecrets = LSASecrets(SECURITYFileName, bootKey, self.__remoteOps,
250255
isRemote=self.__isRemote, history=self.__history)
251-
self.__LSASecrets.dumpCachedHashes()
252-
if self.__outputFileName is not None:
253-
self.__LSASecrets.exportCached(self.__outputFileName)
254-
self.__LSASecrets.dumpSecrets()
255-
if self.__outputFileName is not None:
256-
self.__LSASecrets.exportSecrets(self.__outputFileName)
257-
except Exception as e:
258-
if logging.getLogger().level == logging.DEBUG:
259-
import traceback
260-
traceback.print_exc()
261-
logging.error('LSA hashes extraction failed: %s' % str(e))
256+
self.__LSASecrets.dumpCachedHashes()
257+
if self.__outputFileName is not None:
258+
self.__LSASecrets.exportCached(self.__outputFileName)
259+
self.__LSASecrets.dumpSecrets()
260+
if self.__outputFileName is not None:
261+
self.__LSASecrets.exportSecrets(self.__outputFileName)
262+
except Exception as e:
263+
if logging.getLogger().level == logging.DEBUG:
264+
import traceback
265+
traceback.print_exc()
266+
logging.error('LSA hashes extraction failed: %s' % str(e))
262267

263268
# NTDS Extraction we can try regardless of RemoteOperations failing. It might still work
264269
if self.__isRemote is True:
@@ -273,8 +278,9 @@ def dump(self):
273278
noLMHash=self.__noLMHash, remoteOps=self.__remoteOps,
274279
useVSSMethod=self.__useVSSMethod, justNTLM=self.__justDCNTLM,
275280
pwdLastSet=self.__pwdLastSet, resumeSession=self.__resumeFileName,
276-
outputFileName=self.__outputFileName, justUser=self.__justUser,
277-
ldapFilter=self.__ldapFilter, printUserStatus=self.__printUserStatus)
281+
outputFileName=self.__outputFileName, justUser=self.__justUser,
282+
skipUser=self.__skipUser, ldapFilter=self.__ldapFilter,
283+
printUserStatus=self.__printUserStatus)
278284
try:
279285
self.__NTDSHashes.dump()
280286
except Exception as e:
@@ -360,6 +366,8 @@ def cleanup(self):
360366
parser.add_argument('-resumefile', action='store', help='resume file name to resume NTDS.DIT session dump (only '
361367
'available to DRSUAPI approach). This file will also be used to keep updating the session\'s '
362368
'state')
369+
parser.add_argument('-skip-sam', action='store_true', help='Do NOT parse the SAM hive on remote system')
370+
parser.add_argument('-skip-security', action='store_true', help='Do NOT parse the SECURITY hive on remote system')
363371
parser.add_argument('-outputfile', action='store',
364372
help='base output filename. Extensions will be added for sam, secrets, cached and ntds')
365373
parser.add_argument('-use-vss', action='store_true', default=False,
@@ -382,6 +390,8 @@ def cleanup(self):
382390
help='Extract only NTDS.DIT data (NTLM hashes and Kerberos keys)')
383391
group.add_argument('-just-dc-ntlm', action='store_true', default=False,
384392
help='Extract only NTDS.DIT data (NTLM hashes only)')
393+
group.add_argument('-skip-user', action='store', help='Do NOT extract NTDS.DIT data for the user specified. '
394+
'Can provide comma-separated list of users to skip, or text file with one user per line')
385395
group.add_argument('-pwd-last-set', action='store_true', default=False,
386396
help='Shows pwdLastSet attribute for each NTDS.DIT account. Doesn\'t apply to -outputfile data')
387397
group.add_argument('-user-status', action='store_true', default=False,

examples/smbclient.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ def main():
3535
parser = argparse.ArgumentParser(add_help = True, description = "SMB client implementation.")
3636

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

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

104-
shell = MiniImpacketShell(smbClient)
105+
shell = MiniImpacketShell(smbClient, None, options.outputfile)
105106

106-
if options.file is not None:
107-
logging.info("Executing commands from %s" % options.file.name)
108-
for line in options.file.readlines():
107+
if options.outputfile is not None:
108+
f = open(options.outputfile, 'a')
109+
f.write('=' * 20 + '\n' + options.target_ip + '\n' + '=' * 20 + '\n')
110+
f.close()
111+
112+
if options.inputfile is not None:
113+
logging.info("Executing commands from %s" % options.inputfile.name)
114+
for line in options.inputfile.readlines():
109115
if line[0] != '#':
110116
print("# %s" % line, end=' ')
111117
shell.onecmd(line)
112118
else:
113119
print(line, end=' ')
114120
else:
115121
shell.cmdloop()
122+
116123
except Exception as e:
117124
if logging.getLogger().level == logging.DEBUG:
118125
import traceback

impacket/examples/ntlmrelayx/attacks/httpattacks/adcsattack.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import re
1717
import base64
18+
import os
1819
from OpenSSL import crypto
1920

2021
from impacket import LOG
@@ -58,7 +59,7 @@ def _run(self):
5859
response = self.client.getresponse()
5960

6061
if response.status != 200:
61-
LOG.error("Error getting certificate! Make sure you have entered valid certiface template.")
62+
LOG.error("Error getting certificate! Make sure you have entered valid certificate template.")
6263
return
6364

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

7879
certificate_store = self.generate_pfx(key, certificate)
79-
LOG.info("Base64 certificate of user %s: \n%s" % (self.username, base64.b64encode(certificate_store).decode()))
80+
LOG.info("Writing certificate to %s/%s.pfx" % (self.config.lootdir, self.username))
81+
try:
82+
if not os.path.isdir(self.config.lootdir):
83+
os.mkdir(self.config.lootdir)
84+
with open("%s/%s.pfx" % (self.config.lootdir, self.username), 'wb') as f:
85+
f.write(certificate_store)
86+
LOG.info("Certificate successfully written to file")
87+
except Exception as e:
88+
LOG.info("Unable to write certificate to file, printing B64 of certificate to console instead")
89+
LOG.info("Base64 certificate of user %s: \n%s" % (self.username, base64.b64encode(certificate_store).decode()))
90+
pass
8091

8192
if self.config.altName:
8293
LOG.info("This certificate can also be used for user : {}".format(self.config.altName))

impacket/examples/ntlmrelayx/servers/httprelayserver.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,8 @@ def do_relay(self, messageType, token, proxy, content = None):
469469
writeJohnOutputToFile(ntlm_hash_data['hash_string'], ntlm_hash_data['hash_version'],
470470
self.server.config.outputFile)
471471

472-
self.server.config.target.logTarget(self.target, True, self.authUser)
472+
if not self.server.config.isADCSAttack:
473+
self.server.config.target.logTarget(self.target, True, self.authUser)
473474
self.do_attack()
474475
if self.server.config.disableMulti:
475476
# We won't use the redirect trick, closing connection...
@@ -543,4 +544,4 @@ def run(self):
543544
except KeyboardInterrupt:
544545
pass
545546
LOG.info('Shutting down HTTP Server')
546-
self.server.server_close()
547+
self.server.server_close()

impacket/examples/ntlmrelayx/servers/smbrelayserver.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,8 @@ def SmbSessionSetup(self, connId, smbServer, recvPacket):
359359
# We have a session, create a thread and do whatever we want
360360
LOG.info("Authenticating against %s://%s as %s SUCCEED" % (self.target.scheme, self.target.netloc, self.authUser))
361361
# Log this target as processed for this client
362-
self.targetprocessor.logTarget(self.target, True, self.authUser)
362+
if not self.config.isADCSAttack:
363+
self.targetprocessor.logTarget(self.target, True, self.authUser)
363364

364365
ntlm_hash_data = outputToJohnFormat(connData['CHALLENGE_MESSAGE']['challenge'],
365366
authenticateMessage['user_name'],

impacket/examples/secretsdump.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -1996,7 +1996,7 @@ class CRYPTED_BLOB(Structure):
19961996

19971997
def __init__(self, ntdsFile, bootKey, isRemote=False, history=False, noLMHash=True, remoteOps=None,
19981998
useVSSMethod=False, justNTLM=False, pwdLastSet=False, resumeSession=None, outputFileName=None,
1999-
justUser=None, ldapFilter=None, printUserStatus=False,
1999+
justUser=None, skipUser=None,ldapFilter=None, printUserStatus=False,
20002000
perSecretCallback = lambda secretType, secret : _print_helper(secret),
20012001
resumeSessionMgr=ResumeSessionMgrInFile):
20022002
self.__bootKey = bootKey
@@ -2020,6 +2020,7 @@ def __init__(self, ntdsFile, bootKey, isRemote=False, history=False, noLMHash=Tr
20202020
self.__outputFileName = outputFileName
20212021
self.__justUser = justUser
20222022
self.__ldapFilter = ldapFilter
2023+
self.__skipUser = skipUser
20232024
self.__perSecretCallback = perSecretCallback
20242025

20252026
# these are all the columns that we need to get the secrets.
@@ -2499,7 +2500,16 @@ def dump(self):
24992500
hashesOutputFile = None
25002501
keysOutputFile = None
25012502
clearTextOutputFile = None
2503+
skipUsers = []
25022504

2505+
if self.__skipUser:
2506+
if os.path.isfile(self.__skipUser):
2507+
f = open(self.__skipUser, 'r')
2508+
skipUsers = [ line.strip() for line in f ]
2509+
f.close()
2510+
else:
2511+
skipUsers = self.__skipUser.split(',')
2512+
25032513
if self.__useVSSMethod is True:
25042514
if self.__NTDS is None:
25052515
# No NTDS.dit file provided and were asked to use VSS
@@ -2702,7 +2712,8 @@ def dump(self):
27022712

27032713
for user in resp['Buffer']['Buffer']:
27042714
userName = user['Name']
2705-
2715+
if userName in skipUsers:
2716+
continue
27062717
userSid = "%s-%i" % (self.__remoteOps.getDomainSid(), user['RelativeId'])
27072718
if resumeSid is not None:
27082719
# Means we're looking for a SID before start processing back again

impacket/examples/smbclient.py

+37-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
import readline
4343

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

7172
def emptyline(self):
7273
pass
7374

7475
def precmd(self,line):
7576
# switch to unicode
77+
if self.outputfile is not None:
78+
f = open(self.outputfile, 'a')
79+
f.write('> ' + line + "\n")
80+
f.close()
7681
if PY2:
7782
return line.decode('utf-8')
7883
return line
@@ -325,8 +330,14 @@ def do_shares(self, line):
325330
LOG.error("Not logged in")
326331
return
327332
resp = self.smb.listShares()
333+
if self.outputfile is not None:
334+
f = open(self.outputfile, 'a')
328335
for i in range(len(resp)):
336+
if self.outputfile:
337+
f.write(resp[i]['shi1_netname'][:-1] + '\n')
329338
print(resp[i]['shi1_netname'][:-1])
339+
if self.outputfile:
340+
f.close()
330341

331342
def do_use(self,line):
332343
if self.loggedIn is False:
@@ -372,6 +383,10 @@ def do_pwd(self,line):
372383
LOG.error("Not logged in")
373384
return
374385
print(self.pwd.replace("\\","/"))
386+
if self.outputfile is not None:
387+
f = open(self.outputfile, 'a')
388+
f.write(self.pwd.replace("\\","/"))
389+
f.close()
375390

376391
def do_ls(self, wildcard, display = True):
377392
if self.loggedIn is False:
@@ -387,12 +402,22 @@ def do_ls(self, wildcard, display = True):
387402
self.completion = []
388403
pwd = pwd.replace('/','\\')
389404
pwd = ntpath.normpath(pwd)
405+
if self.outputfile is not None:
406+
of = open(self.outputfile, 'a')
390407
for f in self.smb.listPath(self.share, pwd):
391408
if display is True:
409+
if self.outputfile:
410+
of.write("%crw-rw-rw- %10d %s %s" % (
411+
'd' if f.is_directory() > 0 else '-', f.get_filesize(), time.ctime(float(f.get_mtime_epoch())),
412+
f.get_longname()) + "\n")
413+
392414
print("%crw-rw-rw- %10d %s %s" % (
393415
'd' if f.is_directory() > 0 else '-', f.get_filesize(), time.ctime(float(f.get_mtime_epoch())),
394416
f.get_longname()))
395417
self.completion.append((f.get_longname(), f.is_directory()))
418+
if self.outputfile:
419+
of.close()
420+
396421
def do_lls(self, currentDir):
397422
if currentDir == "":
398423
currentDir = "./"
@@ -460,7 +485,6 @@ def do_tree(self, filepath):
460485
pass
461486
print("Finished - " + str(totalFilesRead) + " files and folders")
462487

463-
464488
def do_rm(self, filename):
465489
if self.tid is None:
466490
LOG.error("No share selected")
@@ -575,14 +599,25 @@ def do_cat(self, filename):
575599
output = fh.getvalue()
576600
encoding = chardet.detect(output)["encoding"]
577601
error_msg = "[-] Output cannot be correctly decoded, are you sure the text is readable ?"
602+
if self.outputfile is not None:
603+
f = open(self.outputfile, 'a')
578604
if encoding:
579605
try:
606+
if self.outputfile:
607+
f.write(output.decode(encoding) + '\n')
608+
f.close()
580609
print(output.decode(encoding))
581610
except:
611+
if self.outputfile:
612+
f.write(error_msg + '\n')
613+
f.close()
582614
print(error_msg)
583615
finally:
584616
fh.close()
585617
else:
618+
if self.outpufile:
619+
f.write(error_msg + '\n')
620+
f.close()
586621
print(error_msg)
587622
fh.close()
588623

0 commit comments

Comments
 (0)