Skip to content

Commit e10e38f

Browse files
Merge branch 'main' into marshall-pwsh-update
Signed-off-by: Marshall Hallenbeck <Marshall.Hallenbeck@gmail.com>
2 parents f865502 + d0a4afe commit e10e38f

38 files changed

Lines changed: 468 additions & 277 deletions
File renamed without changes.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
data/nxc.db
22
hash_spider_default.sqlite3
3+
hash_spider_testing.sqlite3
34
*.bak
45
*.log
56
.venv

nxc/cli.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ def gen_cli_args():
5252
parser.add_argument("--debug", action="store_true", help="enable debug level information")
5353
parser.add_argument("--version", action="store_true", help="Display nxc version")
5454

55+
dns_parser = parser.add_argument_group("DNS")
56+
dns_parser.add_argument("-6", dest="force_ipv6", action="store_true", help="Enable force IPv6")
57+
dns_parser.add_argument("--dns-server", action="store", help="Specify DNS server (default: Use hosts file & System DNS)")
58+
dns_parser.add_argument("--dns-tcp", action="store_true", help="Use TCP instead of UDP for DNS queries")
59+
dns_parser.add_argument("--dns-timeout", action="store", type=int, default=3, help="DNS query timeout in seconds (default: %(default)s)")
60+
5561
# we do module arg parsing here so we can reference the module_list attribute below
5662
module_parser = argparse.ArgumentParser(add_help=False)
5763
mgroup = module_parser.add_mutually_exclusive_group()
@@ -78,7 +84,7 @@ def gen_cli_args():
7884
std_parser.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
7985
std_parser.add_argument("--log", metavar="LOG", help="Export result into a custom file")
8086
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")
87+
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")
8288

8389
fail_group = std_parser.add_mutually_exclusive_group()
8490
fail_group.add_argument("--gfail-limit", metavar="LIMIT", type=int, help="max number of global failed login attempts")

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

nxc/logger.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ def __init__(self, extra=None):
9393
self.logger = logging.getLogger("nxc")
9494
self.extra = extra
9595
self.output_file = None
96-
96+
97+
logging.getLogger("impacket").disabled = True
9798
logging.getLogger("pypykatz").disabled = True
9899
logging.getLogger("minidump").disabled = True
99100
logging.getLogger("lsassy").disabled = True

nxc/modules/add-computer.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import ssl
22
import ldap3
3-
from impacket.dcerpc.v5 import samr, epm, transport
43
import sys
4+
from impacket.dcerpc.v5 import samr, epm, transport
5+
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
56

67
class NXCModule:
78
"""
@@ -61,11 +62,10 @@ def options(self, context, module_options):
6162
def on_login(self, context, connection):
6263
self.__domain = connection.domain
6364
self.__domainNetbios = connection.domain
64-
self.__kdcHost = connection.hostname + "." + connection.domain
65-
self.__target = self.__kdcHost
65+
self.__kdcHost = connection.kdcHost
6666
self.__username = connection.username
6767
self.__password = connection.password
68-
self.__targetIp = connection.host
68+
self.__host = connection.host
6969
self.__port = context.smb_server_port
7070
self.__aesKey = context.aesKey
7171
self.__hashes = context.hash
@@ -101,15 +101,10 @@ def do_samr_add(self, context):
101101
-------
102102
None
103103
"""
104-
target = self.__targetIp or self.__target
105-
string_binding = epm.hept_map(target, samr.MSRPC_UUID_SAMR, protocol="ncacn_np")
104+
string_binding = epm.hept_map(self.__host, samr.MSRPC_UUID_SAMR, protocol="ncacn_np")
106105

107-
rpc_transport = transport.DCERPCTransportFactory(string_binding)
108-
rpc_transport.set_dport(self.__port)
109-
110-
if self.__targetIp is not None:
111-
rpc_transport.setRemoteHost(self.__targetIp)
112-
rpc_transport.setRemoteName(self.__target)
106+
rpc_transport = transport.DCERPCTransportFactory(string_binding.replace(self.__host, self.__kdcHost))
107+
rpc_transport.setRemoteHost(self.__host)
113108

