Module: Net::NTLM

Defined in:
lib/net/ntlm.rb

Defined Under Namespace

Modules: VERSION Classes: Blob, EncodeUtil, Field, FieldSet, Int16LE, Int32LE, Int64LE, Message, SecurityBuffer, String

Constant Summary collapse

SSP_SIGN =
"NTLMSSP\0"
BLOB_SIGN =
0x00000101
LM_MAGIC =
"KGS!@\#$%"
TIME_OFFSET =
11644473600
MAX64 =
0xffffffffffffffff
FLAGS =
{
  :UNICODE              => 0x00000001,
  :OEM                  => 0x00000002,
  :REQUEST_TARGET       => 0x00000004,
  :MBZ9                 => 0x00000008,
  :SIGN                 => 0x00000010,
  :SEAL                 => 0x00000020,
  :NEG_DATAGRAM         => 0x00000040,
  :NETWARE              => 0x00000100,
  :NTLM                 => 0x00000200,
  :NEG_NT_ONLY          => 0x00000400,
  :MBZ7                 => 0x00000800,
  :DOMAIN_SUPPLIED      => 0x00001000,
  :WORKSTATION_SUPPLIED => 0x00002000,
  :LOCAL_CALL           => 0x00004000,
  :ALWAYS_SIGN          => 0x00008000,
  :TARGET_TYPE_DOMAIN   => 0x00010000,
  :TARGET_INFO          => 0x00800000,
  :NTLM2_KEY            => 0x00080000,
  :KEY128               => 0x20000000,
  :KEY56                => 0x80000000
}.freeze
FLAG_KEYS =
FLAGS.keys.sort{|a, b| FLAGS[a] <=> FLAGS[b] }
DEFAULT_FLAGS =
{
  :TYPE1 => FLAGS[:UNICODE] | FLAGS[:OEM] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY],
  :TYPE2 => FLAGS[:UNICODE],
  :TYPE3 => FLAGS[:UNICODE] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY]
}

Class Method Summary collapse

Class Method Details

.apply_des(plain, keys) ⇒ Object



171
172
173
174
175
176
177
# File 'lib/net/ntlm.rb', line 171

def apply_des(plain, keys)
  dec = OpenSSL::Cipher::DES.new
  keys.map {|k|
    dec.key = k
    dec.encrypt.update(plain)
  }
end

.gen_keys(str) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Not sure what this is doing

Parameters:

  • str (String)

    String to generate keys for



163
164
165
166
167
168
169
# File 'lib/net/ntlm.rb', line 163

def gen_keys(str)
  split7(str).map{ |str7|
    bits = split7(str7.unpack("B*")[0]).inject('')\
      {|ret, tkn| ret += tkn + (tkn.gsub('1', '').size % 2).to_s }
    [bits].pack("B*")
  }
end

.lm_hash(password) ⇒ Object

Generates a Lan Manager Hash

Parameters:

  • password (String)

    The password to base the hash on



181
182
183
184
# File 'lib/net/ntlm.rb', line 181

def lm_hash(password)
  keys = gen_keys password.upcase.ljust(14, "\0")
  apply_des(LM_MAGIC, keys).join
end

.lm_response(arg) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
# File 'lib/net/ntlm.rb', line 211

def lm_response(arg)
  begin
    hash = arg[:lm_hash]
    chal = arg[:challenge]
  rescue
    raise ArgumentError
  end
  chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
  keys = gen_keys hash.ljust(21, "\0")
  apply_des(chal, keys).join
end

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



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/net/ntlm.rb', line 265

def lmv2_response(arg, opt = {})
  key = arg[:ntlmv2_hash]
  chal = arg[:challenge]

  chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)

  if opt[:client_challenge]
    cc  = opt[:client_challenge]
  else
    cc = rand(MAX64)
  end
  cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)

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

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



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/net/ntlm.rb', line 281

def ntlm2_session(arg, opt = {})
  begin
    passwd_hash = arg[:ntlm_hash]
    chal = arg[:challenge]
  rescue
    raise ArgumentError
  end

  if opt[:client_challenge]
    cc  = opt[:client_challenge]
  else
    cc = rand(MAX64)
  end
  cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)

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

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

Generate a NTLM Hash

Parameters:

  • password (String)

    The password to base the hash on

  • opt (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opt):

  • :unicode (Object) — default: false

    Unicode encode the password



189
190
191
192
193
194
195
# File 'lib/net/ntlm.rb', line 189

def ntlm_hash(password, opt = {})
  pwd = password.dup
  unless opt[:unicode]
    pwd = EncodeUtil.encode_utf16le(pwd)
  end
  OpenSSL::Digest::MD4.digest pwd
end

.ntlm_response(arg) ⇒ Object



223
224
225
226
227
228
229
# File 'lib/net/ntlm.rb', line 223

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

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

Generate a NTLMv2 Hash

Parameters:

  • user (String)

    The username

  • password (String)

    The password

  • target (String)

    The domain or workstaiton to authenticate to

  • opt (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opt):

  • :unicode (Object) — default: false

    Unicode encode the domain



202
203
204
205
206
207
208
209
# File 'lib/net/ntlm.rb', line 202

def ntlmv2_hash(user, password, target, opt={})
  ntlmhash = ntlm_hash(password, opt)
  userdomain = (user + target).upcase
  unless opt[:unicode]
    userdomain = EncodeUtil.encode_utf16le(userdomain)
  end
  OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
end

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



231
232
233
234
235
236
237
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
# File 'lib/net/ntlm.rb', line 231

def ntlmv2_response(arg, opt = {})
  begin
    key = arg[:ntlmv2_hash]
    chal = arg[:challenge]
    ti = arg[:target_info]
  rescue
    raise ArgumentError
  end
  chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)

  if opt[:client_challenge]
    cc  = opt[:client_challenge]
  else
    cc = rand(MAX64)
  end
  cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)

  if opt[:timestamp]
    ts = opt[:timestamp]
  else
    ts = Time.now.to_i
  end
  # epoch -> milsec from Jan 1, 1601
  ts = 10000000 * (ts + TIME_OFFSET)

  blob = Blob.new
  blob.timestamp = ts
  blob.challenge = cc
  blob.target_info = ti

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

.pack_int64le(val) ⇒ Object

Conver the value to a 64-Bit Little Endian Int

Parameters:

  • val (String)

    The string to convert



145
146
147
# File 'lib/net/ntlm.rb', line 145

def pack_int64le(val)
    [val & 0x00000000ffffffff, val >> 32].pack("V2")
end

.split7(str) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Builds an array of strings that are 7 characters long

Parameters:

  • str (String)

    The string to split



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

def split7(str)
  s = str.dup
  until s.empty?
    (ret ||= []).push s.slice!(0, 7)
  end
  ret
end