Skip to content

Commit 2fa3e83

Browse files
authored
Merge branch 'main' into sepauli/fix-keepass_trigger
2 parents 94e0ab6 + 6858958 commit 2fa3e83

68 files changed

Lines changed: 1847 additions & 869 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

netexec.spec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,10 @@ a = Analysis(
6767
'dploot.triage.browser',
6868
'dploot.triage.credentials',
6969
'dploot.triage.masterkeys',
70+
'dploot.triage.mobaxterm',
7071
'dploot.triage.backupkey',
7172
'dploot.triage.wifi',
73+
'dploot.triage.sccm',
7274
'dploot.lib.target',
7375
'dploot.lib.smb',
7476
'pyasn1_modules.rfc5652',

nxc/cli.py

Lines changed: 64 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from nxc.paths import NXC_PATH
1010
from nxc.loaders.protocolloader import ProtocolLoader
1111
from nxc.helpers.logger import highlight
12+
from nxc.helpers.args import DisplayDefaultsNotNone
1213
from nxc.logger import nxc_logger, setup_debug_logging
1314
import importlib.metadata
1415

@@ -21,10 +22,31 @@ def gen_cli_args():
2122
except ValueError:
2223
VERSION = importlib.metadata.version("netexec")
2324
COMMIT = ""
24-
CODENAME = "nxc4u"
25+
CODENAME = "ItsAlwaysDNS"
2526
nxc_logger.debug(f"NXC VERSION: {VERSION} - {CODENAME} - {COMMIT}")
26-
27-
parser = argparse.ArgumentParser(description=rf"""
27+
28+
generic_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
29+
generic_group = generic_parser.add_argument_group("Generic", "Generic options for nxc across protocols")
30+
generic_group.add_argument("-t", "--threads", type=int, dest="threads", default=256, help="set how many concurrent threads to use")
31+
generic_group.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread")
32+
generic_group.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each authentication")
33+
34+
output_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
35+
output_group = output_parser.add_argument_group("Output", "Options to set verbosity levels and control output")
36+
output_group.add_argument("--verbose", action="store_true", help="enable verbose output")
37+
output_group.add_argument("--debug", action="store_true", help="enable debug level information")
38+
output_group.add_argument("--no-progress", action="store_true", help="do not displaying progress bar during scan")
39+
output_group.add_argument("--log", metavar="LOG", help="export result into a custom file")
40+
41+
dns_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
42+
dns_group = dns_parser.add_argument_group("DNS")
43+
dns_group.add_argument("-6", dest="force_ipv6", action="store_true", help="Enable force IPv6")
44+
dns_group.add_argument("--dns-server", action="store", help="Specify DNS server (default: Use hosts file & System DNS)")
45+
dns_group.add_argument("--dns-tcp", action="store_true", help="Use TCP instead of UDP for DNS queries")
46+
dns_group.add_argument("--dns-timeout", action="store", type=int, default=3, help="DNS query timeout in seconds")
47+
48+
parser = argparse.ArgumentParser(
49+
description=rf"""
2850
. .
2951
.| |. _ _ _ _____
3052
|| || | \ | | ___ | |_ | ____| __ __ ___ ___
@@ -42,56 +64,55 @@ def gen_cli_args():
4264
{highlight('Version', 'red')} : {highlight(VERSION)}
4365
{highlight('Codename', 'red')}: {highlight(CODENAME)}
4466
{highlight('Commit', 'red')} : {highlight(COMMIT)}
45-
""", formatter_class=RawTextHelpFormatter)
46-
47-
parser.add_argument("-t", type=int, dest="threads", default=256, help="set how many concurrent threads to use (default: 256)")
48-
parser.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread (default: None)")
49-
parser.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each authentication (default: None)")
50-
parser.add_argument("--no-progress", action="store_true", help="Not displaying progress bar during scan")
51-
parser.add_argument("--verbose", action="store_true", help="enable verbose output")
52-
parser.add_argument("--debug", action="store_true", help="enable debug level information")
67+
""",
68+
formatter_class=RawTextHelpFormatter,
69+
parents=[generic_parser, output_parser, dns_parser]
70+
)
71+
5372
parser.add_argument("--version", action="store_true", help="Display nxc version")
5473

5574
# we do module arg parsing here so we can reference the module_list attribute below
56-
module_parser = argparse.ArgumentParser(add_help=False)
57-
mgroup = module_parser.add_mutually_exclusive_group()
75+
module_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
76+
mgroup = module_parser.add_argument_group("Modules", "Options for nxc modules")
5877
mgroup.add_argument("-M", "--module", choices=get_module_names(), action="append", metavar="MODULE", help="module to use")
59-
module_parser.add_argument("-o", metavar="MODULE_OPTION", nargs="+", default=[], dest="module_options", help="module options")
60-
module_parser.add_argument("-L", "--list-modules", action="store_true", help="list available modules")
61-
module_parser.add_argument("--options", dest="show_module_options", action="store_true", help="display module options")
62-
module_parser.add_argument("--server", choices={"http", "https"}, default="https", help="use the selected server (default: https)")
63-
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)")
64-
module_parser.add_argument("--server-port", metavar="PORT", type=int, help="start the server on the specified port")
65-
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)")
78+
mgroup.add_argument("-o", metavar="MODULE_OPTION", nargs="+", default=[], dest="module_options", help="module options")
79+
mgroup.add_argument("-L", "--list-modules", action="store_true", help="list available modules")
80+
mgroup.add_argument("--options", dest="show_module_options", action="store_true", help="display module options")
6681

