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:



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

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:



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

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



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

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:



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

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



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

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:



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
311
# File 'lib/rex/proto/ntlm/crypt.rb', line 239

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



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

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



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

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



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

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



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

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)



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

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:



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

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:



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

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



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

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:



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

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:



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

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:



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

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



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

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

.ntlm_response(arg) ⇒ Object



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

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:



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

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:



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

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:



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
208
# File 'lib/rex/proto/ntlm/crypt.rb', line 168

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