Skip to content

Commit 0ee377f

Browse files
committed
Resolve merge conflicts and sync fork with upstream
2 parents 7d1ab3e + 5454d63 commit 0ee377f

37 files changed

Lines changed: 1651 additions & 1241 deletions

netexec.spec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ a = Analysis(
1919
'aardwolf.commons.iosettings',
2020
'aardwolf.commons.target',
2121
'aardwolf.protocol.x224.constants',
22+
'certipy',
2223
'impacket.examples.secretsdump',
2324
'impacket.examples.regsecrets',
2425
'impacket.dcerpc.v5.lsat',
@@ -48,6 +49,7 @@ a = Analysis(
4849
'nxc.helpers.msada_guids',
4950
'nxc.helpers.ntlm_parser',
5051
'paramiko',
52+
'pefile',
5153
'pypsrp.client',
5254
'pylnk3',
5355
'pypykatz',

nxc/connection.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ def __init__(self, args, db, target):
136136
self.db = db
137137
self.logger = nxc_logger
138138
self.conn = None
139+
self.output_file_template = None
140+
self.output_filename = None
139141

140142
# Authentication info
141143
self.password = ""
@@ -160,11 +162,6 @@ def __init__(self, args, db, target):
160162
self.local_ip = None
161163
self.dns_server = self.args.dns_server
162164

163-
# Construct the output file template using os.path.join for OS compatibility
164-
base_log_dir = os.path.join(os.path.expanduser(NXC_PATH), "logs")
165-
filename_pattern = f"{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")
166-
self.output_file_template = os.path.join(base_log_dir, "{output_folder}", filename_pattern)
167-
168165
# DNS resolution
169166
dns_result = self.resolver(target)
170167
if dns_result:
@@ -244,6 +241,14 @@ def proto_flow(self):
244241
else:
245242
self.logger.debug("Created connection object")
246243
self.enum_host_info()
244+
245+
# Construct the output file template using os.path.join for OS compatibility
246+
base_log_dir = os.path.join(NXC_PATH, "logs")
247+
filename_pattern = f"{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")
248+
self.output_file_template = os.path.join(base_log_dir, "{output_folder}", filename_pattern)
249+
# Default output filename for logs
250+
self.output_filename = os.path.join(base_log_dir, filename_pattern)
251+
247252
self.print_host_info()
248253
if self.login() or (self.username == "" and self.password == ""):
249254
if hasattr(self.args, "module") and self.args.module:
@@ -324,7 +329,7 @@ def over_fail_limit(self, username):
324329
if self.failed_logins == self.args.fail_limit:
325330
return True
326331

327-
if username in user_failed_logins and self.args.ufail_limit == user_failed_logins[username]:
332+
if username in user_failed_logins and self.args.ufail_limit == user_failed_logins[username]: # noqa: SIM103
328333
return True
329334

330335
return False

nxc/data/procdump/procdump.exe

773 KB
Binary file not shown.

nxc/helpers/even6_parser.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import uuid
55

66
from datetime import datetime
7+
from nxc.logger import nxc_logger
78

89

910
class Substitution:
@@ -45,7 +46,7 @@ def xml(self, template=None):
4546
elif self._type == 0xf:
4647
return str(uuid.UUID(bytes_le=value.data))
4748
else:
48-
print("Unknown value type", hex(value.type))
49+
nxc_logger.display(f"Unknown value type {hex(value.type)}")
4950

5051

5152
class Value:
@@ -70,7 +71,7 @@ def __init__(self, buf, offset):
7071
elif next_token[0] == 0x0e:
7172
self._value = Substitution(buf, offset + 1 + self._name.length)
7273
else:
73-
print("Unknown attribute next_token", hex(next_token[0]), hex(offset + 1 + self._name.length))
74+
nxc_logger.display(f"Unknown attribute next_token {hex(next_token[0])} {hex(offset + 1 + self._name.length)}")
7475

