Class: Rex::Proto::NTLM::Crypt

Inherits:
Object
  • Object
show all
Defined in:
lib/rex/proto/ntlm/crypt.rb

Constant Summary collapse

CONST =
Rex::Proto::NTLM::Constants
BASE =
Rex::Proto::NTLM::Base
@@loaded_openssl =
false

Class Method Summary collapse

Class Method Details

.apply_des(plain, keys) ⇒ Object

Raises:



88
89
90
91
92
93
94
95
# File 'lib/rex/proto/ntlm/crypt.rb', line 88

def self.apply_des(plain, keys)
	raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
	dec = OpenSSL::Cipher::DES.new
	keys.map do |k|
		dec.key = k
		dec.encrypt.update(plain)
	end
end

.decrypt_sessionkey(encrypted_session_key, user_session_key) ⇒ Object

Raises:



385
386
387
388
389
390
391
# File 'lib/rex/proto/ntlm/crypt.rb', line 385

def self.decrypt_sessionkey(encrypted_session_key, user_session_key)
	raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
	cipher = OpenSSL::Cipher::Cipher.new('rc4')
	cipher.decrypt
	cipher.key = user_session_key
	cipher.update(encrypted_session_key) 
end

.des_56_to_64(ckey56s) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/rex/proto/ntlm/crypt.rb', line 74

def self.des_56_to_64(ckey56s)
	ckey64 = []
	ckey56 = ckey56s.unpack('C*')
	ckey64[0] = ckey56[0]
	ckey64[1] = ((ckey56[0] << 7) & 0xFF) | (ckey56[1] >> 1)
	ckey64[2] = ((ckey56[1] << 6) & 0xFF) | (ckey56[2] >> 2)
	ckey64[3] = ((ckey56[2] << 5) & 0xFF) | (ckey56[3] >> 3)
	ckey64[4] = ((ckey56[3] << 4) & 0xFF) | (ckey56[4] >> 4)
	ckey64[5] = ((ckey56[4] << 3) & 0xFF) | (ckey56[5] >> 5)
	ckey64[6] = ((ckey56[5] << 2) & 0xFF) | (ckey56[6] >> 6)
	ckey64[7] =  (ckey56[6] << 1) & 0xFF
	ckey64.pack('C*')
end

.encrypt_sessionkey(session_key, user_session_key) ⇒ Object

Raises:



377
378
379
380
381
382
383
# File 'lib/rex/proto/ntlm/crypt.rb', line 377

def self.encrypt_sessionkey(session_key, user_session_key)
	raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
	cipher = OpenSSL::Cipher::Cipher.new('rc4')
	cipher.encrypt
	cipher.key = user_session_key
	cipher.update(session_key) 
end

.gen_keys(str) ⇒ Object



70
71
72
# File 'lib/rex/proto/ntlm/crypt.rb', line 70

def self.gen_keys(str)
	str.scan(/.{7}/).map{ |key| des_56_to_64(key) }
end

.is_hash_from_empty_pwd?(arg) ⇒ Boolean

this function will check if the net lm response provided correspond to en empty password

Returns:

  • (Boolean)

Raises:



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/rex/proto/ntlm/crypt.rb', line 238

