1+ import sys
2+
3+ class NXCModule :
4+ """
5+ Module for changing or resetting user passwords
6+ Module by Fagan Afandiyev
7+
8+ This is NXC implementation of changepasswd.py from impacket
9+ """
10+
11+ name = "change-password"
12+ description = "Change or reset user passwords via various protocols"
13+ supported_protocols = ["smb" ]
14+ opsec_safe = True
15+ multiple_hosts = False
16+
17+ def options (self , context , module_options ):
18+ """
19+ Module options for password change
20+
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
27+ """
28+ self .newpass = module_options .get ("NEWPASS" )
29+ self .newhash = module_options .get ("NEWHASH" )
30+ self .oldpass = module_options .get ("OLDPASS" )
31+ self .target_user = module_options .get ("USER" )
32+ self .reset = module_options .get ("RESET" , True )
33+
34+ if not self .newpass and not self .newhash :
35+ context .log .error ("Either NEWPASS or NEWHASH is required!" )
36+ sys .exit (1 )
37+
38+ 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
41+ target_domain = connection .domain
42+
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
59+ new_lmhash , new_nthash = "" , ""
60+
61+ if self .newpass :
62+ # If NEWPASS is provided, use it
63+ new_password = self .newpass
64+
65+ if self .newhash :
66+ # If NEWHASH is provided, split the hash and set new password to None
67+ try :
68+ new_lmhash , new_nthash = self .newhash .split (":" )
69+ new_password = None # Don't set a plain password when using a hash
70+ except ValueError :
71+ new_lmhash = "00000000000000000000000000000000"
72+ 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"
77+
78+ 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 )
87+ 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 )
105+
106+ dce = rpc_transport .get_dce_rpc ()
107+ dce .connect ()
108+ dce .bind (samr .MSRPC_UUID_SAMR )
109+
110+ try :
111+ server_handle = samr .hSamrConnect (dce , connection .host + "\x00 " )["ServerHandle" ]
112+ domain_sid = samr .hSamrLookupDomainInSamServer (dce , server_handle , target_domain )["DomainId" ]
113+ domain_handle = samr .hSamrOpenDomain (dce , server_handle , domainId = domain_sid )["DomainHandle" ]
114+ user_rid = samr .hSamrLookupNamesInDomain (dce , domain_handle , (target_username ,))["RelativeIds" ]["Element" ][0 ]
115+ user_handle = samr .hSamrOpenUser (dce , domain_handle , userId = user_rid )["UserHandle" ]
116+ if self .reset :
117+ samr .hSamrSetNTInternal1 (dce , user_handle , new_password , new_nthash )
118+ context .log .success (f"Successfully reset password for { target_username } " )
119+ else :
120+ try :
121+ if new_password :
122+ # If using new password
123+ samr .hSamrUnicodeChangePasswordUser2 (
124+ dce , "\x00 " , target_username , old_password , new_password , "" , ""
125+ )
126+ elif new_lmhash and new_nthash :
127+ # If using hash (NEWHASH)
128+ samr .hSamrSetNTInternal1 (dce , user_handle , new_password , new_nthash )
129+ context .log .success (f"Successfully changed password for { target_username } " )
130+ except AttributeError as encode_error :
131+ context .log .error (f"Encoding issue in new password: { encode_error !s} " )
132+ return
133+ except Exception as e :
134+ context .log .error (f"SMB-SAMR password change failed: { e !s} " )
135+ finally :
136+ dce .disconnect ()
0 commit comments