Skip to content

Commit 1281b0f

Browse files
authored
Merge pull request Pennyw0rth#671 from Pennyw0rth/fix_host_info
2 parents ff53155 + e5d3b66 commit 1281b0f

3 files changed

Lines changed: 105 additions & 18 deletions

File tree

nxc/helpers/misc.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import inspect
55
import os
66

7+
from ipaddress import ip_address
78

89
def identify_target_file(target_file):
910
with open(target_file) as target_file_handle:
@@ -77,3 +78,10 @@ def _access_check(fn, mode):
7778
name = os.path.join(p, thefile)
7879
if _access_check(name, mode):
7980
return name
81+
82+
def detect_if_ip(target):
83+
try:
84+
ip_address(target)
85+
return True
86+
except Exception:
87+
return False

nxc/protocols/ldap/resolution.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from re import sub, I
2+
from errno import EHOSTUNREACH, ETIMEDOUT, ENETUNREACH
3+
from OpenSSL.SSL import SysCallError
4+
5+
from impacket.ldap import ldap as ldap_impacket
6+
from impacket.ldap import ldapasn1 as ldapasn1_impacket
7+
8+
from nxc.parsers.ldap_results import parse_result_attributes
9+
from nxc.logger import nxc_logger
10+
11+
class LDAPResolution:
12+
13+
def __init__(self, host):
14+
self.host = host
15+
16+
def get_resolution(self):
17+
target = ""
18+
target_domain = ""
19+
base_dn = ""
20+
try:
21+
ldap_url = f"ldap://{self.host}"
22+
nxc_logger.info(f"Connecting to {ldap_url} with no baseDN")
23+
try:
24+
self.ldap_connection = ldap_impacket.LDAPConnection(ldap_url, dstIp=self.host)
25+
if self.ldap_connection:
26+
nxc_logger.debug(f"ldap_connection: {self.ldap_connection}")
27+
except SysCallError as e:
28+
nxc_logger.fail(f"LDAP connection to {ldap_url} failed: {e}")
29+
return False
30+
31+
resp = self.ldap_connection.search(
32+
scope=ldapasn1_impacket.Scope("baseObject"),
33+
attributes=["defaultNamingContext", "dnsHostName"],
34+
sizeLimit=0,
35+
)
36+
resp_parsed = parse_result_attributes(resp)[0]
37+
38+
target = resp_parsed["dnsHostName"]
39+
base_dn = resp_parsed["defaultNamingContext"]
40+
target_domain = sub(
41+
",DC=",
42+
".",
43+
base_dn[base_dn.lower().find("dc="):],
44+
flags=I,
45+
)[3:]
46+
# Extract machine name from target (hostname part of FQDN)
47+
if target:
48+
machine_name = target.split(".")[0]
49+
nxc_logger.debug(f"Extracted machine name: {machine_name}")
50+
51+
self.ldap_connection.close()
52+
except ConnectionRefusedError as e:
53+
nxc_logger.debug(f"{e} on host {self.host}")
54+
return False
55+
except OSError as e:
56+
if e.errno in (EHOSTUNREACH, ENETUNREACH, ETIMEDOUT):
57+
nxc_logger.info(f"Error connecting to {self.host} - {e}")
58+
return False
59+
else:
60+
nxc_logger.error(f"Error getting ldap info {e}")
61+
62+
nxc_logger.debug(f"Target: {machine_name}.{target_domain}; target_domain: {target_domain}; base_dn: {base_dn}")
63+
return machine_name, target_domain

nxc/protocols/smb.py

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
from nxc.helpers.logger import highlight
5656
from nxc.helpers.bloodhound import add_user_bh
5757
from nxc.helpers.powershell import create_ps_command
58+
from nxc.helpers.misc import detect_if_ip
59+
from nxc.protocols.ldap.resolution import LDAPResolution
5860

5961
from dploot.triage.vaults import VaultsTriage
6062
from dploot.triage.browser import BrowserTriage, LoginData, GoogleRefreshToken, Cookie
@@ -125,6 +127,7 @@ def __init__(self, args, db, host):
125127
self.no_ntlm = False
126128
self.protocol = "SMB"
127129
self.is_guest = None
130+
self.isdc = False
128131

129132
connection.__init__(self, args, db, host)
130133

