Skip to content

Commit 70a5e05

Browse files
committed
Updated version of change-password module with ruff changes
1 parent 2e1f225 commit 70a5e05

1 file changed

Lines changed: 136 additions & 0 deletions

File tree

nxc/modules/change-password.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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

Comments
 (0)