67-
subparsers = parser.add_subparsers(title="protocols", dest="protocol", description="available protocols")
82+
subparsers = parser.add_subparsers(title="Available Protocols", dest="protocol")
6883

69-
std_parser = argparse.ArgumentParser(add_help=False)
84+
std_parser = argparse.ArgumentParser(add_help=False, parents=[generic_parser, output_parser, dns_parser], formatter_class=DisplayDefaultsNotNone)
7085
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)")
71-
std_parser.add_argument("-id", metavar="CRED_ID", nargs="+", default=[], type=str, dest="cred_id", help="database credential ID(s) to use for authentication")
72-
std_parser.add_argument("-u", metavar="USERNAME", dest="username", nargs="+", default=[], help="username(s) or file(s) containing usernames")
73-
std_parser.add_argument("-p", metavar="PASSWORD", dest="password", nargs="+", default=[], help="password(s) or file(s) containing passwords")
74-
std_parser.add_argument("--ignore-pw-decoding", action="store_true", help="Ignore non UTF-8 characters when decoding the password file")
75-
std_parser.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
76-
std_parser.add_argument("--no-bruteforce", action="store_true", help="No spray when using file for username and password (user1 => password1, user2 => password2")
77-
std_parser.add_argument("--continue-on-success", action="store_true", help="continues authentication attempts even after successes")
78-
std_parser.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
79-
std_parser.add_argument("--log", metavar="LOG", help="Export result into a custom file")
80-
std_parser.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)")
81-
std_parser.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")
82-
83-
fail_group = std_parser.add_mutually_exclusive_group()
84-
fail_group.add_argument("--gfail-limit", metavar="LIMIT", type=int, help="max number of global failed login attempts")
85-
fail_group.add_argument("--ufail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per username")
86-
fail_group.add_argument("--fail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per host")
86+
credential_group = std_parser.add_argument_group("Authentication", "Options for authenticating")
87+
credential_group.add_argument("-u", "--username", metavar="USERNAME", dest="username", nargs="+", default=[], help="username(s) or file(s) containing usernames")
88+
credential_group.add_argument("-p", "--password", metavar="PASSWORD", dest="password", nargs="+", default=[], help="password(s) or file(s) containing passwords")
89+
credential_group.add_argument("-id", metavar="CRED_ID", nargs="+", default=[], type=str, dest="cred_id", help="database credential ID(s) to use for authentication")
90+
credential_group.add_argument("--ignore-pw-decoding", action="store_true", help="Ignore non UTF-8 characters when decoding the password file")
91+
credential_group.add_argument("--no-bruteforce", action="store_true", help="No spray when using file for username and password (user1 => password1, user2 => password2)")
92+
credential_group.add_argument("--continue-on-success", action="store_true", help="continues authentication attempts even after successes")
93+
credential_group.add_argument("--gfail-limit", metavar="LIMIT", type=int, help="max number of global failed login attempts")
94+
credential_group.add_argument("--ufail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per username")
95+
credential_group.add_argument("--fail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per host")
96+
97+
kerberos_group = std_parser.add_argument_group("Kerberos", "Options for Kerberos authentication")
98+
kerberos_group.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
99+
kerberos_group.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
100+
kerberos_group.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)")
101+
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")
102+
103+
server_group = std_parser.add_argument_group("Servers", "Options for nxc servers")
104+
server_group.add_argument("--server", choices={"http", "https"}, default="https", help="use the selected server")
105+
server_group.add_argument("--server-host", type=str, default="0.0.0.0", metavar="HOST", help="IP to bind the server to")
106+
server_group.add_argument("--server-port", metavar="PORT", type=int, help="start the server on the specified port")
107+
server_group.add_argument("--connectback-host", type=str, metavar="CHOST", help="IP for the remote system to connect back to")
87108

