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



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

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



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

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



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

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

.lm_response(arg) ⇒ Object



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

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



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/watobo/utils/ntlm.rb', line 262

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



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/watobo/utils/ntlm.rb', line 278

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



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

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



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

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



199
200
201
202
203
204
205
206
# File 'lib/watobo/utils/ntlm.rb', line 199

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



228
229
230
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
# File 'lib/watobo/utils/ntlm.rb', line 228

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



142
143
144
# File 'lib/watobo/utils/ntlm.rb', line 142

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



149
150
151
152
153
154
155
# File 'lib/watobo/utils/ntlm.rb', line 149

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