For me the most interesting aspect of pwning the Fuse machine from HTB was dealing with an expired domain user password. I found no other tools except smbpasswd to invoke such a password change remotely from Linux which seemed odd to me. So I decided to create a simple Python script with impacket which binds to the \samr pipe over SMB (MSRPC-SAMR) with a null session and calls SamrUnicodeChangePasswordUser2 to trigger the password change.

htb-badge.svg 5.6/10



Change Password over SMB

There is this user tlavel with an expired password Fabricorp01. When trying to connect to an SMB resource (at FUSE.FABRICORP.LOCAL) STATUS_PASSWORD_MUST_CHANGE (0xC0000224) error is raised:


One way to get out of this situation remotely from Linux is to use this smbpasswd tool:

$ smbpasswd -r fuse.fabricorp.local -U 'tlavel'
Old SMB password: Fabricorp01
New SMB password: snovvcrash1!
Retype new SMB password: snovvcrash1!


Under the hood it will try to authenticate as tlavel:Fabricorp01 (red block in the Wireshark capture) and when it fails because of the above mentioned error, it will initiate a null session and call SamrUnicodeChangePasswordUser2 (55) function via DCE/RPC over MS-SAMR protocol to change tlavel’s password (green block in the Wireshark capture):


So, let’s say I’m annoyed with all this secure-interactive-input stuff like in smbpasswd and I want to be able to do the same thing in one line with password values passed as CLI arguments. When I try to achieve this with rpcclient and null authentication, I shall fail:

$ rpcclient -U'%' -c'chgpasswd2 tlavel Fabricorp01 snovvcrash1!' fuse.fabricorp.local


It’s no secret that rpcclient does a bad job when dealing with null session requests. Long story short, after successfully binding to the IPC$ share and creating \samr pipe, it makes a bunch of undesirable requests (red block in the Wireshark capture) which it does not really have permissions for within a null session and eventually dies with NT_STATUS_ACCESS_DENIED.


Another way to try a password change over SMB is the impacket’s password functionality, but it will not work when the password is expired either:

$ 'tlavel:Fabricorp01@'


Same story with Samba’s NetCommand:

$ net rpc password tlavel 'snovvcrash1!' -S FUSE -U 'tlavel'%'Fabricorp01'
session setup failed: NT_STATUS_PASSWORD_MUST_CHANGE
Failed to set password for 'tlavel' with error: Failed to connect to IPC$ share on FUSE.

That’s not what we want, so we can create a super simple Python script using impacket to initiate a null session and then change tlavel’s password directly with one single call to hSamrUnicodeChangePasswordUser2:


from argparse import ArgumentParser

from impacket.dcerpc.v5 import transport, samr

def connect(host_name_or_ip):
	rpctransport = transport.SMBTransport(host_name_or_ip, filename=r'\samr')
	if hasattr(rpctransport, 'set_credentials'):
		rpctransport.set_credentials(username='', password='', domain='', lmhash='', nthash='', aesKey='') # null session

	dce = rpctransport.get_dce_rpc()

	return dce

def hSamrUnicodeChangePasswordUser2(username, oldpass, newpass, target):
	dce = connect(target)
	resp = samr.hSamrUnicodeChangePasswordUser2(dce, '\x00', username, oldpass, newpass)

parser = ArgumentParser()
parser.add_argument('username', help='username to change password for')
parser.add_argument('oldpass', help='old password')
parser.add_argument('newpass', help='new password')
parser.add_argument('target', help='hostname or IP')
args = parser.parse_args()

hSamrUnicodeChangePasswordUser2(args.username, args.oldpass, args.newpass,

Now we can trigger password change with a single command insecurely leaving all the sensitive information in the bash history… Just as we’ve always wanted :expressionless:

$ ./ tlavel Fabricorp01 'snovvcrash01!' FUSE.FABRICORP.LOCAL



Later on I groomed up the code a little and made a pull request to the impacket’s master branch.

Password Policies Notes

There is a couple of points to be cleared:

1. From now on both passwords will be valid for approximately one hour after the password change. If I try to authorize with the old Fabricorp01 password after the change was made, it actually works and the new snovvcrash01! password is valid too:


2. There is a scheduled task which reverts the password back to Fabricorp01 every 1 minute:


3. If I try to change the password two times in a row but before it was reverted to default Fabricorp01 by the scheduler task (Fabricorp01snovvcrash01!snovvcrash02!), it fails due to the minimum password age policy (1 day):



4. If I try to set the same password again after it was reverted to default Fabricorp01 by the scheduler task (Fabricorp01snovvcrash01!Fabricorp01snovvcrash01!), it fails due to the enforce password history policy (24 passwords):



Cheers and happy hacking!