88109
p_loader = ProtocolLoader()
89110
protocols = p_loader.get_protocols()
90111

91112
try:
92113
for protocol in protocols:
93114
protocol_object = p_loader.load_protocol(protocols[protocol]["argspath"])
94-
subparsers = protocol_object.proto_args(subparsers, std_parser, module_parser)
115+
subparsers = protocol_object.proto_args(subparsers, [std_parser, module_parser])
95116
except Exception as e:
96117
nxc_logger.exception(f"Error loading proto_args from proto_args.py file in protocol folder: {protocol} - {e}")
97118

@@ -106,6 +127,10 @@ def gen_cli_args():
106127
print(f"{VERSION} - {CODENAME} - {COMMIT}")
107128
sys.exit(1)
108129

130+
# Multiply output_tries by 10 to enable more fine granural control, see exec methods
131+
if hasattr(args, "get_output_tries"):
132+
args.get_output_tries = args.get_output_tries * 10
133+
109134
return args
110135

111136

nxc/connection.py

Lines changed: 93 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from functools import wraps
55
from time import sleep
66
from ipaddress import ip_address
7+
from dns import resolver, rdatatype
78
from socket import AF_UNSPEC, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME, getaddrinfo
89

910
from nxc.config import pwned_label
@@ -22,23 +23,63 @@
2223
user_failed_logins = {}
2324

2425

25-
def gethost_addrinfo(hostname):
26-
is_ipv6 = False
27-
is_link_local_ipv6 = False
26+
def get_host_addr_info(target, force_ipv6, dns_server, dns_tcp, dns_timeout):
27+
result = {
28+
"host": "",
29+
"is_ipv6": False,
30+
"is_link_local_ipv6": False
31+
}
2832
address_info = {"AF_INET6": "", "AF_INET": ""}
2933

30-
for res in getaddrinfo(hostname, None, AF_UNSPEC, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME):
31-
af, _, _, canonname, sa = res
32-
address_info[af.name] = sa[0]
34+
try:
35+
if ip_address(target).version == 4:
36+
address_info["AF_INET"] = target
37+
else:
38+
address_info["AF_INET6"] = target
39+
except Exception:
40+
# If the target is not an IP address, we need to resolve it
41+
if not (dns_server or dns_tcp):
42+
for res in getaddrinfo(target, None, AF_UNSPEC, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME):
43+
af, _, _, canonname, sa = res
44+
address_info[af.name] = sa[0]
45+
46+
if address_info["AF_INET6"] and ip_address(address_info["AF_INET6"]).is_link_local:
47+
address_info["AF_INET6"] = canonname
48+
result["is_link_local_ipv6"] = True
49+
else:
50+
dnsresolver = resolver.Resolver()
51+
dnsresolver.timeout = dns_timeout
52+
dnsresolver.lifetime = dns_timeout
53+
54+
if dns_server:
55+
dnsresolver.nameservers = [dns_server]
56+
57+
try:
58+
answers_ipv4 = dnsresolver.resolve(target, rdatatype.A, raise_on_no_answer=False, tcp=dns_tcp)
59+
address_info["AF_INET"] = answers_ipv4[0].address
60+
except Exception:
61+
pass
62+
63+
try:
64+
answers_ipv6 = dnsresolver.resolve(target, rdatatype.AAAA, raise_on_no_answer=False, tcp=dns_tcp)
65+
address_info["AF_INET6"] = answers_ipv6[0].address
66+
67+
if address_info["AF_INET6"] and ip_address(address_info["AF_INET6"]).is_link_local:
68+
result["is_link_local_ipv6"] = True
69+
except Exception:
70+
pass
71+
72+
if not (address_info["AF_INET"] or address_info["AF_INET6"]):
73+
raise Exception(f"The DNS query name does not exist: {target}")
3374

3475
# IPv4 preferred
35-
if address_info["AF_INET"]:
36-
host = address_info["AF_INET"]
76+
if address_info["AF_INET"] and not force_ipv6:
77+
result["host"] = address_info["AF_INET"]
3778
else:
38-
is_ipv6 = True
39-
host, is_link_local_ipv6 = (canonname, True) if ip_address(address_info["AF_INET6"]).is_link_local else (address_info["AF_INET6"], False)
79+
result["is_ipv6"] = True
80+
result["host"] = address_info["AF_INET6"]
4081

41-
return host, is_ipv6, is_link_local_ipv6
82+
return result
4283

4384

4485
def requires_admin(func):
@@ -50,7 +91,7 @@ def _decorator(self, *args, **kwargs):
5091
return wraps(func)(_decorator)
5192