7576
self.length = 1 + self._name.length + self._value.length
7677

@@ -123,7 +124,7 @@ def __init__(self, buf, offset):
123124
elif next_token == 0x0e or next_token == 0x0d:
124125
element = Substitution(buf, ofs)
125126
else:
126-
print("Unknown intern next_token", hex(next_token), hex(ofs))
127+
nxc_logger.display(f"Unknown intern next_token {hex(next_token)} {hex(ofs)}")
127128
break
128129

129130
self._children.append(element)
@@ -135,7 +136,7 @@ def __init__(self, buf, offset):
135136
ofs += 1
136137
break
137138
else:
138-
print("Unknown element next_token", hex(next_token), hex(ofs))
139+
nxc_logger.display(f"Unknown element next_token {hex(next_token)} {hex(ofs)}")
139140
break
140141

141142
self.length = ofs - offset
@@ -181,7 +182,7 @@ def __init__(self, buf, offset):
181182

182183
self.length = 22 + self._xml.length + 5 + num_values * 4 + values_length
183184
else:
184-
print("Unknown template token", hex(next_token))
185+
nxc_logger.display(f"Unknown template token {hex(next_token)}")
185186

186187
def xml(self, template=None):
187188
return self._xml.xml(self)
@@ -196,7 +197,7 @@ def __init__(self, buf, offset):
196197
elif next_token == 0x01 or next_token == 0x41:
197198
self._element = Element(buf, offset + 4)
198199
else:
199-
print("Unknown binxml token", hex(next_token))
200+
nxc_logger.display(f"Unknown binxml token {hex(next_token)}")
200201

201202
self.length = 4 + self._element.length
202203

nxc/helpers/pfx.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@ def sign_authpack(self, data, wrap_signed=False):
129129
def setup(self, dh_params=None):
130130
self.issuer = self.certificate.issuer.native["common_name"]
131131
if dh_params is None:
132-
print("Generating DH params...")
133-
print("DH params generated.")
132+
nxc_logger.display("Generating DH params...")
133+
nxc_logger.display("DH params generated.")
134134
else:
135135
if isinstance(dh_params, dict):
136136
self.diffie = DirtyDH.from_dict(dh_params)
@@ -488,8 +488,8 @@ def pfx_auth(self):
488488
return None
489489

490490
username = self.args.username[0]
491-
timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H%M%S").replace(":", "-")
492-
log_ccache = os.path.normpath(os.path.expanduser(f"{NXC_PATH}/logs/{self.hostname}_{self.host}_{timestamp}-{username}.ccache"))
491+
basename = f"{self.hostname}_{self.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}-{username}.ccache"
492+
log_ccache = os.path.normpath(os.path.expanduser(f"{NXC_PATH}/logs/{basename}"))
493493

494494
# Request a TGT with the cert data
495495
req = ini.build_asreq(self.domain, username)

nxc/logger.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ def __init__(self, extra=None, merge_extra=False):
101101
logging.getLogger("minidump").disabled = True
102102
logging.getLogger("lsassy").disabled = True
103103
logging.getLogger("dploot").disabled = True
104+
logging.getLogger("certipy").disabled = True
104105
logging.getLogger("aardwolf").disabled = True
105106
logging.getLogger("unicrypto").disabled = True
106107
logging.getLogger("asyncio").setLevel(logging.ERROR)
@@ -178,7 +179,7 @@ def add_file_log(self, log_file=None):
178179
file_creation = False
179180

180181
if not os.path.isfile(output_file):
181-
open(output_file, "x")
182+
open(output_file, "x") # noqa: SIM115
182183
file_creation = True
183184

184185
file_handler = RotatingFileHandler(output_file, maxBytes=100000, encoding="utf-8")

nxc/modules/backup_operator.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import contextlib
2-
import os
3-
import datetime
2+
from time import sleep
43

