11import sys
2+ from impacket .dcerpc .v5 import samr , epm , transport
3+ from impacket .dcerpc .v5 .rpcrt import DCERPCException
24
35class NXCModule :
46 """
57 Module for changing or resetting user passwords
68 Module by Fagan Afandiyev
7-
8- This is NXC implementation of changepasswd.py from impacket
99 """
1010
1111 name = "change-password"
@@ -17,122 +17,125 @@ class NXCModule:
1717 def options (self , context , module_options ):
1818 """
1919 Module options for password change
20+
21+ Required options:
22+ If STATUS_PASSWORD_MUST_CHANGE or STATUS_PASSWORD_EXPIRED (Change password for current user)
23+ netexec smb <DC_IP> -u username -p oldpass -M change-password -o OLDPASS='oldpass' NEWPASS='newpass'
24+ netexec smb <DC_IP> -u username -H oldnthash -M change-password -o OLDNTHASH='oldnthash' NEWPASS='newpass'
25+
26+ If want to change other user's password (with forcechangepassword priv or admin rights)
27+ netexec smb <DC_IP> -u username -p password -M change-password -o USER='target_user' NEWPASS='target_user_newpass'
28+ netexec smb <DC_IP> -u username -p password -M change-password -o USER='target_user' NEWNTHASH='target_user_newnthash'
2029
21- Supported options:
22- - NEWPASS: New password to set
23- - NEWHASH: New password hash (NTHASH or LMHASH:NTHASH)
24- - OLDPASS: Current password (optional for reset)
25- - USER: User whose password to change (default is current user)
26- - RESET: Set to True to reset password with admin privileges
30+ NEWPASS or NEWHASH
2731 """
32+ self .context = context
2833 self .newpass = module_options .get ("NEWPASS" )
29- self .newhash = module_options .get ("NEWHASH " )
34+ self .newhash = module_options .get ("NEWNTHASH " )
3035 self .oldpass = module_options .get ("OLDPASS" )
36+ self .oldhash = module_options .get ("OLDNTHASH" )
3137 self .target_user = module_options .get ("USER" )
3238 self .reset = module_options .get ("RESET" , True )
3339
3440 if not self .newpass and not self .newhash :
3541 context .log .error ("Either NEWPASS or NEWHASH is required!" )
3642 sys .exit (1 )
3743
44+ def authenticate (self , context , connection , protocol , anonymous = False ):
45+ try :
46+ string_binding = epm .hept_map (connection .host , samr .MSRPC_UUID_SAMR , protocol = protocol )
47+ rpctransport = transport .DCERPCTransportFactory (string_binding )
48+ rpctransport .setRemoteHost (connection .host )
49+
50+ if anonymous :
51+ rpctransport .set_credentials ("" , "" , "" , "" , "" , "" )
52+ rpctransport .set_kerberos (False , None )
53+ context .log .info ("Connecting with null session credentials." )
54+ else :
55+ rpctransport .set_credentials (
56+ connection .username ,
57+ connection .password ,
58+ connection .domain ,
59+ connection .lmhash ,
60+ connection .nthash ,
61+ aesKey = connection .aesKey ,
62+ )
63+ context .log .info (f"Connecting as { connection .domain } \\ { connection .username } " )
64+
65+ dce = rpctransport .get_dce_rpc ()
66+ dce .connect ()
67+ context .log .info ("[+] Successfully connected to DCE/RPC" )
68+ dce .bind (samr .MSRPC_UUID_SAMR )
69+ context .log .debug ("[+] Successfully bound to SAMR" )
70+ return dce
71+
72+ except DCERPCException as e :
73+ context .log .error (f"DCE/RPC Exception: { e !s} " )
74+ raise
75+
3876 def on_login (self , context , connection ):
39- # Determine which user's password to change (prioritize TARGETUSER)
40- target_username = self .target_user if self .target_user else connection .username
77+ target_username = self .target_user or connection .username
4178 target_domain = connection .domain
4279
43- # Prepare authentication details
44- username = connection .username
45- domain = connection .domain
46- password = connection .password
47- lmhash , nthash = "" , ""
48-
49- if context .hash and ":" in context .hash [0 ]:
50- hash_list = context .hash [0 ].split (":" )
51- nthash = hash_list [- 1 ]
52- lmhash = hash_list [0 ]
53- elif context .hash :
54- nthash = context .hash [0 ]
55- lmhash = "00000000000000000000000000000000"
56-
57- # Prepare new password details
58- new_password = None # Start with None for new_password
5980 new_lmhash , new_nthash = "" , ""
60-
61- if self .newpass :
62- # If NEWPASS is provided, use it
63- new_password = self .newpass
6481
6582 if self .newhash :
66- # If NEWHASH is provided, split the hash and set new password to None
6783 try :
6884 new_lmhash , new_nthash = self .newhash .split (":" )
69- new_password = None # Don't set a plain password when using a hash
7085 except ValueError :
71- new_lmhash = "00000000000000000000000000000000"
7286 new_nthash = self .newhash
73- new_password = None # Ensure no password is set for hash-only change
74-
75- # Use the appropriate protocol based on netexec's context
76- protocol = "smb"
7787
7888 try :
79- if protocol == "smb" :
80- self ._smb_samr_change (
81- context , connection , target_username , target_domain , username , domain , password ,
82- lmhash , nthash , self .oldpass , new_password , new_lmhash , new_nthash
83- )
84- else :
85- context .log .error (f"Unsupported protocol: { protocol } " )
86- sys .exit (1 )
89+ self .anonymous = False
90+ self .dce = self .authenticate (context , connection , protocol = "ncacn_np" , anonymous = self .anonymous )
8791 except Exception as e :
88- context .log .error (f"Password change failed: { e !s} " )
89-
90- def _smb_samr_change (self , context , connection , target_username , target_domain ,
91- username , domain , password , lmhash , nthash ,
92- old_password , new_password , new_lmhash , new_nthash ):
93- """Change password using SMB-SAMR protocol"""
94- from impacket .dcerpc .v5 import samr , epm , transport
95-
96- if not new_password and not new_lmhash and not new_nthash :
97- context .log .error ("New password or hash cannot be None or empty" )
98- return
99- string_binding = epm .hept_map (connection .host , samr .MSRPC_UUID_SAMR , protocol = "ncacn_np" )
100- rpc_transport = transport .DCERPCTransportFactory (string_binding )
101- rpc_transport .setRemoteHost (connection .host )
102-
103- if hasattr (rpc_transport , "set_credentials" ):
104- rpc_transport .set_credentials (username , password , domain , lmhash , nthash )
92+ if "STATUS_PASSWORD_MUST_CHANGE" in str (e ) or "STATUS_PASSWORD_EXPIRED" in str (e ):
93+ context .log .warning ("Password must be changed. Trying with null session." )
94+ self .anonymous = True
95+ self .dce = self .authenticate (context , connection , protocol = "ncacn_ip_tcp" , anonymous = self .anonymous )
96+ elif "STATUS_LOGON_FAILURE" in str (e ):
97+ context .log .critical ("Authentication failure: wrong credentials." )
98+ return False
99+ else :
100+ raise
105101
106- dce = rpc_transport .get_dce_rpc ()
107- dce .connect ()
108- dce .bind (samr .MSRPC_UUID_SAMR )
102+ try :
103+ self ._smb_samr_change (context , connection , target_username , target_domain , self .oldhash , self .newpass , new_nthash )
104+ except Exception as e :
105+ context .log .error (f"Password change failed: { e } " )
109106
107+ def _smb_samr_change (self , context , connection , target_username , target_domain , oldHash , newPassword , newHash ):
110108 try :
111- # Retrieve the user handle by connecting to SAMR and looking up the username.
112- server_handle = samr .hSamrConnect (dce , connection .host + "\x00 " )["ServerHandle" ]
113- domain_sid = samr .hSamrLookupDomainInSamServer (dce , server_handle , target_domain )["DomainId" ]
114- domain_handle = samr .hSamrOpenDomain (dce , server_handle , domainId = domain_sid )["DomainHandle" ]
115- user_rid = samr .hSamrLookupNamesInDomain (dce , domain_handle , (target_username ,))["RelativeIds" ]["Element" ][0 ]
116- user_handle = samr .hSamrOpenUser (dce , domain_handle , userId = user_rid )["UserHandle" ]
117- if self .reset :
118- # Reset the password
119- samr .hSamrSetNTInternal1 (dce , user_handle , new_password , new_nthash )
120- context .log .success (f"Successfully reset password for { target_username } " )
109+ if not self .anonymous :
110+ server_handle = samr .hSamrConnect (self .dce , connection .host + "\x00 " )["ServerHandle" ]
111+ domain_sid = samr .hSamrLookupDomainInSamServer (self .dce , server_handle , target_domain )["DomainId" ]
112+ domain_handle = samr .hSamrOpenDomain (self .dce , server_handle , domainId = domain_sid )["DomainHandle" ]
113+ user_rid = samr .hSamrLookupNamesInDomain (self .dce , domain_handle , (target_username ,))["RelativeIds" ]["Element" ][0 ]
114+ user_handle = samr .hSamrOpenUser (self .dce , domain_handle , userId = user_rid )["UserHandle" ]
115+
116+ if self .reset :
117+ samr .hSamrSetNTInternal1 (self .dce , user_handle , newPassword , newHash )
118+ context .log .success (f"Successfully changed password for { target_username } " )
119+ else :
120+
121+ samr .hSamrUnicodeChangePasswordUser2 (
122+ self .dce , "\x00 " , target_username , self .oldpass , newPassword , "" , ""
123+ )
124+ context .log .success (f"Successfully changed password for { target_username } " )
121125 else :
122- try :
123- if new_password :
124- # If using new password
125- samr .hSamrUnicodeChangePasswordUser2 (
126- dce , "\x00 " , target_username , old_password , new_password , "" , ""
127- )
128- elif new_lmhash and new_nthash :
129- # If using hash (NEWHASH)
130- samr .hSamrSetNTInternal1 (dce , user_handle , new_password , new_nthash )
131- context .log .success (f"Successfully changed password for { target_username } " )
132- except AttributeError as encode_error :
133- context .log .error (f"Encoding issue in new password: { encode_error !s} " )
134- return
126+ self .mustchangePassword (target_username , target_domain , self .oldpass , newPassword , "" , oldHash , "" , newHash )
135127 except Exception as e :
136- context .log .error (f"SMB-SAMR password change failed: { e !s } " )
128+ context .log .fail (f"SMB-SAMR password change failed: { e } " )
137129 finally :
138- dce .disconnect ()
130+ self .dce .disconnect ()
131+
132+ def mustchangePassword (self , target_username , targetDomain , oldPassword , newPassword , oldPwdHashLM , oldPwdHashNT , newPwdHashLM , newPwdHashNT ):
133+ if newPassword and oldPassword :
134+ samr .hSamrUnicodeChangePasswordUser2 (self .dce , "\x00 " , target_username , oldPassword , newPassword , "" , "" )
135+ self .context .log .success (f"Successfully changed password for { target_username } " )
136+ elif newPassword and oldPwdHashNT :
137+ samr .hSamrUnicodeChangePasswordUser2 (self .dce , "\x00 " , target_username , oldPassword , newPassword , "" , oldPwdHashNT )
138+ self .context .log .success (f"Successfully changed password for { target_username } " )
139+ else :
140+ samr .hSamrSetNTInternal1 (self .dce , target_username , newPassword , newPwdHashNT )
141+ self .context .log .success (f"Successfully changed password for { target_username } " )
0 commit comments