114109
if hasattr(rpc_transport, "set_credentials"):
115110
# This method exists only for selected protocol sequences.
@@ -118,10 +113,13 @@ def do_samr_add(self, context):
118113
rpc_transport.set_kerberos(self.__doKerberos, self.__kdcHost)
119114

120115
dce = rpc_transport.get_dce_rpc()
116+
if self.__doKerberos:
117+
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
118+
121119
dce.connect()
122120
dce.bind(samr.MSRPC_UUID_SAMR)
123121

124-
samr_connect_response = samr.hSamrConnect5(dce, f"\\\\{self.__target}\x00", samr.SAM_SERVER_ENUMERATE_DOMAINS | samr.SAM_SERVER_LOOKUP_DOMAIN)
122+
samr_connect_response = samr.hSamrConnect5(dce, f"\\\\{self.__kdcHost}\x00", samr.SAM_SERVER_ENUMERATE_DOMAINS | samr.SAM_SERVER_LOOKUP_DOMAIN)
125123
serv_handle = samr_connect_response["ServerHandle"]
126124

127125
samr_enum_response = samr.hSamrEnumerateDomainsInSamServer(dce, serv_handle)

nxc/modules/dfscoerce.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from impacket.dcerpc.v5 import transport
33
from impacket.dcerpc.v5.ndr import NDRCALL
44
from impacket.dcerpc.v5.dtypes import ULONG, WSTR, DWORD
5-
from impacket.dcerpc.v5.rpcrt import DCERPCException
5+
from impacket.dcerpc.v5.rpcrt import DCERPCException, RPC_C_AUTHN_GSS_NEGOTIATE
66
from impacket.uuid import uuidtup_to_bin
77
from nxc.logger import nxc_logger
88

@@ -33,9 +33,9 @@ def on_login(self, context, connection):
3333
domain=connection.domain,
3434
lmhash=connection.lmhash,
3535
nthash=connection.nthash,
36-
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
36+
target=connection.host,
3737
doKerberos=connection.kerberos,
38-
dcHost=connection.kdcHost,
38+
kdcHost=connection.kdcHost,
3939
aesKey=connection.aesKey,
4040
)
4141

@@ -95,8 +95,9 @@ class NetrDfsAddRootResponse(NDRCALL):
9595

9696

9797
class TriggerAuth:
98-
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost):
98+
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, kdcHost):
9999
rpctransport = transport.DCERPCTransportFactory(r"ncacn_np:%s[\PIPE\netdfs]" % target)
100+
rpctransport.setRemoteHost(target)
100101
if hasattr(rpctransport, "set_credentials"):
101102
rpctransport.set_credentials(
102103
username=username,
@@ -108,11 +109,12 @@ def connect(self, username, password, domain, lmhash, nthash, aesKey, target, do
108109
)
109110

110111
if doKerberos:
111-
rpctransport.set_kerberos(doKerberos, kdcHost=dcHost)
112-
# if target:
112+
rpctransport.set_kerberos(doKerberos, kdcHost)
113113

114-
rpctransport.setRemoteHost(target)
115114
dce = rpctransport.get_dce_rpc()
115+
if doKerberos:
116+
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
117+
116118
nxc_logger.debug("[-] Connecting to {}".format(r"ncacn_np:%s[\PIPE\netdfs]") % target)
117119
try:
118120
dce.connect()
@@ -134,8 +136,7 @@ def NetrDfsRemoveStdRoot(self, dce, listener):
134136
request["ServerName"] = f"{listener}\x00"
135137
request["RootShare"] = "test\x00"
136138
request["ApiFlags"] = 1
137-
if self.args.verbose:
138-
nxc_logger.debug(request.dump())
139+
nxc_logger.debug(request.dump())
139140
dce.request(request)
140141

141142
except Exception as e:

0 commit comments

Comments
 (0)