Skip to content

Commit a81cc67

Browse files
authored
Merge branch 'main' into feat/kerberoast-no-preauth
2 parents e17b5ad + fde3de7 commit a81cc67

32 files changed

Lines changed: 776 additions & 103 deletions

nxc/cli.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ def gen_cli_args():
2525
COMMIT = ""
2626
DISTANCE = ""
2727
CODENAME = "SmoothOperator"
28-
nxc_logger.debug(f"NXC VERSION: {VERSION} - {CODENAME} - {COMMIT} - {DISTANCE}")
2928

3029
generic_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
3130
generic_group = generic_parser.add_argument_group("Generic", "Generic options for nxc across protocols")
@@ -133,7 +132,7 @@ def gen_cli_args():
133132
if hasattr(args, "get_output_tries"):
134133
args.get_output_tries = args.get_output_tries * 10
135134

136-
return args
135+
return args, [CODENAME, VERSION, COMMIT, DISTANCE]
137136

138137

139138
def get_module_names():

nxc/config.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import os
21
from os.path import join as path_join
32
import configparser
4-
from nxc.paths import NXC_PATH, DATA_PATH
3+
from nxc.paths import DATA_PATH, CONFIG_PATH
54
from nxc.first_run import first_run_setup
65
from nxc.logger import nxc_logger
76
from ast import literal_eval
@@ -10,25 +9,25 @@
109
nxc_default_config.read(path_join(DATA_PATH, "nxc.conf"))
1110

1211
nxc_config = configparser.ConfigParser()
13-
nxc_config.read(os.path.join(NXC_PATH, "nxc.conf"))
12+
nxc_config.read(CONFIG_PATH)
1413

1514
if "nxc" not in nxc_config.sections():
1615
first_run_setup()
17-
nxc_config.read(os.path.join(NXC_PATH, "nxc.conf"))
16+
nxc_config.read(CONFIG_PATH)
1817

1918
# Check if there are any missing options in the config file
2019
for section in nxc_default_config.sections():
2120
if not nxc_config.has_section(section):
2221
nxc_logger.display(f"Adding missing section '{section}' to nxc.conf")
2322
nxc_config.add_section(section)
24-
with open(path_join(NXC_PATH, "nxc.conf"), "w") as config_file:
23+
with open(CONFIG_PATH, "w") as config_file:
2524
nxc_config.write(config_file)
2625
for option in nxc_default_config.options(section):
2726
if not nxc_config.has_option(section, option):
2827
nxc_logger.display(f"Adding missing option '{option}' in config section '{section}' to nxc.conf")
2928
nxc_config.set(section, option, nxc_default_config.get(section, option))
3029

31-
with open(path_join(NXC_PATH, "nxc.conf"), "w") as config_file:
30+
with open(CONFIG_PATH, "w") as config_file:
3231
nxc_config.write(config_file)
3332

3433
# THESE OPTIONS HAVE TO EXIST IN THE DEFAULT CONFIG FILE

nxc/context.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import configparser
22
import os
33

4+
from nxc.paths import NXC_PATH, CONFIG_PATH
5+
46

57
class Context:
68
def __init__(self, db, logger, args):
79
for key, value in vars(args).items():
810
setattr(self, key, value)
911

1012
self.db = db
11-
self.log_folder_path = os.path.join(os.path.expanduser("~/.nxc"), "logs")
13+
self.log_folder_path = os.path.join(NXC_PATH, "logs")
1214
self.localip = None
1315

1416
self.conf = configparser.ConfigParser()
15-
self.conf.read(os.path.expanduser("~/.nxc/nxc.conf"))
17+
self.conf.read(CONFIG_PATH)
1618

1719
self.log = logger

nxc/first_run.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,16 @@
88

99

1010
def first_run_setup(logger=nxc_logger):
11-
if not exists(TMP_PATH):
12-
mkdir(TMP_PATH)
13-
1411
if not exists(NXC_PATH):
1512
logger.display("First time use detected")
1613
logger.display("Creating home directory structure")
1714
mkdir(NXC_PATH)
15+
if not exists(TMP_PATH):
16+
mkdir(TMP_PATH)
1817