def self.is_hash_from_empty_pwd?(arg)
	hash_type = arg[:type]
	raise ArgumentError,"arg[:type] is mandatory" if not hash_type 
	raise ArgumentError,"arg[:type] must be lm or ntlm" if not hash_type  =~ /^((lm)|(ntlm))$/

	ntlm_ver = arg[:ntlm_ver]
	raise ArgumentError,"arg[:ntlm_ver] is mandatory" if not ntlm_ver

	hash = arg[:hash]
	raise ArgumentError,"arg[:hash] is mandatory" if not hash

	srv_chall = arg[:srv_challenge] 
	raise ArgumentError,"arg[:srv_challenge] is mandatory" if not srv_chall		
	raise ArgumentError,"Server challenge length must be exactly 8 bytes" if srv_chall.length != 8

	#calculate responses for empty pwd
	case ntlm_ver
	when CONST::NTLM_V1_RESPONSE 
		if hash.length != 24
			raise ArgumentError,"hash length must be exactly 24 bytes "
		end
		case hash_type
		when 'lm'	
			arglm = { 	:lm_hash => self.lm_hash(''),
					:challenge => srv_chall}
			calculatedhash = self.lm_response(arglm)
		when 'ntlm'
			argntlm = { 	:ntlm_hash =>  self.ntlm_hash(''), 
					:challenge => srv_chall }
			calculatedhash = self.ntlm_response(argntlm)
		end
	when CONST::NTLM_V2_RESPONSE
		raise ArgumentError,"hash length must be exactly 16 bytes " if hash.length != 16
		cli_chall = arg[:cli_challenge] 
		raise ArgumentError,"arg[:cli_challenge] is mandatory in this case" if not cli_chall
		user = arg[:user] 
		raise ArgumentError,"arg[:user] is mandatory in this case" if not user
		domain = arg[:domain]
		raise ArgumentError,"arg[:domain] is mandatory in this case" if not domain

		case hash_type
		when 'lm'
			raise ArgumentError,"Client challenge length must be exactly 8 bytes " if cli_chall.length != 8
			arglm = {	:ntlmv2_hash =>  self.ntlmv2_hash(user,'', domain),
					:challenge => srv_chall }
			optlm = {	:client_challenge => cli_chall}
			calculatedhash = self.lmv2_response(arglm, optlm)[0,16]
		when 'ntlm'
			raise ArgumentError,"Client challenge length must be bigger then 8 bytes " if cli_chall.length <= 8
			argntlm = { 	:ntlmv2_hash =>  self.ntlmv2_hash(user, '', domain),
					:challenge => srv_chall }
			optntlm = { 	:nt_client_challenge => cli_chall}
			calculatedhash = self.ntlmv2_response(argntlm,optntlm)[0,16]
		end
	when CONST::NTLM_2_SESSION_RESPONSE
		raise ArgumentError,"hash length must be exactly 16 bytes " if hash.length != 24
		cli_chall = arg[:cli_challenge] 
		raise ArgumentError,"arg[:cli_challenge] is mandatory in this case" if not cli_chall
		raise ArgumentError,"Client challenge length must be exactly 8 bytes " if cli_chall.length != 8
		case hash_type
		when 'lm'
			raise ArgumentError, "ntlm2_session is incompatible with lm"
		when 'ntlm'
			argntlm = { 	:ntlm_hash =>  self.ntlm_hash(''), 
					:challenge => srv_chall }
			optntlm = {	:client_challenge => cli_chall}
		end
		calculatedhash = self.ntlm2_session(argntlm,optntlm).join[24,24]
	else
		raise ArgumentError,"ntlm_ver is of unknow type"
	end
	hash == calculatedhash
end

.lanman_des(password, challenge) ⇒ Object

Synonym of lm_response for old compatibility with lib/rex/proto/smb/crypt



144
145
146
147
148
149
# File 'lib/rex/proto/ntlm/crypt.rb', line 144

def self.lanman_des(password, challenge)		
	lm_response({
		:lm_hash => self.lm_hash(password),
		:challenge => challenge
	})
end

.lanman_session_key(pass, srvchall, opt = {}) ⇒ Object

Used when LanMan Key flag is set



362
363
364
365
366
367
368
369
370
371
372
373
374
375
# File 'lib/rex/proto/ntlm/crypt.rb', line 362

def self.lanman_session_key(pass, srvchall, opt = {})
	if opt[:pass_is_hash]
		halfhash = pass[0,8]
	else
		halfhash = lm_hash(pass.upcase[0,7],true)
	end
	plain = self.lm_response({
		:lm_hash => halfhash[0,7],
		:challenge => srvchall
	}, true )
	key = halfhash  + ["bdbdbdbdbdbd"].pack("H*")
       keys = self.gen_keys(key)
      	apply_des(plain, keys).join
end

.lm_hash(password, half = false) ⇒ Object



97
98
99
100
101
# File 'lib/rex/proto/ntlm/crypt.rb', line 97

def self.lm_hash(password, half = false)
	size = half ? 7 : 14
	keys = gen_keys(password.upcase.ljust(size, "\0"))
	apply_des(CONST::LM_MAGIC, keys).join
