Module: Watobo::NTLM

Defined in:
lib/watobo/utils/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



177
178
179
180
181
182
183
# File 'lib/watobo/utils/ntlm.rb', line 177

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



169
170
171
172
173
174
175
# File 'lib/watobo/utils/ntlm.rb', line 169

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



187
188
189
190
# File 'lib/watobo/utils/ntlm.rb', line 187

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

.lm_response(arg) ⇒ Object



217
218
219
220
221
222
223
224
225
226
227
# File 'lib/watobo/utils/ntlm.rb', line 217

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



271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/watobo/utils/ntlm.rb', line 271

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



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/watobo/utils/ntlm.rb', line 287

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



195
196
197
198
199
200
201
# File 'lib/watobo/utils/ntlm.rb', line 195

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



229
230
231
232
233
234
235
# File 'lib/watobo/utils/ntlm.rb', line 229

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



208
209
210
211
212
213
214
215
# File 'lib/watobo/utils/ntlm.rb', line 208

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



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
264
265
266
267
268
269
# File 'lib/watobo/utils/ntlm.rb', line 237

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



151
152
153
# File 'lib/watobo/utils/ntlm.rb', line 151

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



158
159
160
161
162
163
164
# File 'lib/watobo/utils/ntlm.rb', line 158

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