Skip to content

Commit 1c1194a

Browse files
authored
Merge pull request Pennyw0rth#666 from termanix/dc-list
REopen Update --dc-list Now check trusted domains DCs
2 parents 1a37573 + ed88bee commit 1c1194a

3 files changed

Lines changed: 121 additions & 108 deletions

File tree

nxc/modules/enum_trusts.py

Lines changed: 2 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from impacket.ldap import ldapasn1 as ldapasn1_impacket
21

32

43
class NXCModule:
@@ -8,7 +7,7 @@ class NXCModule:
87
"""
98

109
name = "enum_trusts"
11-
description = "Extract all Trust Relationships, Trusting Direction, and Trust Transitivity"
10+
description = "[REMOVED] Extract all Trust Relationships, Trusting Direction, and Trust Transitivity"
1211
supported_protocols = ["ldap"]
1312
opsec_safe = True
1413
multiple_hosts = True
@@ -17,70 +16,4 @@ def options(self, context, module_options):
1716
pass
1817

1918
def on_login(self, context, connection):
20-
search_filter = "(&(objectClass=trustedDomain))"
21-
attributes = ["flatName", "trustPartner", "trustDirection", "trustAttributes"]
22-
23-
context.log.debug(f"Search Filter={search_filter}")
24-
resp = connection.ldap_connection.search(searchFilter=search_filter, attributes=attributes, sizeLimit=0)
25-
26-
trusts = []
27-
context.log.debug(f"Total of records returned {len(resp)}")
28-
for item in resp:
29-
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
30-
continue
31-
flat_name = ""
32-
trust_partner = ""
33-
trust_direction = ""
34-
trust_transitive = []
35-
try:
36-
for attribute in item["attributes"]:
37-
if str(attribute["type"]) == "flatName":
38-
flat_name = str(attribute["vals"][0])
39-
elif str(attribute["type"]) == "trustPartner":
40-
trust_partner = str(attribute["vals"][0])
41-
elif str(attribute["type"]) == "trustDirection":
42-
if str(attribute["vals"][0]) == "1":
43-
trust_direction = "Inbound"
44-
elif str(attribute["vals"][0]) == "2":
45-
trust_direction = "Outbound"
46-
elif str(attribute["vals"][0]) == "3":
47-
trust_direction = "Bidirectional"
48-
elif str(attribute["type"]) == "trustAttributes":
49-
trust_attributes_value = int(attribute["vals"][0])
50-
if trust_attributes_value & 0x1:
51-
trust_transitive.append("Non-Transitive")
52-
if trust_attributes_value & 0x2:
53-
trust_transitive.append("Uplevel-Only")
54-
if trust_attributes_value & 0x4:
55-
trust_transitive.append("Quarantined Domain")
56-
if trust_attributes_value & 0x8:
57-
trust_transitive.append("Forest Transitive")
58-
if trust_attributes_value & 0x10:
59-
trust_transitive.append("Cross Organization")
60-
if trust_attributes_value & 0x20:
61-
trust_transitive.append("Within Forest")
62-
if trust_attributes_value & 0x40:
63-
trust_transitive.append("Treat as External")
64-
if trust_attributes_value & 0x80:
65-
trust_transitive.append("Uses RC4 Encryption")
66-
if trust_attributes_value & 0x100:
67-
trust_transitive.append("Cross Organization No TGT Delegation")
68-
if trust_attributes_value & 0x2000:
69-
trust_transitive.append("PAM Trust")
70-
if not trust_transitive:
71-
trust_transitive.append("Other")
72-
trust_transitive = ", ".join(trust_transitive)
73-
74-
if flat_name and trust_partner and trust_direction and trust_transitive:
75-
trusts.append((flat_name, trust_partner, trust_direction, trust_transitive))
76-
except Exception as e:
77-
context.log.debug(f"Cannot process trust relationship due to error {e}")
78-
79-
if trusts:
80-
context.log.success("Found the following trust relationships:")
81-
for trust in trusts:
82-
context.log.highlight(f"{trust[1]} -> {trust[2]} -> {trust[3]}")
83-
else:
84-
context.log.display("No trust relationships found")
85-
86-
return True
19+
context.log.fail("[REMOVED] This module moved to the --dc-list LDAP flag.")

nxc/modules/pi.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66

77

88
class NXCModule:
9+
"""
10+
Module for running system command as target PID user's
11+
Module by @termanix
12+
"""
13+
914
name = "pi"
1015
description = "Run command as logged on users via Process Injection"
1116
supported_protocols = ["smb"]

nxc/protocols/ldap.py

Lines changed: 114 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -762,51 +762,126 @@ def dc_list(self):
762762
resolv.nameservers = [self.host]
763763
resolv.timeout = self.args.dns_timeout
764764

765+
# Function to resolve and display hostnames
766+
def resolve_and_display_hostname(name, domain_name=None):
767+
prefix = f"[{domain_name}] " if domain_name else ""
768+
try:
769+
# Resolve using DNS server for A, AAAA, CNAME, PTR, and NS records
770+
for record_type in ["A", "AAAA", "CNAME", "PTR", "NS"]:
771+
try:
772+
answers = resolv.resolve(name, record_type, tcp=self.args.dns_tcp)
773+
for rdata in answers:
774+
if record_type in ["A", "AAAA"]:
775+
ip_address = rdata.to_text()
776+
self.logger.highlight(f"{prefix}{name} = {colored(ip_address, host_info_colors[0])}")
777+
return
778+
elif record_type == "CNAME":
779+
self.logger.highlight(f"{prefix}{name} CNAME = {colored(rdata.to_text(), host_info_colors[0])}")
780+
return
781+
elif record_type == "PTR":
782+
self.logger.highlight(f"{prefix}{name} PTR = {colored(rdata.to_text(), host_info_colors[0])}")
783+
return
784+
elif record_type == "NS":
785+
self.logger.highlight(f"{prefix}{name} NS = {colored(rdata.to_text(), host_info_colors[0])}")
786+
return
787+
except resolver.NXDOMAIN:
788+
self.logger.fail(f"{prefix}{name} = Host not found (NXDOMAIN)")
789+
except resolver.Timeout:
790+
self.logger.fail(f"{prefix}{name} = Connection timed out")
791+
except resolver.NoAnswer:
792+
self.logger.fail(f"{prefix}{name} = DNS server did not respond")
793+
except Exception as e:
794+
self.logger.fail(f"{prefix}{name} encountered an unexpected error: {e}")
795+
except Exception as e:
796+
self.logger.fail(f"Skipping item(dNSHostName) {prefix}{name}, error: {e}")
797+
798+
# Find all domain controllers in the current domain
799+
self.logger.info("Enumerating Domain Controllers in current domain...")
765800
search_filter = "(&(objectCategory=computer)(primaryGroupId=516))"
766801
attributes = ["dNSHostName"]
802+
resp = self.search(search_filter, attributes)
803+
resp_parse = parse_result_attributes(resp)
804+
for item in resp_parse:
805+
if "dNSHostName" in item: # Get dNSHostName attribute
806+
name = item["dNSHostName"]
807+
resolve_and_display_hostname(name)
808+
809+
# Find all trusted domains
810+
self.logger.info("Enumerating Trusted Domains...")
811+
search_filter = "(objectClass=trustedDomain)"
812+
attributes = ["name", "trustDirection", "trustType", "trustAttributes", "flatName"]
767813
resp = self.search(search_filter, attributes, 0)
768-
resp_parsed = parse_result_attributes(resp)
814+
trust_resp_parse = parse_result_attributes(resp)
769815

770-
for item in resp_parsed:
771-
name = item.get("dNSHostName", "") # Get dNSHostName attribute or empty string
816+
for trust in trust_resp_parse:
772817
try:
773-
# Resolve using DNS server for A, AAAA, CNAME, PTR, and NS records
774-
if name:
775-
found_record = False # Flag to check if any record is found
776-
777-
for record_type in ["A", "AAAA", "CNAME", "PTR", "NS"]:
778-
if found_record:
779-
break # If a record has been found, stop checking further
780-
781-
try:
782-
answers = resolv.resolve(name, record_type, tcp=self.args.dns_tcp)
783-
for rdata in answers:
784-
if record_type in ["A", "AAAA"]:
785-
ip_address = rdata.to_text()
786-
self.logger.highlight(f"{name} = {colored(ip_address, host_info_colors[0])}")
787-
found_record = True # Set flag to true since a record is found
788-
elif record_type == "CNAME":
789-
self.logger.highlight(f"{name} CNAME = {colored(rdata.to_text(), host_info_colors[0])}")
790-
found_record = True
791-
elif record_type == "PTR":
792-
self.logger.highlight(f"{name} PTR = {colored(rdata.to_text(), host_info_colors[0])}")
793-
found_record = True
794-
elif record_type == "NS":
795-
self.logger.highlight(f"{name} NS = {colored(rdata.to_text(), host_info_colors[0])}")
796-
found_record = True
797-
except resolv.NXDOMAIN:
798-
self.logger.fail(f"{name} = Host not found (NXDOMAIN)")
799-
except resolv.Timeout:
800-
self.logger.fail(f"{name} = Connection timed out")
801-
except resolv.NoAnswer:
802-
self.logger.fail(f"{name} = DNS server did not respond")
803-
except Exception as e:
804-
self.logger.fail(f"{name} encountered an unexpected error: {e}")
805-
else:
806-
self.logger.fail("dNSHostName value is empty, unable to process.")
818+
trust_name = trust["name"]
819+
trust_flat_name = trust["flatName"]
820+
trust_direction = int(trust["trustDirection"])
821+
trust_type = int(trust["trustType"])
822+
trust_attributes = int(trust["trustAttributes"])
823+
824+
# See: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/e9a2d23c-c31e-4a6f-88a0-6646fdb51a3c
825+
trust_attribute_flags = {
826+
0x1: "Non-Transitive",
827+
0x2: "Uplevel-Only",
828+
0x4: "Quarantined Domain",
829+
0x8: "Forest Transitive",
830+
0x10: "Cross Organization",
831+
0x20: "Within Forest",
832+
0x40: "Treat as External",
833+
0x80: "Uses RC4 Encryption",
834+
0x200: "Cross Organization No TGT Delegation",
835+
0x800: "Cross Organization Enable TGT Delegation",
836+
0x2000: "PAM Trust"
837+
}
838+
839+
# For check if multiple posibble flags, like Uplevel-Only, Treat as External
840+
trust_attributes_text = ", ".join(
841+
text for flag, text in trust_attribute_flags.items()
842+
if trust_attributes & flag
843+
) or "Other" # If Trust attrs not known
844+
845+
# Convert trust direction/type to human-readable format
846+
direction_text = {
847+
0: "Disabled",
848+
1: "Inbound",
849+
2: "Outbound",
850+
3: "Bidirectional",
851+
}[trust_direction]
852+
853+
trust_type_text = {
854+
1: "Windows NT",
855+
2: "Active Directory",
856+
3: "Kerberos",
857+
4: "Unknown",
858+
5: "Azure Active Directory",
859+
}[trust_type]
860+
861+
self.logger.info(f"Processing trusted domain: {trust_name} ({trust_flat_name})")
862+
self.logger.info(f"Trust type: {trust_type_text}, Direction: {direction_text}, Trust Attributes: {trust_attributes_text}")
863+
807864
except Exception as e:
808-
self.logger.fail("General Error:", exc_info=True)
809-
self.logger.fail(f"Skipping item(dNSHostName) {name}, error: {e}")
865+
self.logger.fail(f"Failed {e} in trust entry: {trust}")
866+
867+
# Only process if it's an Active Directory trust
868+
if int(trust_type) == 2:
869+
# Try to find domain controllers in trusted domain using DNS
870+
# Check if we can resolve the trusted domain's DC using DNS
871+
dc_dns_name = f"_ldap._tcp.dc._msdcs.{trust_name}"
872+
try:
873+
srv_records = resolv.resolve(dc_dns_name, "SRV", tcp=self.args.dns_tcp)
874+
self.logger.info(f"Found domain controllers for trusted domain {trust_name} via DNS:")
875+
for srv in srv_records:
876+
dc_hostname = str(srv.target).rstrip(".")
877+
self.logger.success(f"Found DC in trusted domain: {colored(dc_hostname, host_info_colors[0], attrs=['bold'])}")
878+
self.logger.highlight(f"{trust_name} -> {direction_text} -> {trust_attributes_text}")
879+
resolve_and_display_hostname(dc_hostname)
880+
except Exception as e:
881+
self.logger.fail(f"Failed to resolve DCs for {trust_name} via DNS: {e}")
882+
else:
883+
self.logger.display(f"Skipping non-Active Directory trust '{trust_name}' with type: {trust_type_text} and direction: {direction_text}")
884+
self.logger.info("Domain Controller enumeration complete.")
810885

811886
def active_users(self):
812887
if len(self.args.active_users) > 0:

0 commit comments

Comments
 (0)