end

.lm_response(arg, half = false) ⇒ Object

Create the LANMAN response



130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/rex/proto/ntlm/crypt.rb', line 130

def self.lm_response(arg, half = false)
	begin
		hash = arg[:lm_hash]
		chal = arg[:challenge]
	rescue
		raise ArgumentError
	end
	chal = BASE::pack_int64le(chal) if chal.is_a?(Integer)
	if half then size = 7 else  size = 21 end
	keys = gen_keys hash.ljust(size, "\0")
	apply_des(chal, keys).join
end

.lmv1_user_session_key(pass, opt = {}) ⇒ Object

Used when only the LMv1 response is provided (i.e., with Win9x clients)



319
320
321
322
323
324
325
326
# File 'lib/rex/proto/ntlm/crypt.rb', line 319

def self.lmv1_user_session_key(pass, opt = {})
	if opt[:pass_is_hash]
		usk = pass[0,8]
	else
		usk = self.lm_hash(pass.upcase[0,7],true)
	end
	usk.ljust(16,"\x00")
end

.lmv2_response(arg, opt = {}) ⇒ Object

Raises:



209
210
211
212
213
214
215
216
217
218
219
# File 'lib/rex/proto/ntlm/crypt.rb', line 209

def self.lmv2_response(arg, opt = {})
	raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
	key = arg[:ntlmv2_hash]
	chal = arg[:challenge]
       
	chal = BASE::pack_int64le(chal) if chal.is_a?(::Integer)
	cc   = opt[:client_challenge] || rand(CONST::MAX64)
	cc   = BASE::pack_int64le(cc) if cc.is_a?(::Integer)

	OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + cc) + cc
end

.lmv2_user_session_key(user, pass, domain, srv_chall, cli_chall, opt = {}) ⇒ Object Also known as: ntlmv2_user_session_key

Used when the LMv2 response is sent

Raises:



350
351
352
353
354
355
356
# File 'lib/rex/proto/ntlm/crypt.rb', line 350

def self.lmv2_user_session_key(user, pass, domain, srv_chall, cli_chall, opt = {})
	raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl

	ntlmv2_key = self.ntlmv2_hash(user, pass, domain, opt)
	hash1 = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_key, srv_chall + cli_chall)
	OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_key, hash1)
end

.make_weak_sessionkey(session_key, key_size, lanman_key = false) ⇒ Object



393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
# File 'lib/rex/proto/ntlm/crypt.rb', line 393

def self.make_weak_sessionkey(session_key,key_size,lanman_key = false)
	case key_size
	when 40
		if lanman_key
			return session_key[0,5] + "\xe5\x38\xb0" 
		else
			return session_key[0,5] 
		end
	when 56
		if lanman_key
			return session_key[0,7]  + "\xa0"
		else
			return session_key[0,7]  
		end
	else #128 
		return session_key[0,16]
	end
end

.ntlm2_session(arg, opt = {}) ⇒ Object

Raises:



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/rex/proto/ntlm/crypt.rb', line 221

def self.ntlm2_session(arg, opt = {})
	raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
	passwd_hash,chal = arg[:ntlm_hash],arg[:challenge]
	if not (passwd_hash and chal)
		raise RuntimeError, "ntlm_hash and challenge are required"
	end

	cc = opt[:client_challenge] || rand(CONST::MAX64)
	cc = BASE::pack_int64le(cc) if cc.is_a?(Integer)

	keys = gen_keys(passwd_hash.ljust(21, "\0"))
	session_hash = OpenSSL::Digest::MD5.digest(chal + cc)[0,8]
	response = apply_des(session_hash, keys).join
	[cc.ljust(24, "\0"), response]
end

.ntlm2_session_user_session_key(pass, srv_chall, cli_chall, opt = {}) ⇒ Object

Used when NTLMv1 authentication is employed with NTLM2 session security

Raises:



341
342
343
344
345
346
347
# File 'lib/rex/proto/ntlm/crypt.rb', line 341