54
from impacket.examples.secretsdump import SAMHashes, LSASecrets, LocalOperations
65
from impacket.smbconnection import SessionError
76
from impacket.dcerpc.v5 import transport, rrp
87
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
98
from nxc.helpers.misc import CATEGORY
10-
from nxc.paths import NXC_PATH
119

1210

1311
class NXCModule:
@@ -62,7 +60,7 @@ def on_login(self, context, connection):
6260
dce.disconnect()
6361

6462
# copy remote file to local
65-
log_path = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.".replace(":", "-"))
63+
log_path = f"{connection.output_filename}."
6664
for hive in ["SAM", "SECURITY", "SYSTEM"]:
6765
connection.get_file_single(hive, log_path + hive)
6866

@@ -100,6 +98,7 @@ def parse_sam(secret):
10098

10199
context.log.display(f"Cleaning dump with user {self.domain_admin} and hash {self.domain_admin_hash} on domain {connection.domain}")
102100
connection.execute("del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM")
101+
sleep(0.2)
103102
for hive in ["SAM", "SECURITY", "SYSTEM"]:
104103
try:
105104
out = connection.conn.listPath("SYSVOL", hive)
@@ -118,3 +117,11 @@ def parse_sam(secret):
118117
context.log.display("netexec smb dc_ip -u user -p pass -x \"del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM\"") # noqa: Q003
119118
else:
120119
context.log.display("Successfully deleted dump files !")
120+
121+
def _strip_root_key(self, dce, key_name):
122+
# Let's strip the root key
123+
key_name.split("\\")[0]
124+
sub_key = "\\".join(key_name.split("\\")[1:])
125+
ans = rrp.hOpenLocalMachine(dce)
126+
h_root_key = ans["phKey"]
127+
return h_root_key, sub_key

