CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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