def self.ntlm2_session_user_session_key(pass, srv_chall, cli_chall, opt = {})
	raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl

	ntlm_key = self.ntlmv1_user_session_key(pass, opt )
	session_chal = srv_chall + cli_chall
	OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlm_key, session_chal)
end

.ntlm_hash(password, opt = {}) ⇒ Object

Raises:



103
104
105
106
107
108
109
110
# File 'lib/rex/proto/ntlm/crypt.rb', line 103

def self.ntlm_hash(password, opt = {})
	raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
	pwd = password.dup
	unless opt[:unicode]
		pwd = Rex::Text.to_unicode(pwd)
	end
	OpenSSL::Digest::MD4.digest(pwd)
end

.ntlm_md4(password, challenge) ⇒ Object

synonym of ntlm_response for old compatibility with lib/rex/proto/smb/crypt



160
161
162
163
164
165
# File 'lib/rex/proto/ntlm/crypt.rb', line 160

def self.ntlm_md4(password, challenge)
	ntlm_response({
		:ntlm_hash =>  self.ntlm_hash(password), 
		:challenge => challenge
	})
end

.ntlm_response(arg) ⇒ Object



151
152
153
154
155
156
157
# File 'lib/rex/proto/ntlm/crypt.rb', line 151

def self.ntlm_response(arg)
	hash = arg[:ntlm_hash]
	chal = arg[:challenge]
	chal = BASE::pack_int64le(chal) if chal.is_a?(::Integer)
	keys = gen_keys(hash.ljust(21, "\0"))
	apply_des(chal, keys).join
end

.ntlmv1_user_session_key(pass, opt = {}) ⇒ Object

This variant is used when the client sends the NTLMv1 response

Raises:



329
330
331
332
333
334
335
336
337
338
# File 'lib/rex/proto/ntlm/crypt.rb', line 329

def self.ntlmv1_user_session_key(pass, opt = {})
	raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl

	if opt[:pass_is_hash]
		usk = pass
	else
		usk = self.ntlm_hash(pass)
	end
	OpenSSL::Digest::MD4.digest(usk)
end

.ntlmv2_hash(user, password, domain, opt = {}) ⇒ Object

This hash is used for lmv2/ntlmv2 response calculation

Raises:



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/rex/proto/ntlm/crypt.rb', line 113

def self.ntlmv2_hash(user, password, domain, opt={})
	raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
	
	if opt[:pass_is_hash]
		ntlmhash = password
	else
		ntlmhash = ntlm_hash(password, opt)
	end	
	# With Win 7 and maybe other OSs we sometimes get the domain not uppercased
	userdomain = user.upcase  + domain
	unless opt[:unicode]
		userdomain = Rex::Text.to_unicode(userdomain)
	end
	OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
end

.ntlmv2_response(arg, opt = {}) ⇒ Object

Raises:



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/rex/proto/ntlm/crypt.rb', line 167

def self.ntlmv2_response(arg, opt = {})
	raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
	
	key, chal = arg[:ntlmv2_hash], arg[:challenge]
	if not (key and chal)
		raise ArgumentError , 'ntlmv2_hash and challenge are mandatory'
	end

	chal = BASE::pack_int64le(chal) if chal.is_a?(::Integer)
	bb   = nil
	
	if opt[:nt_client_challenge]
		if opt[:nt_client_challenge].to_s.length <= 8
			raise ArgumentError,"nt_client_challenge is not in a correct format " 
		end
		bb = opt[:nt_client_challenge]
	else
		if not arg[:target_info]
			raise ArgumentError, "target_info is mandatory in this case"
		end

		ti = arg[:target_info]
		cc = opt[:client_challenge] || rand(CONST::MAX64)
		cc = BASE::pack_int64le(cc) if cc.is_a?(::Integer)

		ts = opt[:timestamp] || Time.now.to_i

		# Convert the unix timestamp to windows format
		#   epoch -> milsec from Jan 1, 1601
		ts = 10000000 * (ts + CONST::TIME_OFFSET)

		blob = BASE::Blob.new
		blob.timestamp = ts
		blob.challenge = cc
		blob.target_info = ti
	
		bb = blob.serialize
	end

	OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
end