nxc/modules/certipy-find.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/usr/bin/env python3
2+
import json
3+
from os import makedirs
4+
from certipy.commands.find import Find
5+
from certipy.lib.target import Target, DnsResolver
6+
from certipy.lib.formatting import pretty_print
7+
from datetime import datetime
8+
9+
from nxc.helpers.misc import CATEGORY
10+
from nxc.paths import NXC_PATH
11+
12+
13+
class NXCModule:
14+
"""Module made by: @NeffIsBack, @gatariee"""
15+
name = "certipy-find"
16+
description = "certipy find command with options to export the result to text/csv/json. Default: Show only vulnerable templates"
17+
supported_protocols = ["ldap"]
18+
category = CATEGORY.ENUMERATION
19+
20+
def __init__(self, context=None, module_options=None):
21+
self.context = context
22+
self.module_options = module_options
23+
24+
def options(self, context, module_options):
25+
"""
26+
VULN Show only vulnerable configurations (Default: True)
27+
ENABLED Show only enabled templates
28+
29+
Export options:
30+
TEXT Export results to a plain text file
31+
CSV Export results to a CSV file
32+
JSON Export results to a JSON file
33+
"""
34+
self.vuln = True
35+
self.enabled = False
36+
self.output_path = f"{NXC_PATH}/modules/certipy-find"
37+
self.json = False
38+
self.csv = False
39+
self.text = False
40+
41+
if "VULN" in module_options:
42+
self.vuln = module_options["VULN"].lower() in ["true", "1", "yes"]
43+
if "ENABLED" in module_options:
44+
self.enabled = module_options["ENABLED"].lower() in ["true", "1", "yes"]
45+
46+
# Export options
47+
if "JSON" in module_options:
48+
self.json = module_options["JSON"].lower() in ["true", "1", "yes"]
49+
if "CSV" in module_options:
50+
self.csv = module_options["CSV"].lower() in ["true", "1", "yes"]
51+
if "TEXT" in module_options:
52+
self.text = module_options["TEXT"].lower() in ["true", "1", "yes"]
53+
54+
def on_login(self, context, connection):
55+
resolv = DnsResolver.create(connection.args.dns_server if connection.args.dns_server else connection.host)
56+
target = Target(
57+
resolver=resolv,
58+
domain=connection.domain,
59+
username=connection.username,
60+
password=connection.password,
61+
lmhash=connection.lmhash,
62+
nthash=connection.nthash,
63+
target_ip=connection.host,
64+
ldap_port=connection.port,
65+
ldap_scheme="ldaps" if connection.port == 636 else "ldap",
66+
ldap_signing=connection.signing_required,
67+
ldap_channel_binding=connection.cbt_status in ["Always", "When Supported"],
68+
)
69+
70+
finder = Find(
71+
target=target,
72+
json=self.json,
73+
csv=self.csv,
74+
text=self.text,
75+
output_path=self.output_path,
76+
stdout=True,
77+
vulnerable=self.vuln,
78+
enabled=self.enabled,
79+
)
80+
81+
# Get templates and CAs
82+
templates = finder.get_certificate_templates()
83+
cas = finder.get_certificate_authorities()
84+
finder._link_cas_and_templates(cas, templates)
85+
86+
# Get OIDs
87+
oids = finder.get_issuance_policies()
88+
89+
# Process information
90+
finder._link_templates_and_policies(templates, oids)
91+
finder._process_ca_properties(cas)
92+
finder._process_template_properties(templates)
93+
94+
output = finder.get_output_for_text_and_json(templates, cas, oids)
95+
pretty_print(output, print_func=context.log.highlight)
96+
97+
# Save to disk if any export option specified
98+
if self.json or self.csv or self.text:
99+
makedirs(self.output_path, exist_ok=True)
100+
101+
filename = f"certipy_{connection.hostname}_{connection.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")
102+
if self.json:
103+
with open(f"{self.output_path}/{filename}.json", "w") as f:
104+
json.dump(
105+
output,
106+
f,
107+
indent=2,
108+
default=str,
109+
)
110+
if self.csv:
111+
template_output = finder.get_template_output_for_csv(output)
112+
ca_output = finder.get_ca_output_for_csv(output)
113+
with open(f"{self.output_path}/{filename}-templates.csv", "w") as f:
114+
f.write(template_output)
115+
with open(f"{self.output_path}/{filename}-cas.csv", "w") as f:
116+
f.write(ca_output)
117+
if self.text:
118+
with open(f"{self.output_path}/{filename}.txt", "w") as f:
119+
pretty_print(output, print_func=lambda x: f.write(x + "\n"))

nxc/modules/daclread.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ def options(self, context, module_options):
249249
context.log.debug("There is a target specified!")
250250
if isfile(module_options[option]):
251251
try:
252-
target_file = open(module_options[option])
252+
target_file = open(module_options[option]) # noqa: SIM115
253253
for line in target_file:
254254
context.log.debug(f"Adding target from file: {line}")
255255
self.targets.append((line.strip(), SEARCH_FILTERS[option]))

nxc/modules/dump-computers.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
class NXCModule:
66
name = "dump-computers"
7-
description = "Dumps all computers in the domain"
7+
description = "Dumps FQDN and OS of all computers in the domain"
88
supported_protocols = ["ldap"]
99
category = CATEGORY.ENUMERATION
1010

@@ -43,8 +43,11 @@ def on_login(self, context, connection):
4343
context.log.debug(f"Total number of records returned: {len(resp_parsed)}")
4444

4545
for item in resp_parsed:
46-
dns_host_name = item["dNSHostName"]
46+
dns_host_name = item.get("dNSHostName")
4747
operating_system = item.get("operatingSystem", "Unknown OS")
48+
if not dns_host_name:
49+
context.log.debug(f"Skipping computer without dNSHostName: {item.get('cn', '<unknown>')}")
50+
continue
4851

4952
if self.netbios_only:
5053
netbios_name = dns_host_name.split(".")[0]

0 commit comments

Comments
 (0)