ruff autoformat to clean up all the single quotes and other bad formatting
parent
9ae9d01e5a
commit
11ddfd9c79
|
@ -14,7 +14,8 @@ def gen_cli_args():
|
|||
VERSION = importlib.metadata.version("netexec")
|
||||
CODENAME = "A New Beginning"
|
||||
|
||||
parser = argparse.ArgumentParser(description=f"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description=f"""
|
||||
_ _ _ _____
|
||||
| \ | | ___ | |_ | ____| __ __ ___ ___
|
||||
| \| | / _ \ | __| | _| \ \/ / / _ \ / __|
|
||||
|
|
|
@ -45,4 +45,4 @@ if len(host_info_colors) != 4:
|
|||
# this should probably be put somewhere else, but if it's in the config helpers, there is a circular import
|
||||
def process_secret(text):
|
||||
hidden = text[:reveal_chars_of_pwd]
|
||||
return text if not audit_mode else hidden+audit_mode * 8
|
||||
return text if not audit_mode else hidden + audit_mode * 8
|
||||
|
|
|
@ -25,15 +25,16 @@ user_failed_logins = {}
|
|||
|
||||
def gethost_addrinfo(hostname):
|
||||
try:
|
||||
for res in getaddrinfo( hostname, None, AF_INET6, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME):
|
||||
for res in getaddrinfo(hostname, None, AF_INET6, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
host = canonname if ip_address(sa[0]).is_link_local else sa[0]
|
||||
except socket.gaierror:
|
||||
for res in getaddrinfo( hostname, None, AF_INET, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME):
|
||||
for res in getaddrinfo(hostname, None, AF_INET, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
host = sa[0] if sa[0] else canonname
|
||||
return host
|
||||
|
||||
|
||||
def requires_admin(func):
|
||||
def _decorator(self, *args, **kwargs):
|
||||
if self.admin_privs is False:
|
||||
|
@ -42,22 +43,23 @@ def requires_admin(func):
|
|||
|
||||
return wraps(func)(_decorator)
|
||||
|
||||
|
||||
def dcom_FirewallChecker(iInterface, timeout):
|
||||
stringBindings = iInterface.get_cinstance().get_string_bindings()
|
||||
for strBinding in stringBindings:
|
||||
if strBinding['wTowerId'] == 7:
|
||||
if strBinding['aNetworkAddr'].find('[') >= 0:
|
||||
binding, _, bindingPort = strBinding['aNetworkAddr'].partition('[')
|
||||
bindingPort = '[' + bindingPort
|
||||
if strBinding["wTowerId"] == 7:
|
||||
if strBinding["aNetworkAddr"].find("[") >= 0:
|
||||
binding, _, bindingPort = strBinding["aNetworkAddr"].partition("[")
|
||||
bindingPort = "[" + bindingPort
|
||||
else:
|
||||
binding = strBinding['aNetworkAddr']
|
||||
bindingPort = ''
|
||||
binding = strBinding["aNetworkAddr"]
|
||||
bindingPort = ""
|
||||
|
||||
if binding.upper().find(iInterface.get_target().upper()) >= 0:
|
||||
stringBinding = 'ncacn_ip_tcp:' + strBinding['aNetworkAddr'][:-1]
|
||||
stringBinding = "ncacn_ip_tcp:" + strBinding["aNetworkAddr"][:-1]
|
||||
break
|
||||
elif iInterface.is_fqdn() and binding.upper().find(iInterface.get_target().upper().partition('.')[0]) >= 0:
|
||||
stringBinding = 'ncacn_ip_tcp:%s%s' % (iInterface.get_target(), bindingPort)
|
||||
elif iInterface.is_fqdn() and binding.upper().find(iInterface.get_target().upper().partition(".")[0]) >= 0:
|
||||
stringBinding = "ncacn_ip_tcp:%s%s" % (iInterface.get_target(), bindingPort)
|
||||
if "stringBinding" not in locals():
|
||||
return True, None
|
||||
try:
|
||||
|
@ -308,7 +310,7 @@ class connection(object):
|
|||
# Parse usernames
|
||||
for user in self.args.username:
|
||||
if isfile(user):
|
||||
with open(user, 'r') as user_file:
|
||||
with open(user, "r") as user_file:
|
||||
for line in user_file:
|
||||
if "\\" in line:
|
||||
domain_single, username_single = line.split("\\")
|
||||
|
@ -340,34 +342,33 @@ class connection(object):
|
|||
self.logger.error(f"{type(e).__name__}: Could not decode password file. Make sure the file only contains UTF-8 characters.")
|
||||
self.logger.error("You can ignore non UTF-8 characters with the option '--ignore-pw-decoding'")
|
||||
exit(1)
|
||||
|
||||
else:
|
||||
secret.append(password)
|
||||
cred_type.append('plaintext')
|
||||
cred_type.append("plaintext")
|
||||
|
||||
# Parse NTLM-hashes
|
||||
if hasattr(self.args, "hash") and self.args.hash:
|
||||
for ntlm_hash in self.args.hash:
|
||||
if isfile(ntlm_hash):
|
||||
with open(ntlm_hash, 'r') as ntlm_hash_file:
|
||||
with open(ntlm_hash, "r") as ntlm_hash_file:
|
||||
for line in ntlm_hash_file:
|
||||
secret.append(line.strip())
|
||||
cred_type.append('hash')
|
||||
cred_type.append("hash")
|
||||
else:
|
||||
secret.append(ntlm_hash)
|
||||
cred_type.append('hash')
|
||||
cred_type.append("hash")
|
||||
|
||||
# Parse AES keys
|
||||
if self.args.aesKey:
|
||||
for aesKey in self.args.aesKey:
|
||||
if isfile(aesKey):
|
||||
with open(aesKey, 'r') as aesKey_file:
|
||||
with open(aesKey, "r") as aesKey_file:
|
||||
for line in aesKey_file:
|
||||
secret.append(line.strip())
|
||||
cred_type.append('aesKey')
|
||||
cred_type.append("aesKey")
|
||||
else:
|
||||
secret.append(aesKey)
|
||||
cred_type.append('aesKey')
|
||||
cred_type.append("aesKey")
|
||||
|
||||
# Allow trying multiple users with a single password
|
||||
if len(username) > 1 and len(secret) == 1:
|
||||
|
@ -390,26 +391,26 @@ class connection(object):
|
|||
if self.args.continue_on_success and owned:
|
||||
return False
|
||||
# Enforcing FQDN for SMB if not using local authentication. Related issues/PRs: #26, #28, #24, #38
|
||||
if self.args.protocol == 'smb' and not self.args.local_auth and "." not in domain and not self.args.laps and secret != "" and not (self.domain.upper() == self.hostname.upper()) :
|
||||
if self.args.protocol == "smb" and not self.args.local_auth and "." not in domain and not self.args.laps and secret != "" and not (self.domain.upper() == self.hostname.upper()):
|
||||
self.logger.error(f"Domain {domain} for user {username.rstrip()} need to be FQDN ex:domain.local, not domain")
|
||||
return False
|
||||
|
||||
with sem:
|
||||
if cred_type == 'plaintext':
|
||||
if cred_type == "plaintext":
|
||||
if self.args.kerberos:
|
||||
return self.kerberos_login(domain, username, secret, '', '', self.kdcHost, False)
|
||||
return self.kerberos_login(domain, username, secret, "", "", self.kdcHost, False)
|
||||
elif hasattr(self.args, "domain"): # Some protocolls don't use domain for login
|
||||
return self.plaintext_login(domain, username, secret)
|
||||
elif self.args.protocol == 'ssh':
|
||||
elif self.args.protocol == "ssh":
|
||||
return self.plaintext_login(username, secret, data)
|
||||
else:
|
||||
return self.plaintext_login(username, secret)
|
||||
elif cred_type == 'hash':
|
||||
elif cred_type == "hash":
|
||||
if self.args.kerberos:
|
||||
return self.kerberos_login(domain, username, '', secret, '', self.kdcHost, False)
|
||||
return self.kerberos_login(domain, username, "", secret, "", self.kdcHost, False)
|
||||
return self.hash_login(domain, username, secret)
|
||||
elif cred_type == 'aesKey':
|
||||
return self.kerberos_login(domain, username, '', '', secret, self.kdcHost, False)
|
||||
elif cred_type == "aesKey":
|
||||
return self.kerberos_login(domain, username, "", "", secret, self.kdcHost, False)
|
||||
|
||||
def login(self):
|
||||
"""
|
||||
|
|
|
@ -152,9 +152,7 @@ else
|
|||
IEX "$functions"
|
||||
Command-ToExecute
|
||||
}}
|
||||
""".format(
|
||||
command=amsi_bypass + ps_command
|
||||
)
|
||||
""".format(command=amsi_bypass + ps_command)
|
||||
)
|
||||
|
||||
else:
|
||||
|
|
|
@ -34,7 +34,7 @@ class NXCAdapter(logging.LoggerAdapter):
|
|||
logging.getLogger("pypykatz").disabled = True
|
||||
logging.getLogger("minidump").disabled = True
|
||||
logging.getLogger("lsassy").disabled = True
|
||||
#logging.getLogger("impacket").disabled = True
|
||||
# logging.getLogger("impacket").disabled = True
|
||||
|
||||
def format(self, msg, *args, **kwargs):
|
||||
"""
|
||||
|
@ -88,7 +88,7 @@ class NXCAdapter(logging.LoggerAdapter):
|
|||
nxc_console.print(text, *args, **kwargs)
|
||||
self.log_console_to_file(text, *args, **kwargs)
|
||||
|
||||
def success(self, msg, color='green', *args, **kwargs):
|
||||
def success(self, msg, color="green", *args, **kwargs):
|
||||
"""
|
||||
Print some sort of success to the user
|
||||
"""
|
||||
|
@ -118,7 +118,7 @@ class NXCAdapter(logging.LoggerAdapter):
|
|||
nxc_console.print(text, *args, **kwargs)
|
||||
self.log_console_to_file(text, *args, **kwargs)
|
||||
|
||||
def fail(self, msg, color='red', *args, **kwargs):
|
||||
def fail(self, msg, color="red", *args, **kwargs):
|
||||
"""
|
||||
Prints a failure (may or may not be an error) - e.g. login creds didn't work
|
||||
"""
|
||||
|
@ -181,13 +181,13 @@ class NXCAdapter(logging.LoggerAdapter):
|
|||
|
||||
@staticmethod
|
||||
def init_log_file():
|
||||
newpath = os.path.expanduser("~/.nxc") + "/logs/" + datetime.now().strftime('%Y-%m-%d')
|
||||
newpath = os.path.expanduser("~/.nxc") + "/logs/" + datetime.now().strftime("%Y-%m-%d")
|
||||
if not os.path.exists(newpath):
|
||||
os.makedirs(newpath)
|
||||
log_filename = os.path.join(
|
||||
os.path.expanduser("~/.nxc"),
|
||||
"logs",
|
||||
datetime.now().strftime('%Y-%m-%d'),
|
||||
datetime.now().strftime("%Y-%m-%d"),
|
||||
f"log_{datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}.log",
|
||||
)
|
||||
return log_filename
|
||||
|
|
|
@ -14,9 +14,9 @@ class NXCModule:
|
|||
Thanks to the guys at impacket for the original code
|
||||
"""
|
||||
|
||||
name = 'add-computer'
|
||||
description = 'Adds or deletes a domain computer'
|
||||
supported_protocols = ['smb']
|
||||
name = "add-computer"
|
||||
description = "Adds or deletes a domain computer"
|
||||
supported_protocols = ["smb"]
|
||||
opsec_safe = True
|
||||
multiple_hosts = False
|
||||
|
||||
|
@ -39,26 +39,26 @@ class NXCModule:
|
|||
self.__delete = False
|
||||
self.noLDAPRequired = False
|
||||
|
||||
if 'DELETE' in module_options:
|
||||
if "DELETE" in module_options:
|
||||
self.__delete = True
|
||||
|
||||
if 'CHANGEPW' in module_options and ('NAME' not in module_options or 'PASSWORD' not in module_options):
|
||||
context.log.error('NAME and PASSWORD options are required!')
|
||||
elif 'CHANGEPW' in module_options:
|
||||
self.__noAdd = True
|
||||
if "CHANGEPW" in module_options and ("NAME" not in module_options or "PASSWORD" not in module_options):
|
||||
context.log.error("NAME and PASSWORD options are required!")
|
||||
elif "CHANGEPW" in module_options:
|
||||
self.__noAdd = True
|
||||
|
||||
if 'NAME' in module_options:
|
||||
self.__computerName = module_options['NAME']
|
||||
if self.__computerName[-1] != '$':
|
||||
self.__computerName += '$'
|
||||
if "NAME" in module_options:
|
||||
self.__computerName = module_options["NAME"]
|
||||
if self.__computerName[-1] != "$":
|
||||
self.__computerName += "$"
|
||||
else:
|
||||
context.log.error('NAME option is required!')
|
||||
context.log.error("NAME option is required!")
|
||||
exit(1)
|
||||
|
||||
if 'PASSWORD' in module_options:
|
||||
self.__computerPassword = module_options['PASSWORD']
|
||||
elif 'PASSWORD' not in module_options and not self.__delete:
|
||||
context.log.error('PASSWORD option is required!')
|
||||
if "PASSWORD" in module_options:
|
||||
self.__computerPassword = module_options["PASSWORD"]
|
||||
elif "PASSWORD" not in module_options and not self.__delete:
|
||||
context.log.error("PASSWORD option is required!")
|
||||
exit(1)
|
||||
|
||||
def on_login(self, context, connection):
|
||||
|
@ -89,7 +89,7 @@ class NXCModule:
|
|||
|
||||
# If SAMR fails now try over LDAPS
|
||||
if not self.noLDAPRequired:
|
||||
self.do_ldaps_add(connection, context)
|
||||
self.do_ldaps_add(connection, context)
|
||||
else:
|
||||
exit(1)
|
||||
|
||||
|
@ -113,16 +113,9 @@ class NXCModule:
|
|||
rpc_transport.setRemoteHost(self.__targetIp)
|
||||
rpc_transport.setRemoteName(self.__target)
|
||||
|
||||
if hasattr(rpc_transport, 'set_credentials'):
|
||||
if hasattr(rpc_transport, "set_credentials"):
|
||||
# This method exists only for selected protocol sequences.
|
||||
rpc_transport.set_credentials(
|
||||
self.__username,
|
||||
self.__password,
|
||||
self.__domain,
|
||||
self.__lmhash,
|
||||
self.__nthash,
|
||||
self.__aesKey
|
||||
)
|
||||
rpc_transport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey)
|
||||
|
||||
rpc_transport.set_kerberos(self.__doKerberos, self.__kdcHost)
|
||||
|
||||
|
@ -130,22 +123,16 @@ class NXCModule:
|
|||
dce.connect()
|
||||
dce.bind(samr.MSRPC_UUID_SAMR)
|
||||
|
||||
samr_connect_response = samr.hSamrConnect5(
|
||||
dce,
|
||||
'\\\\%s\x00' % self.__target,
|
||||
samr.SAM_SERVER_ENUMERATE_DOMAINS | samr.SAM_SERVER_LOOKUP_DOMAIN
|
||||
)
|
||||
serv_handle = samr_connect_response['ServerHandle']
|
||||
samr_connect_response = samr.hSamrConnect5(dce, "\\\\%s\x00" % self.__target, samr.SAM_SERVER_ENUMERATE_DOMAINS | samr.SAM_SERVER_LOOKUP_DOMAIN)
|
||||
serv_handle = samr_connect_response["ServerHandle"]
|
||||
|
||||
samr_enum_response = samr.hSamrEnumerateDomainsInSamServer(dce, serv_handle)
|
||||
domains = samr_enum_response['Buffer']['Buffer']
|
||||
domains_without_builtin = [
|
||||
domain for domain in domains if domain['Name'].lower() != 'builtin'
|
||||
]
|
||||
domains = samr_enum_response["Buffer"]["Buffer"]
|
||||
domains_without_builtin = [domain for domain in domains if domain["Name"].lower() != "builtin"]
|
||||
if len(domains_without_builtin) > 1:
|
||||
domain = list(filter(lambda x: x['Name'].lower() == self.__domainNetbios, domains))
|
||||
domain = list(filter(lambda x: x["Name"].lower() == self.__domainNetbios, domains))
|
||||
if len(domain) != 1:
|
||||
context.log.highlight(u'{}'.format('This domain does not exist: "' + self.__domainNetbios + '"'))
|
||||
context.log.highlight("{}".format('This domain does not exist: "' + self.__domainNetbios + '"'))
|
||||
context.log.highlight("Available domain(s):")
|
||||
for domain in domains:
|
||||
context.log.highlight(f" * {domain['Name']}")
|
||||
|
@ -155,33 +142,25 @@ class NXCModule:
|
|||
else:
|
||||
selected_domain = domains_without_builtin[0]["Name"]
|
||||
|
||||
samr_lookup_domain_response = samr.hSamrLookupDomainInSamServer(
|
||||
dce, serv_handle, selected_domain
|
||||
)
|
||||
samr_lookup_domain_response = samr.hSamrLookupDomainInSamServer(dce, serv_handle, selected_domain)
|
||||
domain_sid = samr_lookup_domain_response["DomainId"]
|
||||
|
||||
context.log.debug(f"Opening domain {selected_domain}...")
|
||||
samr_open_domain_response = samr.hSamrOpenDomain(
|
||||
dce, serv_handle, samr.DOMAIN_LOOKUP | samr.DOMAIN_CREATE_USER, domain_sid
|
||||
)
|
||||
samr_open_domain_response = samr.hSamrOpenDomain(dce, serv_handle, samr.DOMAIN_LOOKUP | samr.DOMAIN_CREATE_USER, domain_sid)
|
||||
domain_handle = samr_open_domain_response["DomainHandle"]
|
||||
|
||||
if self.__noAdd or self.__delete:
|
||||
try:
|
||||
check_for_user = samr.hSamrLookupNamesInDomain(
|
||||
dce, domain_handle, [self.__computerName]
|
||||
)
|
||||
check_for_user = samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])
|
||||
except samr.DCERPCSessionError as e:
|
||||
if e.error_code == 0xc0000073:
|
||||
context.log.highlight(
|
||||
f"{self.__computerName} not found in domain {selected_domain}"
|
||||
)
|
||||
if e.error_code == 0xC0000073:
|
||||
context.log.highlight(f"{self.__computerName} not found in domain {selected_domain}")
|
||||
self.noLDAPRequired = True
|
||||
raise Exception()
|
||||
else:
|
||||
raise
|
||||
|
||||
user_rid = check_for_user['RelativeIds']['Element'][0]
|
||||
user_rid = check_for_user["RelativeIds"]["Element"][0]
|
||||
if self.__delete:
|
||||
access = samr.DELETE
|
||||
message = "delete"
|
||||
|
@ -190,11 +169,10 @@ class NXCModule:
|
|||
message = "set the password for"
|
||||
try:
|
||||
open_user = samr.hSamrOpenUser(dce, domain_handle, access, user_rid)
|
||||
user_handle = open_user['UserHandle']
|
||||
user_handle = open_user["UserHandle"]
|
||||
except samr.DCERPCSessionError as e:
|
||||
if e.error_code == 0xc0000022:
|
||||
context.log.highlight(u'{}'.format(
|
||||
self.__username + ' does not have the right to ' + message + " " + self.__computerName))
|
||||
if e.error_code == 0xC0000022:
|
||||
context.log.highlight("{}".format(self.__username + " does not have the right to " + message + " " + self.__computerName))
|
||||
self.noLDAPRequired = True
|
||||
raise Exception()
|
||||
else:
|
||||
|
@ -204,11 +182,10 @@ class NXCModule:
|
|||
try:
|
||||
samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])
|
||||
self.noLDAPRequired = True
|
||||
context.log.highlight(u'{}'.format(
|
||||
'Computer account already exists with the name: "' + self.__computerName + '"'))
|
||||
context.log.highlight("{}".format('Computer account already exists with the name: "' + self.__computerName + '"'))
|
||||
raise Exception()
|
||||
except samr.DCERPCSessionError as e:
|
||||
if e.error_code != 0xc0000073:
|
||||
if e.error_code != 0xC0000073:
|
||||
raise
|
||||
else:
|
||||
found_unused = False
|
||||
|
@ -217,52 +194,52 @@ class NXCModule:
|
|||
try:
|
||||
samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])
|
||||
except samr.DCERPCSessionError as e:
|
||||
if e.error_code == 0xc0000073:
|
||||
if e.error_code == 0xC0000073:
|
||||
found_unused = True
|
||||
else:
|
||||
raise
|
||||
try:
|
||||
create_user = samr.hSamrCreateUser2InDomain(dce, domain_handle, self.__computerName, samr.USER_WORKSTATION_TRUST_ACCOUNT, samr.USER_FORCE_PASSWORD_CHANGE,)
|
||||
create_user = samr.hSamrCreateUser2InDomain(
|
||||
dce,
|
||||
domain_handle,
|
||||
self.__computerName,
|
||||
samr.USER_WORKSTATION_TRUST_ACCOUNT,
|
||||
samr.USER_FORCE_PASSWORD_CHANGE,
|
||||
)
|
||||
self.noLDAPRequired = True
|
||||
context.log.highlight('Successfully added the machine account: "' + self.__computerName + '" with Password: "' + self.__computerPassword + '"')
|
||||
except samr.DCERPCSessionError as e:
|
||||
if e.error_code == 0xc0000022:
|
||||
context.log.highlight(u'{}'.format(
|
||||
'The following user does not have the right to create a computer account: "' + self.__username + '"'))
|
||||
if e.error_code == 0xC0000022:
|
||||
context.log.highlight("{}".format('The following user does not have the right to create a computer account: "' + self.__username + '"'))
|
||||
raise Exception()
|
||||
elif e.error_code == 0xc00002e7:
|
||||
context.log.highlight(u'{}'.format(
|
||||
'The following user exceeded their machine account quota: "' + self.__username + '"'))
|
||||
elif e.error_code == 0xC00002E7:
|
||||
context.log.highlight("{}".format('The following user exceeded their machine account quota: "' + self.__username + '"'))
|
||||
raise Exception()
|
||||
else:
|
||||
raise
|
||||
user_handle = create_user['UserHandle']
|
||||
user_handle = create_user["UserHandle"]
|
||||
|
||||
if self.__delete:
|
||||
samr.hSamrDeleteUser(dce, user_handle)
|
||||
context.log.highlight(u'{}'.format('Successfully deleted the "' + self.__computerName + '" Computer account'))
|
||||
self.noLDAPRequired=True
|
||||
context.log.highlight("{}".format('Successfully deleted the "' + self.__computerName + '" Computer account'))
|
||||
self.noLDAPRequired = True
|
||||
user_handle = None
|
||||
else:
|
||||
samr.hSamrSetPasswordInternal4New(dce, user_handle, self.__computerPassword)
|
||||
if self.__noAdd:
|
||||
context.log.highlight(u'{}'.format(
|
||||
'Successfully set the password of machine "' + self.__computerName + '" with password "' + self.__computerPassword + '"'))
|
||||
self.noLDAPRequired=True
|
||||
context.log.highlight("{}".format('Successfully set the password of machine "' + self.__computerName + '" with password "' + self.__computerPassword + '"'))
|
||||
self.noLDAPRequired = True
|
||||
else:
|
||||
check_for_user = samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])
|
||||
user_rid = check_for_user['RelativeIds']['Element'][0]
|
||||
open_user = samr.hSamrOpenUser(
|
||||
dce, domain_handle, access, user_rid
|
||||
)
|
||||
user_handle = open_user['UserHandle']
|
||||
user_rid = check_for_user["RelativeIds"]["Element"][0]
|
||||
open_user = samr.hSamrOpenUser(dce, domain_handle, access, user_rid)
|
||||
user_handle = open_user["UserHandle"]
|
||||
req = samr.SAMPR_USER_INFO_BUFFER()
|
||||
req['tag'] = samr.USER_INFORMATION_CLASS.UserControlInformation
|
||||
req['Control']['UserAccountControl'] = samr.USER_WORKSTATION_TRUST_ACCOUNT
|
||||
req["tag"] = samr.USER_INFORMATION_CLASS.UserControlInformation
|
||||
req["Control"]["UserAccountControl"] = samr.USER_WORKSTATION_TRUST_ACCOUNT
|
||||
samr.hSamrSetInformationUser2(dce, user_handle, req)
|
||||
if not self.noLDAPRequired:
|
||||
context.log.highlight(u'{}'.format(
|
||||
'Successfully added the machine account "' + self.__computerName + '" with Password: "' + self.__computerPassword + '"'))
|
||||
context.log.highlight("{}".format('Successfully added the machine account "' + self.__computerName + '" with Password: "' + self.__computerPassword + '"'))
|
||||
self.noLDAPRequired = True
|
||||
|
||||
if user_handle is not None:
|
||||
|
@ -315,8 +292,7 @@ class NXCModule:
|
|||
elif result is False and c.last_error == "insufficientAccessRights":
|
||||
context.log.highlight(f'Insufficient Access Rights to delete the Computer "{self.__computerName}"')
|
||||
else:
|
||||
context.log.highlight(
|
||||
f'Unable to delete the "{self.__computerName}" Computer account. The error was: {c.last_error}')
|
||||
context.log.highlight(f'Unable to delete the "{self.__computerName}" Computer account. The error was: {c.last_error}')
|
||||
else:
|
||||
result = c.add(
|
||||
f"cn={self.__computerName},cn=Computers,dc={ldap_domain}",
|
||||
|
@ -324,8 +300,7 @@ class NXCModule:
|
|||
ucd
|
||||
)
|
||||
if result:
|
||||
context.log.highlight(
|
||||
f'Successfully added the machine account: "{self.__computerName}" with Password: "{self.__computerPassword}"')
|
||||
context.log.highlight(f'Successfully added the machine account: "{self.__computerName}" with Password: "{self.__computerPassword}"')
|
||||
context.log.highlight("You can try to verify this with the nxc command:")
|
||||
context.log.highlight(f"nxc ldap {connection.host} -u {connection.username} -p {connection.password} -M group-mem -o GROUP='Domain Computers'")
|
||||
elif result is False and c.last_error == "entryAlreadyExists":
|
||||
|
|
|
@ -42,7 +42,7 @@ class NXCModule:
|
|||
command = "powershell -c 'C:\\windows\\system32\\inetsrv\\appcmd.exe list apppool /@t:*'"
|
||||
context.log.info("Checking For Hidden Credentials With Appcmd.exe")
|
||||
output = connection.execute(command, True)
|
||||
|
||||
|
||||
lines = output.splitlines()
|
||||
username = None
|
||||
password = None
|
||||
|
@ -58,12 +58,12 @@ class NXCModule:
|
|||
if "password:" in line:
|
||||
password = line.split("password:")[1].strip().strip('"')
|
||||
|
||||
if apppool_name and username is not None and password is not None:
|
||||
if apppool_name and username is not None and password is not None:
|
||||
current_credentials = (apppool_name, username, password)
|
||||
|
||||
if current_credentials not in credentials_set:
|
||||
credentials_set.add(current_credentials)
|
||||
|
||||
|
||||
if username:
|
||||
context.log.success(f"Credentials Found for APPPOOL: {apppool_name}")
|
||||
if password == "":
|
||||
|
|
|
@ -281,11 +281,7 @@ class NXCModule:
|
|||
searchBase=self.baseDN,
|
||||
searchFilter="(sAMAccountName=%s)" % escape_filter_chars(_lookedup_principal),
|
||||
attributes=["objectSid"],
|
||||
)[0][
|
||||
1
|
||||
][0][
|
||||
1
|
||||
][0]
|
||||
)[0][1][0][1][0]
|
||||
)
|
||||
context.log.highlight("Found principal SID to filter on: %s" % self.principal_sid)
|
||||
except Exception:
|
||||
|
@ -414,18 +410,12 @@ class NXCModule:
|
|||
searchBase=self.baseDN,
|
||||
searchFilter="(objectSid=%s)" % sid,
|
||||
attributes=["sAMAccountName"],
|
||||
)[
|
||||
0
|
||||
][0]
|
||||
)[0][0]
|
||||
samname = self.ldap_session.search(
|
||||
searchBase=self.baseDN,
|
||||
searchFilter="(objectSid=%s)" % sid,
|
||||
attributes=["sAMAccountName"],
|
||||
)[0][
|
||||
1
|
||||
][0][
|
||||
1
|
||||
][0]
|
||||
)[0][1][0][1][0]
|
||||
return samname
|
||||
except Exception:
|
||||
context.log.debug("SID not found in LDAP: %s" % sid)
|
||||
|
|
|
@ -55,7 +55,8 @@ class NXCModule:
|
|||
for service in product["services"]:
|
||||
try:
|
||||
lsa.LsarLookupNames(dce, policyHandle, service["name"])
|
||||
context.log.info(f"Detected installed service on {connection.host}: {product['name']} {service['description']}")
|
||||
context.log.info(
|
||||
f"Detected installed service on {connection.host}: {product['name']} {service['description']}")
|
||||
if product["name"] not in results:
|
||||
results[product["name"]] = {"services": []}
|
||||
results[product["name"]]["services"].append(service)
|
||||
|
@ -72,7 +73,8 @@ class NXCModule:
|
|||
for i, product in enumerate(conf["products"]):
|
||||
for pipe in product["pipes"]:
|
||||
if pathlib.PurePath(fl).match(pipe["name"]):
|
||||
context.log.debug(f"{product['name']} running claim found on {connection.host} by existing pipe {fl} (likely processes: {pipe['processes']})")
|
||||
context.log.debug(
|
||||
f"{product['name']} running claim found on {connection.host} by existing pipe {fl} (likely processes: {pipe['processes']})")
|
||||
if product["name"] not in results:
|
||||
results[product["name"]] = {}
|
||||
if "pipes" not in results[product["name"]]:
|
||||
|
@ -116,16 +118,16 @@ class LsaLookupNames:
|
|||
authn = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
domain="",
|
||||
username="",
|
||||
password="",
|
||||
remote_name="",
|
||||
k=False,
|
||||
kdcHost="",
|
||||
lmhash="",
|
||||
nthash="",
|
||||
aesKey="",
|
||||
self,
|
||||
domain="",
|
||||
username="",
|
||||
password="",
|
||||
remote_name="",
|
||||
k=False,
|
||||
kdcHost="",
|
||||
lmhash="",
|
||||
nthash="",
|
||||
aesKey="",
|
||||
):
|
||||
self.domain = domain
|
||||
self.username = username
|
||||
|
@ -157,7 +159,8 @@ class LsaLookupNames:
|
|||
# Authenticate if specified
|
||||
if self.authn and hasattr(rpc_transport, "set_credentials"):
|
||||
# This method exists only for selected protocol sequences.
|
||||
rpc_transport.set_credentials(self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey)
|
||||
rpc_transport.set_credentials(self.username, self.password, self.domain, self.lmhash, self.nthash,
|
||||
self.aesKey)
|
||||
|
||||
if self.doKerberos:
|
||||
rpc_transport.set_kerberos(self.doKerberos, kdcHost=self.dcHost)
|
||||
|
@ -357,17 +360,14 @@ conf = {
|
|||
"name": "kavfsslp",
|
||||
"description": "Kaspersky Security Exploit Prevention Service",
|
||||
},
|
||||
|
||||
{
|
||||
"name": "KAVFS",
|
||||
"description": "Kaspersky Security Service",
|
||||
},
|
||||
|
||||
{
|
||||
"name": "KAVFSGT",
|
||||
"description": "Kaspersky Security Management Service",
|
||||
},
|
||||
|
||||
{
|
||||
"name": "klnagent",
|
||||
"description": "Kaspersky Security Center",
|
||||
|
@ -378,9 +378,8 @@ conf = {
|
|||
"name": "Exploit_Blocker",
|
||||
"processes": ["kavfswh.exe"],
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "Trend Micro Endpoint Security",
|
||||
"services": [
|
||||
|
@ -388,30 +387,26 @@ conf = {
|
|||
"name": "Trend Micro Endpoint Basecamp",
|
||||
"description": "Trend Micro Endpoint Basecamp",
|
||||
},
|
||||
|
||||
{
|
||||
"name": "TMBMServer",
|
||||
"description": "Trend Micro Unauthorized Change Prevention Service",
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Trend Micro Web Service Communicator",
|
||||
"description": "Trend Micro Web Service Communicator",
|
||||
},
|
||||
|
||||
{
|
||||
"name": "TMiACAgentSvc",
|
||||
"description": "Trend Micro Application Control Service (Agent)",
|
||||
},
|
||||
{
|
||||
{
|
||||
"name": "CETASvc",
|
||||
"description": "Trend Micro Cloud Endpoint Telemetry Service",
|
||||
},
|
||||
{
|
||||
|
||||
"name": "iVPAgent",
|
||||
"description": "Trend Micro Vulnerability Protection Service (Agent)",
|
||||
}
|
||||
},
|
||||
],
|
||||
"pipes": [
|
||||
{
|
||||
|
@ -435,7 +430,7 @@ conf = {
|
|||
"processes": ["Ntrtscan.exe"],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "Symantec Endpoint Protection",
|
||||
"services": [
|
||||
|
@ -455,40 +450,40 @@ conf = {
|
|||
"name": "Sophos Intercept X",
|
||||
"services": [
|
||||
{
|
||||
"name": "SntpService",
|
||||
"description": "Sophos Network Threat Protection"
|
||||
"name": "SntpService",
|
||||
"description": "Sophos Network Threat Protection"
|
||||
},
|
||||
{
|
||||
"name": "Sophos Endpoint Defense Service",
|
||||
"description": "Sophos Endpoint Defense Service"
|
||||
"name": "Sophos Endpoint Defense Service",
|
||||
"description": "Sophos Endpoint Defense Service"
|
||||
},
|
||||
{
|
||||
"name": "Sophos File Scanner Service",
|
||||
"description": "Sophos File Scanner Service"
|
||||
"name": "Sophos File Scanner Service",
|
||||
"description": "Sophos File Scanner Service"
|
||||
},
|
||||
{
|
||||
"name": "Sophos Health Service",
|
||||
"description": "Sophos Health Service"
|
||||
"name": "Sophos Health Service",
|
||||
"description": "Sophos Health Service"
|
||||
},
|
||||
{
|
||||
"name": "Sophos Live Query",
|
||||
"description": "Sophos Live Query"
|
||||
"name": "Sophos Live Query",
|
||||
"description": "Sophos Live Query"
|
||||
},
|
||||
{
|
||||
"name": "Sophos Managed Threat Response",
|
||||
"description": "Sophos Managed Threat Response"
|
||||
"name": "Sophos Managed Threat Response",
|
||||
"description": "Sophos Managed Threat Response"
|
||||
},
|
||||
{
|
||||
"name": "Sophos MCS Agent",
|
||||
"description": "Sophos MCS Agent"
|
||||
"name": "Sophos MCS Agent",
|
||||
"description": "Sophos MCS Agent"
|
||||
},
|
||||
{
|
||||
"name": "Sophos MCS Client",
|
||||
"description": "Sophos MCS Client"
|
||||
"name": "Sophos MCS Client",
|
||||
"description": "Sophos MCS Client"
|
||||
},
|
||||
{
|
||||
"name": "Sophos System Protection Service",
|
||||
"description": "Sophos System Protection Service"
|
||||
"name": "Sophos System Protection Service",
|
||||
"description": "Sophos System Protection Service"
|
||||
}
|
||||
],
|
||||
"pipes": [
|
||||
|
@ -528,10 +523,7 @@ conf = {
|
|||
"name": "PandaAetherAgent",
|
||||
"description": "Panda Endpoint Agent",
|
||||
},
|
||||
{
|
||||
"name": "PSUAService",
|
||||
"description": "Panda Product Service"
|
||||
},
|
||||
{"name": "PSUAService", "description": "Panda Product Service"},
|
||||
{
|
||||
"name": "NanoServiceMain",
|
||||
"description": "Panda Cloud Antivirus Service",
|
||||
|
@ -547,7 +539,6 @@ conf = {
|
|||
"processes": ["PSUAService.exe"],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ class NXCModule:
|
|||
|
||||
name = "example module"
|
||||
description = "I do something"
|
||||
supported_protocols = [] # Example: ['smb', 'mssql']
|
||||
supported_protocols = [] # Example: ['smb', 'mssql']
|
||||
opsec_safe = True # Does the module touch disk?
|
||||
multiple_hosts = True # Does it make sense to run this module on multiple hosts at a time?
|
||||
|
||||
|
@ -30,21 +30,21 @@ class NXCModule:
|
|||
"""
|
||||
# Logging best practice
|
||||
# Mostly you should use these functions to display information to the user
|
||||
context.log.display("I'm doing something") # Use this for every normal message ([*] I'm doing something)
|
||||
context.log.success("I'm doing something") # Use this for when something succeeds ([+] I'm doing something)
|
||||
context.log.fail("I'm doing something") # Use this for when something fails ([-] I'm doing something), for example a remote registry entry is missing which is needed to proceed
|
||||
context.log.highlight("I'm doing something") # Use this for when something is important and should be highlighted, printing credentials for example
|
||||
context.log.display("I'm doing something") # Use this for every normal message ([*] I'm doing something)
|
||||
context.log.success("I'm doing something") # Use this for when something succeeds ([+] I'm doing something)
|
||||
context.log.fail("I'm doing something") # Use this for when something fails ([-] I'm doing something), for example a remote registry entry is missing which is needed to proceed
|
||||
context.log.highlight("I'm doing something") # Use this for when something is important and should be highlighted, printing credentials for example
|
||||
|
||||
# These are for debugging purposes
|
||||
context.log.info("I'm doing something") # This will only be displayed if the user has specified the --verbose flag, so add additional info that might be useful
|
||||
context.log.debug("I'm doing something") # This will only be displayed if the user has specified the --debug flag, so add info that you would might need for debugging errors
|
||||
context.log.info("I'm doing something") # This will only be displayed if the user has specified the --verbose flag, so add additional info that might be useful
|
||||
context.log.debug("I'm doing something") # This will only be displayed if the user has specified the --debug flag, so add info that you would might need for debugging errors
|
||||
|
||||
# These are for more critical error handling
|
||||
context.log.error("I'm doing something") # This will not be printed in the module context and should only be used for critical errors (e.g. a required python file is missing)
|
||||
context.log.error("I'm doing something") # This will not be printed in the module context and should only be used for critical errors (e.g. a required python file is missing)
|
||||
try:
|
||||
raise Exception("Exception that might occure")
|
||||
except Exception as e:
|
||||
context.log.exception(f"Exception occured: {e}") # This will display an exception traceback screen after an exception was raised and should only be used for critical errors
|
||||
context.log.exception(f"Exception occured: {e}") # This will display an exception traceback screen after an exception was raised and should only be used for critical errors
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
"""Concurrent.
|
||||
|
|
|
@ -8,12 +8,12 @@ from impacket.ldap.ldapasn1 import SearchResultEntry
|
|||
|
||||
class NXCModule:
|
||||
"""
|
||||
Module by CyberCelt: @Cyb3rC3lt
|
||||
Module by CyberCelt: @Cyb3rC3lt
|
||||
|
||||
Initial module:
|
||||
https://github.com/Cyb3rC3lt/CrackMapExec-Modules
|
||||
Initial module:
|
||||
https://github.com/Cyb3rC3lt/CrackMapExec-Modules
|
||||
"""
|
||||
|
||||
|
||||
name = "find-computer"
|
||||
description = "Finds computers in the domain via the provided text"
|
||||
supported_protocols = ["ldap"]
|
||||
|
@ -41,11 +41,7 @@ class NXCModule:
|
|||
|
||||
try:
|
||||
context.log.debug(f"Search Filter={search_filter}")
|
||||
resp = connection.ldapConnection.search(
|
||||
searchFilter=search_filter,
|
||||
attributes=["dNSHostName", "operatingSystem"],
|
||||
sizeLimit=0
|
||||
)
|
||||
resp = connection.ldapConnection.search(searchFilter=search_filter, attributes=["dNSHostName", "operatingSystem"], sizeLimit=0)
|
||||
except LDAPSearchError as e:
|
||||
if e.getErrorString().find("sizeLimitExceeded") >= 0:
|
||||
context.log.debug("sizeLimitExceeded exception caught, giving up and processing the data received")
|
||||
|
@ -69,7 +65,7 @@ class NXCModule:
|
|||
elif str(attribute["type"]) == "operatingSystem":
|
||||
operating_system = attribute["vals"][0]
|
||||
if dns_host_name != "" and operating_system != "":
|
||||
answers.append([dns_host_name,operating_system])
|
||||
answers.append([dns_host_name, operating_system])
|
||||
except Exception as e:
|
||||
context.log.debug("Exception:", exc_info=True)
|
||||
context.log.debug(f"Skipping item, cannot process due to error {e}")
|
||||
|
@ -79,10 +75,10 @@ class NXCModule:
|
|||
for answer in answers:
|
||||
try:
|
||||
ip = socket.gethostbyname(answer[0])
|
||||
context.log.highlight(f'{answer[0]} ({answer[1]}) ({ip})')
|
||||
context.log.highlight(f"{answer[0]} ({answer[1]}) ({ip})")
|
||||
context.log.debug("IP found")
|
||||
except socket.gaierror:
|
||||
context.log.debug('Missing IP')
|
||||
context.log.highlight(f'{answer[0]} ({answer[1]}) (No IP Found)')
|
||||
context.log.debug("Missing IP")
|
||||
context.log.highlight(f"{answer[0]} ({answer[1]}) (No IP Found)")
|
||||
else:
|
||||
context.log.success(f"Unable to find any computers with the text {self.TEXT}")
|
||||
|
|
|
@ -5,10 +5,10 @@ from impacket.ldap import ldapasn1 as ldapasn1_impacket
|
|||
|
||||
class NXCModule:
|
||||
"""
|
||||
Module by CyberCelt: @Cyb3rC3lt
|
||||
Module by CyberCelt: @Cyb3rC3lt
|
||||
|
||||
Initial module:
|
||||
https://github.com/Cyb3rC3lt/CrackMapExec-Modules
|
||||
Initial module:
|
||||
https://github.com/Cyb3rC3lt/CrackMapExec-Modules
|
||||
"""
|
||||
|
||||
name = "group-mem"
|
||||
|
@ -26,7 +26,7 @@ class NXCModule:
|
|||
Usage: nxc ldap $DC-IP -u Username -p Password -M group-mem -o GROUP="domain admins"
|
||||
nxc ldap $DC-IP -u Username -p Password -M group-mem -o GROUP="domain controllers"
|
||||
"""
|
||||
self.GROUP = ''
|
||||
self.GROUP = ""
|
||||
|
||||
if "GROUP" in module_options:
|
||||
self.GROUP = module_options["GROUP"]
|
||||
|
@ -39,34 +39,34 @@ class NXCModule:
|
|||
search_filter = "(&(objectCategory=group)(cn=" + self.GROUP + "))"
|
||||
attribute = "objectSid"
|
||||
|
||||
search_result = doSearch(self, context, connection, search_filter, attribute)
|
||||
search_result = do_search(self, context, connection, search_filter, attribute)
|
||||
# If no SID for the Group is returned exit the program
|
||||
if search_result is None:
|
||||
context.log.success('Unable to find any members of the "' + self.GROUP + '" group')
|
||||
return True
|
||||
|
||||
# Convert the binary SID to a primaryGroupID string to be used further
|
||||
sidString = connection.sid_to_str(search_result).split("-")
|
||||
self.primaryGroupID = sidString[-1]
|
||||
sid_string = connection.sid_to_str(search_result).split("-")
|
||||
self.primaryGroupID = sid_string[-1]
|
||||
|
||||
# Look up the groups DN
|
||||
search_filter = "(&(objectCategory=group)(cn=" + self.GROUP + "))"
|
||||
attribute = "distinguishedName"
|
||||
distinguished_name = (doSearch(self, context, connection, search_filter, attribute)).decode("utf-8")
|
||||
distinguished_name = (do_search(self, context, connection, search_filter, attribute)).decode("utf-8")
|
||||
|
||||
# Carry out the search
|
||||
search_filter = "(|(memberOf="+distinguished_name+")(primaryGroupID="+self.primaryGroupID+"))"
|
||||
search_filter = "(|(memberOf=" + distinguished_name + ")(primaryGroupID=" + self.primaryGroupID + "))"
|
||||
attribute = "sAMAccountName"
|
||||
search_result = doSearch(self, context, connection, search_filter, attribute)
|
||||
search_result = do_search(self, context, connection, search_filter, attribute)
|
||||
|
||||
if len(self.answers) > 0:
|
||||
context.log.success('Found the following members of the ' + self.GROUP + ' group:')
|
||||
context.log.success("Found the following members of the " + self.GROUP + " group:")
|
||||
for answer in self.answers:
|
||||
context.log.highlight(u'{}'.format(answer[0]))
|
||||
context.log.highlight("{}".format(answer[0]))
|
||||
|
||||
|
||||
# Carry out an LDAP search for the Group with the supplied Group name
|
||||
def doSearch(self,context, connection,searchFilter,attributeName):
|
||||
def do_search(self, context, connection, searchFilter, attributeName):
|
||||
try:
|
||||
context.log.debug(f"Search Filter={searchFilter}")
|
||||
resp = connection.ldapConnection.search(
|
||||
|
@ -78,18 +78,18 @@ def doSearch(self,context, connection,searchFilter,attributeName):
|
|||
for item in resp:
|
||||
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
|
||||
continue
|
||||
attribute_value = ''
|
||||
attribute_value = ""
|
||||
try:
|
||||
for attribute in item['attributes']:
|
||||
if str(attribute['type']) == attributeName:
|
||||
for attribute in item["attributes"]:
|
||||
if str(attribute["type"]) == attributeName:
|
||||
if attributeName == "objectSid":
|
||||
attribute_value = bytes(attribute['vals'][0])
|
||||
return attribute_value
|
||||
attribute_value = bytes(attribute["vals"][0])
|
||||
return attribute_value
|
||||
elif attributeName == "distinguishedName":
|
||||
attribute_value = bytes(attribute['vals'][0])
|
||||
return attribute_value
|
||||
attribute_value = bytes(attribute["vals"][0])
|
||||
return attribute_value
|
||||
else:
|
||||
attribute_value = str(attribute['vals'][0])
|
||||
attribute_value = str(attribute["vals"][0])
|
||||
if attribute_value is not None:
|
||||
self.answers.append([attribute_value])
|
||||
except Exception as e:
|
||||
|
|
|
@ -151,10 +151,10 @@ class NXCModule:
|
|||
def save_credentials(context, connection, domain, username, password, lmhash, nthash):
|
||||
host_id = context.db.get_computers(connection.host)[0][0]
|
||||
if password is not None:
|
||||
credential_type = 'plaintext'
|
||||
credential_type = "plaintext"
|
||||
else:
|
||||
credential_type = 'hash'
|
||||
password = ':'.join(h for h in [lmhash, nthash] if h is not None)
|
||||
credential_type = "hash"
|
||||
password = ":".join(h for h in [lmhash, nthash] if h is not None)
|
||||
context.db.add_credential(credential_type, domain, username, password, pillaged_from=host_id)
|
||||
|
||||
def options(self, context, module_options):
|
||||
|
@ -221,23 +221,17 @@ class NXCModule:
|
|||
cred["lmhash"],
|
||||
cred["nthash"],
|
||||
] not in credentials_unique:
|
||||
credentials_unique.append([
|
||||
cred["domain"],
|
||||
cred["username"],
|
||||
cred["password"],
|
||||
cred["lmhash"],
|
||||
cred["nthash"],
|
||||
])
|
||||
credentials_output.append(cred)
|
||||
self.save_credentials(
|
||||
context,
|
||||
connection,
|
||||
cred["domain"],
|
||||
cred["username"],
|
||||
cred["password"],
|
||||
cred["lmhash"],
|
||||
cred["nthash"]
|
||||
credentials_unique.append(
|
||||
[
|
||||
cred["domain"],
|
||||
cred["username"],
|
||||
cred["password"],
|
||||
cred["lmhash"],
|
||||
cred["nthash"],
|
||||
]
|
||||
)
|
||||
credentials_output.append(cred)
|
||||
self.save_credentials(context, connection, cred["domain"], cred["username"], cred["password"], cred["lmhash"], cred["nthash"])
|
||||
global credentials_data
|
||||
credentials_data = credentials_output
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -53,15 +53,7 @@ class NXCModule:
|
|||
values = {str(attr["type"]).lower(): attr["vals"][0] for attr in computer["attributes"]}
|
||||
if "mslaps-encryptedpassword" in values:
|
||||
msMCSAdmPwd = values["mslaps-encryptedpassword"]
|
||||
d = LAPSv2Extract(
|
||||
bytes(msMCSAdmPwd),
|
||||
connection.username if connection.username else "",
|
||||
connection.password if connection.password else "",
|
||||
connection.domain,
|
||||
connection.nthash if connection.nthash else "",
|
||||
connection.kerberos,
|
||||
connection.kdcHost,
|
||||
339)
|
||||
d = LAPSv2Extract(bytes(msMCSAdmPwd), connection.username if connection.username else "", connection.password if connection.password else "", connection.domain, connection.nthash if connection.nthash else "", connection.kerberos, connection.kdcHost, 339)
|
||||
try:
|
||||
data = d.run()
|
||||
except Exception as e:
|
||||
|
|
|
@ -13,6 +13,7 @@ from asyauth.common.credentials.kerberos import KerberosCredential
|
|||
|
||||
from asysocks.unicomm.common.target import UniTarget, UniProto
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Checks whether LDAP signing and channelbinding are required.
|
||||
|
@ -131,7 +132,7 @@ class NXCModule:
|
|||
else:
|
||||
context.log.fail(str(err))
|
||||
|
||||
# Run trough all our code blocks to determine LDAP signing and channel binding settings.
|
||||
# Run trough all our code blocks to determine LDAP signing and channel binding settings.
|
||||
stype = asyauthSecret.PASS if not connection.nthash else asyauthSecret.NT
|
||||
secret = connection.password if not connection.nthash else connection.nthash
|
||||
if not connection.kerberos:
|
||||
|
@ -142,15 +143,7 @@ class NXCModule:
|
|||
stype=stype,
|
||||
)
|
||||
else:
|
||||
kerberos_target = UniTarget(
|
||||
connection.hostname + '.' + connection.domain,
|
||||
88,
|
||||
UniProto.CLIENT_TCP,
|
||||
proxies=None,
|
||||
dns=None,
|
||||
dc_ip=connection.domain,
|
||||
domain=connection.domain
|
||||
)
|
||||
kerberos_target = UniTarget(connection.hostname + "." + connection.domain, 88, UniProto.CLIENT_TCP, proxies=None, dns=None, dc_ip=connection.domain, domain=connection.domain)
|
||||
credential = KerberosCredential(
|
||||
target=kerberos_target,
|
||||
secret=secret,
|
||||
|
|
|
@ -33,18 +33,18 @@ class SmbHeader(Structure):
|
|||
_fields_ = [
|
||||
("server_component", c_uint32),
|
||||
("smb_command", c_uint8),
|
||||
("error_class", c_uint8),
|
||||
("reserved1", c_uint8),
|
||||
("error_code", c_uint16),
|
||||
("flags", c_uint8),
|
||||
("flags2", c_uint16),
|
||||
("process_id_high", c_uint16),
|
||||
("signature", c_uint64),
|
||||
("reserved2", c_uint16),
|
||||
("tree_id", c_uint16),
|
||||
("process_id", c_uint16),
|
||||
("user_id", c_uint16),
|
||||
("multiplex_id", c_uint16),
|
||||
("error_class", c_uint8),
|
||||
("reserved1", c_uint8),
|
||||
("error_code", c_uint16),
|
||||
("flags", c_uint8),
|
||||
("flags2", c_uint16),
|
||||
("process_id_high", c_uint16),
|
||||
("signature", c_uint64),
|
||||
("reserved2", c_uint16),
|
||||
("tree_id", c_uint16),
|
||||
("process_id", c_uint16),
|
||||
("user_id", c_uint16),
|
||||
("multiplex_id", c_uint16),
|
||||
]
|
||||
|
||||
def __new__(cls, buffer=None):
|
||||
|
@ -99,7 +99,7 @@ def negotiate_proto_request():
|
|||
# Define the NetBIOS header
|
||||
netbios = [
|
||||
"\x00", # Message Type
|
||||
"\x00\x00\x54" # Length
|
||||
"\x00\x00\x54", # Length
|
||||
]
|
||||
|
||||
# Define the SMB header
|
||||
|
@ -115,7 +115,7 @@ def negotiate_proto_request():
|
|||
"\x00\x00", # Tree ID
|
||||
"\x2F\x4B", # Process ID
|
||||
"\x00\x00", # User ID
|
||||
"\xC5\x5E" # Multiplex ID
|
||||
"\xC5\x5E", # Multiplex ID
|
||||
]
|
||||
|
||||
# Define the negotiate_proto_request
|
||||
|
@ -129,7 +129,7 @@ def negotiate_proto_request():
|
|||
"\x02", # Requested Dialects Count
|
||||
"\x4E\x54\x20\x4C\x41\x4E\x4D\x41\x4E\x20\x31\x2E\x30\x00", # Requested Dialects
|
||||
"\x02", # Requested Dialects Count
|
||||
"\x4E\x54\x20\x4C\x4D\x20\x30\x2E\x31\x32\x00" # Requested Dialects
|
||||
"\x4E\x54\x20\x4C\x4D\x20\x30\x2E\x31\x32\x00", # Requested Dialects
|
||||
]
|
||||
|
||||
# Return the generated SMB protocol payload
|
||||
|
@ -140,45 +140,45 @@ def session_setup_andx_request():
|
|||
"""Generate session setup andx request."""
|
||||
# Define the NetBIOS bytes
|
||||
netbios = [
|
||||
"\x00", # length
|
||||
"\x00\x00\x63" # session service
|
||||
"\x00", # length
|
||||
"\x00\x00\x63", # session service
|
||||
]
|
||||
|
||||
# Define the SMB header bytes
|
||||
smb_header = [
|
||||
"\xFF\x53\x4D\x42", # server component
|
||||
"\x73", # command
|
||||
"\x00\x00\x00\x00", # NT status
|
||||
"\x18", # flags
|
||||
"\x01\x20", # flags2
|
||||
"\x00\x00", # PID high
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00", # signature
|
||||
"\x00\x00", # reserved
|
||||
"\x00\x00", # tid
|
||||
"\x2F\x4B", # pid
|
||||
"\x00\x00", # uid
|
||||
"\xC5\x5E" # mid
|
||||
"\xFF\x53\x4D\x42", # server component
|
||||
"\x73", # command
|
||||
"\x00\x00\x00\x00", # NT status
|
||||
"\x18", # flags
|
||||
"\x01\x20", # flags2
|
||||
"\x00\x00", # PID high
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00", # signature
|
||||
"\x00\x00", # reserved
|
||||
"\x00\x00", # tid
|
||||
"\x2F\x4B", # pid
|
||||
"\x00\x00", # uid
|
||||
"\xC5\x5E", # mid
|
||||
]
|
||||
|
||||
# Define the session setup andx request bytes
|
||||
session_setup_andx_request = [
|
||||
"\x0D", # word count
|
||||
"\xFF", # andx command
|
||||
"\x00", # reserved
|
||||
"\x00\x00", # andx offset
|
||||
"\xDF\xFF", # max buffer
|
||||
"\x02\x00", # max mpx count
|
||||
"\x01\x00", # VC number
|
||||
"\x00\x00\x00\x00", # session key
|
||||
"\x00\x00", # ANSI password length
|
||||
"\x00\x00", # Unicode password length
|
||||
"\x00\x00\x00\x00", # reserved
|
||||
"\x40\x00\x00\x00", # capabilities
|
||||
"\x26\x00", # byte count
|
||||
"\x00", # account name length
|
||||
"\x2e\x00", # account name offset
|
||||
"\x57\x69\x6e\x64\x6f\x77\x73\x20\x32\x30\x30\x30\x20\x32\x31\x39\x35\x00", # account name
|
||||
"\x57\x69\x6e\x64\x6f\x77\x73\x20\x32\x30\x30\x30\x20\x35\x2e\x30\x00" # primary domain
|
||||
"\x0D", # word count
|
||||
"\xFF", # andx command
|
||||
"\x00", # reserved
|
||||
"\x00\x00", # andx offset
|
||||
"\xDF\xFF", # max buffer
|
||||
"\x02\x00", # max mpx count
|
||||
"\x01\x00", # VC number
|
||||
"\x00\x00\x00\x00", # session key
|
||||
"\x00\x00", # ANSI password length
|
||||
"\x00\x00", # Unicode password length
|
||||
"\x00\x00\x00\x00", # reserved
|
||||
"\x40\x00\x00\x00", # capabilities
|
||||
"\x26\x00", # byte count
|
||||
"\x00", # account name length
|
||||
"\x2e\x00", # account name offset
|
||||
"\x57\x69\x6e\x64\x6f\x77\x73\x20\x32\x30\x30\x30\x20\x32\x31\x39\x35\x00", # account name
|
||||
"\x57\x69\x6e\x64\x6f\x77\x73\x20\x32\x30\x30\x30\x20\x35\x2e\x30\x00", # primary domain
|
||||
]
|
||||
|
||||
# Call the generate_smb_proto_payload function and return the result
|
||||
|
|
|
@ -162,10 +162,7 @@ class NXCModule:
|
|||
try:
|
||||
context.log.success("Dumping the NTDS, this could take a while so go grab a redbull...")
|
||||
NTDS.dump()
|
||||
context.log.success(
|
||||
f"Dumped {highlight(add_ntds_hash.ntds_hashes)} NTDS hashes to {connection.output_filename}.ntds "
|
||||
f"of which {highlight(add_ntds_hash.added_to_db)} were added to the database"
|
||||
)
|
||||
context.log.success(f"Dumped {highlight(add_ntds_hash.ntds_hashes)} NTDS hashes to {connection.output_filename}.ntds " f"of which {highlight(add_ntds_hash.added_to_db)} were added to the database")
|
||||
|
||||
context.log.display("To extract only enabled accounts from the output file, run the following command: ")
|
||||
context.log.display(f"grep -iv disabled {connection.output_filename}.ntds | cut -d ':' -f1")
|
||||
|
@ -176,10 +173,6 @@ class NXCModule:
|
|||
|
||||
if self.no_delete:
|
||||
context.log.display(f"Raw NTDS dump copied to {self.dir_result}, parse it with:")
|
||||
context.log.display(
|
||||
f'secretsdump.py -system {self.dir_result}/registry/SYSTEM '
|
||||
f'-security {self.dir_result}/registry/SECURITY '
|
||||
f'-ntds "{self.dir_result}/Active Directory/ntds.dit" LOCAL'
|
||||
)
|
||||
context.log.display(f"secretsdump.py -system {self.dir_result}/registry/SYSTEM " f"-security {self.dir_result}/registry/SECURITY " f'-ntds "{self.dir_result}/Active Directory/ntds.dit" LOCAL')
|
||||
else:
|
||||
shutil.rmtree(self.dir_result)
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -8,9 +8,9 @@ from math import fabs
|
|||
|
||||
class NXCModule:
|
||||
"""
|
||||
Created by fplazar and wanetty
|
||||
Module by @gm_eduard and @ferranplaza
|
||||
Based on: https://github.com/juliourena/CrackMapExec/blob/master/cme/modules/get_description.py
|
||||
Created by fplazar and wanetty
|
||||
Module by @gm_eduard and @ferranplaza
|
||||
Based on: https://github.com/juliourena/CrackMapExec/blob/master/cme/modules/get_description.py
|
||||
"""
|
||||
|
||||
name = "pso"
|
||||
|
@ -18,7 +18,7 @@ class NXCModule:
|
|||
supported_protocols = ["ldap"]
|
||||
opsec_safe = True
|
||||
multiple_hosts = True
|
||||
|
||||
|
||||
pso_fields = [
|
||||
"cn",
|
||||
"msDS-PasswordReversibleEncryptionEnabled",
|
||||
|
@ -39,20 +39,15 @@ class NXCModule:
|
|||
No options available.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def convert_time_field(self, field, value):
|
||||
time_fields = {
|
||||
"msDS-LockoutObservationWindow": (60, "mins"),
|
||||
"msDS-MinimumPasswordAge": (86400, "days"),
|
||||
"msDS-MaximumPasswordAge": (86400, "days"),
|
||||
"msDS-LockoutDuration": (60, "mins")
|
||||
}
|
||||
time_fields = {"msDS-LockoutObservationWindow": (60, "mins"), "msDS-MinimumPasswordAge": (86400, "days"), "msDS-MaximumPasswordAge": (86400, "days"), "msDS-LockoutDuration": (60, "mins")}
|
||||
|
||||
if field in time_fields.keys():
|
||||
value = f"{int((fabs(float(value)) / (10000000 * time_fields[field][0])))} {time_fields[field][1]}"
|
||||
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def on_login(self, context, connection):
|
||||
"""Concurrent. Required if on_admin_login is not present. This gets called on each authenticated connection"""
|
||||
# Building the search filter
|
||||
|
@ -60,11 +55,7 @@ class NXCModule:
|
|||
|
||||
try:
|
||||
context.log.debug(f"Search Filter={search_filter}")
|
||||
resp = connection.ldapConnection.search(
|
||||
searchFilter=search_filter,
|
||||
attributes=self.pso_fields,
|
||||
sizeLimit=0
|
||||
)
|
||||
resp = connection.ldapConnection.search(searchFilter=search_filter, attributes=self.pso_fields, sizeLimit=0)
|
||||
except ldap_impacket.LDAPSearchError as e:
|
||||
if e.getErrorString().find("sizeLimitExceeded") >= 0:
|
||||
context.log.debug("sizeLimitExceeded exception caught, giving up and processing the data received")
|
||||
|
|
|
@ -44,12 +44,12 @@ class NXCModule:
|
|||
exit(1)
|
||||
|
||||
self.action = module_options["ACTION"].lower()
|
||||
|
||||
|
||||
if "METHOD" not in module_options:
|
||||
self.method = "wmi"
|
||||
else:
|
||||
self.method = module_options['METHOD'].lower()
|
||||
|
||||
self.method = module_options["METHOD"].lower()
|
||||
|
||||
if context.protocol != "smb" and self.method == "smb":
|
||||
context.log.fail(f"Protocol: {context.protocol} not support this method")
|
||||
exit(1)
|
||||
|
@ -58,11 +58,11 @@ class NXCModule:
|
|||
self.dcom_timeout = 10
|
||||
else:
|
||||
try:
|
||||
self.dcom_timeout = int(module_options['DCOM-TIMEOUT'])
|
||||
self.dcom_timeout = int(module_options["DCOM-TIMEOUT"])
|
||||
except Exception:
|
||||
context.log.fail("Wrong DCOM timeout value!")
|
||||
exit(1)
|
||||
|
||||
|
||||
if "OLD" not in module_options:
|
||||
self.oldSystem = False
|
||||
else:
|
||||
|
@ -85,7 +85,7 @@ class NXCModule:
|
|||
|
||||
wmi_rdp = RdpWmi(context, connection, self.dcom_timeout)
|
||||
|
||||
if hasattr(wmi_rdp, '_rdp_WMI__iWbemLevel1Login'):
|
||||
if hasattr(wmi_rdp, "_rdp_WMI__iWbemLevel1Login"):
|
||||
if "ram" in self.action:
|
||||
# Nt version under 6 not support RAM.
|
||||
try:
|
||||
|
@ -144,7 +144,7 @@ class RdpSmb:
|
|||
self.logger.success("Enable RDP via SMB(ncacn_np) successfully")
|
||||
elif int(data) == 1:
|
||||
self.logger.success("Disable RDP via SMB(ncacn_np) successfully")
|
||||
|
||||
|
||||
self.firewall_cmd(action)
|
||||
|
||||
if action == "enable":
|
||||
|
@ -199,7 +199,7 @@ class RdpSmb:
|
|||
key_handle = ans["phkResult"]
|
||||
|
||||
rtype, data = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, key_handle, "PortNumber")
|
||||
|
||||
|
||||
self.logger.success(f"RDP Port: {str(data)}")
|
||||
|
||||
# https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/manage/enable_rdp.rb
|
||||
|
@ -218,15 +218,15 @@ class RdpWmi:
|
|||
self.logger = context.log
|
||||
self.__currentprotocol = context.protocol
|
||||
# From dfscoerce.py
|
||||
self.__username=connection.username
|
||||
self.__password=connection.password
|
||||
self.__domain=connection.domain
|
||||
self.__lmhash=connection.lmhash
|
||||
self.__nthash=connection.nthash
|
||||
self.__target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain
|
||||
self.__doKerberos=connection.kerberos
|
||||
self.__kdcHost=connection.kdcHost
|
||||
self.__aesKey=connection.aesKey
|
||||
self.__username = connection.username
|
||||
self.__password = connection.password
|
||||
self.__domain = connection.domain
|
||||
self.__lmhash = connection.lmhash
|
||||
self.__nthash = connection.nthash
|
||||
self.__target = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain
|
||||
self.__doKerberos = connection.kerberos
|
||||
self.__kdcHost = connection.kdcHost
|
||||
self.__aesKey = connection.aesKey
|
||||
self.__timeout = timeout
|
||||
|
||||
try:
|
||||
|
@ -245,13 +245,13 @@ class RdpWmi:
|
|||
|
||||
i_interface = self.__dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
|
||||
if self.__currentprotocol == "smb":
|
||||
flag, self.__stringBinding = dcom_FirewallChecker(i_interface, self.__timeout)
|
||||
flag, self.__stringBinding = dcom_FirewallChecker(i_interface, self.__timeout)
|
||||
if not flag or not self.__stringBinding:
|
||||
error_msg = f'RDP-WMI: Dcom initialization failed on connection with stringbinding: "{self.__stringBinding}", please increase the timeout with the module option "DCOM-TIMEOUT=10". If it\'s still failing maybe something is blocking the RPC connection, please try to use "-o" with "METHOD=smb"'
|
||||
|
||||
|
||||
if not self.__stringBinding:
|
||||
error_msg = "RDP-WMI: Dcom initialization failed: can't get target stringbinding, maybe cause by IPv6 or any other issues, please check your target again"
|
||||
|
||||
|
||||
self.logger.fail(error_msg) if not flag else self.logger.debug(error_msg)
|
||||
# Make it force break function
|
||||
self.__dcom.disconnect()
|
||||
|
@ -265,15 +265,11 @@ class RdpWmi:
|
|||
if old is False:
|
||||
# According to this document: https://learn.microsoft.com/en-us/windows/win32/termserv/win32-tslogonsetting
|
||||
# Authentication level must set to RPC_C_AUTHN_LEVEL_PKT_PRIVACY when accessing namespace "//./root/cimv2/TerminalServices"
|
||||
i_wbem_services = self.__iWbemLevel1Login.NTLMLogin(
|
||||
"//./root/cimv2/TerminalServices",
|
||||
NULL,
|
||||
NULL
|
||||
)
|
||||
i_wbem_services = self.__iWbemLevel1Login.NTLMLogin("//./root/cimv2/TerminalServices", NULL, NULL)
|
||||
i_wbem_services.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
|
||||
self.__iWbemLevel1Login.RemRelease()
|
||||
i_enum_wbem_class_object = i_wbem_services.ExecQuery("SELECT * FROM Win32_TerminalServiceSetting")
|
||||
i_wbem_class_object = i_enum_wbem_class_object.Next(0xffffffff, 1)[0]
|
||||
i_wbem_class_object = i_enum_wbem_class_object.Next(0xFFFFFFFF, 1)[0]
|
||||
if action == "enable":
|
||||
self.logger.info("Enabled RDP services and setting up firewall.")
|
||||
i_wbem_class_object.SetAllowTSConnections(1, 1)
|
||||
|
@ -281,30 +277,30 @@ class RdpWmi:
|
|||
self.logger.info("Disabled RDP services and setting up firewall.")
|
||||
i_wbem_class_object.SetAllowTSConnections(0, 0)
|
||||
else:
|
||||
i_wbem_services = self.__iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
|
||||
i_wbem_services = self.__iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL)
|
||||
self.__iWbemLevel1Login.RemRelease()
|
||||
i_enum_wbem_class_object = i_wbem_services.ExecQuery("SELECT * FROM Win32_TerminalServiceSetting")
|
||||
i_wbem_class_object = i_enum_wbem_class_object.Next(0xffffffff, 1)[0]
|
||||
i_wbem_class_object = i_enum_wbem_class_object.Next(0xFFFFFFFF, 1)[0]
|
||||
if action == "enable":
|
||||
self.logger.info("Enabling RDP services (old system not support setting up firewall)")
|
||||
i_wbem_class_object.SetAllowTSConnections(1)
|
||||
elif action == "disable":
|
||||
self.logger.info("Disabling RDP services (old system not support setting up firewall)")
|
||||
i_wbem_class_object.SetAllowTSConnections(0)
|
||||
|
||||
|
||||
self.query_rdp_result(old)
|
||||
|
||||
if action == 'enable':
|
||||
if action == "enable":
|
||||
self.query_rdp_port()
|
||||
# Need to create new iWbemServices interface in order to flush results
|
||||
|
||||
|
||||
def query_rdp_result(self, old=False):
|
||||
if old is False:
|
||||
i_wbem_services = self.__iWbemLevel1Login.NTLMLogin("//./root/cimv2/TerminalServices", NULL, NULL)
|
||||
i_wbem_services.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
|
||||
self.__iWbemLevel1Login.RemRelease()
|
||||
i_enum_wbem_class_object = i_wbem_services.ExecQuery("SELECT * FROM Win32_TerminalServiceSetting")
|
||||
i_wbem_class_object = i_enum_wbem_class_object.Next(0xffffffff, 1)[0]
|
||||
i_wbem_class_object = i_enum_wbem_class_object.Next(0xFFFFFFFF, 1)[0]
|
||||
result = dict(i_wbem_class_object.getProperties())
|
||||
result = result["AllowTSConnections"]["value"]
|
||||
if result == 0:
|
||||
|
@ -315,7 +311,7 @@ class RdpWmi:
|
|||
i_wbem_services = self.__iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL)
|
||||
self.__iWbemLevel1Login.RemRelease()
|
||||
i_enum_wbem_class_object = i_wbem_services.ExecQuery("SELECT * FROM Win32_TerminalServiceSetting")
|
||||
i_wbem_class_object = i_enum_wbem_class_object.Next(0xffffffff, 1)[0]
|
||||
i_wbem_class_object = i_enum_wbem_class_object.Next(0xFFFFFFFF, 1)[0]
|
||||
result = dict(i_wbem_class_object.getProperties())
|
||||
result = result["AllowTSConnections"]["value"]
|
||||
if result == 0:
|
||||
|
@ -327,11 +323,7 @@ class RdpWmi:
|
|||
i_wbem_services = self.__iWbemLevel1Login.NTLMLogin("//./root/DEFAULT", NULL, NULL)
|
||||
self.__iWbemLevel1Login.RemRelease()
|
||||
std_reg_prov, resp = i_wbem_services.GetObject("StdRegProv")
|
||||
out = std_reg_prov.GetDWORDValue(
|
||||
2147483650,
|
||||
"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp",
|
||||
"PortNumber"
|
||||
)
|
||||
out = std_reg_prov.GetDWORDValue(2147483650, "SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp", "PortNumber")
|
||||
self.logger.success(f"RDP Port: {str(out.uValue)}")
|
||||
|
||||
# Nt version under 6 not support RAM.
|
||||
|
@ -341,7 +333,7 @@ class RdpWmi:
|
|||
std_reg_prov, resp = i_wbem_services.GetObject("StdRegProv")
|
||||
if action == "enable-ram":
|
||||
self.logger.info("Enabling Restricted Admin Mode.")
|
||||
std_reg_prov.SetDWORDValue(2147483650, 'System\\CurrentControlSet\\Control\\Lsa', "DisableRestrictedAdmin", 0)
|
||||
std_reg_prov.SetDWORDValue(2147483650, "System\\CurrentControlSet\\Control\\Lsa", "DisableRestrictedAdmin", 0)
|
||||
elif action == "disable-ram":
|
||||
self.logger.info("Disabling Restricted Admin Mode (Clear).")
|
||||
std_reg_prov.DeleteValue(2147483650, "System\\CurrentControlSet\\Control\\Lsa", "DisableRestrictedAdmin")
|
||||
|
|
|
@ -45,7 +45,7 @@ def get_dns_resolver(server, context):
|
|||
|
||||
|
||||
def ldap2domain(ldap):
|
||||
return re.sub(",DC=", ".", ldap[ldap.lower().find("dc="):], flags=re.I)[3:]
|
||||
return re.sub(",DC=", ".", ldap[ldap.lower().find("dc=") :], flags=re.I)[3:]
|
||||
|
||||
|
||||
def new_record(rtype, serial):
|
||||
|
@ -162,8 +162,7 @@ class NXCModule:
|
|||
"value": address.formatCanonical(),
|
||||
}
|
||||
)
|
||||
if dr["Type"] in [a for a in RECORD_TYPE_MAPPING if
|
||||
RECORD_TYPE_MAPPING[a] in ["CNAME", "NS", "PTR"]]:
|
||||
if dr["Type"] in [a for a in RECORD_TYPE_MAPPING if RECORD_TYPE_MAPPING[a] in ["CNAME", "NS", "PTR"]]:
|
||||
address = DNS_RPC_RECORD_NODE_NAME(dr["Data"])
|
||||
if str(recordname) != "DomainDnsZones" and str(recordname) != "ForestDnsZones":
|
||||
outdata.append(
|
||||
|
@ -185,8 +184,7 @@ class NXCModule:
|
|||
)
|
||||
|
||||
context.log.highlight("Found %d records" % len(outdata))
|
||||
path = expanduser(
|
||||
"~/.nxc/logs/{}_network_{}.log".format(connection.domain, datetime.now().strftime("%Y-%m-%d_%H%M%S")))
|
||||
path = expanduser("~/.nxc/logs/{}_network_{}.log".format(connection.domain, datetime.now().strftime("%Y-%m-%d_%H%M%S")))
|
||||
with codecs.open(path, "w", "utf-8") as outfile:
|
||||
for row in outdata:
|
||||
if self.showhosts:
|
||||
|
@ -197,9 +195,7 @@ class NXCModule:
|
|||
outfile.write("{}\n".format(row["value"]))
|
||||
context.log.success("Dumped {} records to {}".format(len(outdata), path))
|
||||
if not self.showall and not self.showhosts:
|
||||
context.log.display(
|
||||
"To extract CIDR from the {} ip, run the following command: cat" " your_file | mapcidr -aa -silent | mapcidr -a -silent".format(
|
||||
len(outdata)))
|
||||
context.log.display("To extract CIDR from the {} ip, run the following command: cat" " your_file | mapcidr -aa -silent | mapcidr -a -silent".format(len(outdata)))
|
||||
|
||||
|
||||
class DNS_RECORD(Structure):
|
||||
|
@ -256,8 +252,8 @@ class DNS_COUNT_NAME(Structure):
|
|||
ind = 0
|
||||
labels = []
|
||||
for i in range(self["LabelCount"]):
|
||||
nextlen = unpack("B", self["RawName"][ind: ind + 1])[0]
|
||||
labels.append(self["RawName"][ind + 1: ind + 1 + nextlen].decode("utf-8"))
|
||||
nextlen = unpack("B", self["RawName"][ind : ind + 1])[0]
|
||||
labels.append(self["RawName"][ind + 1 : ind + 1 + nextlen].decode("utf-8"))
|
||||
ind += nextlen + 1
|
||||
# For the final dot
|
||||
labels.append("")
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -99,8 +99,7 @@ class NXCModule:
|
|||
)
|
||||
if len([server for server in list_servers if isinstance(server, ldapasn1_impacket.SearchResultEntry)]) == 0:
|
||||
if len(site_description) != 0:
|
||||
context.log.highlight(
|
||||
f'Site "{site_name}" (Subnet:{subnet_name}) (description:"{site_description}")')
|
||||
context.log.highlight(f'Site "{site_name}" (Subnet:{subnet_name}) (description:"{site_description}")')
|
||||
else:
|
||||
context.log.highlight(f'Site "{site_name}" (Subnet:{subnet_name})')
|
||||
else:
|
||||
|
@ -109,14 +108,11 @@ class NXCModule:
|
|||
continue
|
||||
server = search_res_entry_to_dict(server)["cn"]
|
||||
if len(site_description) != 0:
|
||||
context.log.highlight(
|
||||
f"Site: '{site_name}' (Subnet:{subnet_name}) (description:'{site_description}') (Server:'{server}')")
|
||||
context.log.highlight(f"Site: '{site_name}' (Subnet:{subnet_name}) (description:'{site_description}') (Server:'{server}')")
|
||||
else:
|
||||
context.log.highlight(
|
||||
f'Site "{site_name}" (Subnet:{subnet_name}) (Server:{server})')
|
||||
context.log.highlight(f'Site "{site_name}" (Subnet:{subnet_name}) (Server:{server})')
|
||||
else:
|
||||
if len(site_description) != 0:
|
||||
context.log.highlight(
|
||||
f'Site "{site_name}" (Subnet:{subnet_name}) (description:"{site_description}")')
|
||||
context.log.highlight(f'Site "{site_name}" (Subnet:{subnet_name}) (description:"{site_description}")')
|
||||
else:
|
||||
context.log.highlight(f'Site "{site_name}" (Subnet:{subnet_name})')
|
||||
|
|
|
@ -5,9 +5,10 @@ from impacket.ldap import ldapasn1 as ldapasn1_impacket
|
|||
|
||||
class NXCModule:
|
||||
"""
|
||||
Extract all Trust Relationships, Trusting Direction, and Trust Transitivity
|
||||
Module by Brandon Fisher @shad0wcntr0ller
|
||||
Extract all Trust Relationships, Trusting Direction, and Trust Transitivity
|
||||
Module by Brandon Fisher @shad0wcntr0ller
|
||||
"""
|
||||
|
||||
name = "enum_trusts"
|
||||
description = "Extract all Trust Relationships, Trusting Direction, and Trust Transitivity"
|
||||
supported_protocols = ["ldap"]
|
||||
|
@ -23,22 +24,17 @@ class NXCModule:
|
|||
attributes = ["flatName", "trustPartner", "trustDirection", "trustAttributes"]
|
||||
|
||||
context.log.debug(f"Search Filter={search_filter}")
|
||||
resp = connection.ldapConnection.search(
|
||||
searchBase=domain_dn,
|
||||
searchFilter=search_filter,
|
||||
attributes=attributes,
|
||||
sizeLimit=0
|
||||
)
|
||||
resp = connection.ldapConnection.search(searchBase=domain_dn, searchFilter=search_filter, attributes=attributes, sizeLimit=0)
|
||||
|
||||
trusts = []
|
||||
context.log.debug(f"Total of records returned {len(resp)}")
|
||||
for item in resp:
|
||||
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
|
||||
continue
|
||||
flat_name = ''
|
||||
trust_partner = ''
|
||||
trust_direction = ''
|
||||
trust_transitive = []
|
||||
flat_name = ""
|
||||
trust_partner = ""
|
||||
trust_direction = ""
|
||||
trust_transitive = []
|
||||
try:
|
||||
for attribute in item["attributes"]:
|
||||
if str(attribute["type"]) == "flatName":
|
||||
|
@ -92,4 +88,3 @@ class NXCModule:
|
|||
context.log.display("No trust relationships found")
|
||||
|
||||
return True
|
||||
|
||||
|
|
|
@ -56,7 +56,11 @@ class NXCModule:
|
|||
|
||||
# Veeam v12 check
|
||||
try:
|
||||
ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, "SOFTWARE\\Veeam\\Veeam Backup and Replication\\DatabaseConfigurations",)
|
||||
ans = rrp.hBaseRegOpenKey(
|
||||
remoteOps._RemoteOperations__rrp,
|
||||
regHandle,
|
||||
"SOFTWARE\\Veeam\\Veeam Backup and Replication\\DatabaseConfigurations",
|
||||
)
|
||||
keyHandle = ans["phkResult"]
|
||||
|
||||
database_config = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "SqlActiveConfiguration")[1].split("\x00")[:-1][0]
|
||||
|
@ -64,16 +68,28 @@ class NXCModule:
|
|||
context.log.success("Veeam v12 installation found!")
|
||||
if database_config == "PostgreSql":
|
||||
# Find the PostgreSql installation path containing "psql.exe"
|
||||
ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, "SOFTWARE\\PostgreSQL Global Development Group\\PostgreSQL",)
|
||||
ans = rrp.hBaseRegOpenKey(
|
||||
remoteOps._RemoteOperations__rrp,
|
||||
regHandle,
|
||||
"SOFTWARE\\PostgreSQL Global Development Group\\PostgreSQL",
|
||||
)
|
||||
keyHandle = ans["phkResult"]
|
||||
PostgreSqlExec = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "Location")[1].split("\x00")[:-1][0] + "\\bin\\psql.exe"
|
||||
|
||||
ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, "SOFTWARE\\Veeam\\Veeam Backup and Replication\\DatabaseConfigurations\\PostgreSQL",)
|
||||
ans = rrp.hBaseRegOpenKey(
|
||||
remoteOps._RemoteOperations__rrp,
|
||||
regHandle,
|
||||
"SOFTWARE\\Veeam\\Veeam Backup and Replication\\DatabaseConfigurations\\PostgreSQL",
|
||||
)
|
||||
keyHandle = ans["phkResult"]
|
||||
PostgresUserForWindowsAuth = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "PostgresUserForWindowsAuth")[1].split("\x00")[:-1][0]
|
||||
SqlDatabaseName = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "SqlDatabaseName")[1].split("\x00")[:-1][0]
|
||||
elif database_config == "MsSql":
|
||||
ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, "SOFTWARE\\Veeam\\Veeam Backup and Replication\\DatabaseConfigurations\\MsSql",)
|
||||
ans = rrp.hBaseRegOpenKey(
|
||||
remoteOps._RemoteOperations__rrp,
|
||||
regHandle,
|
||||
"SOFTWARE\\Veeam\\Veeam Backup and Replication\\DatabaseConfigurations\\MsSql",
|
||||
)
|
||||
keyHandle = ans["phkResult"]
|
||||
|
||||
SqlDatabase = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "SqlDatabaseName")[1].split("\x00")[:-1][0]
|
||||
|
@ -88,7 +104,11 @@ class NXCModule:
|
|||
|
||||
# Veeam v11 check
|
||||
try:
|
||||
ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, "SOFTWARE\\Veeam\\Veeam Backup and Replication",)
|
||||
ans = rrp.hBaseRegOpenKey(
|
||||
remoteOps._RemoteOperations__rrp,
|
||||
regHandle,
|
||||
"SOFTWARE\\Veeam\\Veeam Backup and Replication",
|
||||
)
|
||||
keyHandle = ans["phkResult"]
|
||||
|
||||
SqlDatabase = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "SqlDatabaseName")[1].split("\x00")[:-1][0]
|
||||
|
@ -126,7 +146,7 @@ class NXCModule:
|
|||
|
||||
def stripXmlOutput(self, context, output):
|
||||
return output.split("CLIXML")[1].split("<Objs Version")[0]
|
||||
|
||||
|
||||
def executePsMssql(self, context, connection, SqlDatabase, SqlInstance, SqlServer):
|
||||
self.psScriptMssql = self.psScriptMssql.replace("REPLACE_ME_SqlDatabase", SqlDatabase)
|
||||
self.psScriptMssql = self.psScriptMssql.replace("REPLACE_ME_SqlInstance", SqlInstance)
|
||||
|
@ -163,8 +183,8 @@ class NXCModule:
|
|||
user, password = account.split(" ", 1)
|
||||
password = password.replace("WHITESPACE_ERROR", " ")
|
||||
context.log.highlight(user + ":" + f"{password}")
|
||||
if ' ' in password:
|
||||
context.log.fail(f"Password contains whitespaces! The password for user \"{user}\" is: \"{password}\"")
|
||||
if " " in password:
|
||||
context.log.fail(f'Password contains whitespaces! The password for user "{user}" is: "{password}"')
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
self.checkVeeamInstalled(context, connection)
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -86,7 +86,6 @@ class NXCModule:
|
|||
else:
|
||||
context.log.highlight(f"[{wifi_cred.auth.upper()}] {wifi_cred.ssid} - {wifi_cred.eap_type}")
|
||||
except Exception:
|
||||
context.log.highlight(
|
||||
f"[{wifi_cred.auth.upper()}] {wifi_cred.ssid} - Passphrase: {wifi_cred.password}")
|
||||
context.log.highlight(f"[{wifi_cred.auth.upper()}] {wifi_cred.ssid} - Passphrase: {wifi_cred.password}")
|
||||
else:
|
||||
context.log.highlight(f"[WPA-EAP] {wifi_cred.ssid} - {wifi_cred.eap_type}")
|
||||
|
|
|
@ -63,11 +63,13 @@ class NXCModule:
|
|||
except DCERPCException:
|
||||
self.context.log.fail("Error while connecting to host: DCERPCException, " "which means this is probably not a DC!")
|
||||
|
||||
|
||||
def fail(msg):
|
||||
nxc_logger.debug(msg)
|
||||
nxc_logger.fail("This might have been caused by invalid arguments or network issues.")
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def try_zero_authenticate(rpc_con, dc_handle, dc_ip, target_computer):
|
||||
# Connect to the DC's Netlogon service.
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ from sys import platform
|
|||
# Increase file_limit to prevent error "Too many open files"
|
||||
if platform != "win32":
|
||||
import resource
|
||||
|
||||
file_limit = list(resource.getrlimit(resource.RLIMIT_NOFILE))
|
||||
if file_limit[1] > 10000:
|
||||
file_limit[0] = 10000
|
||||
|
|
24
nxc/nxcdb.py
24
nxc/nxcdb.py
|
@ -172,7 +172,7 @@ class DatabaseNavigator(cmd.Cmd):
|
|||
if cred[4] == "hash":
|
||||
usernames.append(cred[2])
|
||||
passwords.append(cred[3])
|
||||
output_list = [':'.join(combination) for combination in zip(usernames, passwords)]
|
||||
output_list = [":".join(combination) for combination in zip(usernames, passwords)]
|
||||
write_list(filename, output_list)
|
||||
else:
|
||||
print(f"[-] No such export option: {line[1]}")
|
||||
|
@ -243,9 +243,9 @@ class DatabaseNavigator(cmd.Cmd):
|
|||
formatted_shares = []
|
||||
for share in shares:
|
||||
user = self.db.get_users(share[2])[0]
|
||||
if self.db.get_hosts(share[1]):
|
||||
share_host = self.db.get_hosts(share[1])[0][2]
|
||||
else:
|
||||
if self.db.get_hosts(share[1]):
|
||||
share_host = self.db.get_hosts(share[1])[0][2]
|
||||
else:
|
||||
share_host = "ERROR"
|
||||
|
||||
entry = (
|
||||
|
@ -352,15 +352,7 @@ class DatabaseNavigator(cmd.Cmd):
|
|||
"check",
|
||||
"status",
|
||||
)
|
||||
csv_header_detailed = (
|
||||
"id",
|
||||
"ip",
|
||||
"hostname",
|
||||
"check",
|
||||
"description",
|
||||
"status",
|
||||
"reasons"
|
||||
)
|
||||
csv_header_detailed = ("id", "ip", "hostname", "check", "description", "status", "reasons")
|
||||
filename = line[2]
|
||||
host_mapping = {}
|
||||
check_mapping = {}
|
||||
|
@ -370,12 +362,12 @@ class DatabaseNavigator(cmd.Cmd):
|
|||
check_results = self.db.get_check_results()
|
||||
rows = []
|
||||
|
||||
for result_id,hostid,checkid,secure,reasons in check_results:
|
||||
for result_id, hostid, checkid, secure, reasons in check_results:
|
||||
row = [result_id]
|
||||
if hostid in host_mapping:
|
||||
row.extend(host_mapping[hostid])
|
||||
else:
|
||||
for host_id,ip,hostname,_,_,_,_,_,_,_,_ in hosts:
|
||||
for host_id, ip, hostname, _, _, _, _, _, _, _, _ in hosts:
|
||||
if host_id == hostid:
|
||||
row.extend([ip, hostname])
|
||||
host_mapping[hostid] = [ip, hostname]
|
||||
|
@ -389,7 +381,7 @@ class DatabaseNavigator(cmd.Cmd):
|
|||
row.extend([name, description])
|
||||
check_mapping[checkid] = [name, description]
|
||||
break
|
||||
row.append('OK' if secure else 'KO')
|
||||
row.append("OK" if secure else "KO")
|
||||
row.append(reasons)
|
||||
rows.append(row)
|
||||
|
||||
|
|
|
@ -6,38 +6,14 @@ from nxc.logger import nxc_logger
|
|||
|
||||
# right now we are only referencing the port numbers, not the service name, but this should be sufficient for 99% cases
|
||||
protocol_dict = {
|
||||
"ftp": {
|
||||
"ports": [21],
|
||||
"services": ["ftp"]
|
||||
},
|
||||
"ssh": {
|
||||
"ports": [22, 2222],
|
||||
"services": ["ssh"]
|
||||
},
|
||||
"smb": {
|
||||
"ports": [139, 445],
|
||||
"services": ["netbios-ssn", "microsoft-ds"]
|
||||
},
|
||||
"ldap": {
|
||||
"ports": [389, 636],
|
||||
"services": ["ldap", "ldaps"]
|
||||
},
|
||||
"mssql": {
|
||||
"ports": [1433],
|
||||
"services": ["ms-sql-s"]
|
||||
},
|
||||
"rdp": {
|
||||
"ports": [3389],
|
||||
"services": ["ms-wbt-server"]
|
||||
},
|
||||
"winrm": {
|
||||
"ports": [5985, 5986],
|
||||
"services": ["wsman"]
|
||||
},
|
||||
"vnc": {
|
||||
"ports": [5900, 5901, 5902, 5903, 5904, 5905, 5906],
|
||||
"services": ["vnc"]
|
||||
},
|
||||
"ftp": {"ports": [21], "services": ["ftp"]},
|
||||
"ssh": {"ports": [22, 2222], "services": ["ssh"]},
|
||||
"smb": {"ports": [139, 445], "services": ["netbios-ssn", "microsoft-ds"]},
|
||||
"ldap": {"ports": [389, 636], "services": ["ldap", "ldaps"]},
|
||||
"mssql": {"ports": [1433], "services": ["ms-sql-s"]},
|
||||
"rdp": {"ports": [3389], "services": ["ms-wbt-server"]},
|
||||
"winrm": {"ports": [5985, 5986], "services": ["wsman"]},
|
||||
"vnc": {"ports": [5900, 5901, 5902, 5903, 5904, 5905, 5906], "services": ["vnc"]},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -96,7 +96,6 @@ class ftp(connection):
|
|||
return True
|
||||
self.conn.close()
|
||||
|
||||
|
||||
def list_directory_full(self):
|
||||
# in the future we can use mlsd/nlst if we want, but this gives a full output like `ls -la`
|
||||
# ftplib's "dir" prints directly to stdout, and "nlst" only returns the folder name, not full details
|
||||
|
|
|
@ -31,47 +31,47 @@ class database:
|
|||
|
||||
@staticmethod
|
||||
def db_schema(db_conn):
|
||||
db_conn.execute("""CREATE TABLE "credentials" (
|
||||
db_conn.execute(
|
||||
"""CREATE TABLE "credentials" (
|
||||
"id" integer PRIMARY KEY,
|
||||
"username" text,
|
||||
"password" text
|
||||
)""")
|
||||
)"""
|
||||
)
|
||||
|
||||
db_conn.execute("""CREATE TABLE "hosts" (
|
||||
db_conn.execute(
|
||||
"""CREATE TABLE "hosts" (
|
||||
"id" integer PRIMARY KEY,
|
||||
"host" text,
|
||||
"port" integer,
|
||||
"banner" text
|
||||
)""")
|
||||
db_conn.execute("""CREATE TABLE "loggedin_relations" (
|
||||
)"""
|
||||
)
|
||||
db_conn.execute(
|
||||
"""CREATE TABLE "loggedin_relations" (
|
||||
"id" integer PRIMARY KEY,
|
||||
"credid" integer,
|
||||
"hostid" integer,
|
||||
FOREIGN KEY(credid) REFERENCES credentials(id),
|
||||
FOREIGN KEY(hostid) REFERENCES hosts(id)
|
||||
)""")
|
||||
db_conn.execute("""CREATE TABLE "directory_listings" (
|
||||
)"""
|
||||
)
|
||||
db_conn.execute(
|
||||
"""CREATE TABLE "directory_listings" (
|
||||
"id" integer PRIMARY KEY,
|
||||
"lir_id" integer,
|
||||
"data" text,
|
||||
FOREIGN KEY(lir_id) REFERENCES loggedin_relations(id)
|
||||
)""")
|
||||
)"""
|
||||
)
|
||||
|
||||
def reflect_tables(self):
|
||||
with self.db_engine.connect():
|
||||
try:
|
||||
self.CredentialsTable = Table(
|
||||
"credentials", self.metadata, autoload_with=self.db_engine
|
||||
)
|
||||
self.HostsTable = Table(
|
||||
"hosts", self.metadata, autoload_with=self.db_engine
|
||||
)
|
||||
self.LoggedinRelationsTable = Table(
|
||||
"loggedin_relations", self.metadata, autoload_with=self.db_engine
|
||||
)
|
||||
self.DirectoryListingsTable = Table(
|
||||
"directory_listings", self.metadata, autoload_with=self.db_engine
|
||||
)
|
||||
self.CredentialsTable = Table("credentials", self.metadata, autoload_with=self.db_engine)
|
||||
self.HostsTable = Table("hosts", self.metadata, autoload_with=self.db_engine)
|
||||
self.LoggedinRelationsTable = Table("loggedin_relations", self.metadata, autoload_with=self.db_engine)
|
||||
self.DirectoryListingsTable = Table("directory_listings", self.metadata, autoload_with=self.db_engine)
|
||||
except (NoInspectionAvailable, NoSuchTableError):
|
||||
print(
|
||||
f"""
|
||||
|
@ -135,10 +135,7 @@ class database:
|
|||
# TODO: find a way to abstract this away to a single Upsert call
|
||||
q = Insert(self.HostsTable) # .returning(self.HostsTable.c.id)
|
||||
update_columns = {col.name: col for col in q.excluded if col.name not in "id"}
|
||||
q = q.on_conflict_do_update(
|
||||
index_elements=self.HostsTable.primary_key,
|
||||
set_=update_columns
|
||||
)
|
||||
q = q.on_conflict_do_update(index_elements=self.HostsTable.primary_key, set_=update_columns)
|
||||
|
||||
self.sess.execute(q, hosts) # .scalar()
|
||||
# we only return updated IDs for now - when RETURNING clause is allowed we can return inserted
|
||||
|
@ -152,10 +149,7 @@ class database:
|
|||
"""
|
||||
credentials = []
|
||||
|
||||
q = select(self.CredentialsTable).filter(
|
||||
func.lower(self.CredentialsTable.c.username) == func.lower(username),
|
||||
func.lower(self.CredentialsTable.c.password) == func.lower(password)
|
||||
)
|
||||
q = select(self.CredentialsTable).filter(func.lower(self.CredentialsTable.c.username) == func.lower(username), func.lower(self.CredentialsTable.c.password) == func.lower(password))
|
||||
results = self.sess.execute(q).all()
|
||||
|
||||
# add new credential
|
||||
|
@ -182,10 +176,7 @@ class database:
|
|||
# TODO: find a way to abstract this away to a single Upsert call
|
||||
q_users = Insert(self.CredentialsTable) # .returning(self.CredentialsTable.c.id)
|
||||
update_columns_users = {col.name: col for col in q_users.excluded if col.name not in "id"}
|
||||
q_users = q_users.on_conflict_do_update(
|
||||
index_elements=self.CredentialsTable.primary_key,
|
||||
set_=update_columns_users
|
||||
)
|
||||
q_users = q_users.on_conflict_do_update(index_elements=self.CredentialsTable.primary_key, set_=update_columns_users)
|
||||
nxc_logger.debug(f"Adding credentials: {credentials}")
|
||||
|
||||
self.sess.execute(q_users, credentials) # .scalar()
|
||||
|
@ -310,10 +301,7 @@ class database:
|
|||
|
||||
# only add one if one doesn't already exist
|
||||
if not results:
|
||||
relation = {
|
||||
"credid": cred_id,
|
||||
"hostid": host_id
|
||||
}
|
||||
relation = {"credid": cred_id, "hostid": host_id}
|
||||
try:
|
||||
nxc_logger.debug(f"Inserting loggedin_relations: {relation}")
|
||||
# TODO: find a way to abstract this away to a single Upsert call
|
||||
|
|
|
@ -6,41 +6,49 @@ from nxc.nxcdb import DatabaseNavigator, print_table, print_help
|
|||
|
||||
class navigator(DatabaseNavigator):
|
||||
def display_creds(self, creds):
|
||||
data = [[
|
||||
"CredID",
|
||||
"Total Logins",
|
||||
"Username",
|
||||
"Password",
|
||||
]]
|
||||
data = [
|
||||
[
|
||||
"CredID",
|
||||
"Total Logins",
|
||||
"Username",
|
||||
"Password",
|
||||
]
|
||||
]
|
||||
|
||||
for cred in creds:
|
||||
total_users = self.db.get_loggedin_relations(cred_id=cred[0])
|
||||
data.append([
|
||||
cred[0],
|
||||
str(len(total_users)) + " Host(s)",
|
||||
cred[1],
|
||||
cred[2],
|
||||
])
|
||||
data.append(
|
||||
[
|
||||
cred[0],
|
||||
str(len(total_users)) + " Host(s)",
|
||||
cred[1],
|
||||
cred[2],
|
||||
]
|
||||
)
|
||||
print_table(data, title="Credentials")
|
||||
|
||||
def display_hosts(self, hosts):
|
||||
data = [[
|
||||
"HostID",
|
||||
"Total Users",
|
||||
"Host",
|
||||
"Port",
|
||||
"Banner",
|
||||
]]
|
||||
data = [
|
||||
[
|
||||
"HostID",
|
||||
"Total Users",
|
||||
"Host",
|
||||
"Port",
|
||||
"Banner",
|
||||
]
|
||||
]
|
||||
|
||||
for h in hosts:
|
||||
total_users = self.db.get_loggedin_relations(host_id=h[0])
|
||||
data.append([
|
||||
h[0],
|
||||
str(len(total_users)) + " User(s)",
|
||||
h[1],
|
||||
h[2],
|
||||
h[3],
|
||||
])
|
||||
data.append(
|
||||
[
|
||||
h[0],
|
||||
str(len(total_users)) + " User(s)",
|
||||
h[1],
|
||||
h[2],
|
||||
h[3],
|
||||
]
|
||||
)
|
||||
print_table(data, title="Hosts")
|
||||
|
||||
def do_hosts(self, line):
|
||||
|
@ -55,12 +63,7 @@ class navigator(DatabaseNavigator):
|
|||
if len(hosts) > 1:
|
||||
self.display_hosts(hosts)
|
||||
elif len(hosts) == 1:
|
||||
data = [[
|
||||
"HostID",
|
||||
"Host",
|
||||
"Port",
|
||||
"Banner"
|
||||
]]
|
||||
data = [["HostID", "Host", "Port", "Banner"]]
|
||||
host_id_list = [h[0] for h in hosts]
|
||||
|
||||
for h in hosts:
|
||||
|
@ -68,11 +71,7 @@ class navigator(DatabaseNavigator):
|
|||
|
||||
print_table(data, title="Host")
|
||||
|
||||
login_data = [[
|
||||
"CredID",
|
||||
"UserName",
|
||||
"Password"
|
||||
]]
|
||||
login_data = [["CredID", "UserName", "Password"]]
|
||||
for host_id in host_id_list:
|
||||
login_links = self.db.get_loggedin_relations(host_id=host_id)
|
||||
|
||||
|
@ -85,7 +84,10 @@ class navigator(DatabaseNavigator):
|
|||
login_data.append(cred_data)
|
||||
|
||||
if len(login_data) > 1:
|
||||
print_table(login_data, title="Credential(s) with Logins",)
|
||||
print_table(
|
||||
login_data,
|
||||
title="Credential(s) with Logins",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def help_hosts(self):
|
||||
|
|
|
@ -129,6 +129,7 @@ def resolve_collection_methods(methods):
|
|||
nxc_logger.error("Invalid collection method specified: %s", method)
|
||||
return False
|
||||
|
||||
|
||||
class ldap(connection):
|
||||
def __init__(self, args, db, host):
|
||||
self.domain = None
|
||||
|
@ -306,8 +307,8 @@ class ldap(connection):
|
|||
else:
|
||||
self.logger.extra["protocol"] = "SMB" if not self.no_ntlm else "LDAP"
|
||||
self.logger.extra["port"] = "445" if not self.no_ntlm else "389"
|
||||
signing = colored(f"signing:{self.signing}", host_info_colors[0], attrs=['bold']) if self.signing else colored(f"signing:{self.signing}", host_info_colors[1], attrs=['bold'])
|
||||
smbv1 = colored(f"SMBv1:{self.smbv1}", host_info_colors[2], attrs=['bold']) if self.smbv1 else colored(f"SMBv1:{self.smbv1}", host_info_colors[3], attrs=['bold'])
|
||||
signing = colored(f"signing:{self.signing}", host_info_colors[0], attrs=["bold"]) if self.signing else colored(f"signing:{self.signing}", host_info_colors[1], attrs=["bold"])
|
||||
smbv1 = colored(f"SMBv1:{self.smbv1}", host_info_colors[2], attrs=["bold"]) if self.smbv1 else colored(f"SMBv1:{self.smbv1}", host_info_colors[3], attrs=["bold"])
|
||||
self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.domain}) ({signing}) ({smbv1})")
|
||||
self.logger.extra["protocol"] = "LDAP"
|
||||
# self.logger.display(self.endpoint)
|
||||
|
@ -741,7 +742,7 @@ class ldap(connection):
|
|||
try:
|
||||
if self.ldapConnection:
|
||||
self.logger.debug(f"Search Filter={searchFilter}")
|
||||
|
||||
|
||||
# Microsoft Active Directory set an hard limit of 1000 entries returned by any search
|
||||
paged_search_control = ldapasn1_impacket.SimplePagedResultsControl(criticality=True, size=1000)
|
||||
resp = self.ldapConnection.search(
|
||||
|
@ -818,18 +819,17 @@ class ldap(connection):
|
|||
self.logger.debug(f"Skipping item, cannot process due to error {e}")
|
||||
pass
|
||||
return
|
||||
|
||||
|
||||
def dc_list(self):
|
||||
|
||||
# Building the search filter
|
||||
search_filter = "(&(objectCategory=computer)(primaryGroupId=516))"
|
||||
attributes = ["dNSHostName"]
|
||||
resp = self.search(search_filter, attributes, 0)
|
||||
for item in resp:
|
||||
for item in resp:
|
||||
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
|
||||
continue
|
||||
name = ""
|
||||
try:
|
||||
try:
|
||||
for attribute in item["attributes"]:
|
||||
if str(attribute["type"]) == "dNSHostName":
|
||||
name = str(attribute["vals"][0])
|
||||
|
|
|
@ -252,16 +252,14 @@ class KerberosAttacks:
|
|||
return
|
||||
|
||||
# Let's output the TGT enc-part/cipher in Hashcat format, in case somebody wants to use it.
|
||||
if asRep['enc-part']['etype'] == 17 or asRep['enc-part']['etype'] == 18:
|
||||
if asRep["enc-part"]["etype"] == 17 or asRep["enc-part"]["etype"] == 18:
|
||||
hash_TGT = "$krb5asrep$%d$%s@%s:%s$%s" % (
|
||||
asRep["enc-part"]["etype"], clientName, domain,
|
||||
asRep["enc-part"]["etype"],
|
||||
clientName,
|
||||
domain,
|
||||
hexlify(asRep["enc-part"]["cipher"].asOctets()[:12]).decode(),
|
||||
hexlify(asRep["enc-part"]["cipher"].asOctets()[12:]).decode(),
|
||||
)
|
||||
else:
|
||||
hash_TGT = '$krb5asrep$%d$%s@%s:%s$%s' % (
|
||||
asRep['enc-part']['etype'], clientName, domain,
|
||||
hexlify(asRep['enc-part']['cipher'].asOctets()[:16]).decode(),
|
||||
hexlify(asRep['enc-part']['cipher'].asOctets()[16:]).decode()
|
||||
)
|
||||
hash_TGT = "$krb5asrep$%d$%s@%s:%s$%s" % (asRep["enc-part"]["etype"], clientName, domain, hexlify(asRep["enc-part"]["cipher"].asOctets()[:16]).decode(), hexlify(asRep["enc-part"]["cipher"].asOctets()[16:]).decode())
|
||||
return hash_TGT
|
||||
|
|
|
@ -224,13 +224,7 @@ class LAPSv2Extract:
|
|||
string_binding = hept_map(destHost=self.domain, remoteIf=MSRPC_UUID_GKDI, protocol="ncacn_ip_tcp")
|
||||
rpc_transport = transport.DCERPCTransportFactory(string_binding)
|
||||
if hasattr(rpc_transport, "set_credentials"):
|
||||
rpc_transport.set_credentials(
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
domain=self.domain,
|
||||
lmhash=self.lmhash,
|
||||
nthash=self.nthash
|
||||
)
|
||||
rpc_transport.set_credentials(username=self.username, password=self.password, domain=self.domain, lmhash=self.lmhash, nthash=self.nthash)
|
||||
if self.do_kerberos:
|
||||
self.logger.info("Connecting using kerberos")
|
||||
rpc_transport.set_kerberos(self.do_kerberos, kdcHost=self.kdcHost)
|
||||
|
@ -253,17 +247,10 @@ class LAPSv2Extract:
|
|||
self.logger.info("Successfully bound")
|
||||
self.logger.info("Calling MS-GKDI GetKey")
|
||||
|
||||
resp = GkdiGetKey(
|
||||
dce,
|
||||
target_sd=target_sd,
|
||||
l0=key_id["L0Index"],
|
||||
l1=key_id["L1Index"],
|
||||
l2=key_id["L2Index"],
|
||||
root_key_id=key_id["RootKeyId"]
|
||||
)
|
||||
resp = GkdiGetKey(dce, target_sd=target_sd, l0=key_id["L0Index"], l1=key_id["L1Index"], l2=key_id["L2Index"], root_key_id=key_id["RootKeyId"])
|
||||
self.logger.info("Decrypting password")
|
||||
# Unpack GroupKeyEnvelope
|
||||
gke = GroupKeyEnvelope(b''.join(resp["pbbOut"]))
|
||||
gke = GroupKeyEnvelope(b"".join(resp["pbbOut"]))
|
||||
kds_cache[gke["RootKeyId"]] = gke
|
||||
|
||||
kek = compute_kek(gke, key_id)
|
||||
|
@ -276,4 +263,4 @@ class LAPSv2Extract:
|
|||
self.logger.info("CEK:\t%s" % cek)
|
||||
plaintext = decrypt_plaintext(cek, iv, remaining)
|
||||
self.logger.info(plaintext[:-18].decode("utf-16le"))
|
||||
return plaintext[:-18].decode("utf-16le")
|
||||
return plaintext[:-18].decode("utf-16le")
|
||||
|
|
|
@ -2,19 +2,19 @@ from argparse import _StoreTrueAction
|
|||
|
||||
|
||||
def proto_args(parser, std_parser, module_parser):
|
||||
ldap_parser = parser.add_parser('ldap', help="own stuff using LDAP", parents=[std_parser, module_parser])
|
||||
ldap_parser.add_argument("-H", '--hash', metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes')
|
||||
ldap_parser = parser.add_parser("ldap", help="own stuff using LDAP", parents=[std_parser, module_parser])
|
||||
ldap_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
|
||||
ldap_parser.add_argument("--port", type=int, choices={389, 636}, default=389, help="LDAP port (default: 389)")
|
||||
no_smb_arg = ldap_parser.add_argument("--no-smb", action=get_conditional_action(_StoreTrueAction), make_required=[], help='No smb connection')
|
||||
no_smb_arg = ldap_parser.add_argument("--no-smb", action=get_conditional_action(_StoreTrueAction), make_required=[], help="No smb connection")
|
||||
|
||||
dgroup = ldap_parser.add_mutually_exclusive_group()
|
||||
domain_arg = dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, default=None, help="domain to authenticate to")
|
||||
dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target')
|
||||
domain_arg = dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, default=None, help="domain to authenticate to")
|
||||
dgroup.add_argument("--local-auth", action="store_true", help="authenticate locally to each target")
|
||||
no_smb_arg.make_required = [domain_arg]
|
||||
|
||||
egroup = ldap_parser.add_argument_group("Retrevie hash on the remote DC", "Options to get hashes from Kerberos")
|
||||
egroup.add_argument("--asreproast", help="Get AS_REP response ready to crack with hashcat")
|
||||
egroup.add_argument("--kerberoasting", help='Get TGS ticket ready to crack with hashcat')
|
||||
egroup.add_argument("--kerberoasting", help="Get TGS ticket ready to crack with hashcat")
|
||||
|
||||
vgroup = ldap_parser.add_argument_group("Retrieve useful information on the domain", "Options to to play with Kerberos")
|
||||
vgroup.add_argument("--trusted-for-delegation", action="store_true", help="Get the list of users and computers with flag TRUSTED_FOR_DELEGATION")
|
||||
|
@ -41,7 +41,7 @@ def proto_args(parser, std_parser, module_parser):
|
|||
def get_conditional_action(baseAction):
|
||||
class ConditionalAction(baseAction):
|
||||
def __init__(self, option_strings, dest, **kwargs):
|
||||
x = kwargs.pop('make_required', [])
|
||||
x = kwargs.pop("make_required", [])
|
||||
super(ConditionalAction, self).__init__(option_strings, dest, **kwargs)
|
||||
self.make_required = x
|
||||
|
||||
|
|
|
@ -389,7 +389,7 @@ class mssql(connection):
|
|||
remote_path = self.args.get_file[0]
|
||||
download_path = self.args.get_file[1]
|
||||
self.logger.display(f'Copying "{remote_path}" to "{download_path}"')
|
||||
|
||||
|
||||
try:
|
||||
exec_method = MSSQLEXEC(self.conn)
|
||||
exec_method.get_file(self.args.get_file[0], self.args.get_file[1])
|
||||
|
|
|
@ -204,7 +204,6 @@ class database:
|
|||
self.conn.execute(q)
|
||||
|
||||
def add_admin_user(self, credtype, domain, username, password, host, user_id=None):
|
||||
|
||||
if user_id:
|
||||
q = select(self.UsersTable).filter(self.UsersTable.c.id == user_id)
|
||||
users = self.conn.execute(q).all()
|
||||
|
|
|
@ -1,38 +1,40 @@
|
|||
from argparse import _StoreTrueAction
|
||||
|
||||
|
||||
def proto_args(parser, std_parser, module_parser):
|
||||
mssql_parser = parser.add_parser('mssql', help="own stuff using MSSQL", parents=[std_parser, module_parser])
|
||||
mssql_parser.add_argument("-H", '--hash', metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes')
|
||||
mssql_parser.add_argument("--port", default=1433, type=int, metavar='PORT', help='MSSQL port (default: 1433)')
|
||||
mssql_parser.add_argument("-q", "--query", dest='mssql_query', metavar='QUERY', type=str, help='execute the specified query against the MSSQL DB')
|
||||
no_smb_arg = mssql_parser.add_argument("--no-smb", action=get_conditional_action(_StoreTrueAction), make_required=[], help='No smb connection')
|
||||
mssql_parser = parser.add_parser("mssql", help="own stuff using MSSQL", parents=[std_parser, module_parser])
|
||||
mssql_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
|
||||
mssql_parser.add_argument("--port", default=1433, type=int, metavar="PORT", help="MSSQL port (default: 1433)")
|
||||
mssql_parser.add_argument("-q", "--query", dest="mssql_query", metavar="QUERY", type=str, help="execute the specified query against the MSSQL DB")
|
||||
no_smb_arg = mssql_parser.add_argument("--no-smb", action=get_conditional_action(_StoreTrueAction), make_required=[], help="No smb connection")
|
||||
|
||||
dgroup = mssql_parser.add_mutually_exclusive_group()
|
||||
domain_arg = dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="domain name")
|
||||
dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target')
|
||||
domain_arg = dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, help="domain name")
|
||||
dgroup.add_argument("--local-auth", action="store_true", help="authenticate locally to each target")
|
||||
no_smb_arg.make_required = [domain_arg]
|
||||
|
||||
cgroup = mssql_parser.add_argument_group("Command Execution", "options for executing commands")
|
||||
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')
|
||||
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")
|
||||
xgroup = cgroup.add_mutually_exclusive_group()
|
||||
xgroup.add_argument("-x", metavar="COMMAND", dest='execute', help="execute the specified command")
|
||||
xgroup.add_argument("-X", metavar="PS_COMMAND", dest='ps_execute', help='execute the specified PowerShell command')
|
||||
xgroup.add_argument("-x", metavar="COMMAND", dest="execute", help="execute the specified command")
|
||||
xgroup.add_argument("-X", metavar="PS_COMMAND", dest="ps_execute", help="execute the specified PowerShell command")
|
||||
|
||||
psgroup = mssql_parser.add_argument_group('Powershell Obfuscation', "Options for PowerShell script obfuscation")
|
||||
psgroup.add_argument('--obfs', action='store_true', help='Obfuscate PowerShell scripts')
|
||||
psgroup.add_argument('--clear-obfscripts', action='store_true', help='Clear all cached obfuscated PowerShell scripts')
|
||||
psgroup = mssql_parser.add_argument_group("Powershell Obfuscation", "Options for PowerShell script obfuscation")
|
||||
psgroup.add_argument("--obfs", action="store_true", help="Obfuscate PowerShell scripts")
|
||||
psgroup.add_argument("--clear-obfscripts", action="store_true", help="Clear all cached obfuscated PowerShell scripts")
|
||||
|
||||
tgroup = mssql_parser.add_argument_group("Files", "Options for put and get remote files")
|
||||
tgroup.add_argument("--put-file", nargs=2, metavar="FILE", help='Put a local file into remote target, ex: whoami.txt C:\\Windows\\Temp\\whoami.txt')
|
||||
tgroup.add_argument("--get-file", nargs=2, metavar="FILE", help='Get a remote file, ex: C:\\Windows\\Temp\\whoami.txt whoami.txt')
|
||||
tgroup.add_argument("--put-file", nargs=2, metavar="FILE", help="Put a local file into remote target, ex: whoami.txt C:\\Windows\\Temp\\whoami.txt")
|
||||
tgroup.add_argument("--get-file", nargs=2, metavar="FILE", help="Get a remote file, ex: C:\\Windows\\Temp\\whoami.txt whoami.txt")
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def get_conditional_action(baseAction):
|
||||
class ConditionalAction(baseAction):
|
||||
def __init__(self, option_strings, dest, **kwargs):
|
||||
x = kwargs.pop('make_required', [])
|
||||
x = kwargs.pop("make_required", [])
|
||||
super(ConditionalAction, self).__init__(option_strings, dest, **kwargs)
|
||||
self.make_required = x
|
||||
|
||||
|
@ -41,4 +43,4 @@ def get_conditional_action(baseAction):
|
|||
x.required = True
|
||||
super(ConditionalAction, self).__call__(parser, namespace, values, option_string)
|
||||
|
||||
return ConditionalAction
|
||||
return ConditionalAction
|
||||
|
|
|
@ -25,6 +25,7 @@ from asyauth.common.credentials.kerberos import KerberosCredential
|
|||
from asyauth.common.constants import asyauthSecret
|
||||
from asysocks.unicomm.common.target import UniTarget, UniProto
|
||||
|
||||
|
||||
class rdp(connection):
|
||||
def __init__(self, args, db, host):
|
||||
self.domain = None
|
||||
|
@ -104,7 +105,7 @@ class rdp(connection):
|
|||
)
|
||||
|
||||
def print_host_info(self):
|
||||
nla = colored(f"nla:{self.nla}", host_info_colors[3], attrs=['bold']) if self.nla else colored(f"nla:{self.nla}", host_info_colors[2], attrs=['bold'])
|
||||
nla = colored(f"nla:{self.nla}", host_info_colors[3], attrs=["bold"]) if self.nla else colored(f"nla:{self.nla}", host_info_colors[2], attrs=["bold"])
|
||||
if self.domain is None:
|
||||
self.logger.display("Probably old, doesn't not support HYBRID or HYBRID_EX" f" ({nla})")
|
||||
else:
|
||||
|
@ -220,15 +221,7 @@ class rdp(connection):
|
|||
else:
|
||||
stype = asyauthSecret.PASS if not nthash else asyauthSecret.NT
|
||||
|
||||
kerberos_target = UniTarget(
|
||||
self.domain,
|
||||
88,
|
||||
UniProto.CLIENT_TCP,
|
||||
proxies=None,
|
||||
dns=None,
|
||||
dc_ip=self.domain,
|
||||
domain=self.domain
|
||||
)
|
||||
kerberos_target = UniTarget(self.domain, 88, UniProto.CLIENT_TCP, proxies=None, dns=None, dc_ip=self.domain, domain=self.domain)
|
||||
self.auth = KerberosCredential(
|
||||
target=kerberos_target,
|
||||
secret=password,
|
||||
|
@ -246,9 +239,7 @@ class rdp(connection):
|
|||
username,
|
||||
(
|
||||
# Show what was used between cleartext, nthash, aesKey and ccache
|
||||
" from ccache"
|
||||
if useCache
|
||||
else ":%s" % (process_secret(kerb_pass))
|
||||
" from ccache" if useCache else ":%s" % (process_secret(kerb_pass))
|
||||
),
|
||||
self.mark_pwned(),
|
||||
)
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
def proto_args(parser, std_parser, module_parser):
|
||||
rdp_parser = parser.add_parser('rdp', help="own stuff using RDP", parents=[std_parser, module_parser])
|
||||
rdp_parser.add_argument("-H", '--hash', metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes')
|
||||
rdp_parser = parser.add_parser("rdp", help="own stuff using RDP", parents=[std_parser, module_parser])
|
||||
rdp_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
|
||||
rdp_parser.add_argument("--port", type=int, default=3389, help="Custom RDP port")
|
||||
rdp_parser.add_argument("--rdp-timeout", type=int, default=5, help="RDP timeout on socket connection, defalut is %(default)ss")
|
||||
rdp_parser.add_argument("--nla-screenshot", action="store_true", help="Screenshot RDP login prompt if NLA is disabled")
|
||||
|
||||
dgroup = rdp_parser.add_mutually_exclusive_group()
|
||||
dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, default=None, help="domain to authenticate to")
|
||||
dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target')
|
||||
dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, default=None, help="domain to authenticate to")
|
||||
dgroup.add_argument("--local-auth", action="store_true", help="authenticate locally to each target")
|
||||
|
||||
egroup = rdp_parser.add_argument_group("Screenshot", "Remote Desktop Screenshot")
|
||||
egroup.add_argument("--screenshot", action="store_true", help="Screenshot RDP if connection success")
|
||||
egroup.add_argument('--screentime', type=int, default=10, help='Time to wait for desktop image, default is %(default)ss')
|
||||
egroup.add_argument('--res', default='1024x768', help='Resolution in "WIDTHxHEIGHT" format. Default: "1024x768"')
|
||||
egroup.add_argument("--screentime", type=int, default=10, help="Time to wait for desktop image, default is %(default)ss")
|
||||
egroup.add_argument("--res", default="1024x768", help='Resolution in "WIDTHxHEIGHT" format. Default: "1024x768"')
|
||||
|
||||
return parser
|
||||
return parser
|
||||
|
|
|
@ -314,15 +314,7 @@ class smb(connection):
|
|||
values = {str(attr["type"]).lower(): attr["vals"][0] for attr in host["attributes"]}
|
||||
if "mslaps-encryptedpassword" in values:
|
||||
msMCSAdmPwd = values["mslaps-encryptedpassword"]
|
||||
d = LAPSv2Extract(
|
||||
bytes(msMCSAdmPwd),
|
||||
username[0] if username else "",
|
||||
password[0] if password else "",
|
||||
domain,
|
||||
ntlm_hash[0] if ntlm_hash else "",
|
||||
self.args.kerberos,
|
||||
self.args.kdcHost,
|
||||
339)
|
||||
d = LAPSv2Extract(bytes(msMCSAdmPwd), username[0] if username else "", password[0] if password else "", domain, ntlm_hash[0] if ntlm_hash else "", self.args.kerberos, self.args.kdcHost, 339)
|
||||
try:
|
||||
data = d.run()
|
||||
except Exception as e:
|
||||
|
@ -362,8 +354,8 @@ class smb(connection):
|
|||
return True
|
||||
|
||||
def print_host_info(self):
|
||||
signing = colored(f"signing:{self.signing}", host_info_colors[0], attrs=['bold']) if self.signing else colored(f"signing:{self.signing}", host_info_colors[1], attrs=['bold'])
|
||||
smbv1 = colored(f"SMBv1:{self.smbv1}", host_info_colors[2], attrs=['bold']) if self.smbv1 else colored(f"SMBv1:{self.smbv1}", host_info_colors[3], attrs=['bold'])
|
||||
signing = colored(f"signing:{self.signing}", host_info_colors[0], attrs=["bold"]) if self.signing else colored(f"signing:{self.signing}", host_info_colors[1], attrs=["bold"])
|
||||
smbv1 = colored(f"SMBv1:{self.smbv1}", host_info_colors[2], attrs=["bold"]) if self.smbv1 else colored(f"SMBv1:{self.smbv1}", host_info_colors[3], attrs=["bold"])
|
||||
self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.domain}) ({signing}) ({smbv1})")
|
||||
if self.args.laps:
|
||||
return self.laps_search(self.args.username, self.args.password, self.args.hash, self.domain)
|
||||
|
@ -402,7 +394,7 @@ class smb(connection):
|
|||
kerb_pass = ""
|
||||
self.logger.debug(f"Attempting to do Kerberos Login with useCache: {useCache}")
|
||||
|
||||
self.conn.kerberosLogin( username, password, domain, lmhash, nthash, aesKey, kdcHost, useCache=useCache)
|
||||
self.conn.kerberosLogin(username, password, domain, lmhash, nthash, aesKey, kdcHost, useCache=useCache)
|
||||
self.check_if_admin()
|
||||
|
||||
if username == "":
|
||||
|
@ -677,28 +669,13 @@ class smb(connection):
|
|||
payload = self.args.execute
|
||||
if not self.args.no_output:
|
||||
get_output = True
|
||||
|
||||
|
||||
current_method = ""
|
||||
for method in methods:
|
||||
current_method = method
|
||||
if method == "wmiexec":
|
||||
try:
|
||||
exec_method = WMIEXEC(
|
||||
self.host if not self.kerberos else self.hostname + "." + self.domain,
|
||||
self.smb_share_name,
|
||||
self.username,
|
||||
self.password,
|
||||
self.domain,
|
||||
self.conn,
|
||||
self.kerberos,
|
||||
self.aesKey,
|
||||
self.kdcHost,
|
||||
self.hash,
|
||||
self.args.share,
|
||||
logger=self.logger,
|
||||
timeout=self.args.dcom_timeout,
|
||||
tries=self.args.get_output_tries
|
||||
)
|
||||
exec_method = WMIEXEC(self.host if not self.kerberos else self.hostname + "." + self.domain, self.smb_share_name, self.username, self.password, self.domain, self.conn, self.kerberos, self.aesKey, self.kdcHost, self.hash, self.args.share, logger=self.logger, timeout=self.args.dcom_timeout, tries=self.args.get_output_tries)
|
||||
self.logger.info("Executed command via wmiexec")
|
||||
break
|
||||
except:
|
||||
|
@ -707,19 +684,7 @@ class smb(connection):
|
|||
continue
|
||||
elif method == "mmcexec":
|
||||
try:
|
||||
exec_method = MMCEXEC(
|
||||
self.host if not self.kerberos else self.hostname + "." + self.domain,
|
||||
self.smb_share_name,
|
||||
self.username,
|
||||
self.password,
|
||||
self.domain,
|
||||
self.conn,
|
||||
self.args.share,
|
||||
self.hash,
|
||||
self.logger,
|
||||
self.args.get_output_tries,
|
||||
self.args.dcom_timeout
|
||||
)
|
||||
exec_method = MMCEXEC(self.host if not self.kerberos else self.hostname + "." + self.domain, self.smb_share_name, self.username, self.password, self.domain, self.conn, self.args.share, self.hash, self.logger, self.args.get_output_tries, self.args.dcom_timeout)
|
||||
self.logger.info("Executed command via mmcexec")
|
||||
break
|
||||
except:
|
||||
|
@ -728,20 +693,7 @@ class smb(connection):
|
|||
continue
|
||||
elif method == "atexec":
|
||||
try:
|
||||
exec_method = TSCH_EXEC(
|
||||
self.host if not self.kerberos else self.hostname + "." + self.domain,
|
||||
self.smb_share_name,
|
||||
self.username,
|
||||
self.password,
|
||||
self.domain,
|
||||
self.kerberos,
|
||||
self.aesKey,
|
||||
self.kdcHost,
|
||||
self.hash,
|
||||
self.logger,
|
||||
self.args.get_output_tries,
|
||||
self.args.share
|
||||
)
|
||||
exec_method = TSCH_EXEC(self.host if not self.kerberos else self.hostname + "." + self.domain, self.smb_share_name, self.username, self.password, self.domain, self.kerberos, self.aesKey, self.kdcHost, self.hash, self.logger, self.args.get_output_tries, self.args.share)
|
||||
self.logger.info("Executed command via atexec")
|
||||
break
|
||||
except:
|
||||
|
@ -750,23 +702,7 @@ class smb(connection):
|
|||
continue
|
||||
elif method == "smbexec":
|
||||
try:
|
||||
exec_method = SMBEXEC(
|
||||
self.host if not self.kerberos else self.hostname + "." + self.domain,
|
||||
self.smb_share_name,
|
||||
self.conn,
|
||||
self.args.port,
|
||||
self.username,
|
||||
self.password,
|
||||
self.domain,
|
||||
self.kerberos,
|
||||
self.aesKey,
|
||||
self.kdcHost,
|
||||
self.hash,
|
||||
self.args.share,
|
||||
self.args.port,
|
||||
self.logger,
|
||||
self.args.get_output_tries
|
||||
)
|
||||
exec_method = SMBEXEC(self.host if not self.kerberos else self.hostname + "." + self.domain, self.smb_share_name, self.conn, self.args.port, self.username, self.password, self.domain, self.kerberos, self.aesKey, self.kdcHost, self.hash, self.args.share, self.args.port, self.logger, self.args.get_output_tries)
|
||||
self.logger.info("Executed command via smbexec")
|
||||
break
|
||||
except:
|
||||
|
@ -776,7 +712,7 @@ class smb(connection):
|
|||
|
||||
if hasattr(self, "server"):
|
||||
self.server.track_host(self.host)
|
||||
|
||||
|
||||
if "exec_method" in locals():
|
||||
output = exec_method.execute(payload, get_output)
|
||||
try:
|
||||
|
@ -798,7 +734,7 @@ class smb(connection):
|
|||
else:
|
||||
self.logger.fail(f"Execute command failed with {current_method}")
|
||||
return False
|
||||
|
||||
|
||||
@requires_admin
|
||||
def ps_execute(
|
||||
self,
|
||||
|
@ -1014,9 +950,7 @@ class smb(connection):
|
|||
group_id = self.db.get_groups(
|
||||
group_name=self.args.local_groups,
|
||||
group_domain=domain,
|
||||
)[
|
||||
0
|
||||
][0]
|
||||
)[0][0]
|
||||
except IndexError:
|
||||
group_id = self.db.add_group(
|
||||
domain,
|
||||
|
@ -1093,9 +1027,7 @@ class smb(connection):
|
|||
group_id = self.db.get_groups(
|
||||
group_name=self.args.groups,
|
||||
group_domain=group.groupdomain,
|
||||
)[
|
||||
0
|
||||
][0]
|
||||
)[0][0]
|
||||
except IndexError:
|
||||
group_id = self.db.add_group(
|
||||
group.groupdomain,
|
||||
|
@ -1219,54 +1151,43 @@ class smb(connection):
|
|||
def wmi(self, wmi_query=None, namespace=None):
|
||||
records = []
|
||||
if not wmi_query:
|
||||
wmi_query = self.args.wmi.strip('\n')
|
||||
wmi_query = self.args.wmi.strip("\n")
|
||||
|
||||
if not namespace:
|
||||
namespace = self.args.wmi_namespace
|
||||
|
||||
try:
|
||||
dcom = DCOMConnection(
|
||||
self.host if not self.kerberos else self.hostname + "." + self.domain,
|
||||
self.username,
|
||||
self.password,
|
||||
self.domain,
|
||||
self.lmhash,
|
||||
self.nthash,
|
||||
oxidResolver=True,
|
||||
doKerberos=self.kerberos,
|
||||
kdcHost=self.kdcHost,
|
||||
aesKey=self.aesKey
|
||||
)
|
||||
iInterface = dcom.CoCreateInstanceEx(CLSID_WbemLevel1Login,IID_IWbemLevel1Login)
|
||||
flag, stringBinding = dcom_FirewallChecker(iInterface, self.args.dcom_timeout)
|
||||
dcom = DCOMConnection(self.host if not self.kerberos else self.hostname + "." + self.domain, self.username, self.password, self.domain, self.lmhash, self.nthash, oxidResolver=True, doKerberos=self.kerberos, kdcHost=self.kdcHost, aesKey=self.aesKey)
|
||||
iInterface = dcom.CoCreateInstanceEx(CLSID_WbemLevel1Login, IID_IWbemLevel1Login)
|
||||
flag, stringBinding = dcom_FirewallChecker(iInterface, self.args.dcom_timeout)
|
||||
if not flag or not stringBinding:
|
||||
error_msg = f'WMI Query: Dcom initialization failed on connection with stringbinding: "{stringBinding}", please increase the timeout with the option "--dcom-timeout". If it\'s still failing maybe something is blocking the RPC connection, try another exec method'
|
||||
|
||||
|
||||
if not stringBinding:
|
||||
error_msg = "WMI Query: Dcom initialization failed: can't get target stringbinding, maybe cause by IPv6 or any other issues, please check your target again"
|
||||
|
||||
|
||||
self.logger.fail(error_msg) if not flag else self.logger.debug(error_msg)
|
||||
# Make it force break function
|
||||
dcom.disconnect()
|
||||
iWbemLevel1Login = IWbemLevel1Login(iInterface)
|
||||
iWbemServices= iWbemLevel1Login.NTLMLogin(namespace , NULL, NULL)
|
||||
iWbemServices = iWbemLevel1Login.NTLMLogin(namespace, NULL, NULL)
|
||||
iWbemLevel1Login.RemRelease()
|
||||
iEnumWbemClassObject = iWbemServices.ExecQuery(wmi_query)
|
||||
except Exception as e:
|
||||
self.logger.fail('Execute WQL error: {}'.format(e))
|
||||
self.logger.fail("Execute WQL error: {}".format(e))
|
||||
if "iWbemLevel1Login" in locals():
|
||||
dcom.disconnect()
|
||||
else:
|
||||
self.logger.info(f"Executing WQL syntax: {wmi_query}")
|
||||
while True:
|
||||
try:
|
||||
wmi_results = iEnumWbemClassObject.Next(0xffffffff, 1)[0]
|
||||
wmi_results = iEnumWbemClassObject.Next(0xFFFFFFFF, 1)[0]
|
||||
record = wmi_results.getProperties()
|
||||
records.append(record)
|
||||
for k,v in record.items():
|
||||
for k, v in record.items():
|
||||
self.logger.highlight(f"{k} => {v['value']}")
|
||||
except Exception as e:
|
||||
if str(e).find('S_FALSE') < 0:
|
||||
if str(e).find("S_FALSE") < 0:
|
||||
raise e
|
||||
else:
|
||||
break
|
||||
|
@ -1486,7 +1407,7 @@ class smb(connection):
|
|||
SAM.finish()
|
||||
except SessionError as e:
|
||||
if "STATUS_ACCESS_DENIED" in e.getErrorString():
|
||||
self.logger.fail("Error \"STATUS_ACCESS_DENIED\" while dumping SAM. This is likely due to an endpoint protection.")
|
||||
self.logger.fail('Error "STATUS_ACCESS_DENIED" while dumping SAM. This is likely due to an endpoint protection.')
|
||||
except Exception as e:
|
||||
self.logger.exception(str(e))
|
||||
|
||||
|
@ -1658,7 +1579,7 @@ class smb(connection):
|
|||
if dump_cookies:
|
||||
self.logger.display("Start Dumping Cookies")
|
||||
for cookie in cookies:
|
||||
if cookie.cookie_value != '':
|
||||
if cookie.cookie_value != "":
|
||||
self.logger.highlight(f"[{credential.winuser}][{cookie.browser.upper()}] {cookie.host}{cookie.path} - {cookie.cookie_name}:{cookie.cookie_value}")
|
||||
self.logger.display("End Dumping Cookies")
|
||||
|
||||
|
@ -1744,7 +1665,7 @@ class smb(connection):
|
|||
LSA.finish()
|
||||
except SessionError as e:
|
||||
if "STATUS_ACCESS_DENIED" in e.getErrorString():
|
||||
self.logger.fail("Error \"STATUS_ACCESS_DENIED\" while dumping LSA. This is likely due to an endpoint protection.")
|
||||
self.logger.fail('Error "STATUS_ACCESS_DENIED" while dumping LSA. This is likely due to an endpoint protection.')
|
||||
except Exception as e:
|
||||
self.logger.exception(str(e))
|
||||
|
||||
|
|
|
@ -10,21 +10,7 @@ from time import sleep
|
|||
|
||||
|
||||
class TSCH_EXEC:
|
||||
def __init__(
|
||||
self,
|
||||
target,
|
||||
share_name,
|
||||
username,
|
||||
password,
|
||||
domain,
|
||||
doKerberos=False,
|
||||
aesKey=None,
|
||||
kdcHost=None,
|
||||
hashes=None,
|
||||
logger=None,
|
||||
tries=None,
|
||||
share=None
|
||||
):
|
||||
def __init__(self, target, share_name, username, password, domain, doKerberos=False, aesKey=None, kdcHost=None, hashes=None, logger=None, tries=None, share=None):
|
||||
self.__target = target
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
|
@ -144,7 +130,7 @@ class TSCH_EXEC:
|
|||
dce.set_credentials(*self.__rpctransport.get_credentials())
|
||||
dce.connect()
|
||||
# dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
|
||||
|
||||
|
||||
tmpName = gen_random_string(8)
|
||||
|
||||
xml = self.gen_xml(command, fileless)
|
||||
|
@ -207,7 +193,7 @@ class TSCH_EXEC:
|
|||
if tries >= self.__tries:
|
||||
self.logger.fail("ATEXEC: Could not retrieve output file, it may have been detected by AV. Please increase the number of tries with the option '--get-output-tries'. If it is still failing, try the 'wmi' protocol or another exec method")
|
||||
break
|
||||
if str(e).find("STATUS_BAD_NETWORK_NAME") >0 :
|
||||
if str(e).find("STATUS_BAD_NETWORK_NAME") > 0:
|
||||
self.logger.fail(f"ATEXEC: Getting the output file failed - target has blocked access to the share: {self.__share} (but the command may have executed!)")
|
||||
break
|
||||
if str(e).find("SHARING") > 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
|
||||
|
|
|
@ -457,7 +457,6 @@ class database:
|
|||
return results
|
||||
|
||||
def get_credential(self, cred_type, domain, username, password):
|
||||
|
||||
q = select(self.UsersTable).filter(
|
||||
self.UsersTable.c.domain == domain,
|
||||
self.UsersTable.c.username == username,
|
||||
|
@ -899,11 +898,11 @@ class database:
|
|||
def get_checks(self):
|
||||
q = select(self.ConfChecksTable)
|
||||
return self.conn.execute(q).all()
|
||||
|
||||
|
||||
def get_check_results(self):
|
||||
q = select(self.ConfChecksResultsTable)
|
||||
return self.conn.execute(q).all()
|
||||
|
||||
|
||||
def insert_data(self, table, select_results=[], **new_row):
|
||||
"""
|
||||
Insert a new row in the given table.
|
||||
|
@ -919,20 +918,20 @@ class database:
|
|||
else:
|
||||
for row in select_results:
|
||||
row_data = row._asdict()
|
||||
for column,value in new_row.items():
|
||||
for column, value in new_row.items():
|
||||
row_data[column] = value
|
||||
|
||||
# Only add data to be updated if it has changed
|
||||
if row_data not in results:
|
||||
results.append(row_data)
|
||||
updated_ids.append(row_data['id'])
|
||||
updated_ids.append(row_data["id"])
|
||||
|
||||
nxc_logger.debug(f'Update data: {results}')
|
||||
nxc_logger.debug(f"Update data: {results}")
|
||||
# TODO: find a way to abstract this away to a single Upsert call
|
||||
q = Insert(table) # .returning(table.c.id)
|
||||
update_column = {col.name: col for col in q.excluded if col.name not in 'id'}
|
||||
q = Insert(table) # .returning(table.c.id)
|
||||
update_column = {col.name: col for col in q.excluded if col.name not in "id"}
|
||||
q = q.on_conflict_do_update(index_elements=table.primary_key, set_=update_column)
|
||||
self.conn.execute(q, results) # .scalar()
|
||||
self.conn.execute(q, results) # .scalar()
|
||||
# we only return updated IDs for now - when RETURNING clause is allowed we can return inserted
|
||||
return updated_ids
|
||||
|
||||
|
@ -943,7 +942,7 @@ class database:
|
|||
q = select(self.ConfChecksTable).filter(self.ConfChecksTable.c.name == name)
|
||||
select_results = self.conn.execute(q).all()
|
||||
context = locals()
|
||||
new_row = dict(((column, context[column]) for column in ('name', 'description')))
|
||||
new_row = dict(((column, context[column]) for column in ("name", "description")))
|
||||
updated_ids = self.insert_data(self.ConfChecksTable, select_results, **new_row)
|
||||
|
||||
if updated_ids:
|
||||
|
@ -957,7 +956,7 @@ class database:
|
|||
q = select(self.ConfChecksResultsTable).filter(self.ConfChecksResultsTable.c.host_id == host_id, self.ConfChecksResultsTable.c.check_id == check_id)
|
||||
select_results = self.conn.execute(q).all()
|
||||
context = locals()
|
||||
new_row = dict(((column, context[column]) for column in ('host_id', 'check_id', 'secure', 'reasons')))
|
||||
new_row = dict(((column, context[column]) for column in ("host_id", "check_id", "secure", "reasons")))
|
||||
updated_ids = self.insert_data(self.ConfChecksResultsTable, select_results, **new_row)
|
||||
|
||||
if updated_ids:
|
||||
|
|
|
@ -6,8 +6,9 @@ from nxc.nxcdb import DatabaseNavigator, print_table, print_help
|
|||
from termcolor import colored
|
||||
import functools
|
||||
|
||||
help_header = functools.partial(colored, color='cyan', attrs=['bold'])
|
||||
help_kw = functools.partial(colored, color='green', attrs=['bold'])
|
||||
help_header = functools.partial(colored, color="cyan", attrs=["bold"])
|
||||
help_kw = functools.partial(colored, color="green", attrs=["bold"])
|
||||
|
||||
|
||||
class navigator(DatabaseNavigator):
|
||||
def display_creds(self, creds):
|
||||
|
@ -361,35 +362,21 @@ class navigator(DatabaseNavigator):
|
|||
print_table(data, title="Credential(s) with Admin Access")
|
||||
|
||||
def do_wcc(self, line):
|
||||
valid_columns = {
|
||||
'ip':'IP',
|
||||
'hostname':'Hostname',
|
||||
'check':'Check',
|
||||
'description':'Description',
|
||||
'status':'Status',
|
||||
'reasons':'Reasons'
|
||||
}
|
||||
valid_columns = {"ip": "IP", "hostname": "Hostname", "check": "Check", "description": "Description", "status": "Status", "reasons": "Reasons"}
|
||||
|
||||
line = line.strip()
|
||||
|
||||
if line.lower() == 'full':
|
||||
if line.lower() == "full":
|
||||
columns_to_display = list(valid_columns.values())
|
||||
else:
|
||||
requested_columns = line.split(' ')
|
||||
requested_columns = line.split(" ")
|
||||
columns_to_display = list(valid_columns[column.lower()] for column in requested_columns if column.lower() in valid_columns)
|
||||
|
||||
results = self.db.get_check_results()
|
||||
self.display_wcc_results(results, columns_to_display)
|
||||
|
||||
def display_wcc_results(self, results, columns_to_display=None):
|
||||
data = [
|
||||
[
|
||||
"IP",
|
||||
"Hostname",
|
||||
"Check",
|
||||
"Status"
|
||||
]
|
||||
]
|
||||
data = [["IP", "Hostname", "Check", "Status"]]
|
||||
if columns_to_display:
|
||||
data = [columns_to_display]
|
||||
|
||||
|
@ -397,25 +384,25 @@ class navigator(DatabaseNavigator):
|
|||
checks_dict = {}
|
||||
for check in checks:
|
||||
check = check._asdict()
|
||||
checks_dict[check['id']] = check
|
||||
checks_dict[check["id"]] = check
|
||||
|
||||
for (result_id, host_id, check_id, secure, reasons) in results:
|
||||
status = 'OK' if secure else 'KO'
|
||||
for result_id, host_id, check_id, secure, reasons in results:
|
||||
status = "OK" if secure else "KO"
|
||||
host = self.db.get_hosts(host_id)[0]._asdict()
|
||||
check = checks_dict[check_id]
|
||||
row = []
|
||||
for column in data[0]:
|
||||
if column == 'IP':
|
||||
row.append(host['ip'])
|
||||
if column == 'Hostname':
|
||||
row.append(host['hostname'])
|
||||
if column == 'Check':
|
||||
row.append(check['name'])
|
||||
if column == 'Description':
|
||||
row.append(check['description'])
|
||||
if column == 'Status':
|
||||
if column == "IP":
|
||||
row.append(host["ip"])
|
||||
if column == "Hostname":
|
||||
row.append(host["hostname"])
|
||||
if column == "Check":
|
||||
row.append(check["name"])
|
||||
if column == "Description":
|
||||
row.append(check["description"])
|
||||
if column == "Status":
|
||||
row.append(status)
|
||||
if column == 'Reasons':
|
||||
if column == "Reasons":
|
||||
row.append(reasons)
|
||||
data.append(row)
|
||||
|
||||
|
|
|
@ -103,13 +103,13 @@ class MMCEXEC:
|
|||
except:
|
||||
# Make it force break function
|
||||
self.__dcom.disconnect()
|
||||
flag, self.__stringBinding = dcom_FirewallChecker(iInterface, self.__timeout)
|
||||
flag, self.__stringBinding = dcom_FirewallChecker(iInterface, self.__timeout)
|
||||
if not flag or not self.__stringBinding:
|
||||
error_msg = f'MMCEXEC: Dcom initialization failed on connection with stringbinding: "{self.__stringBinding}", please increase the timeout with the option "--dcom-timeout". If it\'s still failing maybe something is blocking the RPC connection, try another exec method'
|
||||
|
||||
|
||||
if not self.__stringBinding:
|
||||
error_msg = "MMCEXEC: Dcom initialization failed: can't get target stringbinding, maybe cause by IPv6 or any other issues, please check your target again"
|
||||
|
||||
|
||||
self.logger.fail(error_msg) if not flag else self.logger.debug(error_msg)
|
||||
# Make it force break function
|
||||
self.__dcom.disconnect()
|
||||
|
@ -254,7 +254,7 @@ class MMCEXEC:
|
|||
if tries >= self.__tries:
|
||||
self.logger.fail("MMCEXEC: Could not retrieve output file, it may have been detected by AV. Please increase the number of tries with the option '--get-output-tries'. If it is still failing, try the 'wmi' protocol or another exec method")
|
||||
break
|
||||
if str(e).find("STATUS_BAD_NETWORK_NAME") >0 :
|
||||
if str(e).find("STATUS_BAD_NETWORK_NAME") > 0:
|
||||
self.logger.fail(f"MMCEXEC: Getting the output file failed - target has blocked access to the share: {self.__share} (but the command may have executed!)")
|
||||
break
|
||||
if str(e).find("STATUS_SHARING_VIOLATION") >= 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
|
||||
|
@ -263,7 +263,7 @@ class MMCEXEC:
|
|||
tries += 1
|
||||
else:
|
||||
self.logger.debug(str(e))
|
||||
|
||||
|
||||
if self.__outputBuffer:
|
||||
self.logger.debug(f"Deleting file {self.__share}\\{self.__output}")
|
||||
self.__smbconnection.deleteFile(self.__share, self.__output)
|
||||
self.__smbconnection.deleteFile(self.__share, self.__output)
|
||||
|
|
|
@ -1,101 +1,77 @@
|
|||
def proto_args(parser, std_parser, module_parser):
|
||||
smb_parser = parser.add_parser("smb", help="own stuff using SMB", 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("--port", type=int, choices={445, 139}, 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("--smb-server-port", default="445", help="specify a server port for SMB", type=int)
|
||||
smb_parser.add_argument("--gen-relay-list", metavar="OUTPUT_FILE",
|
||||
help="outputs all hosts that don't require SMB signing to the specified file")
|
||||
smb_parser.add_argument("--smb-timeout", help="SMB connection timeout, default 2 secondes", type=int, default=2)
|
||||
smb_parser.add_argument("--laps", dest="laps", metavar="LAPS", type=str, help="LAPS authentification",
|
||||
nargs="?", const="administrator")
|
||||
smb_parser = parser.add_parser("smb", help="own stuff using SMB", 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("--port", type=int, choices={445, 139}, 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("--smb-server-port", default="445", help="specify a server port for SMB", type=int)
|
||||
smb_parser.add_argument("--gen-relay-list", metavar="OUTPUT_FILE", help="outputs all hosts that don't require SMB signing to the specified file")
|
||||
smb_parser.add_argument("--smb-timeout", help="SMB connection timeout, default 2 secondes", type=int, default=2)
|
||||
smb_parser.add_argument("--laps", dest="laps", metavar="LAPS", type=str, help="LAPS authentification", nargs="?", const="administrator")
|
||||
|
||||
cgroup = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
|
||||
cgroup.add_argument("--sam", action="store_true", help="dump SAM hashes from target systems")
|
||||
cgroup.add_argument("--lsa", action="store_true", help="dump LSA secrets from target systems")
|
||||
cgroup.add_argument("--ntds", choices={"vss", "drsuapi"}, nargs="?", const="drsuapi",
|
||||
help="dump the NTDS.dit from target DCs using the specifed method\n(default: drsuapi)")
|
||||
cgroup.add_argument("--dpapi", choices={"cookies","nosystem"}, nargs="*",
|
||||
help="dump DPAPI secrets from target systems, can dump cookies if you add \"cookies\", will not dump SYSTEM dpapi if you add nosystem\n")
|
||||
# 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')
|
||||
cgroup = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
|
||||
cgroup.add_argument("--sam", action="store_true", help="dump SAM hashes from target systems")
|
||||
cgroup.add_argument("--lsa", action="store_true", help="dump LSA secrets from target systems")
|
||||
cgroup.add_argument("--ntds", choices={"vss", "drsuapi"}, nargs="?", const="drsuapi", help="dump the NTDS.dit from target DCs using the specifed method\n(default: drsuapi)")
|
||||
cgroup.add_argument("--dpapi", choices={"cookies", "nosystem"}, nargs="*", help='dump DPAPI secrets from target systems, can dump cookies if you add "cookies", will not dump SYSTEM dpapi if you add nosystem\n')
|
||||
# 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')
|
||||
|
||||
ngroup = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
|
||||
ngroup.add_argument("--mkfile", action="store",
|
||||
help="DPAPI option. File with masterkeys in form of {GUID}:SHA1")
|
||||
ngroup.add_argument("--pvk", action="store", help="DPAPI option. File with domain backupkey")
|
||||
ngroup.add_argument("--enabled", action="store_true", help="Only dump enabled targets from DC")
|
||||
ngroup.add_argument("--user", dest="userntds", type=str, help="Dump selected user from DC")
|
||||
ngroup = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
|
||||
ngroup.add_argument("--mkfile", action="store", help="DPAPI option. File with masterkeys in form of {GUID}:SHA1")
|
||||
ngroup.add_argument("--pvk", action="store", help="DPAPI option. File with domain backupkey")
|
||||
ngroup.add_argument("--enabled", action="store_true", help="Only dump enabled targets from DC")
|
||||
ngroup.add_argument("--user", dest="userntds", type=str, help="Dump selected user from DC")
|
||||
|
||||
egroup = smb_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating")
|
||||
egroup.add_argument("--shares", action="store_true", help="enumerate shares and access")
|
||||
egroup.add_argument("--no-write-check", action="store_true", help="Skip write check on shares (avoid leaving traces when missing delete permissions)")
|
||||
egroup = smb_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating")
|
||||
egroup.add_argument("--shares", action="store_true", help="enumerate shares and access")
|
||||
egroup.add_argument("--no-write-check", action="store_true", help="Skip write check on shares (avoid leaving traces when missing delete permissions)")
|
||||
|
||||
egroup.add_argument("--filter-shares", nargs="+",
|
||||
help="Filter share by access, option 'read' 'write' or 'read,write'")
|
||||
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-filter", action="store",
|
||||
help="only search for specific user, works with regex")
|
||||
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("--computers", nargs="?", const="", metavar="COMPUTER", help="enumerate computer users")
|
||||
egroup.add_argument("--local-groups", nargs="?", const="", metavar="GROUP",
|
||||
help="enumerate local groups, if a group is specified then its members are enumerated")
|
||||
egroup.add_argument("--pass-pol", action="store_true", help="dump password policy")
|
||||
egroup.add_argument("--rid-brute", nargs="?", type=int, const=4000, metavar="MAX_RID",
|
||||
help="enumerate users by bruteforcing RID's (default: 4000)")
|
||||
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)")
|
||||
egroup.add_argument("--filter-shares", nargs="+", help="Filter share by access, option 'read' 'write' or 'read,write'")
|
||||
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-filter", action="store", help="only search for specific user, works with regex")
|
||||
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("--computers", nargs="?", const="", metavar="COMPUTER", help="enumerate computer users")
|
||||
egroup.add_argument("--local-groups", nargs="?", const="", metavar="GROUP", help="enumerate local groups, if a group is specified then its members are enumerated")
|
||||
egroup.add_argument("--pass-pol", action="store_true", help="dump password policy")
|
||||
egroup.add_argument("--rid-brute", nargs="?", type=int, const=4000, metavar="MAX_RID", help="enumerate users by bruteforcing RID's (default: 4000)")
|
||||
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)")
|
||||
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")
|
||||
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)")
|
||||
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")
|
||||
|
||||
tgroup = smb_parser.add_argument_group("Files", "Options for put and get remote files")
|
||||
tgroup.add_argument("--put-file", nargs=2, metavar="FILE", help="Put a local file into remote target, ex: whoami.txt \\\\Windows\\\\Temp\\\\whoami.txt")
|
||||
tgroup.add_argument("--get-file", nargs=2, metavar="FILE", help="Get a remote file, ex: \\\\Windows\\\\Temp\\\\whoami.txt whoami.txt")
|
||||
tgroup.add_argument("--append-host", action="store_true", help="append the host to the get-file filename")
|
||||
tgroup = smb_parser.add_argument_group("Files", "Options for put and get remote files")
|
||||
tgroup.add_argument("--put-file", nargs=2, metavar="FILE", help="Put a local file into remote target, ex: whoami.txt \\\\Windows\\\\Temp\\\\whoami.txt")
|
||||
tgroup.add_argument("--get-file", nargs=2, metavar="FILE", help="Get a remote file, ex: \\\\Windows\\\\Temp\\\\whoami.txt whoami.txt")
|
||||
tgroup.add_argument("--append-host", action="store_true", help="append the host to the get-file filename")
|
||||
|
||||
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("--dcom-timeout", help="DCOM connection timeout, default is 5 secondes", type=int, default=5)
|
||||
cgroup.add_argument("--get-output-tries", help="Number of times atexec/smbexec/mmcexec tries to get results, default is 5", type=int, default=5)
|
||||
cgroup.add_argument("--codec", default="utf-8",
|
||||
help="Set encoding used (codec) from the target's output (default "
|
||||
"\"utf-8\"). If errors are detected, run chcp.com at the target, "
|
||||
"map the result with "
|
||||
"https://docs.python.org/3/library/codecs.html#standard-encodings and then execute "
|
||||
"again with --codec and the corresponding codec")
|
||||
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 CMD command")
|
||||
cegroup.add_argument("-X", metavar="PS_COMMAND", dest="ps_execute", help="execute the specified PowerShell command")
|
||||
psgroup = smb_parser.add_argument_group("Powershell Obfuscation", "Options for PowerShell script obfuscation")
|
||||
psgroup.add_argument("--obfs", action="store_true", help="Obfuscate PowerShell scripts")
|
||||
psgroup.add_argument('--amsi-bypass', nargs=1, metavar="FILE", help='File with a custom AMSI bypass')
|
||||
psgroup.add_argument("--clear-obfscripts", action="store_true", help="Clear all cached obfuscated PowerShell scripts")
|
||||
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("--dcom-timeout", help="DCOM connection timeout, default is 5 secondes", type=int, default=5)
|
||||
cgroup.add_argument("--get-output-tries", help="Number of times atexec/smbexec/mmcexec tries to get results, default is 5", type=int, default=5)
|
||||
cgroup.add_argument("--codec", default="utf-8", help="Set encoding used (codec) from the target's output (default " '"utf-8"). If errors are detected, run chcp.com at the target, ' "map the result with " "https://docs.python.org/3/library/codecs.html#standard-encodings and then execute " "again with --codec and the corresponding codec")
|
||||
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 CMD command")
|
||||
cegroup.add_argument("-X", metavar="PS_COMMAND", dest="ps_execute", help="execute the specified PowerShell command")
|
||||
psgroup = smb_parser.add_argument_group("Powershell Obfuscation", "Options for PowerShell script obfuscation")
|
||||
psgroup.add_argument("--obfs", action="store_true", help="Obfuscate PowerShell scripts")
|
||||
psgroup.add_argument("--amsi-bypass", nargs=1, metavar="FILE", help="File with a custom AMSI bypass")
|
||||
psgroup.add_argument("--clear-obfscripts", action="store_true", help="Clear all cached obfuscated PowerShell scripts")
|
||||
|
||||
return parser
|
||||
return parser
|
||||
|
|
|
@ -46,16 +46,7 @@ class SamrFunc:
|
|||
kerberos=self.doKerberos,
|
||||
aesKey=self.aesKey,
|
||||
)
|
||||
self.lsa_query = LSAQuery(
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
domain=self.domain,
|
||||
remote_name=self.addr,
|
||||
remote_host=self.addr,
|
||||
kerberos=self.doKerberos,
|
||||
aesKey=self.aesKey,
|
||||
logger=self.logger
|
||||
)
|
||||
self.lsa_query = LSAQuery(username=self.username, password=self.password, domain=self.domain, remote_name=self.addr, remote_host=self.addr, kerberos=self.doKerberos, aesKey=self.aesKey, logger=self.logger)
|
||||
|
||||
def get_builtin_groups(self):
|
||||
domains = self.samr_query.get_domains()
|
||||
|
@ -204,18 +195,7 @@ class SAMRQuery:
|
|||
|
||||
|
||||
class LSAQuery:
|
||||
def __init__(
|
||||
self,
|
||||
username="",
|
||||
password="",
|
||||
domain="",
|
||||
port=445,
|
||||
remote_name="",
|
||||
remote_host="",
|
||||
aesKey="",
|
||||
kerberos=None,
|
||||
logger=None
|
||||
):
|
||||
def __init__(self, username="", password="", domain="", port=445, remote_name="", remote_host="", aesKey="", kerberos=None, logger=None):
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__domain = domain
|
||||
|
|
|
@ -10,24 +10,7 @@ from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
|
|||
|
||||
|
||||
class SMBEXEC:
|
||||
def __init__(
|
||||
self,
|
||||
host,
|
||||
share_name,
|
||||
smbconnection,
|
||||
protocol,
|
||||
username="",
|
||||
password="",
|
||||
domain="",
|
||||
doKerberos=False,
|
||||
aesKey=None,
|
||||
kdcHost=None,
|
||||
hashes=None,
|
||||
share=None,
|
||||
port=445,
|
||||
logger=None,
|
||||
tries=None
|
||||
):
|
||||
def __init__(self, host, share_name, smbconnection, protocol, username="", password="", domain="", doKerberos=False, aesKey=None, kdcHost=None, hashes=None, share=None, port=445, logger=None, tries=None):
|
||||
self.__host = host
|
||||
self.__share_name = "C$"
|
||||
self.__port = port
|
||||
|
@ -127,7 +110,7 @@ class SMBEXEC:
|
|||
self.logger.debug("Command to execute: " + command)
|
||||
|
||||
self.logger.debug(f"Remote service {self.__serviceName} created.")
|
||||
|
||||
|
||||
try:
|
||||
resp = scmr.hRCreateServiceW(
|
||||
self.__scmr,
|
||||
|
@ -143,7 +126,7 @@ class SMBEXEC:
|
|||
self.logger.fail("SMBEXEC: Create services got blocked.")
|
||||
else:
|
||||
self.logger.fail(str(e))
|
||||
|
||||
|
||||
return self.__outputBuffer
|
||||
|
||||
try:
|
||||
|
@ -181,7 +164,7 @@ class SMBEXEC:
|
|||
tries += 1
|
||||
else:
|
||||
self.logger.debug(str(e))
|
||||
|
||||
|
||||
if self.__outputBuffer:
|
||||
self.logger.debug(f"Deleting file {self.__share}\\{self.__output}")
|
||||
self.__smbconnection.deleteFile(self.__share, self.__output)
|
||||
|
|
|
@ -12,23 +12,7 @@ from impacket.dcerpc.v5.dtypes import NULL
|
|||
|
||||
|
||||
class WMIEXEC:
|
||||
def __init__(
|
||||
self,
|
||||
target,
|
||||
share_name,
|
||||
username,
|
||||
password,
|
||||
domain,
|
||||
smbconnection,
|
||||
doKerberos=False,
|
||||
aesKey=None,
|
||||
kdcHost=None,
|
||||
hashes=None,
|
||||
share=None,
|
||||
logger=None,
|
||||
timeout=None,
|
||||
tries=None
|
||||
):
|
||||
def __init__(self, target, share_name, username, password, domain, smbconnection, doKerberos=False, aesKey=None, kdcHost=None, hashes=None, share=None, logger=None, timeout=None, tries=None):
|
||||
self.__target = target
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
|
@ -73,13 +57,13 @@ class WMIEXEC:
|
|||
kdcHost=self.__kdcHost,
|
||||
)
|
||||
iInterface = self.__dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
|
||||
flag, self.__stringBinding = dcom_FirewallChecker(iInterface, self.__timeout)
|
||||
flag, self.__stringBinding = dcom_FirewallChecker(iInterface, self.__timeout)
|
||||
if not flag or not self.__stringBinding:
|
||||
error_msg = f'WMIEXEC: Dcom initialization failed on connection with stringbinding: "{self.__stringBinding}", please increase the timeout with the option "--dcom-timeout". If it\'s still failing maybe something is blocking the RPC connection, try another exec method'
|
||||
|
||||
|
||||
if not self.__stringBinding:
|
||||
error_msg = "WMIEXEC: Dcom initialization failed: can't get target stringbinding, maybe cause by IPv6 or any other issues, please check your target again"
|
||||
|
||||
|
||||
self.logger.fail(error_msg) if not flag else self.logger.debug(error_msg)
|
||||
# Make it force break function
|
||||
self.__dcom.disconnect()
|
||||
|
@ -156,7 +140,7 @@ class WMIEXEC:
|
|||
if self.__retOutput is False:
|
||||
self.__outputBuffer = ""
|
||||
return
|
||||
|
||||
|
||||
tries = 1
|
||||
while True:
|
||||
try:
|
||||
|
@ -167,7 +151,7 @@ class WMIEXEC:
|
|||
if tries >= self.__tries:
|
||||
self.logger.fail("WMIEXEC: Could not retrieve output file, it may have been detected by AV. If it is still failing, try the 'wmi' protocol or another exec method")
|
||||
break
|
||||
if str(e).find("STATUS_BAD_NETWORK_NAME") >0 :
|
||||
if str(e).find("STATUS_BAD_NETWORK_NAME") > 0:
|
||||
self.logger.fail(f"SMB connection: target has blocked {self.__share} access (maybe command executed!)")
|
||||
break
|
||||
if str(e).find("STATUS_SHARING_VIOLATION") >= 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
|
||||
|
@ -179,4 +163,4 @@ class WMIEXEC:
|
|||
|
||||
if self.__outputBuffer:
|
||||
self.logger.debug(f"Deleting file {self.__share}\\{self.__output}")
|
||||
self.__smbconnection.deleteFile(self.__share, self.__output)
|
||||
self.__smbconnection.deleteFile(self.__share, self.__output)
|
||||
|
|
|
@ -42,41 +42,51 @@ class database:
|
|||
|
||||
@staticmethod
|
||||
def db_schema(db_conn):
|
||||
db_conn.execute("""CREATE TABLE "credentials" (
|
||||
db_conn.execute(
|
||||
"""CREATE TABLE "credentials" (
|
||||
"id" integer PRIMARY KEY,
|
||||
"username" text,
|
||||
"password" text,
|
||||
"credtype" text
|
||||
)""")
|
||||
db_conn.execute("""CREATE TABLE "hosts" (
|
||||
)"""
|
||||
)
|
||||
db_conn.execute(
|
||||
"""CREATE TABLE "hosts" (
|
||||
"id" integer PRIMARY KEY,
|
||||
"host" text,
|
||||
"port" integer,
|
||||
"banner" text,
|
||||
"os" text
|
||||
)""")
|
||||
db_conn.execute("""CREATE TABLE "loggedin_relations" (
|
||||
)"""
|
||||
)
|
||||
db_conn.execute(
|
||||
"""CREATE TABLE "loggedin_relations" (
|
||||
"id" integer PRIMARY KEY,
|
||||
"credid" integer,
|
||||
"hostid" integer,
|
||||
"shell" boolean,
|
||||
FOREIGN KEY(credid) REFERENCES credentials(id),
|
||||
FOREIGN KEY(hostid) REFERENCES hosts(id)
|
||||
)""")
|
||||
)"""
|
||||
)
|
||||
# "admin" access with SSH means we have root access, which implies shell access since we run commands to check
|
||||
db_conn.execute("""CREATE TABLE "admin_relations" (
|
||||
db_conn.execute(
|
||||
"""CREATE TABLE "admin_relations" (
|
||||
"id" integer PRIMARY KEY,
|
||||
"credid" integer,
|
||||
"hostid" integer,
|
||||
FOREIGN KEY(credid) REFERENCES credentials(id),
|
||||
FOREIGN KEY(hostid) REFERENCES hosts(id)
|
||||
)""")
|
||||
db_conn.execute("""CREATE TABLE "keys" (
|
||||
)"""
|
||||
)
|
||||
db_conn.execute(
|
||||
"""CREATE TABLE "keys" (
|
||||
"id" integer PRIMARY KEY,
|
||||
"credid" integer,
|
||||
"data" text,
|
||||
FOREIGN KEY(credid) REFERENCES credentials(id)
|
||||
)""")
|
||||
)"""
|
||||
)
|
||||
|
||||
def reflect_tables(self):
|
||||
with self.db_engine.connect():
|
||||
|
|
|
@ -18,6 +18,7 @@ from nxc.helpers.bloodhound import add_user_bh
|
|||
from nxc.protocols.ldap.laps import LDAPConnect, LAPSv2Extract
|
||||
from nxc.logger import NXCAdapter
|
||||
|
||||
|
||||
class winrm(connection):
|
||||
def __init__(self, args, db, host):
|
||||
self.domain = None
|
||||
|
@ -141,22 +142,16 @@ class winrm(connection):
|
|||
values = {str(attr["type"]).lower(): attr["vals"][0] for attr in host["attributes"]}
|
||||
if "mslaps-encryptedpassword" in values:
|
||||
from json import loads
|
||||
|
||||
msMCSAdmPwd = values["mslaps-encryptedpassword"]
|
||||
d = LAPSv2Extract(
|
||||
bytes(msMCSAdmPwd),
|
||||
username[0] if username else "",
|
||||
password[0] if password else "",
|
||||
domain,
|
||||
ntlm_hash[0] if ntlm_hash else "",
|
||||
self.args.kerberos,
|
||||
self.args.kdcHost,
|
||||
339)
|
||||
d = LAPSv2Extract(bytes(msMCSAdmPwd), username[0] if username else "", password[0] if password else "", domain, ntlm_hash[0] if ntlm_hash else "", self.args.kerberos, self.args.kdcHost, 339)
|
||||
data = d.run()
|
||||
r = loads(data)
|
||||
msMCSAdmPwd = r["p"]
|
||||
username_laps = r["n"]
|
||||
elif "mslaps-password" in values:
|
||||
from json import loads
|
||||
|
||||
r = loads(str(values["mslaps-password"]))
|
||||
msMCSAdmPwd = r["p"]
|
||||
username_laps = r["n"]
|
||||
|
@ -224,7 +219,6 @@ class winrm(connection):
|
|||
|
||||
def plaintext_login(self, domain, username, password):
|
||||
try:
|
||||
|
||||
# log.addFilter(SuppressFilter())
|
||||
if not self.args.laps:
|
||||
self.password = password
|
||||
|
@ -331,7 +325,6 @@ class winrm(connection):
|
|||
for line in buf:
|
||||
self.logger.highlight(line.strip())
|
||||
|
||||
|
||||
def ps_execute(self, payload=None, get_output=False):
|
||||
r = self.conn.execute_ps(self.args.ps_execute)
|
||||
self.logger.success("Executed command")
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from argparse import _StoreTrueAction
|
||||
|
||||
|
||||
def proto_args(parser, std_parser, module_parser):
|
||||
winrm_parser = parser.add_parser("winrm", help="own stuff using WINRM", parents=[std_parser, module_parser])
|
||||
winrm_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
|
||||
|
@ -8,7 +9,7 @@ def proto_args(parser, std_parser, module_parser):
|
|||
winrm_parser.add_argument("--ignore-ssl-cert", action="store_true", help="Ignore Certificate Verification")
|
||||
winrm_parser.add_argument("--laps", dest="laps", metavar="LAPS", type=str, help="LAPS authentification", nargs="?", const="administrator")
|
||||
winrm_parser.add_argument("--http-timeout", dest="http_timeout", type=int, default=10, help="HTTP timeout for WinRM connections")
|
||||
no_smb_arg = winrm_parser.add_argument("--no-smb", action=get_conditional_action(_StoreTrueAction), make_required=[], help='No smb connection')
|
||||
no_smb_arg = winrm_parser.add_argument("--no-smb", action=get_conditional_action(_StoreTrueAction), make_required=[], help="No smb connection")
|
||||
|
||||
dgroup = winrm_parser.add_mutually_exclusive_group()
|
||||
domain_arg = dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, default=None, help="domain to authenticate to")
|
||||
|
@ -21,22 +22,18 @@ def proto_args(parser, std_parser, module_parser):
|
|||
cegroup.add_argument("--lsa", action="store_true", help="dump LSA secrets from target systems")
|
||||
|
||||
cgroup = winrm_parser.add_argument_group("Command Execution", "Options for executing commands")
|
||||
cgroup.add_argument("--codec", default="utf-8",
|
||||
help="Set encoding used (codec) from the target's output (default "
|
||||
"\"utf-8\"). If errors are detected, run chcp.com at the target, "
|
||||
"map the result with "
|
||||
"https://docs.python.org/3/library/codecs.html#standard-encodings and then execute "
|
||||
"again with --codec and the corresponding codec")
|
||||
cgroup.add_argument("--codec", default="utf-8", help="Set encoding used (codec) from the target's output (default " '"utf-8"). If errors are detected, run chcp.com at the target, ' "map the result with " "https://docs.python.org/3/library/codecs.html#standard-encodings and then execute " "again with --codec and the corresponding codec")
|
||||
cgroup.add_argument("--no-output", action="store_true", help="do not retrieve command output")
|
||||
cgroup.add_argument("-x", metavar="COMMAND", dest="execute", help="execute the specified command")
|
||||
cgroup.add_argument("-X", metavar="PS_COMMAND", dest="ps_execute", help="execute the specified PowerShell command")
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def get_conditional_action(baseAction):
|
||||
class ConditionalAction(baseAction):
|
||||
def __init__(self, option_strings, dest, **kwargs):
|
||||
x = kwargs.pop('make_required', [])
|
||||
x = kwargs.pop("make_required", [])
|
||||
super(ConditionalAction, self).__init__(option_strings, dest, **kwargs)
|
||||
self.make_required = x
|
||||
|
||||
|
@ -45,4 +42,4 @@ def get_conditional_action(baseAction):
|
|||
x.required = True
|
||||
super(ConditionalAction, self).__call__(parser, namespace, values, option_string)
|
||||
|
||||
return ConditionalAction
|
||||
return ConditionalAction
|
||||
|
|
|
@ -17,50 +17,33 @@ from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_
|
|||
from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
||||
from impacket.dcerpc.v5.dcom.wmi import CLSID_WbemLevel1Login, IID_IWbemLevel1Login, IWbemLevel1Login
|
||||
|
||||
MSRPC_UUID_PORTMAP = uuidtup_to_bin(('E1AF8308-5D1F-11C9-91A4-08002B14A0FA', '3.0'))
|
||||
MSRPC_UUID_PORTMAP = uuidtup_to_bin(("E1AF8308-5D1F-11C9-91A4-08002B14A0FA", "3.0"))
|
||||
|
||||
|
||||
class wmi(connection):
|
||||
|
||||
def __init__(self, args, db, host):
|
||||
self.domain = None
|
||||
self.hash = ''
|
||||
self.lmhash = ''
|
||||
self.nthash = ''
|
||||
self.fqdn = ''
|
||||
self.remoteName = ''
|
||||
self.hash = ""
|
||||
self.lmhash = ""
|
||||
self.nthash = ""
|
||||
self.fqdn = ""
|
||||
self.remoteName = ""
|
||||
self.server_os = None
|
||||
self.doKerberos = False
|
||||
self.stringBinding = None
|
||||
# From: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/18d8fbe8-a967-4f1c-ae50-99ca8e491d2d
|
||||
self.rpc_error_status = {
|
||||
"0000052F" : "STATUS_ACCOUNT_RESTRICTION",
|
||||
"00000533" : "STATUS_ACCOUNT_DISABLED",
|
||||
"00000775" : "STATUS_ACCOUNT_LOCKED_OUT",
|
||||
"00000701" : "STATUS_ACCOUNT_EXPIRED",
|
||||
"00000532" : "STATUS_PASSWORD_EXPIRED",
|
||||
"00000530" : "STATUS_INVALID_LOGON_HOURS",
|
||||
"00000531" : "STATUS_INVALID_WORKSTATION",
|
||||
"00000569" : "STATUS_LOGON_TYPE_NOT_GRANTED",
|
||||
"00000773" : "STATUS_PASSWORD_MUST_CHANGE",
|
||||
"00000005" : "STATUS_ACCESS_DENIED",
|
||||
"0000052E" : "STATUS_LOGON_FAILURE",
|
||||
"0000052B" : "STATUS_WRONG_PASSWORD",
|
||||
"00000721" : "RPC_S_SEC_PKG_ERROR"
|
||||
}
|
||||
self.rpc_error_status = {"0000052F": "STATUS_ACCOUNT_RESTRICTION", "00000533": "STATUS_ACCOUNT_DISABLED", "00000775": "STATUS_ACCOUNT_LOCKED_OUT", "00000701": "STATUS_ACCOUNT_EXPIRED", "00000532": "STATUS_PASSWORD_EXPIRED", "00000530": "STATUS_INVALID_LOGON_HOURS", "00000531": "STATUS_INVALID_WORKSTATION", "00000569": "STATUS_LOGON_TYPE_NOT_GRANTED", "00000773": "STATUS_PASSWORD_MUST_CHANGE", "00000005": "STATUS_ACCESS_DENIED", "0000052E": "STATUS_LOGON_FAILURE", "0000052B": "STATUS_WRONG_PASSWORD", "00000721": "RPC_S_SEC_PKG_ERROR"}
|
||||
|
||||
connection.__init__(self, args, db, host)
|
||||
|
||||
def proto_logger(self):
|
||||
self.logger = NXCAdapter(extra={'protocol': 'WMI',
|
||||
'host': self.host,
|
||||
'port': self.args.port,
|
||||
'hostname': self.hostname})
|
||||
|
||||
self.logger = NXCAdapter(extra={"protocol": "WMI", "host": self.host, "port": self.args.port, "hostname": self.hostname})
|
||||
|
||||
def create_conn_obj(self):
|
||||
if self.remoteName == '':
|
||||
if self.remoteName == "":
|
||||
self.remoteName = self.host
|
||||
try:
|
||||
rpctansport = transport.DCERPCTransportFactory(r'ncacn_ip_tcp:{0}[{1}]'.format(self.remoteName, str(self.args.port)))
|
||||
rpctansport = transport.DCERPCTransportFactory(r"ncacn_ip_tcp:{0}[{1}]".format(self.remoteName, str(self.args.port)))
|
||||
rpctansport.set_credentials(username="", password="", domain="", lmhash="", nthash="", aesKey="")
|
||||
rpctansport.setRemoteHost(self.host)
|
||||
rpctansport.set_connect_timeout(self.args.rpc_timeout)
|
||||
|
@ -75,36 +58,36 @@ class wmi(connection):
|
|||
else:
|
||||
self.conn = rpctansport
|
||||
return True
|
||||
|
||||
|
||||
def enum_host_info(self):
|
||||
# All code pick from DumpNTLNInfo.py
|
||||
# https://github.com/fortra/impacket/blob/master/examples/DumpNTLMInfo.py
|
||||
ntlmChallenge = None
|
||||
|
||||
|
||||
bind = MSRPCBind()
|
||||
item = CtxItem()
|
||||
item['AbstractSyntax'] = epm.MSRPC_UUID_PORTMAP
|
||||
item['TransferSyntax'] = uuidtup_to_bin(('8a885d04-1ceb-11c9-9fe8-08002b104860', '2.0'))
|
||||
item['ContextID'] = 0
|
||||
item['TransItems'] = 1
|
||||
item["AbstractSyntax"] = epm.MSRPC_UUID_PORTMAP
|
||||
item["TransferSyntax"] = uuidtup_to_bin(("8a885d04-1ceb-11c9-9fe8-08002b104860", "2.0"))
|
||||
item["ContextID"] = 0
|
||||
item["TransItems"] = 1
|
||||
bind.addCtxItem(item)
|
||||
|
||||
packet = MSRPCHeader()
|
||||
packet['type'] = MSRPC_BIND
|
||||
packet['pduData'] = bind.getData()
|
||||
packet['call_id'] = 1
|
||||
packet["type"] = MSRPC_BIND
|
||||
packet["pduData"] = bind.getData()
|
||||
packet["call_id"] = 1
|
||||
|
||||
auth = ntlm.getNTLMSSPType1('', '', signingRequired=True, use_ntlmv2=True)
|
||||
auth = ntlm.getNTLMSSPType1("", "", signingRequired=True, use_ntlmv2=True)
|
||||
sec_trailer = SEC_TRAILER()
|
||||
sec_trailer['auth_type'] = RPC_C_AUTHN_WINNT
|
||||
sec_trailer['auth_level'] = RPC_C_AUTHN_LEVEL_PKT_INTEGRITY
|
||||
sec_trailer['auth_ctx_id'] = 0 + 79231
|
||||
sec_trailer["auth_type"] = RPC_C_AUTHN_WINNT
|
||||
sec_trailer["auth_level"] = RPC_C_AUTHN_LEVEL_PKT_INTEGRITY
|
||||
sec_trailer["auth_ctx_id"] = 0 + 79231
|
||||
pad = (4 - (len(packet.get_packet()) % 4)) % 4
|
||||
if pad != 0:
|
||||
packet['pduData'] += b'\xFF'*pad
|
||||
sec_trailer['auth_pad_len']=pad
|
||||
packet['sec_trailer'] = sec_trailer
|
||||
packet['auth_data'] = auth
|
||||
packet["pduData"] += b"\xFF" * pad
|
||||
sec_trailer["auth_pad_len"] = pad
|
||||
packet["sec_trailer"] = sec_trailer
|
||||
packet["auth_data"] = auth
|
||||
|
||||
try:
|
||||
self.conn.connect()
|
||||
|
@ -117,29 +100,29 @@ class wmi(connection):
|
|||
response = MSRPCHeader(buffer)
|
||||
bindResp = MSRPCBindAck(response.getData())
|
||||
|
||||
ntlmChallenge = ntlm.NTLMAuthChallenge(bindResp['auth_data'])
|
||||
ntlmChallenge = ntlm.NTLMAuthChallenge(bindResp["auth_data"])
|
||||
|
||||
if ntlmChallenge['TargetInfoFields_len'] > 0:
|
||||
av_pairs = ntlm.AV_PAIRS(ntlmChallenge['TargetInfoFields'][:ntlmChallenge['TargetInfoFields_len']])
|
||||
if ntlmChallenge["TargetInfoFields_len"] > 0:
|
||||
av_pairs = ntlm.AV_PAIRS(ntlmChallenge["TargetInfoFields"][: ntlmChallenge["TargetInfoFields_len"]])
|
||||
if av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1] is not None:
|
||||
try:
|
||||
self.hostname = av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1].decode('utf-16le')
|
||||
self.hostname = av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1].decode("utf-16le")
|
||||
except:
|
||||
self.hostname = self.host
|
||||
if av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1] is not None:
|
||||
try:
|
||||
self.domain = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1].decode('utf-16le')
|
||||
self.domain = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1].decode("utf-16le")
|
||||
except:
|
||||
self.domain = self.args.domain
|
||||
if av_pairs[ntlm.NTLMSSP_AV_DNS_HOSTNAME][1] is not None:
|
||||
try:
|
||||
self.fqdn = av_pairs[ntlm.NTLMSSP_AV_DNS_HOSTNAME][1].decode('utf-16le')
|
||||
self.fqdn = av_pairs[ntlm.NTLMSSP_AV_DNS_HOSTNAME][1].decode("utf-16le")
|
||||
except:
|
||||
pass
|
||||
if 'Version' in ntlmChallenge.fields:
|
||||
version = ntlmChallenge['Version']
|
||||
if "Version" in ntlmChallenge.fields:
|
||||
version = ntlmChallenge["Version"]
|
||||
if len(version) >= 4:
|
||||
self.server_os = "Windows NT %d.%d Build %d" % (indexbytes(version,0), indexbytes(version,1), struct.unpack('<H',version[2:4])[0])
|
||||
self.server_os = "Windows NT %d.%d Build %d" % (indexbytes(version, 0), indexbytes(version, 1), struct.unpack("<H", version[2:4])[0])
|
||||
else:
|
||||
self.hostname = self.host
|
||||
|
||||
|
@ -154,16 +137,14 @@ class wmi(connection):
|
|||
self.output_filename = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-"))
|
||||
|
||||
def print_host_info(self):
|
||||
self.logger.extra['protocol'] = "RPC"
|
||||
self.logger.extra['port'] = "135"
|
||||
self.logger.display(u"{} (name:{}) (domain:{})".format(self.server_os,
|
||||
self.hostname,
|
||||
self.domain))
|
||||
self.logger.extra["protocol"] = "RPC"
|
||||
self.logger.extra["port"] = "135"
|
||||
self.logger.display("{} (name:{}) (domain:{})".format(self.server_os, self.hostname, self.domain))
|
||||
return True
|
||||
|
||||
def check_if_admin(self):
|
||||
try:
|
||||
dcom = DCOMConnection(self.conn.getRemoteName(), self.username, self.password, self.domain, self.lmhash, self.nthash, oxidResolver=True, doKerberos=self.doKerberos ,kdcHost=self.kdcHost, aesKey=self.aesKey)
|
||||
dcom = DCOMConnection(self.conn.getRemoteName(), self.username, self.password, self.domain, self.lmhash, self.nthash, oxidResolver=True, doKerberos=self.doKerberos, kdcHost=self.kdcHost, aesKey=self.aesKey)
|
||||
iInterface = dcom.CoCreateInstanceEx(CLSID_WbemLevel1Login, IID_IWbemLevel1Login)
|
||||
flag, self.stringBinding = dcom_FirewallChecker(iInterface, self.args.rpc_timeout)
|
||||
except Exception as e:
|
||||
|
@ -176,15 +157,15 @@ class wmi(connection):
|
|||
if not flag or not self.stringBinding:
|
||||
dcom.disconnect()
|
||||
error_msg = f'Check admin error: dcom initialization failed with stringbinding: "{self.stringBinding}", please try "--rpc-timeout" option. (probably is admin)'
|
||||
|
||||
|
||||
if not self.stringBinding:
|
||||
error_msg = "Check admin error: dcom initialization failed: can't get target stringbinding, maybe cause by IPv6 or any other issues, please check your target again"
|
||||
|
||||
|
||||
self.logger.fail(error_msg) if not flag else self.logger.debug(error_msg)
|
||||
else:
|
||||
try:
|
||||
iWbemLevel1Login = IWbemLevel1Login(iInterface)
|
||||
iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
|
||||
iWbemServices = iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL)
|
||||
except Exception as e:
|
||||
dcom.disconnect()
|
||||
|
||||
|
@ -192,37 +173,37 @@ class wmi(connection):
|
|||
self.logger.fail(str(e))
|
||||
else:
|
||||
dcom.disconnect()
|
||||
self.logger.extra['protocol'] = "WMI"
|
||||
self.logger.extra["protocol"] = "WMI"
|
||||
self.admin_privs = True
|
||||
return
|
||||
|
||||
def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False):
|
||||
logging.getLogger("impacket").disabled = True
|
||||
lmhash = ''
|
||||
nthash = ''
|
||||
lmhash = ""
|
||||
nthash = ""
|
||||
self.password = password
|
||||
self.username = username
|
||||
self.domain = domain
|
||||
self.remoteName = self.fqdn
|
||||
self.create_conn_obj()
|
||||
|
||||
|
||||
if password == "":
|
||||
if ntlm_hash.find(':') != -1:
|
||||
lmhash, nthash = ntlm_hash.split(':')
|
||||
if ntlm_hash.find(":") != -1:
|
||||
lmhash, nthash = ntlm_hash.split(":")
|
||||
else:
|
||||
nthash = ntlm_hash
|
||||
self.nthash = nthash
|
||||
self.lmhash = lmhash
|
||||
|
||||
|
||||
if not all("" == s for s in [nthash, password, aesKey]):
|
||||
kerb_pass = next(s for s in [nthash, password, aesKey] if s)
|
||||
else:
|
||||
kerb_pass = ""
|
||||
|
||||
|
||||
if useCache:
|
||||
if kerb_pass == "":
|
||||
ccache = CCache.loadFile(os.getenv("KRB5CCNAME"))
|
||||
username = ccache.credentials[0].header['client'].prettyPrint().decode().split("@")[0]
|
||||
username = ccache.credentials[0].header["client"].prettyPrint().decode().split("@")[0]
|
||||
self.username = username
|
||||
|
||||
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
|
||||
|
@ -255,12 +236,12 @@ class wmi(connection):
|
|||
# Get data from rpc connection if got vaild creds
|
||||
entry_handle = epm.ept_lookup_handle_t()
|
||||
request = epm.ept_lookup()
|
||||
request['inquiry_type'] = 0x0
|
||||
request['object'] = NULL
|
||||
request['Ifid'] = NULL
|
||||
request['vers_option'] = 0x1
|
||||
request['entry_handle'] = entry_handle
|
||||
request['max_ents'] = 1
|
||||
request["inquiry_type"] = 0x0
|
||||
request["object"] = NULL
|
||||
request["Ifid"] = NULL
|
||||
request["vers_option"] = 0x1
|
||||
request["entry_handle"] = entry_handle
|
||||
request["max_ents"] = 1
|
||||
dce.request(request)
|
||||
except Exception as e:
|
||||
dce.disconnect()
|
||||
|
@ -301,14 +282,14 @@ class wmi(connection):
|
|||
# Get data from rpc connection if got vaild creds
|
||||
entry_handle = epm.ept_lookup_handle_t()
|
||||
request = epm.ept_lookup()
|
||||
request['inquiry_type'] = 0x0
|
||||
request['object'] = NULL
|
||||
request['Ifid'] = NULL
|
||||
request['vers_option'] = 0x1
|
||||
request['entry_handle'] = entry_handle
|
||||
request['max_ents'] = 1
|
||||
request["inquiry_type"] = 0x0
|
||||
request["object"] = NULL
|
||||
request["Ifid"] = NULL
|
||||
request["vers_option"] = 0x1
|
||||
request["entry_handle"] = entry_handle
|
||||
request["max_ents"] = 1
|
||||
dce.request(request)
|
||||
except Exception as e:
|
||||
except Exception as e:
|
||||
dce.disconnect()
|
||||
error_msg = str(e).lower()
|
||||
self.logger.debug(error_msg)
|
||||
|
@ -325,20 +306,20 @@ class wmi(connection):
|
|||
out += "(Default allow anonymous login)"
|
||||
self.logger.success(out)
|
||||
return True
|
||||
|
||||
|
||||
def hash_login(self, domain, username, ntlm_hash):
|
||||
self.username = username
|
||||
lmhash = ''
|
||||
nthash = ''
|
||||
if ntlm_hash.find(':') != -1:
|
||||
self.lmhash, self.nthash = ntlm_hash.split(':')
|
||||
lmhash = ""
|
||||
nthash = ""
|
||||
if ntlm_hash.find(":") != -1:
|
||||
self.lmhash, self.nthash = ntlm_hash.split(":")
|
||||
else:
|
||||
lmhash = ''
|
||||
lmhash = ""
|
||||
nthash = ntlm_hash
|
||||
|
||||
|
||||
self.nthash = nthash
|
||||
self.lmhash = lmhash
|
||||
|
||||
|
||||
try:
|
||||
self.conn.set_credentials(username=self.username, password=self.password, domain=self.domain, lmhash=lmhash, nthash=nthash)
|
||||
dce = self.conn.get_dce_rpc()
|
||||
|
@ -356,14 +337,14 @@ class wmi(connection):
|
|||
# Get data from rpc connection if got vaild creds
|
||||
entry_handle = epm.ept_lookup_handle_t()
|
||||
request = epm.ept_lookup()
|
||||
request['inquiry_type'] = 0x0
|
||||
request['object'] = NULL
|
||||
request['Ifid'] = NULL
|
||||
request['vers_option'] = 0x1
|
||||
request['entry_handle'] = entry_handle
|
||||
request['max_ents'] = 1
|
||||
request["inquiry_type"] = 0x0
|
||||
request["object"] = NULL
|
||||
request["Ifid"] = NULL
|
||||
request["vers_option"] = 0x1
|
||||
request["entry_handle"] = entry_handle
|
||||
request["max_ents"] = 1
|
||||
dce.request(request)
|
||||
except Exception as e:
|
||||
except Exception as e:
|
||||
dce.disconnect()
|
||||
error_msg = str(e).lower()
|
||||
self.logger.debug(error_msg)
|
||||
|
@ -381,39 +362,39 @@ class wmi(connection):
|
|||
self.logger.success(out)
|
||||
return True
|
||||
|
||||
# It's very complex to use wmi from rpctansport "convert" to dcom, so let we use dcom directly.
|
||||
# It's very complex to use wmi from rpctansport "convert" to dcom, so let we use dcom directly.
|
||||
@requires_admin
|
||||
def wmi(self, WQL=None, namespace=None):
|
||||
records = []
|
||||
if not WQL:
|
||||
WQL = self.args.wmi.strip('\n')
|
||||
WQL = self.args.wmi.strip("\n")
|
||||
|
||||
if not namespace:
|
||||
namespace = self.args.wmi_namespace
|
||||
|
||||
try:
|
||||
dcom = DCOMConnection(self.conn.getRemoteName(), self.username, self.password, self.domain, self.lmhash, self.nthash, oxidResolver=True, doKerberos=self.doKerberos ,kdcHost=self.kdcHost, aesKey=self.aesKey)
|
||||
iInterface = dcom.CoCreateInstanceEx(CLSID_WbemLevel1Login,IID_IWbemLevel1Login)
|
||||
dcom = DCOMConnection(self.conn.getRemoteName(), self.username, self.password, self.domain, self.lmhash, self.nthash, oxidResolver=True, doKerberos=self.doKerberos, kdcHost=self.kdcHost, aesKey=self.aesKey)
|
||||
iInterface = dcom.CoCreateInstanceEx(CLSID_WbemLevel1Login, IID_IWbemLevel1Login)
|
||||
iWbemLevel1Login = IWbemLevel1Login(iInterface)
|
||||
iWbemServices= iWbemLevel1Login.NTLMLogin(namespace , NULL, NULL)
|
||||
iWbemServices = iWbemLevel1Login.NTLMLogin(namespace, NULL, NULL)
|
||||
iWbemLevel1Login.RemRelease()
|
||||
iEnumWbemClassObject = iWbemServices.ExecQuery(WQL)
|
||||
except Exception as e:
|
||||
dcom.disconnect()
|
||||
self.logger.debug(str(e))
|
||||
self.logger.fail('Execute WQL error: {}'.format(str(e)))
|
||||
self.logger.fail("Execute WQL error: {}".format(str(e)))
|
||||
return False
|
||||
else:
|
||||
self.logger.info(f"Executing WQL syntax: {WQL}")
|
||||
while True:
|
||||
try:
|
||||
wmi_results = iEnumWbemClassObject.Next(0xffffffff, 1)[0]
|
||||
wmi_results = iEnumWbemClassObject.Next(0xFFFFFFFF, 1)[0]
|
||||
record = wmi_results.getProperties()
|
||||
records.append(record)
|
||||
for k,v in record.items():
|
||||
for k, v in record.items():
|
||||
self.logger.highlight(f"{k} => {v['value']}")
|
||||
except Exception as e:
|
||||
if str(e).find('S_FALSE') < 0:
|
||||
if str(e).find("S_FALSE") < 0:
|
||||
self.logger.debug(str(e))
|
||||
else:
|
||||
break
|
||||
|
@ -434,7 +415,7 @@ class wmi(connection):
|
|||
if "systeminfo" in command and self.args.exec_timeout < 10:
|
||||
self.logger.fail("Execute 'systeminfo' must set the interval time higher than 10 seconds")
|
||||
return False
|
||||
|
||||
|
||||
if self.server_os is not None and "NT 5" in self.server_os:
|
||||
self.logger.fail("Execute command failed, not support current server os (version < NT 6)")
|
||||
return False
|
||||
|
@ -442,7 +423,7 @@ class wmi(connection):
|
|||
if self.args.exec_method == "wmiexec":
|
||||
exec_method = wmiexec.WMIEXEC(self.conn.getRemoteName(), self.username, self.password, self.domain, self.lmhash, self.nthash, self.doKerberos, self.kdcHost, self.aesKey, self.logger, self.args.exec_timeout, self.args.codec)
|
||||
output = exec_method.execute(command, get_output)
|
||||
|
||||
|
||||
elif self.args.exec_method == "wmiexec-event":
|
||||
exec_method = wmiexec_event.WMIEXEC_EVENT(self.conn.getRemoteName(), self.username, self.password, self.domain, self.lmhash, self.nthash, self.doKerberos, self.kdcHost, self.aesKey, self.logger, self.args.exec_timeout, self.args.codec)
|
||||
output = exec_method.execute(command, get_output)
|
||||
|
@ -456,4 +437,4 @@ class wmi(connection):
|
|||
buf = StringIO(output).readlines()
|
||||
for line in buf:
|
||||
self.logger.highlight(line.strip())
|
||||
return output
|
||||
return output
|
||||
|
|
|
@ -1,40 +1,31 @@
|
|||
|
||||
def proto_args(parser, std_parser, module_parser):
|
||||
wmi_parser = parser.add_parser('wmi', help="own stuff using WMI", parents=[std_parser, module_parser], conflict_handler='resolve')
|
||||
wmi_parser.add_argument("-H", '--hash', metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes')
|
||||
wmi_parser = parser.add_parser("wmi", help="own stuff using WMI", parents=[std_parser, module_parser], conflict_handler="resolve")
|
||||
wmi_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
|
||||
wmi_parser.add_argument("--port", type=int, choices={135}, default=135, help="WMI port (default: 135)")
|
||||
wmi_parser.add_argument("--rpc-timeout", help="RPC/DCOM(WMI) connection timeout, default is %(default)s secondes", type=int, default=2)
|
||||
|
||||
# For domain options
|
||||
dgroup = wmi_parser.add_mutually_exclusive_group()
|
||||
dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', default=None, type=str, help="Domain to authenticate to")
|
||||
dgroup.add_argument("--local-auth", action='store_true', help='Authenticate locally to each target')
|
||||
dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", default=None, type=str, help="Domain to authenticate to")
|
||||
dgroup.add_argument("--local-auth", action="store_true", help="Authenticate locally to each target")
|
||||
|
||||
egroup = wmi_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating")
|
||||
egroup.add_argument("--wmi", metavar='QUERY', dest='wmi',type=str, help='Issues the specified WMI query')
|
||||
egroup.add_argument("--wmi-namespace", metavar='NAMESPACE', type=str, default='root\\cimv2', help='WMI Namespace (default: root\\cimv2)')
|
||||
egroup.add_argument("--wmi", metavar="QUERY", dest="wmi", type=str, help="Issues the specified WMI query")
|
||||
egroup.add_argument("--wmi-namespace", metavar="NAMESPACE", type=str, default="root\\cimv2", help="WMI Namespace (default: root\\cimv2)")
|
||||
|
||||
cgroup = wmi_parser.add_argument_group("Command Execution", "Options for executing commands")
|
||||
cgroup.add_argument("--no-output", action="store_true", help="do not retrieve command output")
|
||||
cgroup.add_argument("-x", metavar='COMMAND', dest='execute', type=str, help='Creates a new cmd process and executes the specified command with output')
|
||||
cgroup.add_argument("--exec-method", choices={"wmiexec", "wmiexec-event"}, default="wmiexec",
|
||||
help="method to execute the command. (default: wmiexec). "
|
||||
"[wmiexec (win32_process + StdRegProv)]: get command results over registry instead of using smb connection. "
|
||||
"[wmiexec-event (T1546.003)]: this method is not very stable, highly recommend use this method in single host, "
|
||||
"using on multiple hosts may crash (just try again if it crashed).")
|
||||
cgroup.add_argument("--exec-timeout", default=5, metavar='exec_timeout', dest='exec_timeout', type=int, help='Set timeout (in seconds) when executing a command, minimum 5 seconds is recommended. Default: %(default)s')
|
||||
cgroup.add_argument("--codec", default="utf-8",
|
||||
help="Set encoding used (codec) from the target's output (default "
|
||||
"\"utf-8\"). If errors are detected, run chcp.com at the target, "
|
||||
"map the result with "
|
||||
"https://docs.python.org/3/library/codecs.html#standard-encodings and then execute "
|
||||
"again with --codec and the corresponding codec")
|
||||
cgroup.add_argument("-x", metavar="COMMAND", dest="execute", type=str, help="Creates a new cmd process and executes the specified command with output")
|
||||
cgroup.add_argument("--exec-method", choices={"wmiexec", "wmiexec-event"}, default="wmiexec", help="method to execute the command. (default: wmiexec). " "[wmiexec (win32_process + StdRegProv)]: get command results over registry instead of using smb connection. " "[wmiexec-event (T1546.003)]: this method is not very stable, highly recommend use this method in single host, " "using on multiple hosts may crash (just try again if it crashed).")
|
||||
cgroup.add_argument("--exec-timeout", default=5, metavar="exec_timeout", dest="exec_timeout", type=int, help="Set timeout (in seconds) when executing a command, minimum 5 seconds is recommended. Default: %(default)s")
|
||||
cgroup.add_argument("--codec", default="utf-8", help="Set encoding used (codec) from the target's output (default " '"utf-8"). If errors are detected, run chcp.com at the target, ' "map the result with " "https://docs.python.org/3/library/codecs.html#standard-encodings and then execute " "again with --codec and the corresponding codec")
|
||||
return parser
|
||||
|
||||
|
||||
def get_conditional_action(baseAction):
|
||||
class ConditionalAction(baseAction):
|
||||
def __init__(self, option_strings, dest, **kwargs):
|
||||
x = kwargs.pop('make_required', [])
|
||||
x = kwargs.pop("make_required", [])
|
||||
super(ConditionalAction, self).__init__(option_strings, dest, **kwargs)
|
||||
self.make_required = x
|
||||
|
||||
|
@ -43,4 +34,4 @@ def get_conditional_action(baseAction):
|
|||
x.required = True
|
||||
super(ConditionalAction, self).__call__(parser, namespace, values, option_string)
|
||||
|
||||
return ConditionalAction
|
||||
return ConditionalAction
|
||||
|
|
|
@ -6,17 +6,17 @@
|
|||
# Link: https://github.com/XiaoliChan/wmiexec-RegOut/blob/main/wmiexec-regOut.py
|
||||
# Note: windows version under NT6 not working with this command execution way
|
||||
# https://github.com/XiaoliChan/wmiexec-RegOut/blob/main/wmiexec-reg-sch-UnderNT6-wip.py -- WIP
|
||||
#
|
||||
# Description:
|
||||
#
|
||||
# Description:
|
||||
# For more details, please check out my repository.
|
||||
# https://github.com/XiaoliChan/wmiexec-RegOut
|
||||
#
|
||||
# Workflow:
|
||||
# Stage 1:
|
||||
# cmd.exe /Q /c {command} > C:\windows\temp\{random}.txt (aka command results)
|
||||
#
|
||||
#
|
||||
# powershell convert the command results into base64, and save it into C:\windows\temp\{random2}.txt (now the command results was base64 encoded)
|
||||
#
|
||||
#
|
||||
# Create registry path: HKLM:\Software\Classes\hello, then add C:\windows\temp\{random2}.txt into HKLM:\Software\Classes\hello\{NewKey}
|
||||
#
|
||||
# Remove anythings which in C:\windows\temp\
|
||||
|
@ -33,6 +33,7 @@ from impacket.dcerpc.v5.dtypes import NULL
|
|||
from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
||||
from impacket.dcerpc.v5.dcom.wmi import CLSID_WbemLevel1Login, IID_IWbemLevel1Login, IWbemLevel1Login
|
||||
|
||||
|
||||
class WMIEXEC:
|
||||
def __init__(self, host, username, password, domain, lmhash, nthash, doKerberos, kdcHost, aesKey, logger, exec_timeout, codec):
|
||||
self.__host = host
|
||||
|
@ -50,19 +51,19 @@ class WMIEXEC:
|
|||
self.__outputBuffer = ""
|
||||
self.__retOutput = True
|
||||
|
||||
self.__shell = 'cmd.exe /Q /c '
|
||||
#self.__pwsh = 'powershell.exe -NoP -NoL -sta -NonI -W Hidden -Exec Bypass -Enc '
|
||||
#self.__pwsh = 'powershell.exe -Enc '
|
||||
self.__pwd = str('C:\\')
|
||||
self.__shell = "cmd.exe /Q /c "
|
||||
# self.__pwsh = 'powershell.exe -NoP -NoL -sta -NonI -W Hidden -Exec Bypass -Enc '
|
||||
# self.__pwsh = 'powershell.exe -Enc '
|
||||
self.__pwd = str("C:\\")
|
||||
self.__codec = codec
|
||||
|
||||
self.__dcom = DCOMConnection(self.__host, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, oxidResolver=True, doKerberos=self.__doKerberos ,kdcHost=self.__kdcHost, aesKey=self.__aesKey)
|
||||
self.__dcom = DCOMConnection(self.__host, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost, aesKey=self.__aesKey)
|
||||
iInterface = self.__dcom.CoCreateInstanceEx(CLSID_WbemLevel1Login, IID_IWbemLevel1Login)
|
||||
iWbemLevel1Login = IWbemLevel1Login(iInterface)
|
||||
self.__iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
|
||||
self.__iWbemServices = iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL)
|
||||
iWbemLevel1Login.RemRelease()
|
||||
self.__win32Process, _ = self.__iWbemServices.GetObject('Win32_Process')
|
||||
|
||||
self.__win32Process, _ = self.__iWbemServices.GetObject("Win32_Process")
|
||||
|
||||
def execute(self, command, output=False):
|
||||
self.__retOutput = output
|
||||
if self.__retOutput:
|
||||
|
@ -88,7 +89,7 @@ class WMIEXEC:
|
|||
keyName = str(uuid.uuid4())
|
||||
self.__registry_Path = f"Software\\Classes\\{gen_random_string(6)}"
|
||||
|
||||
command = fr'''{self.__shell} {command} 1> {result_output} 2>&1 && certutil -encodehex -f {result_output} {result_output_b64} 0x40000001 && for /F "usebackq" %G in ("{result_output_b64}") do reg add HKLM\{self.__registry_Path} /v {keyName} /t REG_SZ /d "%G" /f && del /q /f /s {result_output} {result_output_b64}'''
|
||||
command = rf"""{self.__shell} {command} 1> {result_output} 2>&1 && certutil -encodehex -f {result_output} {result_output_b64} 0x40000001 && for /F "usebackq" %G in ("{result_output_b64}") do reg add HKLM\{self.__registry_Path} /v {keyName} /t REG_SZ /d "%G" /f && del /q /f /s {result_output} {result_output_b64}"""
|
||||
|
||||
self.execute_remote(command)
|
||||
self.logger.info("Waiting {}s for command completely executed.".format(self.__exec_timeout))
|
||||
|
@ -99,15 +100,15 @@ class WMIEXEC:
|
|||
def queryRegistry(self, keyName):
|
||||
try:
|
||||
self.logger.debug(f"Querying registry key: HKLM\\{self.__registry_Path}")
|
||||
descriptor, _ = self.__iWbemServices.GetObject('StdRegProv')
|
||||
descriptor, _ = self.__iWbemServices.GetObject("StdRegProv")
|
||||
descriptor = descriptor.SpawnInstance()
|
||||
retVal = descriptor.GetStringValue(2147483650, self.__registry_Path, keyName)
|
||||
self.__outputBuffer = base64.b64decode(retVal.sValue).decode(self.__codec, errors='replace').rstrip('\r\n')
|
||||
self.__outputBuffer = base64.b64decode(retVal.sValue).decode(self.__codec, errors="replace").rstrip("\r\n")
|
||||
except Exception:
|
||||
self.logger.fail("WMIEXEC: Could not retrieve output file, it may have been detected by AV. Please try increasing the timeout with the '--exec-timeout' option. If it is still failing, try the 'smb' protocol or another exec method")
|
||||
|
||||
|
||||
try:
|
||||
self.logger.debug(f"Removing temporary registry path: HKLM\\{self.__registry_Path}")
|
||||
retVal = descriptor.DeleteKey(2147483650, self.__registry_Path)
|
||||
except Exception as e:
|
||||
self.logger.debug(f"Target: {self.__host} removing temporary registry path error: {str(e)}")
|
||||
self.logger.debug(f"Target: {self.__host} removing temporary registry path error: {str(e)}")
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
# Link: https://github.com/XiaoliChan/wmiexec-Pro
|
||||
# Note: windows version under NT6 not working with this command execution way, it need Win32_ScheduledJob.
|
||||
# https://github.com/XiaoliChan/wmiexec-Pro/blob/main/lib/modules/exec_command.py
|
||||
#
|
||||
# Description:
|
||||
#
|
||||
# Description:
|
||||
# For more details, please check out my repository.
|
||||
# https://github.com/XiaoliChan/wmiexec-Pro/blob/main/lib/modules/exec_command.py
|
||||
#
|
||||
|
@ -49,22 +49,22 @@ class WMIEXEC_EVENT:
|
|||
self.__aesKey = aesKey
|
||||
self.__outputBuffer = ""
|
||||
self.__retOutput = True
|
||||
|
||||
|
||||
self.logger = logger
|
||||
self.__exec_timeout = exec_timeout
|
||||
self.__codec = codec
|
||||
self.__instanceID = f"windows-object-{str(uuid.uuid4())}"
|
||||
self.__instanceID_StoreResult = f"windows-object-{str(uuid.uuid4())}"
|
||||
|
||||
self.__dcom = DCOMConnection(self.__host, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, oxidResolver=True, doKerberos=self.__doKerberos ,kdcHost=self.__kdcHost, aesKey=self.__aesKey)
|
||||
self.__dcom = DCOMConnection(self.__host, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost, aesKey=self.__aesKey)
|
||||
iInterface = self.__dcom.CoCreateInstanceEx(CLSID_WbemLevel1Login, IID_IWbemLevel1Login)
|
||||
iWbemLevel1Login = IWbemLevel1Login(iInterface)
|
||||
self.__iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/subscription', NULL, NULL)
|
||||
self.__iWbemServices = iWbemLevel1Login.NTLMLogin("//./root/subscription", NULL, NULL)
|
||||
iWbemLevel1Login.RemRelease()
|
||||
|
||||
def execute(self, command, output=False):
|
||||
if "'" in command:
|
||||
command = command.replace("'",r'"')
|
||||
command = command.replace("'", r'"')
|
||||
self.__retOutput = output
|
||||
self.execute_handler(command)
|
||||
|
||||
|
@ -83,7 +83,7 @@ class WMIEXEC_EVENT:
|
|||
# Generate vbsript and execute it
|
||||
self.logger.debug(f"{self.__host}: Execute command via wmi event, job instance id: {self.__instanceID}, command result instance id: {self.__instanceID_StoreResult}")
|
||||
self.execute_remote(command)
|
||||
|
||||
|
||||
# Get command results
|
||||
self.logger.info(f"Waiting {self.__exec_timeout}s for command completely executed.")
|
||||
time.sleep(self.__exec_timeout)
|
||||
|
@ -123,7 +123,7 @@ class WMIEXEC_EVENT:
|
|||
try:
|
||||
error_name = WBEMSTATUS.enumItems(call_status).name
|
||||
except ValueError:
|
||||
error_name = 'Unknown'
|
||||
error_name = "Unknown"
|
||||
self.logger.debug("{} - ERROR: {} (0x{:08x})".format(banner, error_name, call_status))
|
||||
else:
|
||||
self.logger.debug(f"{banner} - OK")
|
||||
|
@ -131,21 +131,21 @@ class WMIEXEC_EVENT:
|
|||
def execute_vbs(self, vbs_content):
|
||||
# Copy from wmipersist.py
|
||||
# Install ActiveScriptEventConsumer
|
||||
active_script, _ = self.__iWbemServices.GetObject('ActiveScriptEventConsumer')
|
||||
active_script, _ = self.__iWbemServices.GetObject("ActiveScriptEventConsumer")
|
||||
active_script = active_script.SpawnInstance()
|
||||
active_script.Name = self.__instanceID
|
||||
active_script.ScriptingEngine = 'VBScript'
|
||||
active_script.ScriptingEngine = "VBScript"
|
||||
active_script.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0]
|
||||
active_script.ScriptText = vbs_content
|
||||
# Don't output impacket default verbose
|
||||
current=sys.stdout
|
||||
current = sys.stdout
|
||||
sys.stdout = StringIO()
|
||||
resp = self.__iWbemServices.PutInstance(active_script.marshalMe())
|
||||
sys.stdout = current
|
||||
self.check_error(f'Adding ActiveScriptEventConsumer.Name="{self.__instanceID}"', resp.GetCallStatus(0) & 0xffffffff)
|
||||
self.check_error(f'Adding ActiveScriptEventConsumer.Name="{self.__instanceID}"', resp.GetCallStatus(0) & 0xFFFFFFFF)
|
||||
|
||||
# Timer means the amount of milliseconds after the script will be triggered, hard coding to 1 second it in this case.
|
||||
wmi_timer, _ = self.__iWbemServices.GetObject('__IntervalTimerInstruction')
|
||||
wmi_timer, _ = self.__iWbemServices.GetObject("__IntervalTimerInstruction")
|
||||
wmi_timer = wmi_timer.SpawnInstance()
|
||||
wmi_timer.TimerId = self.__instanceID
|
||||
wmi_timer.IntervalBetweenEvents = 1000
|
||||
|
@ -155,57 +155,57 @@ class WMIEXEC_EVENT:
|
|||
sys.stdout = StringIO()
|
||||
resp = self.__iWbemServices.PutInstance(wmi_timer.marshalMe())
|
||||
sys.stdout = current
|
||||
self.check_error(f'Adding IntervalTimerInstruction.TimerId="{self.__instanceID}"', resp.GetCallStatus(0) & 0xffffffff)
|
||||
self.check_error(f'Adding IntervalTimerInstruction.TimerId="{self.__instanceID}"', resp.GetCallStatus(0) & 0xFFFFFFFF)
|
||||
|
||||
# EventFilter
|
||||
event_filter, _ = self.__iWbemServices.GetObject('__EventFilter')
|
||||
event_filter, _ = self.__iWbemServices.GetObject("__EventFilter")
|
||||
event_filter = event_filter.SpawnInstance()
|
||||
event_filter.Name = self.__instanceID
|
||||
event_filter.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0]
|
||||
event_filter.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0]
|
||||
event_filter.Query = f'select * from __TimerEvent where TimerID = "{self.__instanceID}" '
|
||||
event_filter.QueryLanguage = 'WQL'
|
||||
event_filter.EventNamespace = r'root\subscription'
|
||||
event_filter.QueryLanguage = "WQL"
|
||||
event_filter.EventNamespace = r"root\subscription"
|
||||
# Don't output verbose
|
||||
current=sys.stdout
|
||||
current = sys.stdout
|
||||
sys.stdout = StringIO()
|
||||
resp = self.__iWbemServices.PutInstance(event_filter.marshalMe())
|
||||
sys.stdout = current
|
||||
self.check_error(f'Adding EventFilter.Name={self.__instanceID}"', resp.GetCallStatus(0) & 0xffffffff)
|
||||
self.check_error(f'Adding EventFilter.Name={self.__instanceID}"', resp.GetCallStatus(0) & 0xFFFFFFFF)
|
||||
|
||||
# Binding EventFilter & EventConsumer
|
||||
filter_binding, _ = self.__iWbemServices.GetObject('__FilterToConsumerBinding')
|
||||
filter_binding, _ = self.__iWbemServices.GetObject("__FilterToConsumerBinding")
|
||||
filter_binding = filter_binding.SpawnInstance()
|
||||
filter_binding.Filter = f'__EventFilter.Name="{self.__instanceID}"'
|
||||
filter_binding.Consumer = f'ActiveScriptEventConsumer.Name="{self.__instanceID}"'
|
||||
filter_binding.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0]
|
||||
# Don't output verbose
|
||||
current=sys.stdout
|
||||
current = sys.stdout
|
||||
sys.stdout = StringIO()
|
||||
resp = self.__iWbemServices.PutInstance(filter_binding.marshalMe())
|
||||
sys.stdout = current
|
||||
self.check_error(fr'Adding FilterToConsumerBinding.Consumer="ActiveScriptEventConsumer.Name=\"{self.__instanceID}\"", Filter="__EventFilter.Name=\"{self.__instanceID}\""', resp.GetCallStatus(0) & 0xffffffff)
|
||||
self.check_error(rf'Adding FilterToConsumerBinding.Consumer="ActiveScriptEventConsumer.Name=\"{self.__instanceID}\"", Filter="__EventFilter.Name=\"{self.__instanceID}\""', resp.GetCallStatus(0) & 0xFFFFFFFF)
|
||||
|
||||
def get_command_result(self):
|
||||
try:
|
||||
command_result_object, _ = self.__iWbemServices.GetObject(f'ActiveScriptEventConsumer.Name="{self.__instanceID_StoreResult}"')
|
||||
record = dict(command_result_object.getProperties())
|
||||
self.__outputBuffer = base64.b64decode(record['ScriptText']['value']).decode(self.__codec, errors='replace')
|
||||
self.__outputBuffer = base64.b64decode(record["ScriptText"]["value"]).decode(self.__codec, errors="replace")
|
||||
except Exception:
|
||||
self.logger.fail("WMIEXEC-EVENT: Could not retrieve output file, it may have been detected by AV. Please try increasing the timeout with the '--exec-timeout' option. If it is still failing, try the 'smb' protocol or another exec method")
|
||||
|
||||
def remove_instance(self):
|
||||
if self.__retOutput:
|
||||
resp = self.__iWbemServices.DeleteInstance(f'ActiveScriptEventConsumer.Name="{self.__instanceID_StoreResult}"')
|
||||
self.check_error(f'Removing ActiveScriptEventConsumer.Name="{self.__instanceID}"', resp.GetCallStatus(0) & 0xffffffff)
|
||||
self.check_error(f'Removing ActiveScriptEventConsumer.Name="{self.__instanceID}"', resp.GetCallStatus(0) & 0xFFFFFFFF)
|
||||
|
||||
resp = self.__iWbemServices.DeleteInstance(f'ActiveScriptEventConsumer.Name="{self.__instanceID}"')
|
||||
self.check_error(f'Removing ActiveScriptEventConsumer.Name="{self.__instanceID}"', resp.GetCallStatus(0) & 0xffffffff)
|
||||
self.check_error(f'Removing ActiveScriptEventConsumer.Name="{self.__instanceID}"', resp.GetCallStatus(0) & 0xFFFFFFFF)
|
||||
|
||||
resp = self.__iWbemServices.DeleteInstance(f'__IntervalTimerInstruction.TimerId="{self.__instanceID}"')
|
||||
self.check_error(f'Removing IntervalTimerInstruction.TimerId="{self.__instanceID}"', resp.GetCallStatus(0) & 0xffffffff)
|
||||
self.check_error(f'Removing IntervalTimerInstruction.TimerId="{self.__instanceID}"', resp.GetCallStatus(0) & 0xFFFFFFFF)
|
||||
|
||||
resp = self.__iWbemServices.DeleteInstance(f'__EventFilter.Name="{self.__instanceID}"')
|
||||
self.check_error(f'Removing EventFilter.Name="{self.__instanceID}"', resp.GetCallStatus(0) & 0xffffffff)
|
||||
self.check_error(f'Removing EventFilter.Name="{self.__instanceID}"', resp.GetCallStatus(0) & 0xFFFFFFFF)
|
||||
|
||||
resp = self.__iWbemServices.DeleteInstance(fr'__FilterToConsumerBinding.Consumer="ActiveScriptEventConsumer.Name=\"{self.__instanceID}\"",Filter="__EventFilter.Name=\"{self.__instanceID}\""')
|
||||
self.check_error(fr'Removing FilterToConsumerBinding.Consumer="ActiveScriptEventConsumer.Name=\"{self.__instanceID}\"", Filter="__EventFilter.Name=\"{self.__instanceID}\""', resp.GetCallStatus(0) & 0xffffffff)
|
||||
resp = self.__iWbemServices.DeleteInstance(rf'__FilterToConsumerBinding.Consumer="ActiveScriptEventConsumer.Name=\"{self.__instanceID}\"",Filter="__EventFilter.Name=\"{self.__instanceID}\""')
|
||||
self.check_error(rf'Removing FilterToConsumerBinding.Consumer="ActiveScriptEventConsumer.Name=\"{self.__instanceID}\"", Filter="__EventFilter.Name=\"{self.__instanceID}\""', resp.GetCallStatus(0) & 0xFFFFFFFF)
|
||||
|
|
|
@ -63,6 +63,7 @@ rich = "^13.3.5"
|
|||
python-libnmap = "^0.7.3"
|
||||
resource = "^0.2.1"
|
||||
oscrypto = { git = "https://github.com/NeffIsBack/oscrypto" }
|
||||
ruff = "^0.0.291"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
flake8 = "*"
|
||||
|
@ -74,3 +75,53 @@ pytest = "^7.2.2"
|
|||
[build-system]
|
||||
requires = ["poetry-core>=1.2.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.ruff]
|
||||
# Ruff doesn't enable pycodestyle warnings (`W`) or
|
||||
# McCabe complexity (`C901`) by default.
|
||||
select = ["E", "F"]
|
||||
ignore = [ "E501", "F405", "F841"]
|
||||
|
||||
# Allow autofix for all enabled rules (when `--fix`) is provided.
|
||||
fixable = ["ALL"]
|
||||
unfixable = []
|
||||
|
||||
# Exclude a variety of commonly ignored directories.
|
||||
exclude = [
|
||||
".bzr",
|
||||
".direnv",
|
||||
".eggs",
|
||||
".git",
|
||||
".git-rewrite",
|
||||
".hg",
|
||||
".mypy_cache",
|
||||
".nox",
|
||||
".pants.d",
|
||||
".pytype",
|
||||
".ruff_cache",
|
||||
".svn",
|
||||
".tox",
|
||||
".venv",
|
||||
"__pypackages__",
|
||||
"_build",
|
||||
"buck-out",
|
||||
"build",
|
||||
"dist",
|
||||
"node_modules",
|
||||
"venv",
|
||||
]
|
||||
per-file-ignores = {}
|
||||
|
||||
line-length = 65000
|
||||
|
||||
# Allow unused variables when underscore-prefixed.
|
||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
|
||||
target-version = "py37"
|
||||
|
||||
[tool.ruff.flake8-quotes]
|
||||
docstring-quotes = "double"
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "double"
|
||||
indent-style = "space"
|
Loading…
Reference in New Issue