5293

53-
def dcom_FirewallChecker(iInterface, timeout):
94+
def dcom_FirewallChecker(iInterface, remoteHost, timeout):
5495
stringBindings = iInterface.get_cinstance().get_string_bindings()
5596
for strBinding in stringBindings:
5697
if strBinding["wTowerId"] == 7:
@@ -70,6 +111,7 @@ def dcom_FirewallChecker(iInterface, timeout):
70111
return True, None
71112
try:
72113
rpctransport = transport.DCERPCTransportFactory(stringBinding)
114+
rpctransport.setRemoteHost(remoteHost)
73115
rpctransport.set_connect_timeout(timeout)
74116
rpctransport.connect()
75117
rpctransport.disconnect()
@@ -81,45 +123,67 @@ def dcom_FirewallChecker(iInterface, timeout):
81123

82124

83125
class connection:
84-
def __init__(self, args, db, host):
85-
self.domain = None
126+
def __init__(self, args, db, target):
86127
self.args = args
87128
self.db = db
88-
self.hostname = host
89-
self.port = self.args.port
129+
self.logger = nxc_logger
90130
self.conn = None
91-
self.admin_privs = False
131+
132+
# Authentication info
92133
self.password = ""
93134
self.username = ""
94135
self.kerberos = bool(self.args.kerberos or self.args.use_kcache or self.args.aesKey)
95136
self.aesKey = None if not self.args.aesKey else self.args.aesKey[0]
96-
self.kdcHost = None if not self.args.kdcHost else self.args.kdcHost
97137
self.use_kcache = None if not self.args.use_kcache else self.args.use_kcache
138+
self.admin_privs = False
98139
self.failed_logins = 0
140+
141+
# Network info
142+
self.domain = None
143+
self.host = None # IP address of the target. If kerberos this is the hostname
144+
self.hostname = target # Target info supplied by the user, may be an IP address or a hostname
145+
self.remoteName = target # hostname + domain, defaults to target if domain could not be resolved/not specified
146+
self.kdcHost = self.args.kdcHost
147+
self.port = self.args.port
99148
self.local_ip = None
100-
self.logger = nxc_logger
101149

102-
try:
103-
self.host, self.is_ipv6, self.is_link_local_ipv6 = gethost_addrinfo(self.hostname)
104-
if self.args.kerberos:
105-
self.host = self.hostname
106-
self.logger.info(f"Socket info: host={self.host}, hostname={self.hostname}, kerberos={self.kerberos}, ipv6={self.is_ipv6}, link-local ipv6={self.is_link_local_ipv6}")
107-
except Exception as e:
108-
self.logger.info(f"Error resolving hostname {self.hostname}: {e}")
150+
# DNS resolution
151+
dns_result = self.resolver(target)
152+
if dns_result:
153+
self.host, self.is_ipv6, self.is_link_local_ipv6 = dns_result["host"], dns_result["is_ipv6"], dns_result["is_link_local_ipv6"]
154+
else:
109155
return
110156

157+
if self.args.kerberos:
158+
self.host = self.hostname
159+
160+
self.logger.info(f"Socket info: host={self.host}, hostname={self.hostname}, kerberos={self.kerberos}, ipv6={self.is_ipv6}, link-local ipv6={self.is_link_local_ipv6}")
161+
111162
try:
112163
self.proto_flow()
113164
except Exception as e:
114165
if "ERROR_DEPENDENT_SERVICES_RUNNING" in str(e):
115-
self.logger.error(f"Exception while calling proto_flow() on target {self.host}: {e}")
166+
self.logger.error(f"Exception while calling proto_flow() on target {target}: {e}")
116167
else:
117-
self.logger.exception(f"Exception while calling proto_flow() on target {self.host}: {e}")
168+
self.logger.exception(f"Exception while calling proto_flow() on target {target}: {e}")
118169
finally:
119-
self.logger.debug(f"Closing connection to: {host}")
170+
self.logger.debug(f"Closing connection to: {target}")
120171
with contextlib.suppress(Exception):
121172
self.conn.close()
122173

174+
def resolver(self, target):
175+
try:
176+
return get_host_addr_info(
177+
target=target,
178+
force_ipv6=self.args.force_ipv6,
179+
dns_server=self.args.dns_server,
180+
dns_tcp=self.args.dns_tcp,
181+
dns_timeout=self.args.dns_timeout
182+
)
183+
except Exception as e:
184+
self.logger.info(f"Error resolving hostname {target}: {e}")
185+
return None
186+
123187
@staticmethod
124188
def proto_args(std_parser, module_parser):
125189
return

0 commit comments

Comments
 (0)