@@ -185,20 +188,30 @@ def enum_host_info(self):
185188
if not self.targetDomain: # Not sure if that can even happen but now we are safe
186189
self.targetDomain = self.hostname
187190
else:
188-
# If we can't authenticate with NTLM and the target is supplied as a FQDN we must parse it
189191
try:
190-
import socket
191-
socket.inet_aton(self.host)
192-
self.logger.debug("NTLM authentication not available! Authentication will fail without a valid hostname and domain name")
193-
self.hostname = self.host
194-
self.targetDomain = self.host
192+
# If we know the host is a DC we can still get the hostname over LDAP if NTLM is not available
193+
if self.is_host_dc() and detect_if_ip(self.host):
194+
self.hostname, self.domain = LDAPResolution(self.host).get_resolution()
195+
self.targetDomain = self.domain
196+
# If we can't authenticate with NTLM and the target is supplied as a FQDN we must parse it
197+
else:
198+
# Check if the host is a valid IP address, if not we parse the FQDN in the Exception
199+
import socket
200+
socket.inet_aton(self.host)
201+
self.logger.debug("NTLM authentication not available! Authentication will fail without a valid hostname and domain name")
202+
self.hostname = self.host
203+
self.targetDomain = self.host
195204
except OSError:
196205
if self.host.count(".") >= 1:
197206
self.hostname = self.host.split(".")[0]
198207
self.targetDomain = ".".join(self.host.split(".")[1:])
199208
else:
200209
self.hostname = self.host
201210
self.targetDomain = self.host
211+
except Exception as e:
212+
self.logger.debug(f"Error getting hostname from LDAP: {e}")
213+
self.hostname = self.host
214+
self.targetDomain = self.host
202215

203216
if self.args.domain:
204217
self.domain = self.args.domain
@@ -283,21 +296,12 @@ def print_host_info(self):
283296
self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.targetDomain}) ({signing}) ({smbv1}) {ntlm}")
284297

285298
if self.args.generate_hosts_file or self.args.generate_krb5_file:
286-
from impacket.dcerpc.v5 import nrpc, epm
287-
self.logger.debug("Performing authentication attempts...")
288-
isdc = False
289-
try:
290-
epm.hept_map(self.host, nrpc.MSRPC_UUID_NRPC, protocol="ncacn_ip_tcp")
291-
isdc = True
292-
except DCERPCException:
293-
self.logger.debug("Error while connecting to host: DCERPCException, which means this is probably not a DC!")
294-
295299
if self.args.generate_hosts_file:
296300
with open(self.args.generate_hosts_file, "a+") as host_file:
297-
dc_part = f" {self.targetDomain}" if isdc else ""
301+
dc_part = f" {self.targetDomain}" if self.isdc else ""
298302
host_file.write(f"{self.host} {self.hostname}.{self.targetDomain}{dc_part} {self.hostname}\n")
299-
self.logger.debug(f"{self.host} {self.hostname}.{self.targetDomain}{dc_part} {self.hostname}")
300-
elif self.args.generate_krb5_file and isdc:
303+
self.logger.debug(f"Line added to {self.args.generate_hosts_file} {self.host} {self.hostname}.{self.targetDomain}{dc_part} {self.hostname}")
304+
elif self.args.generate_krb5_file and self.isdc:
301305
with open(self.args.generate_krb5_file, "w+") as host_file:
302306
data = f"""
303307
[libdefaults]
@@ -658,6 +662,18 @@ def generate_tgt(self):
658662
except Exception as e:
659663
self.logger.fail(f"Failed to get TGT: {e}")
660664

665+
def is_host_dc(self):
666+
from impacket.dcerpc.v5 import nrpc, epm
667+
self.logger.debug("Performing authentication attempts...")
668+
try:
669+
epm.hept_map(self.host, nrpc.MSRPC_UUID_NRPC, protocol="ncacn_ip_tcp")
670+
self.isdc = True
671+
return True
672+
except DCERPCException:
673+
self.logger.debug("Error while connecting to host: DCERPCException, which means this is probably not a DC!")
674+
self.isdc = False
675+
return False
676+
661677
@requires_admin
662678
def execute(self, payload=None, get_output=False, methods=None) -> str:
663679
"""

0 commit comments

Comments
 (0)