forked from fortra/impacket
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsmbpasswd.py
executable file
·200 lines (173 loc) · 7.69 KB
/
smbpasswd.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#!/usr/bin/env python
# Impacket - Collection of Python classes for working with network protocols.
#
# SECUREAUTH LABS. Copyright (C) 2021 SecureAuth Corporation. All rights reserved.
#
# This software is provided under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Description:
# This script is an alternative to smbpasswd tool and intended to be used
# for changing passwords remotely over SMB (MSRPC-SAMR). It can perform the
# password change when the current password is expired, and supports NTLM
# hashes as a new password value instead of a plaintext value. As for the
# latter approach the new password is flagged as expired after the change
# due to how SamrChangePasswordUser function works.
#
# Examples:
# smbpasswd.py [email protected]
# smbpasswd.py contoso.local/j.doe@DC1 -hashes :fc525c9683e8fe067095ba2ddc971889
# smbpasswd.py contoso.local/j.doe:'Passw0rd!'@DC1 -newpass 'N3wPassw0rd!'
# smbpasswd.py contoso.local/j.doe:'Passw0rd!'@DC1 -newhashes :126502da14a98b58f2c319b81b3a49cb
#
# Author:
# @snovvcrash
# @bransh
#
# References:
# https://snovvcrash.github.io/2020/10/31/pretending-to-be-smbpasswd-with-impacket.html
# https://github.com/samba-team/samba/blob/master/source3/utils/smbpasswd.c
# https://github.com/SecureAuthCorp/impacket/pull/381
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/acb3204a-da8b-478e-9139-1ea589edb880
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/9699d8ca-e1a4-433c-a8c3-d7bebeb01476
#
import sys
import logging
from getpass import getpass
from argparse import ArgumentParser
from impacket import version
from impacket.examples import logger
from impacket.examples.utils import parse_target
from impacket.dcerpc.v5 import transport, samr
class SMBPasswd:
def __init__(self, domain, username, oldPassword, newPassword, oldPwdHashLM, oldPwdHashNT, newPwdHashLM, newPwdHashNT, hostname):
self.domain = domain
self.username = username
self.oldPassword = oldPassword
self.newPassword = newPassword
self.oldPwdHashLM = oldPwdHashLM
self.oldPwdHashNT = oldPwdHashNT
self.newPwdHashLM = newPwdHashLM
self.newPwdHashNT = newPwdHashNT
self.hostname = hostname
self.dce = None
def connect(self, anonymous=False):
rpctransport = transport.SMBTransport(self.hostname, filename=r'\samr')
if anonymous:
rpctransport.set_credentials(username='', password='', domain='', lmhash='', nthash='', aesKey='')
else:
rpctransport.set_credentials(self.username, self.oldPassword, self.domain, self.oldPwdHashLM, self.oldPwdHashNT, aesKey='')
self.dce = rpctransport.get_dce_rpc()
self.dce.connect()
self.dce.bind(samr.MSRPC_UUID_SAMR)
def hSamrUnicodeChangePasswordUser2(self):
try:
resp = samr.hSamrUnicodeChangePasswordUser2(self.dce, '\x00', self.username, self.oldPassword, self.newPassword, self.oldPwdHashLM, self.oldPwdHashNT)
except Exception as e:
if 'STATUS_PASSWORD_RESTRICTION' in str(e):
logging.critical('Some password update rule has been violated. For example, the password may not meet length criteria.')
else:
raise e
else:
if resp['ErrorCode'] == 0:
logging.info('Password was changed successfully.')
else:
logging.error('Non-zero return code, something weird happened.')
resp.dump()
def hSamrChangePasswordUser(self):
serverHandle = samr.hSamrConnect(self.dce, self.hostname + '\x00')['ServerHandle']
domainSID = samr.hSamrLookupDomainInSamServer(self.dce, serverHandle, self.domain)['DomainId']
domainHandle = samr.hSamrOpenDomain(self.dce, serverHandle, domainId=domainSID)['DomainHandle']
userRID = samr.hSamrLookupNamesInDomain(self.dce, domainHandle, (self.username,))['RelativeIds']['Element'][0]
userHandle = samr.hSamrOpenUser(self.dce, domainHandle, userId=userRID)['UserHandle']
try:
resp = samr.hSamrChangePasswordUser(self.dce, userHandle, self.oldPassword, newPassword='', oldPwdHashNT=self.oldPwdHashNT,
newPwdHashLM=self.newPwdHashLM, newPwdHashNT=self.newPwdHashNT)
except Exception as e:
if 'STATUS_PASSWORD_RESTRICTION' in str(e):
logging.critical('Some password update rule has been violated. For example, the password history policy may prohibit the use of recent passwords.')
else:
raise e
else:
if resp['ErrorCode'] == 0:
logging.info('NTLM hashes were changed successfully.')
else:
logging.error('Non-zero return code, something weird happened.')
resp.dump()
def init_logger(options):
logger.init(options.ts)
if options.debug is True:
logging.getLogger().setLevel(logging.DEBUG)
logging.debug(version.getInstallationPath())
else:
logging.getLogger().setLevel(logging.INFO)
def parse_args():
parser = ArgumentParser(description='Change password over SMB.')
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
parser.add_argument('-ts', action='store_true', help='adds timestamp to every logging output')
parser.add_argument('-debug', action='store_true', help='turn DEBUG output ON')
group = parser.add_mutually_exclusive_group()
group.add_argument('-newpass', action='store', default=None, help='new SMB password')
group.add_argument('-newhashes', action='store', default=None, metavar='LMHASH:NTHASH', help='new NTLM hashes, format is LMHASH:NTHASH '
'(the user will be asked to change their password at next logon)')
group = parser.add_argument_group('authentication')
group.add_argument('-hashes', action='store', default=None, metavar='LMHASH:NTHASH', help='NTLM hashes, format is LMHASH:NTHASH')
return parser.parse_args()
if __name__ == '__main__':
print(version.BANNER)
options = parse_args()
init_logger(options)
domain, username, oldPassword, address = parse_target(options.target)
if domain is None:
domain = 'Builtin'
if options.hashes is not None:
try:
oldPwdHashLM, oldPwdHashNT = options.hashes.split(':')
except ValueError:
logging.critical('Wrong hashes string format. For more information run with --help option.')
sys.exit(1)
else:
oldPwdHashLM = ''
oldPwdHashNT = ''
if oldPassword == '' and oldPwdHashNT == '':
oldPassword = getpass('Current SMB password: ')
if options.newhashes is not None:
try:
newPwdHashLM, newPwdHashNT = options.newhashes.split(':')
except ValueError:
logging.critical('Wrong new hashes string format. For more information run with --help option.')
sys.exit(1)
newPassword = ''
else:
newPwdHashLM = ''
newPwdHashNT = ''
if options.newpass is None:
newPassword = getpass('New SMB password: ')
if newPassword != getpass('Retype new SMB password: '):
logging.critical('Passwords do not match, try again.')
sys.exit(1)
else:
newPassword = options.newpass
smbpasswd = SMBPasswd(domain, username, oldPassword, newPassword, oldPwdHashLM, oldPwdHashNT, newPwdHashLM, newPwdHashNT, address)
try:
smbpasswd.connect()
except Exception as e:
if any(msg in str(e) for msg in ['STATUS_PASSWORD_MUST_CHANGE', 'STATUS_PASSWORD_EXPIRED']):
if newPassword:
logging.warning('Password is expired, trying to bind with a null session.')
smbpasswd.connect(anonymous=True)
else:
logging.critical('Cannot set new NTLM hashes when current password is expired. Provide a plaintext value for the new password.')
sys.exit(1)
elif 'STATUS_LOGON_FAILURE' in str(e):
logging.critical('Authentication failure.')
sys.exit(1)
else:
raise e
if newPassword:
# If using a plaintext value for the new password
smbpasswd.hSamrUnicodeChangePasswordUser2()
else:
# If using NTLM hashes for the new password
smbpasswd.hSamrChangePasswordUser()