Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/util/windows_crypto_helpers.rb
21537 views
1
module Msf
2
module Util
3
module WindowsCryptoHelpers
4
5
EMPTY_LM = "\xaa\xd3\xb4\x35\xb5\x14\x04\xee\xaa\xd3\xb4\x35\xb5\x14\x04\xee".b
6
EMPTY_NT = "\x31\xd6\xcf\xe0\xd1\x6a\xe9\x31\xb7\x3c\x59\xd7\xe0\xc0\x89\xc0".b
7
8
#class Error < RuntimeError; end
9
#class Unknown < Error; end
10
11
# Converts DES 56 key to DES 64 key
12
#
13
# See [2.2.11.1.2 Encrypting a 64-Bit Block with a 7-Byte Key](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/ebdb15df-8d0d-4347-9d62-082e6eccac40)
14
#
15
# @param kstr [String] The key to convert
16
# @return [String] The converted key
17
def convert_des_56_to_64(kstr)
18
des_odd_parity = [
19
1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14,
20
16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31,
21
32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47,
22
49, 49, 50, 50, 52, 52, 55, 55, 56, 56, 59, 59, 61, 61, 62, 62,
23
64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79,
24
81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93, 94, 94,
25
97, 97, 98, 98,100,100,103,103,104,104,107,107,109,109,110,110,
26
112,112,115,115,117,117,118,118,121,121,122,122,124,124,127,127,
27
128,128,131,131,133,133,134,134,137,137,138,138,140,140,143,143,
28
145,145,146,146,148,148,151,151,152,152,155,155,157,157,158,158,
29
161,161,162,162,164,164,167,167,168,168,171,171,173,173,174,174,
30
176,176,179,179,181,181,182,182,185,185,186,186,188,188,191,191,
31
193,193,194,194,196,196,199,199,200,200,203,203,205,205,206,206,
32
208,208,211,211,213,213,214,214,217,217,218,218,220,220,223,223,
33
224,224,227,227,229,229,230,230,233,233,234,234,236,236,239,239,
34
241,241,242,242,244,244,247,247,248,248,251,251,253,253,254,254
35
]
36
37
key = []
38
str = kstr.unpack("C*")
39
40
key[0] = str[0] >> 1
41
key[1] = ((str[0] & 0x01) << 6) | (str[1] >> 2)
42
key[2] = ((str[1] & 0x03) << 5) | (str[2] >> 3)
43
key[3] = ((str[2] & 0x07) << 4) | (str[3] >> 4)
44
key[4] = ((str[3] & 0x0F) << 3) | (str[4] >> 5)
45
key[5] = ((str[4] & 0x1F) << 2) | (str[5] >> 6)
46
key[6] = ((str[5] & 0x3F) << 1) | (str[6] >> 7)
47
key[7] = str[6] & 0x7F
48
49
0.upto(7) do |i|
50
key[i] = ( key[i] << 1)
51
key[i] = des_odd_parity[key[i]]
52
end
53
return key.pack("C*")
54
end
55
56
# Decrypts "Secret" encrypted data
57
#
58
# Ruby implementation of SystemFunction005. The original python code
59
# has been taken from Credump
60
#
61
# @param secret [String] The secret to decrypt
62
# @param key [String] The key to decrypt the secret
63
# @return [String] The decrypted data
64
def decrypt_secret_data(secret, key)
65
66
j = 0
67
decrypted_data = ''
68
69
for i in (0...secret.length).step(8)
70
enc_block = secret[i..i+7]
71
block_key = key[j..j+6]
72
des_key = convert_des_56_to_64(block_key)
73
d1 = OpenSSL::Cipher.new('des-ecb')
74
d1.decrypt
75
d1.padding = 0
76
d1.key = des_key
77
d1o = d1.update(enc_block)
78
d1o << d1.final
79
decrypted_data += d1o
80
j += 7
81
if (key[j..j+7].length < 7 )
82
j = key[j..j+7].length
83
end
84
end
85
dec_data_len = decrypted_data[0,4].unpack('L<').first
86
87
return decrypted_data[8, dec_data_len]
88
89
end
90
91
# Decrypts LSA encrypted data
92
#
93
# @param policy_secret [String] The encrypted data stored in the registry
94
# @param lsa_key [String] The LSA key
95
# @return [String] The decrypted data
96
def decrypt_lsa_data(policy_secret, lsa_key)
97
98
sha256x = Digest::SHA256.new()
99
sha256x << lsa_key
100
1000.times do
101
sha256x << policy_secret[28,32]
102
end
103
104
aes = OpenSSL::Cipher.new("aes-256-cbc")
105
aes.decrypt
106
aes.key = sha256x.digest
107
108
# vprint_status("digest #{sha256x.digest.unpack("H*")[0]}")
109
110
decrypted_data = ''
111
112
(60...policy_secret.length).step(16) do |i|
113
aes.reset
114
aes.padding = 0
115
aes.iv = "\x00" * 16
116
decrypted_data << aes.update(policy_secret[i,16])
117
end
118
119
return decrypted_data
120
end
121
122
# Derive DES Key1 and Key2 from user RID.
123
#
124
# @param rid [String] The user RID
125
# @return [Array] A two element array containing Key1 and Key2, in this order
126
def rid_to_key(rid)
127
# See [2.2.11.1.3 Deriving Key1 and Key2 from a Little-Endian, Unsigned Integer Key](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/b1b0094f-2546-431f-b06d-582158a9f2bb)
128
s1 = [rid].pack('V')
129
s1 << s1[0, 3]
130
131
s2b = [rid].pack('V').unpack('C4')
132
s2 = [s2b[3], s2b[0], s2b[1], s2b[2]].pack('C4')
133
s2 << s2[0, 3]
134
135
[convert_des_56_to_64(s1), convert_des_56_to_64(s2)]
136
end
137
138
# This decrypt an encrypted NT or LM hash.
139
# See [2.2.11.1.1 Encrypting an NT or LM Hash Value with a Specified Key](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/a5252e8c-25e7-4616-a375-55ced086b19b)
140
#
141
# @param rid [String] The user RID
142
# @param hboot_key [String] The hashedBootKey
143
# @param enc_hash [String] The encrypted hash
144
# @param pass [String] The password used for revision 1 hashes
145
# @param default [String] The default hash to return if something goes wrong
146
# @return [String] The decrypted NT or LM hash
147
def decrypt_user_hash(rid, hboot_key, enc_hash, pass, default)
148
revision = enc_hash[2, 2]&.unpack('v')&.first
149
150
case revision
151
when 1
152
return default if enc_hash.length < 20
153
154
md5 = Digest::MD5.new
155
md5.update(hboot_key[0, 16] + [rid].pack('V') + pass)
156
157
rc4 = OpenSSL::Cipher.new('rc4')
158
rc4.decrypt
159
rc4.key = md5.digest
160
okey = rc4.update(enc_hash[4, 16])
161
when 2
162
return default if enc_hash.length < 40
163
164
aes = OpenSSL::Cipher.new('aes-128-cbc')
165
aes.decrypt
166
aes.key = hboot_key[0, 16]
167
aes.padding = 0
168
aes.iv = enc_hash[8, 16]
169
okey = aes.update(enc_hash[24, 16]) # we need only 16 bytes
170
else
171
elog("decrypt_user_hash: Unknown user hash revision: #{revision}, returning default")
172
return default
173
end
174
175
des_k1, des_k2 = rid_to_key(rid)
176
177
d1 = OpenSSL::Cipher.new('des-ecb')
178
d1.decrypt
179
d1.padding = 0
180
d1.key = des_k1
181
182
d2 = OpenSSL::Cipher.new('des-ecb')
183
d2.decrypt
184
d2.padding = 0
185
d2.key = des_k2
186
187
d1o = d1.update(okey[0, 8])
188
d1o << d1.final
189
190
d2o = d2.update(okey[8, 8])
191
d1o << d2.final
192
d1o + d2o
193
end
194
195
# Decrypts the user V key value and return the NT amd LM hashes. The V value
196
# can be found under the
197
# HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\<RID> registry key.
198
#
199
# @param hboot_key [String] The hashedBootKey
200
# @param user_v [String] The user V value
201
# @param rid [String] The user RID
202
# @return [Array] Array with the first and second element containing the NT and LM hashes respectively
203
def decrypt_user_key(hboot_key, user_v, rid)
204
sam_lmpass = "LMPASSWORD\x00"
205
sam_ntpass = "NTPASSWORD\x00"
206
207
# TODO: use a proper structure for V data, instead of unpacking directly
208
hashlm_off = user_v[0x9c, 4]&.unpack('V')&.first
209
hashlm_len = user_v[0xa0, 4]&.unpack('V')&.first
210
if hashlm_off && hashlm_len
211
hashlm_enc = user_v[hashlm_off + 0xcc, hashlm_len]
212
hashlm = decrypt_user_hash(rid, hboot_key, hashlm_enc, sam_lmpass, EMPTY_LM)
213
else
214
elog('decrypt_user_key: Unable to extract LM hash, using empty LM hash instead')
215
hashlm = EMPTY_LM
216
end
217
218
hashnt_off = user_v[0xa8, 4]&.unpack('V')&.first
219
hashnt_len = user_v[0xac, 4]&.unpack('V')&.first
220
if hashnt_off && hashnt_len
221
hashnt_enc = user_v[hashnt_off + 0xcc, hashnt_len]
222
hashnt = decrypt_user_hash(rid, hboot_key, hashnt_enc, sam_ntpass, EMPTY_NT)
223
else
224
elog('decrypt_user_key: Unable to extract NT hash, using empty NT hash instead')
225
hashnt = EMPTY_NT
226
end
227
228
[hashnt, hashlm]
229
end
230
231
# Decrypt a cipher using AES in CBC mode. The key length is deduced from
232
# `key` argument length. The supported key length are 16, 24 and 32. Also, it
233
# will take care of padding the last block if the cipher length is not modulo
234
# 16.
235
#
236
# @param edata [String] The cipher to decrypt
237
# @param key [String] The key used to decrypt
238
# @param iv [String] The IV
239
# @return [String, nil] The decrypted plaintext or nil if the key size is not supported
240
def decrypt_aes(edata, key, iv)
241
cipher_str = case key.length
242
when 16
243
'aes-128-cbc'
244
when 24
245
'aes-192-cbc'
246
when 32
247
'aes-256-cbc'
248
else
249
elog("decrypt_aes: Unknown key length (#{key.length} bytes)")
250
return
251
end
252
aes = OpenSSL::Cipher.new(cipher_str)
253
aes.decrypt
254
aes.key = key
255
aes.padding = 0
256
aes.iv = iv
257
258
decrypted = ''
259
(0...edata.length).step(aes.block_size) do |i|
260
block_str = edata[i, aes.block_size]
261
# Pad buffer with \x00 if needed
262
if block_str.length < aes.block_size
263
block_str << "\x00".b * (aes.block_size - block_str.length)
264
end
265
decrypted << aes.update(block_str)
266
end
267
268
return decrypted
269
end
270
271
# Decrypt encrypted cached entry from HKLM\Security\Cache\NL$XX
272
#
273
# @param edata [String] The encrypted hash entry to decrypt
274
# @param key [String] The key used to decrypt
275
# @param iv [String] The IV
276
# @return [String, nil] The decrypted plaintext or nil if the key size is not supported
277
def decrypt_hash(edata, key, iv)
278
rc4key = OpenSSL::HMAC.digest(OpenSSL::Digest.new('md5'), key, iv)
279
rc4 = OpenSSL::Cipher.new('rc4')
280
rc4.decrypt
281
rc4.key = rc4key
282
decrypted = rc4.update(edata)
283
decrypted << rc4.final
284
285
return decrypted
286
end
287
288
def add_parity(byte_str)
289
byte_str.map do |byte|
290
if byte.to_s(2).count('1').odd?
291
(byte << 1) & 0b11111110
292
else
293
(byte << 1) | 0b00000001
294
end
295
end
296
end
297
298
def fix_parity(byte_str)
299
byte_str.map do |byte|
300
t = byte.to_s(2).rjust(8, '0')
301
if t[0, 7].count('1').odd?
302
("#{t[0, 7]}0").to_i(2).chr
303
else
304
("#{t[0, 7]}1").to_i(2).chr
305
end
306
end
307
end
308
309
def weak_des_key?(key)
310
[
311
"\x01\x01\x01\x01\x01\x01\x01\x01",
312
"\xFE\xFE\xFE\xFE\xFE\xFE\xFE\xFE",
313
"\x1F\x1F\x1F\x1F\x0E\x0E\x0E\x0E",
314
"\xE0\xE0\xE0\xE0\xF1\xF1\xF1\xF1",
315
"\x01\xFE\x01\xFE\x01\xFE\x01\xFE",
316
"\xFE\x01\xFE\x01\xFE\x01\xFE\x01",
317
"\x1F\xE0\x1F\xE0\x0E\xF1\x0E\xF1",
318
"\xE0\x1F\xE0\x1F\xF1\x0E\xF1\x0E",
319
"\x01\xE0\x01\xE0\x01\xF1\x01\xF1",
320
"\xE0\x01\xE0\x01\xF1\x01\xF1\x01",
321
"\x1F\xFE\x1F\xFE\x0E\xFE\x0E\xFE",
322
"\xFE\x1F\xFE\x1F\xFE\x0E\xFE\x0E",
323
"\x01\x1F\x01\x1F\x01\x0E\x01\x0E",
324
"\x1F\x01\x1F\x01\x0E\x01\x0E\x01",
325
"\xE0\xFE\xE0\xFE\xF1\xFE\xF1\xFE",
326
"\xFE\xE0\xFE\xE0\xFE\xF1\xFE\xF1"
327
].include?(key)
328
end
329
330
# Encrypt using MIT Kerberos des-cbc-md5
331
# http://web.mit.edu/kerberos/krb5-latest/doc/admin/enctypes.html?highlight=des#enctype-compatibility
332
#
333
# @param raw_secret [String] The data to encrypt
334
# @param key [String] The salt used by the encryption algorithm
335
# @return [String, nil] The encrypted data
336
def des_cbc_md5(raw_secret, salt)
337
odd = true
338
tmp_byte_str = [0, 0, 0, 0, 0, 0, 0, 0]
339
plaintext = raw_secret + salt
340
plaintext += "\x00".b * (8 - (plaintext.size % 8))
341
plaintext.bytes.each_slice(8) do |block|
342
tmp_56 = block.map { |byte| byte & 0b01111111 }
343
if !odd
344
# rubocop:disable Style/FormatString
345
tmp_56_str = tmp_56.map { |byte| '%07b' % byte }.join
346
# rubocop:enable Style/FormatString
347
tmp_56_str.reverse!
348
tmp_56 = tmp_56_str.bytes.each_slice(7).map do |bits7|
349
bits7.map(&:chr).join.to_i(2)
350
end
351
end
352
odd = !odd
353
tmp_byte_str = tmp_byte_str.zip(tmp_56).map { |a, b| a ^ b }
354
end
355
tempkey = add_parity(tmp_byte_str).map(&:chr).join
356
if weak_des_key?(tempkey)
357
tempkey[7] = (tempkey[7].ord ^ 0xF0).chr
358
end
359
cipher = OpenSSL::Cipher.new('DES-CBC')
360
cipher.encrypt
361
cipher.iv = tempkey
362
cipher.key = tempkey
363
chekcsumkey = cipher.update(plaintext)[-8..-1]
364
chekcsumkey = fix_parity(chekcsumkey.bytes).map(&:chr).join
365
if weak_des_key?(chekcsumkey)
366
chekcsumkey[7] = (chekcsumkey[7].ord ^ 0xF0).chr
367
end
368
chekcsumkey.unpack('H*')[0]
369
end
370
371
# Encrypt using MIT Kerberos aesXXX-cts-hmac-sha1-96
372
# http://web.mit.edu/kerberos/krb5-latest/doc/admin/enctypes.html?highlight=des#enctype-compatibility
373
#
374
# @param algorithm [String] The AES algorithm to use (e.g. `128-CBC` or `256-CBC`)
375
# @param raw_secret [String] The data to encrypt
376
# @param key [String] The salt used by the encryption algorithm
377
# @return [String, nil] The encrypted data
378
def aes_cts_hmac_sha1_96(algorithm, raw_secret, salt)
379
iterations = 4096
380
cipher = OpenSSL::Cipher::AES.new(algorithm)
381
key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(raw_secret, salt, iterations, cipher.key_len)
382
plaintext = "kerberos\x7B\x9B\x5B\x2B\x93\x13\x2B\x93".b
383
rnd_seed = ''.b
384
loop do
385
cipher.reset
386
cipher.encrypt
387
cipher.iv = "\x00".b * 16
388
cipher.key = key
389
ciphertext = cipher.update(plaintext)
390
rnd_seed += ciphertext
391
break unless rnd_seed.size < cipher.key_len
392
393
plaintext = ciphertext
394
end
395
rnd_seed
396
end
397
398
# Encrypt using MIT Kerberos aes128-cts-hmac-sha1-96
399
# http://web.mit.edu/kerberos/krb5-latest/doc/admin/enctypes.html?highlight=des#enctype-compatibility
400
#
401
# @param raw_secret [String] The data to encrypt
402
# @param salt [String] The salt used by the encryption algorithm
403
# @return [String, nil] The encrypted data
404
def aes128_cts_hmac_sha1_96(raw_secret, salt)
405
aes_cts_hmac_sha1_96('128-CBC', raw_secret, salt)
406
end
407
408
# Encrypt using MIT Kerberos aes256-cts-hmac-sha1-96
409
# http://web.mit.edu/kerberos/krb5-latest/doc/admin/enctypes.html?highlight=des#enctype-compatibility
410
#
411
# @param raw_secret [String] The data to encrypt
412
# @param salt [String] The salt used by the encryption algorithm
413
# @return [String, nil] The encrypted data
414
def aes256_cts_hmac_sha1_96(raw_secret, salt)
415
aes_cts_hmac_sha1_96('256-CBC', raw_secret, salt)
416
end
417
418
# Encrypt using MIT Kerberos rc4_hmac
419
# http://web.mit.edu/kerberos/krb5-latest/doc/admin/enctypes.html?highlight=des#enctype-compatibility
420
#
421
# @param raw_secret [String] The data to encrypt
422
# @param salt [String] The salt used by the encryption algorithm
423
# @return [String, nil] The encrypted data
424
def rc4_hmac(raw_secret, salt = nil)
425
Rex::Proto::Kerberos::Crypto::Rc4Hmac.new.string_to_key(raw_secret, salt)
426
end
427
end
428
end
429
end
430
431