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/rex/proto/ntlm/crypt.rb
Views: 11704
1
# -*- coding: binary -*-
2
#
3
# An NTLM Authentication Library for Ruby
4
#
5
# This code is a derivative of "dbf2.rb" written by yrock
6
# and Minero Aoki. You can find original code here:
7
# http://jp.rubyist.net/magazine/?0013-CodeReview
8
# -------------------------------------------------------------
9
# Copyright (c) 2005,2006 yrock
10
#
11
# This program is free software.
12
# You can distribute/modify this program under the terms of the
13
# Ruby License.
14
#
15
# 2011-03-08 improved through a code merge with Metasploit's SMB::Crypt
16
# -------------------------------------------------------------
17
#
18
# 2011-02-23 refactored and improved by Alexandre Maloteaux for Metasploit Project
19
# -------------------------------------------------------------
20
#
21
# 2006-02-11 refactored by Minero Aoki
22
# -------------------------------------------------------------
23
#
24
# All protocol information used to write this code stems from
25
# "The NTLM Authentication Protocol" by Eric Glass. The author
26
# would thank to him for this tremendous work and making it
27
# available on the net.
28
# http://davenport.sourceforge.net/ntlm.html
29
# -------------------------------------------------------------
30
# Copyright (c) 2003 Eric Glass
31
#
32
# Permission to use, copy, modify, and distribute this document
33
# for any purpose and without any fee is hereby granted,
34
# provided that the above copyright notice and this list of
35
# conditions appear in all copies.
36
# -------------------------------------------------------------
37
#
38
# The author also looked Mozilla-Firefox-1.0.7 source code,
39
# namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and
40
# Jonathan Bastien-Filiatrault's libntlm-ruby.
41
# "http://x2a.org/websvn/filedetails.php?
42
# repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"
43
# The latter has a minor bug in its separate_keys function.
44
# The third key has to begin from the 14th character of the
45
# input string instead of 13th:)
46
47
48
module Rex
49
module Proto
50
module NTLM
51
class Crypt
52
53
CONST = Rex::Proto::NTLM::Constants
54
BASE = Rex::Proto::NTLM::Base
55
56
@@loaded_openssl = false
57
58
begin
59
require 'openssl'
60
require 'openssl/digest'
61
@@loaded_openssl = true
62
rescue ::Exception
63
end
64
65
def self.gen_keys(str)
66
str.scan(/.{7}/).map{ |key| des_56_to_64(key) }
67
end
68
69
def self.des_56_to_64(ckey56s)
70
ckey64 = []
71
ckey56 = ckey56s.unpack('C*')
72
ckey64[0] = ckey56[0]
73
ckey64[1] = ((ckey56[0] << 7) & 0xFF) | (ckey56[1] >> 1)
74
ckey64[2] = ((ckey56[1] << 6) & 0xFF) | (ckey56[2] >> 2)
75
ckey64[3] = ((ckey56[2] << 5) & 0xFF) | (ckey56[3] >> 3)
76
ckey64[4] = ((ckey56[3] << 4) & 0xFF) | (ckey56[4] >> 4)
77
ckey64[5] = ((ckey56[4] << 3) & 0xFF) | (ckey56[5] >> 5)
78
ckey64[6] = ((ckey56[5] << 2) & 0xFF) | (ckey56[6] >> 6)
79
ckey64[7] = (ckey56[6] << 1) & 0xFF
80
ckey64.pack('C*')
81
end
82
83
def self.apply_des(plain, keys)
84
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
85
dec = OpenSSL::Cipher.new('DES')
86
keys.map do |k|
87
dec.encrypt
88
dec.key = k
89
dec.update(plain) + dec.final
90
end
91
end
92
93
def self.lm_hash(password, half = false)
94
size = half ? 7 : 14
95
keys = gen_keys(password.upcase.ljust(size, "\0"))
96
apply_des(CONST::LM_MAGIC, keys).join
97
end
98
99
def self.ntlm_hash(password, opt = {})
100
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
101
pwd = password.dup
102
unless opt[:unicode]
103
pwd = Rex::Text.to_unicode(pwd)
104
end
105
OpenSSL::Digest::MD4.digest(pwd)
106
end
107
108
# This hash is used for lmv2/ntlmv2 response calculation
109
def self.ntlmv2_hash(user, password, domain, opt={})
110
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
111
112
if opt[:pass_is_hash]
113
ntlmhash = password
114
else
115
ntlmhash = ntlm_hash(password, opt)
116
end
117
# With Win 7 and maybe other OSs we sometimes get the domain not uppercased
118
userdomain = user.upcase + domain
119
unless opt[:unicode]
120
userdomain = Rex::Text.to_unicode(userdomain)
121
end
122
OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), ntlmhash, userdomain)
123
end
124
125
# Create the LANMAN response
126
def self.lm_response(arg, half = false)
127
begin
128
hash = arg[:lm_hash]
129
chal = arg[:challenge]
130
rescue
131
raise ArgumentError
132
end
133
chal = BASE::pack_int64le(chal) if chal.is_a?(Integer)
134
if half then size = 7 else size = 21 end
135
keys = gen_keys hash.ljust(size, "\0")
136
apply_des(chal, keys).join
137
end
138
139
# Synonym of lm_response for old compatibility with lib/rex/proto/smb/crypt
140
def self.lanman_des(password, challenge)
141
lm_response({
142
:lm_hash => self.lm_hash(password),
143
:challenge => challenge
144
})
145
end
146
147
def self.ntlm_response(arg)
148
hash = arg[:ntlm_hash]
149
chal = arg[:challenge]
150
chal = BASE::pack_int64le(chal) if chal.is_a?(::Integer)
151
keys = gen_keys(hash.ljust(21, "\0"))
152
apply_des(chal, keys).join
153
end
154
155
#synonym of ntlm_response for old compatibility with lib/rex/proto/smb/crypt
156
def self.ntlm_md4(password, challenge)
157
ntlm_response({
158
:ntlm_hash => self.ntlm_hash(password),
159
:challenge => challenge
160
})
161
end
162
163
def self.ntlmv2_response(arg, opt = {})
164
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
165
166
key, chal = arg[:ntlmv2_hash], arg[:challenge]
167
if not (key and chal)
168
raise ArgumentError , 'ntlmv2_hash and challenge are mandatory'
169
end
170
171
chal = BASE::pack_int64le(chal) if chal.is_a?(::Integer)
172
bb = nil
173
174
if opt[:nt_client_challenge]
175
if opt[:nt_client_challenge].to_s.length <= 8
176
raise ArgumentError,"nt_client_challenge is not in a correct format "
177
end
178
bb = opt[:nt_client_challenge]
179
else
180
if not arg[:target_info]
181
raise ArgumentError, "target_info is mandatory in this case"
182
end
183
184
ti = arg[:target_info]
185
cc = opt[:client_challenge] || rand(CONST::MAX64)
186
cc = BASE::pack_int64le(cc) if cc.is_a?(::Integer)
187
188
ts = opt[:timestamp] || ::Time.now.to_i
189
190
# Convert the unix timestamp to windows format
191
# epoch -> milsec from Jan 1, 1601
192
ts = 10000000 * (ts + CONST::TIME_OFFSET)
193
194
blob = BASE::Blob.new
195
blob.timestamp = ts
196
blob.challenge = cc
197
blob.target_info = ti
198
199
bb = blob.serialize
200
end
201
202
OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), key, chal + bb) + bb
203
end
204
205
def self.lmv2_response(arg, opt = {})
206
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
207
key = arg[:ntlmv2_hash]
208
chal = arg[:challenge]
209
210
chal = BASE::pack_int64le(chal) if chal.is_a?(::Integer)
211
cc = opt[:client_challenge] || rand(CONST::MAX64)
212
cc = BASE::pack_int64le(cc) if cc.is_a?(::Integer)
213
214
OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), key, chal + cc) + cc
215
end
216
217
def self.ntlm2_session(arg, opt = {})
218
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
219
passwd_hash,chal = arg[:ntlm_hash],arg[:challenge]
220
if not (passwd_hash and chal)
221
raise RuntimeError, "ntlm_hash and challenge are required"
222
end
223
224
cc = opt[:client_challenge] || rand(CONST::MAX64)
225
cc = BASE::pack_int64le(cc) if cc.is_a?(Integer)
226
227
keys = gen_keys(passwd_hash.ljust(21, "\0"))
228
session_hash = OpenSSL::Digest::MD5.digest(chal + cc)[0,8]
229
response = apply_des(session_hash, keys).join
230
[cc.ljust(24, "\0"), response]
231
end
232
233
#this function will check if the net lm response provided correspond to en empty password
234
def self.is_hash_from_empty_pwd?(arg)
235
hash_type = arg[:type]
236
raise ArgumentError,"arg[:type] is mandatory" if not hash_type
237
raise ArgumentError,"arg[:type] must be lm or ntlm" if not hash_type =~ /^((lm)|(ntlm))$/
238
239
ntlm_ver = arg[:ntlm_ver]
240
raise ArgumentError,"arg[:ntlm_ver] is mandatory" if not ntlm_ver
241
242
hash = arg[:hash]
243
raise ArgumentError,"arg[:hash] is mandatory" if not hash
244
245
srv_chall = arg[:srv_challenge]
246
raise ArgumentError,"arg[:srv_challenge] is mandatory" if not srv_chall
247
raise ArgumentError,"Server challenge length must be exactly 8 bytes" if srv_chall.length != 8
248
249
#calculate responses for empty pwd
250
case ntlm_ver
251
when CONST::NTLM_V1_RESPONSE
252
if hash.length != 24
253
raise ArgumentError,"hash length must be exactly 24 bytes "
254
end
255
case hash_type
256
when 'lm'
257
arglm = { :lm_hash => self.lm_hash(''),
258
:challenge => srv_chall}
259
calculatedhash = self.lm_response(arglm)
260
when 'ntlm'
261
argntlm = { :ntlm_hash => self.ntlm_hash(''),
262
:challenge => srv_chall }
263
calculatedhash = self.ntlm_response(argntlm)
264
end
265
when CONST::NTLM_V2_RESPONSE
266
raise ArgumentError,"hash length must be exactly 16 bytes " if hash.length != 16
267
cli_chall = arg[:cli_challenge]
268
raise ArgumentError,"arg[:cli_challenge] is mandatory in this case" if not cli_chall
269
user = arg[:user]
270
raise ArgumentError,"arg[:user] is mandatory in this case" if not user
271
domain = arg[:domain]
272
raise ArgumentError,"arg[:domain] is mandatory in this case" if not domain
273
274
case hash_type
275
when 'lm'
276
raise ArgumentError,"Client challenge length must be exactly 8 bytes " if cli_chall.length != 8
277
arglm = { :ntlmv2_hash => self.ntlmv2_hash(user,'', domain),
278
:challenge => srv_chall }
279
optlm = { :client_challenge => cli_chall}
280
calculatedhash = self.lmv2_response(arglm, optlm)[0,16]
281
when 'ntlm'
282
raise ArgumentError,"Client challenge length must be bigger then 8 bytes " if cli_chall.length <= 8
283
argntlm = { :ntlmv2_hash => self.ntlmv2_hash(user, '', domain),
284
:challenge => srv_chall }
285
optntlm = { :nt_client_challenge => cli_chall}
286
calculatedhash = self.ntlmv2_response(argntlm,optntlm)[0,16]
287
end
288
when CONST::NTLM_2_SESSION_RESPONSE
289
raise ArgumentError,"hash length must be exactly 16 bytes " if hash.length != 24
290
cli_chall = arg[:cli_challenge]
291
raise ArgumentError,"arg[:cli_challenge] is mandatory in this case" if not cli_chall
292
raise ArgumentError,"Client challenge length must be exactly 8 bytes " if cli_chall.length != 8
293
case hash_type
294
when 'lm'
295
raise ArgumentError, "ntlm2_session is incompatible with lm"
296
when 'ntlm'
297
argntlm = { :ntlm_hash => self.ntlm_hash(''),
298
:challenge => srv_chall }
299
optntlm = { :client_challenge => cli_chall}
300
end
301
calculatedhash = self.ntlm2_session(argntlm,optntlm).join[24,24]
302
else
303
raise ArgumentError,"ntlm_ver is of unknown type"
304
end
305
hash == calculatedhash
306
end
307
308
309
310
#
311
# Signing method added for metasploit project
312
#
313
314
# Used when only the LMv1 response is provided (i.e., with Win9x clients)
315
def self.lmv1_user_session_key(pass, opt = {})
316
if opt[:pass_is_hash]
317
usk = pass[0,8]
318
else
319
usk = self.lm_hash(pass.upcase[0,7],true)
320
end
321
usk.ljust(16,"\x00")
322
end
323
324
# This variant is used when the client sends the NTLMv1 response
325
def self.ntlmv1_user_session_key(pass, opt = {})
326
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
327
328
if opt[:pass_is_hash]
329
usk = pass
330
else
331
usk = self.ntlm_hash(pass)
332
end
333
OpenSSL::Digest::MD4.digest(usk)
334
end
335
336
# Used when NTLMv1 authentication is employed with NTLM2 session security
337
def self.ntlm2_session_user_session_key(pass, srv_chall, cli_chall, opt = {})
338
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
339
340
ntlm_key = self.ntlmv1_user_session_key(pass, opt )
341
session_chal = srv_chall + cli_chall
342
OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), ntlm_key, session_chal)
343
end
344
345
# Used when the LMv2 response is sent
346
def self.lmv2_user_session_key(user, pass, domain, srv_chall, cli_chall, opt = {})
347
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
348
349
ntlmv2_key = self.ntlmv2_hash(user, pass, domain, opt)
350
hash1 = OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), ntlmv2_key, srv_chall + cli_chall)
351
OpenSSL::HMAC.digest(OpenSSL::Digest.new('MD5'), ntlmv2_key, hash1)
352
end
353
354
# Used when the NTLMv2 response is sent
355
class << self; alias_method :ntlmv2_user_session_key, :lmv2_user_session_key; end
356
357
# Used when LanMan Key flag is set
358
def self.lanman_session_key(pass, srvchall, opt = {})
359
if opt[:pass_is_hash]
360
halfhash = pass[0,8]
361
else
362
halfhash = lm_hash(pass.upcase[0,7],true)
363
end
364
plain = self.lm_response({
365
:lm_hash => halfhash[0,7],
366
:challenge => srvchall
367
}, true )
368
key = halfhash + ["bdbdbdbdbdbd"].pack("H*")
369
keys = self.gen_keys(key)
370
apply_des(plain, keys).join
371
end
372
373
def self.encrypt_sessionkey(session_key, user_session_key)
374
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
375
cipher = OpenSSL::Cipher.new('rc4')
376
cipher.encrypt
377
cipher.key = user_session_key
378
cipher.update(session_key)
379
end
380
381
def self.decrypt_sessionkey(encrypted_session_key, user_session_key)
382
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
383
cipher = OpenSSL::Cipher.new('rc4')
384
cipher.decrypt
385
cipher.key = user_session_key
386
cipher.update(encrypted_session_key)
387
end
388
389
def self.make_weak_sessionkey(session_key,key_size,lanman_key = false)
390
case key_size
391
when 40
392
if lanman_key
393
return session_key[0,5] + "\xe5\x38\xb0"
394
else
395
return session_key[0,5]
396
end
397
when 56
398
if lanman_key
399
return session_key[0,7] + "\xa0"
400
else
401
return session_key[0,7]
402
end
403
else #128
404
return session_key[0,16]
405
end
406
end
407
408
end
409
end
410
end
411
end
412
413