1918
folders = (
2019
"logs",
2120
"modules",
22-
"protocols",
2321
"workspaces",
2422
"obfuscated_scripts",
2523
"screenshots",
@@ -46,7 +44,3 @@ def first_run_setup(logger=nxc_logger):
4644
logger.display("Copying default configuration file")
4745
default_path = path_join(DATA_PATH, "nxc.conf")
4846
shutil.copy(default_path, NXC_PATH)
49-
50-
# if not exists(CERT_PATH):
51-
# if os.name != 'nt':
52-
# if e.errno == errno.ENOENT:

nxc/helpers/logger.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import os
22
from termcolor import colored
3+
from nxc.paths import NXC_PATH
34

45

56
def write_log(data, log_name):
6-
logs_dir = os.path.join(os.path.expanduser("~/.nxc"), "logs")
7+
logs_dir = os.path.join(NXC_PATH, "logs")
78
with open(os.path.join(logs_dir, log_name), "w") as log_output:
89
log_output.write(data)
910

nxc/loaders/protocolloader.py

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22
from importlib.machinery import SourceFileLoader
33
from os import listdir
44
from os.path import join as path_join
5-
from os.path import dirname, exists, expanduser
5+
from os.path import dirname, exists
6+
67
import nxc
78

89

910
class ProtocolLoader:
10-
def __init__(self):
11-
self.nxc_path = expanduser("~/.nxc")
12-
1311
def load_protocol(self, protocol_path):
1412
loader = SourceFileLoader("protocol", protocol_path)
1513
protocol = ModuleType(loader.name)
@@ -18,27 +16,22 @@ def load_protocol(self, protocol_path):
1816

1917
def get_protocols(self):
2018
protocols = {}
21-
protocol_paths = [
22-
path_join(dirname(nxc.__file__), "protocols"),
23-
path_join(self.nxc_path, "protocols"),
24-
]
25-
26-
for path in protocol_paths:
27-
for protocol in listdir(path):
28-
if protocol[-3:] == ".py" and protocol[:-3] != "__init__":
29-
protocol_path = path_join(path, protocol)
30-
protocol_name = protocol[:-3]
31-
32-
protocols[protocol_name] = {"path": protocol_path}
33-
34-
db_file_path = path_join(path, protocol_name, "database.py")
35-
db_nav_path = path_join(path, protocol_name, "db_navigator.py")
36-
protocol_args_path = path_join(path, protocol_name, "proto_args.py")
37-
if exists(db_file_path):
38-
protocols[protocol_name]["dbpath"] = db_file_path
39-
if exists(db_nav_path):
40-
protocols[protocol_name]["nvpath"] = db_nav_path
41-
if exists(protocol_args_path):
42-
protocols[protocol_name]["argspath"] = protocol_args_path
4319

20+
proto_path = path_join(dirname(nxc.__file__), "protocols")
21+
for protocol in listdir(proto_path):
22+
if protocol[-3:] == ".py" and protocol[:-3] != "__init__":
23+
protocol_path = path_join(proto_path, protocol)
24+
protocol_name = protocol[:-3]
25+
26+
protocols[protocol_name] = {"path": protocol_path}
27+
28+
db_file_path = path_join(proto_path, protocol_name, "database.py")
29+
db_nav_path = path_join(proto_path, protocol_name, "db_navigator.py")
30+
protocol_args_path = path_join(proto_path, protocol_name, "proto_args.py")
31+
if exists(db_file_path):
32+
protocols[protocol_name]["dbpath"] = db_file_path
33+
if exists(db_nav_path):
34+
protocols[protocol_name]["nvpath"] = db_nav_path
35+
if exists(protocol_args_path):
36+
protocols[protocol_name]["argspath"] = protocol_args_path
4437
return protocols

nxc/modules/aws-credentials.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
class NXCModule:
2+
"""
3+
Search for aws credentials files on linux and windows machines
4+
5+
Module by Fortress
6+
"""
7+
8+
name = "aws-credentials"
9+
description = "Search for aws credentials files."
10+
supported_protocols = ["ssh", "smb", "winrm"]
11+
opsec_safe = True
12+
multiple_hosts = True
13+
14+
def __init__(self):
15+
self.search_path_linux = "'/home/' '/tmp/'"
16+
self.search_path_win = "'C:\\Users\\', 'C:\\ProgramData\\AWSCLI\\', 'C:\\Temp\\'"
17+
18+
def options(self, context, module_options):
19+
r"""
20+
SEARCH_PATH_LINUX Linux location where to search for aws credentials related files
21+
Default: "'/home/' '/tmp/'"
22+
23+
SEARCH_PATH_WIN Windows locations where to search for aws credentials related files
24+
Default: "'C:\\Users\\', 'C:\\ProgramData\\AWSCLI\\', 'C:\\Temp\\'"
25+
"""
26+
if "SEARCH_PATH_LINUX" in module_options:
27+
self.search_path_linux = module_options["SEARCH_PATH_LINUX"]
28+
29+
if "SEARCH_PATH_WIN" in module_options:
30+
self.search_path_win = module_options["SEARCH_PATH_WIN"]
31+
32+
def on_login(self, context, connection):
33+
# search for aws_credentials-related files on linux systems
34+
if "ssh" in context.protocol:
35+
search_aws_creds_files_payload = f"find {self.search_path_linux} -type f -name credentials -exec grep -l 'aws_' {{}} \\; 2>&1 | grep -v 'Permission denied$'"
36+
search_aws_creds_files_cmd = f'/bin/bash -c "{search_aws_creds_files_payload}"'
37+
output = connection.execute(search_aws_creds_files_cmd)
38+
else:
39+
# search for aws_credentials-related files on windows systems
40+
# we have to exclude "Application Data" as this creates an infinite recursion, see: https://www.reddit.com/r/PowerShell/comments/17pctnv/symbolic_link_application_data_in_appdatalocal/
41+
search_aws_creds_files_payload_win = f"Get-ChildItem -Path {self.search_path_win} -Recurse -Force -Include 'credentials' -ErrorAction SilentlyContinue | Where-Object {{ Select-String -Path $_.FullName -Pattern 'aws' -Quiet }} | Select-Object -ExpandProperty FullName"
42+
search_aws_creds_files_cmd_win = f'powershell.exe "{search_aws_creds_files_payload_win}"'
43+
output = connection.execute(search_aws_creds_files_cmd_win, True)
44+
45+
if output:
46+
context.log.success("The following files were found:")
47+
for line in output.splitlines():
48+
context.log.highlight(line.rstrip())

nxc/modules/backup_operator.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from impacket.smbconnection import SessionError
77
from impacket.dcerpc.v5 import transport, rrp
88
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
9-
109
from nxc.paths import NXC_PATH
1110

1211

nxc/modules/badsuccessor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ def on_login(self, context, connection):
187187
for dc in parsed_resp:
188188
if "2025" in dc["operatingSystem"]:
189189
out = connection.resolver(dc["dNSHostName"])
190-
dc_ip = out[0] if out else "Unknown IP"
190+
dc_ip = out["host"] if out else "Unknown IP"
191191
context.log.success(f"Found domain controller with operating system Windows Server 2025: {dc_ip} ({dc['dNSHostName']})")
192192
else:
193193
context.log.fail("No domain controller with operating system Windows Server 2025 found, attack not possible. Enumerate dMSA objects anyway.")

nxc/modules/change-password.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ def options(self, context, module_options):
3434
netexec smb <DC_IP> -u username -p password -M change-password -o USER='target_user' NEWPASS='target_user_newpass'
3535
netexec smb <DC_IP> -u username -p password -M change-password -o USER='target_user' NEWNTHASH='target_user_newnthash'
3636
"""
37-
self.context = context
3837
self.newpass = module_options.get("NEWPASS")
3938
self.newhash = module_options.get("NEWNTHASH")
4039
self.target_user = module_options.get("USER")
@@ -78,6 +77,7 @@ def authenticate(self, context, connection, protocol, anonymous=False):
7877
raise
7978

8079
def on_login(self, context, connection):
80+
self.context = context
8181
target_username = self.target_user or connection.username
8282
target_domain = connection.domain
8383

0 commit comments

Comments
 (0)