From f56541944d8cfc404057c77e6234c45c1ecff090 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:04:09 -0400 Subject: [PATCH 01/21] Update examples/authenticate.rb to use OptionParser Replace raw ARGV access with OptionParser following the convention used in examples/read_file.rb and examples/anonymous_auth.rb. Adds --[no-]smbv1/2/3, --username (with domain\username splitting), and --password flags. Prints the help banner when target is missing or when -h/--help is given. The existing five-combination test cycle is preserved but filtered by the user's selected SMB versions. --- examples/authenticate.rb | 80 ++++++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 16 deletions(-) diff --git a/examples/authenticate.rb b/examples/authenticate.rb index 912d5fba1..aff6d9ea0 100644 --- a/examples/authenticate.rb +++ b/examples/authenticate.rb @@ -4,14 +4,15 @@ # including protocol negotiation and authentication. require 'bundler/setup' +require 'optparse' require 'ruby_smb' -def run_authentication(address, smb1, smb2, smb3, username, password) +def run_authentication(address, smb1, smb2, smb3, username, password, domain) # Create our socket and add it to the dispatcher sock = TCPSocket.new address, 445 dispatcher = RubySMB::Dispatcher::Socket.new(sock) - client = RubySMB::Client.new(dispatcher, smb1: smb1, smb2: smb2, smb3: smb3, username: username, password: password) + client = RubySMB::Client.new(dispatcher, smb1: smb1, smb2: smb2, smb3: smb3, username: username, password: password, domain: domain) protocol = client.negotiate status = client.authenticate puts "#{protocol} : #{status}" @@ -28,17 +29,64 @@ def run_authentication(address, smb1, smb2, smb3, username, password) puts "OS Version: #{client.os_version}" end -address = ARGV[0] -username = ARGV[1] -password = ARGV[2] - -# Negotiate with SMB1, SMB2 and SMB3 enabled on the client -run_authentication(address, true, true, true, username, password) -# Negotiate with both SMB1 and SMB2 enabled on the client -run_authentication(address, true, true, false, username, password) -# Negotiate with only SMB1 enabled -run_authentication(address, true, false, false, username, password) -# Negotiate with only SMB2 enabled -run_authentication(address, false, true, false, username, password) -# Negotiate with only SMB3 enabled -run_authentication(address, false, false, true, username, password) +args = ARGV.dup +options = { + domain: '.', + username: '', + password: '', + smbv1: true, + smbv2: true, + smbv3: true, + target: nil +} +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target" + 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 + opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username| + if username.include?('\\') + options[:domain], options[:username] = username.split('\\', 2) + else + options[:username] = username + end + end + opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password| + options[:password] = password + end +end +optparser.parse!(args) + +if options[:target] == '-h' || options[:target] == '--help' + puts optparser.help + exit +end + +if options[:target].nil? + abort(optparser.help) +end + +# (smb1, smb2, smb3) combinations to exercise — filtered by the user's +# --[no-]smbv{1,2,3} flags so that any combo requiring a disabled version +# is skipped. +combinations = [ + [true, true, true], # SMB1, SMB2 and SMB3 enabled + [true, true, false], # SMB1 and SMB2 enabled + [true, false, false], # only SMB1 enabled + [false, true, false], # only SMB2 enabled + [false, false, true] # only SMB3 enabled +] + +combinations.each do |smb1, smb2, smb3| + next if smb1 && !options[:smbv1] + next if smb2 && !options[:smbv2] + next if smb3 && !options[:smbv3] + run_authentication(options[:target], smb1, smb2, smb3, options[:username], options[:password], options[:domain]) +end From cf9d8e5519ffef744574f0be84840afbd39d9f39 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:09:05 -0400 Subject: [PATCH 02/21] Update examples/append_file.rb to use OptionParser Replace raw ARGV access with OptionParser following the existing convention. Adds --[no-]smbv1/2/3, --username (with domain\username splitting), and --password flags. Prints the help banner when a required positional is missing or when -h/--help is given. --- examples/append_file.rb | 71 +++++++++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/examples/append_file.rb b/examples/append_file.rb index da91c06c5..e94a1de27 100644 --- a/examples/append_file.rb +++ b/examples/append_file.rb @@ -2,27 +2,70 @@ # This example script is used for testing the appending to a file. # It will attempt to connect to a specific share and then append to a specified file. -# Example usage: ruby append_file.rb 192.168.172.138 msfadmin msfadmin TEST_SHARE test.txt "data to write" +# Example usage: ruby append_file.rb --username msfadmin --password msfadmin 192.168.172.138 TEST_SHARE test.txt "data to write" # This will try to connect to \\192.168.172.138\TEST_SHARE with the msfadmin:msfadmin credentials -# and write "data to write" the end of the file test.txt +# and append "data to write" to the end of the file test.txt require 'bundler/setup' +require 'optparse' require 'ruby_smb' -address = ARGV[0] -username = ARGV[1] -password = ARGV[2] -share = ARGV[3] -file = ARGV[4] -data = ARGV[5] -smb_versions = ARGV[6]&.split(',') || ['1','2','3'] +args = ARGV.dup +options = { + domain: '.', + username: '', + password: '', + smbv1: true, + smbv2: true, + smbv3: true, + target: nil, + share: nil, + file: nil, + data: nil +} +options[:data] = args.pop +options[:file] = args.pop +options[:share] = args.pop +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target share file data" + 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 + opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username| + if username.include?('\\') + options[:domain], options[:username] = username.split('\\', 2) + else + options[:username] = username + end + end + opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password| + options[:password] = password + end +end +optparser.parse!(args) + +if [options[:target], options[:share], options[:file], options[:data]].any? { |a| a == '-h' || a == '--help' } + puts optparser.help + exit +end + +if options[:target].nil? || options[:share].nil? || options[:file].nil? || options[:data].nil? + abort(optparser.help) +end -path = "\\\\#{address}\\#{share}" +path = "\\\\#{options[:target]}\\#{options[:share]}" -sock = TCPSocket.new address, 445 +sock = TCPSocket.new options[:target], 445 dispatcher = RubySMB::Dispatcher::Socket.new(sock) -client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password) +client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain]) protocol = client.negotiate status = client.authenticate @@ -35,8 +78,8 @@ puts "Failed to connect to #{path}: #{e.message}" end -file = tree.open_file(filename: file, write: true, disposition: RubySMB::Dispositions::FILE_OPEN_IF) +file = tree.open_file(filename: options[:file], write: true, disposition: RubySMB::Dispositions::FILE_OPEN_IF) -result = file.append(data: data) +result = file.append(data: options[:data]) puts result.to_s file.close From e6868f8e10bc838e8d7e3fe1d4498dc42069f6d7 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:09:46 -0400 Subject: [PATCH 03/21] Update examples/delete_file.rb to use OptionParser Replace raw ARGV access with OptionParser following the existing convention. Adds --[no-]smbv1/2/3, --username (with domain\username splitting), and --password flags. Prints the help banner when a required positional is missing or when -h/--help is given. --- examples/delete_file.rb | 64 ++++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/examples/delete_file.rb b/examples/delete_file.rb index 8b313aa8b..b5210fce3 100644 --- a/examples/delete_file.rb +++ b/examples/delete_file.rb @@ -2,26 +2,68 @@ # This example script is used for testing the deleting of a file. # It will attempt to connect to a specific share and then delete a specified file. -# Example usage: ruby delete_file.rb 192.168.172.138 msfadmin msfadmin TEST_SHARE short.txt +# Example usage: ruby delete_file.rb --username msfadmin --password msfadmin 192.168.172.138 TEST_SHARE short.txt # This will try to connect to \\192.168.172.138\TEST_SHARE with the msfadmin:msfadmin credentials # and delete the file short.txt require 'bundler/setup' +require 'optparse' require 'ruby_smb' -address = ARGV[0] -username = ARGV[1] -password = ARGV[2] -share = ARGV[3] -file = ARGV[4] -smb_versions = ARGV[5]&.split(',') || ['1','2','3'] +args = ARGV.dup +options = { + domain: '.', + username: '', + password: '', + smbv1: true, + smbv2: true, + smbv3: true, + target: nil, + share: nil, + file: nil +} +options[:file] = args.pop +options[:share] = args.pop +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target share file" + 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 + opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username| + if username.include?('\\') + options[:domain], options[:username] = username.split('\\', 2) + else + options[:username] = username + end + end + opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password| + options[:password] = password + end +end +optparser.parse!(args) + +if [options[:target], options[:share], options[:file]].any? { |a| a == '-h' || a == '--help' } + puts optparser.help + exit +end + +if options[:target].nil? || options[:share].nil? || options[:file].nil? + abort(optparser.help) +end -path = "\\\\#{address}\\#{share}" +path = "\\\\#{options[:target]}\\#{options[:share]}" -sock = TCPSocket.new address, 445 +sock = TCPSocket.new options[:target], 445 dispatcher = RubySMB::Dispatcher::Socket.new(sock) -client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password) +client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain]) protocol = client.negotiate status = client.authenticate @@ -35,7 +77,7 @@ puts "Failed to connect to #{path}: #{e.message}" end -file = tree.open_file(filename: file, delete: true) +file = tree.open_file(filename: options[:file], delete: true) data = file.delete puts data From c1bf166af6f979a80db625b9fcba115d3eb005b9 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:12:36 -0400 Subject: [PATCH 04/21] Update examples/dump_secrets_from_sid.rb to use OptionParser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace raw ARGV access with OptionParser following the existing convention. Adds --username (with domain\username splitting) and --password flags; keeps the AD lookup domain and SID as positional arguments. Prints the help banner when a required positional is missing or when -h/--help is given. Also requires 'ruby_smb' so the script loads independently — the previous 'ruby_smb/dcerpc/client' require alone was failing with an uninitialized Net::NTLM constant. --- examples/dump_secrets_from_sid.rb | 51 ++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/examples/dump_secrets_from_sid.rb b/examples/dump_secrets_from_sid.rb index f88f5a5cb..4b9533927 100644 --- a/examples/dump_secrets_from_sid.rb +++ b/examples/dump_secrets_from_sid.rb @@ -2,26 +2,61 @@ # This example script is used for testing DCERPC client and DRSR structures. # It will attempt to connect to a host and enumerate user secrets. -# Example usage: ruby dump_secrets_from_sid.rb 192.168.172.138 msfadmin msfadmin MYDOMAIN S-1-5-21-419547006-9448028-4223375872-500 +# Example usage: ruby dump_secrets_from_sid.rb --username msfadmin --password msfadmin 192.168.172.138 MYDOMAIN S-1-5-21-419547006-9448028-4223375872-500 # This will try to connect to \\192.168.172.138 with the msfadmin:msfadmin # credentials and enumerate secrets of domain user with SID # S-1-5-21-419547006-9448028-4223375872-500 require 'bundler/setup' +require 'optparse' +require 'ruby_smb' require 'ruby_smb/dcerpc/client' +args = ARGV.dup +options = { + domain: '.', + username: '', + password: '', + target: nil, + lookup_domain: nil, + sid: nil +} +options[:sid] = args.pop +options[:lookup_domain] = args.pop +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target domain sid" + opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username| + if username.include?('\\') + options[:domain], options[:username] = username.split('\\', 2) + else + options[:username] = username + end + end + opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password| + options[:password] = password + end +end +optparser.parse!(args) + +if [options[:target], options[:lookup_domain], options[:sid]].any? { |a| a == '-h' || a == '--help' } + puts optparser.help + exit +end + +if options[:target].nil? || options[:lookup_domain].nil? || options[:sid].nil? + abort(optparser.help) +end -address = ARGV[0] -username = ARGV[1] -password = ARGV[2] -domain = ARGV[3] -sid = ARGV[4] +address = options[:target] +domain = options[:lookup_domain] +sid = options[:sid] client = RubySMB::Dcerpc::Client.new( address, RubySMB::Dcerpc::Drsr, - username: username, - password: password, + username: options[:username], + password: options[:password], ) client.connect puts('Binding to DRSR...') From fd9c2bf56c0c641306842fcc20e5eebf98ac43d0 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:13:31 -0400 Subject: [PATCH 05/21] Update examples/enum_domain_users.rb to use OptionParser Replace raw ARGV access with OptionParser following the existing convention. Adds --[no-]smbv1/2/3, --username (with domain\username splitting), and --password flags; keeps the AD lookup domain as a positional argument. Prints the help banner when a required positional is missing or when -h/--help is given. --- examples/enum_domain_users.rb | 59 ++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/examples/enum_domain_users.rb b/examples/enum_domain_users.rb index 12a21c771..c2179fd63 100644 --- a/examples/enum_domain_users.rb +++ b/examples/enum_domain_users.rb @@ -2,21 +2,65 @@ # This example script is used for testing DCERPC SAMR requests. # It will attempt to connect to a server object and enumerate domain users. -# Example usage: ruby enum_domain_users.rb 192.168.172.138 msfadmin msfadmin MyDomain +# Example usage: ruby enum_domain_users.rb --username msfadmin --password msfadmin 192.168.172.138 MyDomain require 'bundler/setup' +require 'optparse' require 'ruby_smb' -address = ARGV[0] -username = ARGV[1] -password = ARGV[2] -domain = ARGV[3] -smb_versions = ARGV[4]&.split(',') || ['1','2','3'] +args = ARGV.dup +options = { + domain: '.', + username: '', + password: '', + smbv1: true, + smbv2: true, + smbv3: true, + target: nil, + lookup_domain: nil +} +options[:lookup_domain] = args.pop +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target domain" + 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 + opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username| + if username.include?('\\') + options[:domain], options[:username] = username.split('\\', 2) + else + options[:username] = username + end + end + opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password| + options[:password] = password + end +end +optparser.parse!(args) + +if [options[:target], options[:lookup_domain]].any? { |a| a == '-h' || a == '--help' } + puts optparser.help + exit +end + +if options[:target].nil? || options[:lookup_domain].nil? + abort(optparser.help) +end + +address = options[:target] +domain = options[:lookup_domain] sock = TCPSocket.new address, 445 dispatcher = RubySMB::Dispatcher::Socket.new(sock, read_timeout: 60) -client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password) +client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain]) protocol = client.negotiate status = client.authenticate @@ -72,4 +116,3 @@ samr.close_handle(server_handle) client.disconnect! - From cae8892f8d4c6fd50443927f897699d0befdba54 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:14:27 -0400 Subject: [PATCH 06/21] Update examples/enum_registry_key.rb to use OptionParser Replace raw ARGV access with OptionParser following the existing convention. Adds --[no-]smbv1/2/3, --username (with domain\username splitting), and --password flags. Prints the help banner when a required positional is missing or when -h/--help is given. --- examples/enum_registry_key.rb | 58 ++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/examples/enum_registry_key.rb b/examples/enum_registry_key.rb index 172013477..7afefea2e 100644 --- a/examples/enum_registry_key.rb +++ b/examples/enum_registry_key.rb @@ -2,22 +2,66 @@ # This example script is used for testing Winreg registry key enumeration functionality # It will attempt to connect to a host and enumerate registry subkeys of a specified registry key. -# Example usage: ruby enum_registry_key.rb 192.168.172.138 msfadmin msfadmin HKLM\\My\\Key +# Example usage: ruby enum_registry_key.rb --username msfadmin --password msfadmin 192.168.172.138 HKLM\\My\\Key # This will try to connect to \\192.168.172.138 with the msfadmin:msfadmin credentialas and enumerate HKLM\\My\\Key subkeys. require 'bundler/setup' +require 'optparse' require 'ruby_smb' -address = ARGV[0] -username = ARGV[1] -password = ARGV[2] -registry_key = ARGV[3] -smb_versions = ARGV[4]&.split(',') || ['1','2','3'] +args = ARGV.dup +options = { + domain: '.', + username: '', + password: '', + smbv1: true, + smbv2: true, + smbv3: true, + target: nil, + registry_key: nil +} +options[:registry_key] = args.pop +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target registry_key" + 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 + opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username| + if username.include?('\\') + options[:domain], options[:username] = username.split('\\', 2) + else + options[:username] = username + end + end + opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password| + options[:password] = password + end +end +optparser.parse!(args) + +if [options[:target], options[:registry_key]].any? { |a| a == '-h' || a == '--help' } + puts optparser.help + exit +end + +if options[:target].nil? || options[:registry_key].nil? + abort(optparser.help) +end + +address = options[:target] +registry_key = options[:registry_key] sock = TCPSocket.new address, 445 dispatcher = RubySMB::Dispatcher::Socket.new(sock, read_timeout: 60) -client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password) +client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain]) protocol = client.negotiate status = client.authenticate From 94d0192a3c8baa6742daaac2215b204e222a59d0 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:14:59 -0400 Subject: [PATCH 07/21] Update examples/enum_registry_values.rb to use OptionParser Replace raw ARGV access with OptionParser following the existing convention. Adds --[no-]smbv1/2/3, --username (with domain\username splitting), and --password flags. Prints the help banner when a required positional is missing or when -h/--help is given. --- examples/enum_registry_values.rb | 60 +++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/examples/enum_registry_values.rb b/examples/enum_registry_values.rb index bd09f24be..a4edec7c8 100644 --- a/examples/enum_registry_values.rb +++ b/examples/enum_registry_values.rb @@ -2,22 +2,66 @@ # This example script is used for testing values enumeration of a specific Winreg registry. # It will attempt to connect to a host and enumerate values of a specified registry key. -# Example usage: ruby enum_registry_values.rb 192.168.172.138 msfadmin msfadmin HKLM\\My\\Key +# Example usage: ruby enum_registry_values.rb --username msfadmin --password msfadmin 192.168.172.138 HKLM\\My\\Key # This will try to connect to \\192.168.172.138 with the msfadmin:msfadmin credentialas and enumerate HKLM\\My\\Key values. require 'bundler/setup' +require 'optparse' require 'ruby_smb' -address = ARGV[0] -username = ARGV[1] -password = ARGV[2] -registry_key = ARGV[3] -smb_versions = ARGV[4]&.split(',') || ['1','2','3'] +args = ARGV.dup +options = { + domain: '.', + username: '', + password: '', + smbv1: true, + smbv2: true, + smbv3: true, + target: nil, + registry_key: nil +} +options[:registry_key] = args.pop +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target registry_key" + 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 + opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username| + if username.include?('\\') + options[:domain], options[:username] = username.split('\\', 2) + else + options[:username] = username + end + end + opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password| + options[:password] = password + end +end +optparser.parse!(args) + +if [options[:target], options[:registry_key]].any? { |a| a == '-h' || a == '--help' } + puts optparser.help + exit +end + +if options[:target].nil? || options[:registry_key].nil? + abort(optparser.help) +end + +address = options[:target] +registry_key = options[:registry_key] sock = TCPSocket.new address, 445 dispatcher = RubySMB::Dispatcher::Socket.new(sock, read_timeout: 60) -client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password) +client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain]) protocol = client.negotiate status = client.authenticate @@ -27,5 +71,3 @@ puts enum_result client.disconnect! - - From 58f8c9609c68aa6ecaed2f6cc36cda813cdd70cf Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:15:34 -0400 Subject: [PATCH 08/21] Update examples/get_computer_info.rb to use OptionParser Replace raw ARGV access with OptionParser following the existing convention. Adds --[no-]smbv1/2/3, --username (with domain\username splitting), and --password flags. Prints the help banner when target is missing or when -h/--help is given. --- examples/get_computer_info.rb | 56 ++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/examples/get_computer_info.rb b/examples/get_computer_info.rb index 55ee17f30..3788ba76f 100644 --- a/examples/get_computer_info.rb +++ b/examples/get_computer_info.rb @@ -2,20 +2,62 @@ # This example script is used for testing DCERPC WKST requests. # It will attempt to retrieve configuration information of a remote computer/server. -# Example usage: ruby enum_domain_users.rb 192.168.172.138 msfadmin msfadmin MyDomain +# Example usage: ruby get_computer_info.rb --username msfadmin --password msfadmin 192.168.172.138 require 'bundler/setup' +require 'optparse' require 'ruby_smb' -address = ARGV[0] -username = ARGV[1] -password = ARGV[2] -smb_versions = ARGV[3]&.split(',') || ['1','2','3'] +args = ARGV.dup +options = { + domain: '.', + username: '', + password: '', + smbv1: true, + smbv2: true, + smbv3: true, + target: nil +} +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target" + 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 + opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username| + if username.include?('\\') + options[:domain], options[:username] = username.split('\\', 2) + else + options[:username] = username + end + end + opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password| + options[:password] = password + end +end +optparser.parse!(args) + +if options[:target] == '-h' || options[:target] == '--help' + puts optparser.help + exit +end + +if options[:target].nil? + abort(optparser.help) +end + +address = options[:target] sock = TCPSocket.new address, 445 dispatcher = RubySMB::Dispatcher::Socket.new(sock, read_timeout: 60) -client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password) +client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain]) protocol = client.negotiate status = client.authenticate @@ -38,5 +80,3 @@ puts "OS Version: #{info.wki100_ver_major}.#{info.wki100_ver_minor}" client.disconnect! - - From 63f22549051365518142d51b8cb60fd73fcd1cbf Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:16:23 -0400 Subject: [PATCH 09/21] Update examples/list_directory.rb to use OptionParser Replace raw ARGV access with OptionParser following the existing convention. Adds --[no-]smbv1/2/3, --username (with domain\username splitting), and --password flags. Prints the help banner when a required positional is missing or when -h/--help is given. --- examples/list_directory.rb | 66 +++++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 12 deletions(-) diff --git a/examples/list_directory.rb b/examples/list_directory.rb index 02790f140..ee387fa46 100644 --- a/examples/list_directory.rb +++ b/examples/list_directory.rb @@ -2,27 +2,69 @@ # This example script is used for testing directory listing functionality # It will attempt to connect to a specific share and then list all files in a -# specified directory.. -# Example usage: ruby list_directory.rb 192.168.172.138 msfadmin msfadmin TEST_SHARE subdir1 +# specified directory. +# Example usage: ruby list_directory.rb --username msfadmin --password msfadmin 192.168.172.138 TEST_SHARE subdir1 # This will try to connect to \\192.168.172.138\TEST_SHARE with the msfadmin:msfadmin credentials, # and then list the contents of the directory 'subdir1' require 'bundler/setup' +require 'optparse' require 'ruby_smb' -address = ARGV[0] -username = ARGV[1] -password = ARGV[2] -share = ARGV[3] -dir = ARGV[4] -smb_versions = ARGV[5]&.split(',') || ['1','2','3'] +args = ARGV.dup +options = { + domain: '.', + username: '', + password: '', + smbv1: true, + smbv2: true, + smbv3: true, + target: nil, + share: nil, + directory: nil +} +options[:directory] = args.pop +options[:share] = args.pop +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target share directory" + 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 + opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username| + if username.include?('\\') + options[:domain], options[:username] = username.split('\\', 2) + else + options[:username] = username + end + end + opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password| + options[:password] = password + end +end +optparser.parse!(args) + +if [options[:target], options[:share], options[:directory]].any? { |a| a == '-h' || a == '--help' } + puts optparser.help + exit +end + +if options[:target].nil? || options[:share].nil? || options[:directory].nil? + abort(optparser.help) +end -path = "\\\\#{address}\\#{share}" +path = "\\\\#{options[:target]}\\#{options[:share]}" -sock = TCPSocket.new address, 445 +sock = TCPSocket.new options[:target], 445 dispatcher = RubySMB::Dispatcher::Socket.new(sock) -client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password) +client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain]) protocol = client.negotiate status = client.authenticate @@ -35,7 +77,7 @@ puts "Failed to connect to #{path}: #{e.message}" end -files = tree.list(directory: dir) +files = tree.list(directory: options[:directory]) files.each do |file| create_time = file.create_time.to_datetime.to_s From 551d485e523ab748836072c99f07d04ef2d0c5a6 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:17:10 -0400 Subject: [PATCH 10/21] Update examples/negotiate.rb to use OptionParser Replace raw ARGV access with OptionParser following the existing convention used in examples/authenticate.rb. Adds --[no-]smbv1/2/3 flags that filter the seven hardcoded SMB version combinations. Drops the unused hardcoded msfadmin credentials (negotiate does not authenticate). Prints the help banner when target is missing or when -h/--help is given. --- examples/negotiate.rb | 96 ++++++++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 42 deletions(-) diff --git a/examples/negotiate.rb b/examples/negotiate.rb index b150f293f..61d735d8c 100644 --- a/examples/negotiate.rb +++ b/examples/negotiate.rb @@ -5,63 +5,75 @@ # without any other parts. require 'bundler/setup' +require 'optparse' require 'ruby_smb' -def run_negotiation(address, smb1, smb2, smb3, opts = {}) +def run_negotiation(address, smb1, smb2, smb3) # Create our socket and add it to the dispatcher sock = TCPSocket.new address, 445 dispatcher = RubySMB::Dispatcher::Socket.new(sock) - client = RubySMB::Client.new(dispatcher, smb1: smb1, smb2: smb2, smb3: smb3, username: 'msfadmin', password: 'msfadmin') + client = RubySMB::Client.new(dispatcher, smb1: smb1, smb2: smb2, smb3: smb3, username: '', password: '') client.negotiate end -begin - puts "Negotiate with only SMB1 enabled..." - puts " Negotiated version: #{run_negotiation(ARGV[0], true, false, false)}" -rescue RubySMB::Error::RubySMBError => e - puts "Error: #{e.message}" +args = ARGV.dup +options = { + smbv1: true, + smbv2: true, + smbv3: true, + target: nil +} +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target" + 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 +optparser.parse!(args) -begin - puts "Negotiate with only SMB2 enabled..." - puts " Negotiated version: #{run_negotiation(ARGV[0], false, true, false)}" -rescue RubySMB::Error::RubySMBError => e - puts "Error: #{e.message}" +if options[:target] == '-h' || options[:target] == '--help' + puts optparser.help + exit end -begin - puts "Negotiate with only SMB3 enabled..." - puts " Negotiated version: #{run_negotiation(ARGV[0], false, false, true)}" -rescue RubySMB::Error::RubySMBError => e - puts "Error: #{e.message}" +if options[:target].nil? + abort(optparser.help) end -begin - puts "Negotiate with both SMB1 and SMB2 enabled on the client..." - puts " Negotiated version: #{run_negotiation(ARGV[0], true, true, false)}" -rescue RubySMB::Error::RubySMBError => e - puts "Error: #{e.message}" -end +# (smb1, smb2, smb3) combinations to exercise — filtered by the user's +# --[no-]smbv{1,2,3} flags so any combo requiring a disabled version +# is skipped. +combinations = [ + [true, false, false], # only SMB1 + [false, true, false], # only SMB2 + [false, false, true], # only SMB3 + [true, true, false], # SMB1 and SMB2 + [false, true, true], # SMB2 and SMB3 + [true, false, true], # SMB1 and SMB3 + [true, true, true] # SMB1, SMB2 and SMB3 +] -begin - puts "Negotiate with both SMB2 and SMB3 enabled on the client..." - puts " Negotiated version: #{run_negotiation(ARGV[0], false, true, true)}" -rescue RubySMB::Error::RubySMBError => e - puts "Error: #{e.message}" -end +combinations.each do |smb1, smb2, smb3| + next if smb1 && !options[:smbv1] + next if smb2 && !options[:smbv2] + next if smb3 && !options[:smbv3] -begin - puts "Negotiate with both SMB1 and SMB3 enabled on the client..." - puts " Negotiated version: #{run_negotiation(ARGV[0], true, false, true)}" -rescue RubySMB::Error::RubySMBError => e - puts "Error: #{e.message}" + enabled = [] + enabled << 'SMB1' if smb1 + enabled << 'SMB2' if smb2 + enabled << 'SMB3' if smb3 + puts "Negotiate with #{enabled.join(', ')} enabled..." + begin + puts " Negotiated version: #{run_negotiation(options[:target], smb1, smb2, smb3)}" + rescue RubySMB::Error::RubySMBError => e + puts "Error: #{e.message}" + end end - -begin - puts "Negotiate with SMB1, SMB2 and SMB3 enabled on the client..." - puts " Negotiated version: #{run_negotiation(ARGV[0], true, true, true)}" -rescue RubySMB::Error::RubySMBError => e - puts "Error: #{e.message}" -end - From b9c913ce2200a165b43d872eda21b3f8cdbefe2d Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:17:49 -0400 Subject: [PATCH 11/21] Update examples/negotiate_with_netbios_service.rb to use OptionParser Replace raw ARGV access with OptionParser following the existing convention. Adds --[no-]smbv1/2/3 flags that filter the five hardcoded SMB version combinations and a --netbios-name flag (default *SMBSERVER) replacing the optional second positional. Drops the unused hardcoded msfadmin credentials (this script does not authenticate). Prints the help banner when target is missing or when -h/--help is given. --- examples/negotiate_with_netbios_service.rb | 72 +++++++++++++++++----- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/examples/negotiate_with_netbios_service.rb b/examples/negotiate_with_netbios_service.rb index ed398d7b9..1be8ab741 100644 --- a/examples/negotiate_with_netbios_service.rb +++ b/examples/negotiate_with_netbios_service.rb @@ -1,19 +1,20 @@ #!/usr/bin/ruby # This script is for testing the NetBIOS Session Service Request on port 139/tcp. -# Example usage: ruby negotiate.rb 192.168.172.138 NBNAME -# This will connect to 192.168.172.138 (139/TCP) and request a NetBIOS session with NBNAME as the called name. +# Example usage: ruby negotiate_with_netbios_service.rb 192.168.172.138 NBNAME +# This will connect to 192.168.172.138 (139/TCP) and request a NetBIOS session with NBNAME as the called name. # If successful, a SMB negotiation is performed using this NetBIOS session. # The default *SMBSERVER name is used if the NetBIOS name is not provided. require 'bundler/setup' +require 'optparse' require 'ruby_smb' def run_negotiation(address, smb1, smb2, smb3, netbios_name) sock = TCPSocket.new address, 139 dispatcher = RubySMB::Dispatcher::Socket.new(sock) - client = RubySMB::Client.new(dispatcher, smb1: smb1, smb2: smb2, smb3: smb3, username: 'msfadmin', password: 'msfadmin') + client = RubySMB::Client.new(dispatcher, smb1: smb1, smb2: smb2, smb3: smb3, username: '', password: '') begin client.session_request(netbios_name) rescue RubySMB::Error::NetBiosSessionService => e @@ -25,16 +26,55 @@ def run_negotiation(address, smb1, smb2, smb3, netbios_name) puts "#{smb_version} successfully negotiated." end -address = ARGV[0] -netbios_name = ARGV[1] || '*SMBSERVER' - -# Negotiate with SMB1, SMB2 and SMB3 enabled on the client -run_negotiation(ARGV[0], true, true, true, netbios_name) -# Negotiate with both SMB1 and SMB2 enabled on the client -run_negotiation(ARGV[0], true, true, false, netbios_name) -# Negotiate with only SMB1 enabled -run_negotiation(ARGV[0], true, false, false, netbios_name) -# Negotiate with only SMB2 enabled -run_negotiation(ARGV[0], false, true, false, netbios_name) -# Negotiate with only SMB3 enabled -run_negotiation(ARGV[0], false, false, true, netbios_name) +args = ARGV.dup +options = { + smbv1: true, + smbv2: true, + smbv3: true, + netbios_name: '*SMBSERVER', + target: nil +} +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target" + 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 + opts.on("--netbios-name NAME", "The NetBIOS called name (default: #{options[:netbios_name]})") do |name| + options[:netbios_name] = name + end +end +optparser.parse!(args) + +if options[:target] == '-h' || options[:target] == '--help' + puts optparser.help + exit +end + +if options[:target].nil? + abort(optparser.help) +end + +# (smb1, smb2, smb3) combinations to exercise — filtered by the user's +# --[no-]smbv{1,2,3} flags so any combo requiring a disabled version +# is skipped. +combinations = [ + [true, true, true], # SMB1, SMB2 and SMB3 enabled + [true, true, false], # SMB1 and SMB2 enabled + [true, false, false], # only SMB1 enabled + [false, true, false], # only SMB2 enabled + [false, false, true] # only SMB3 enabled +] + +combinations.each do |smb1, smb2, smb3| + next if smb1 && !options[:smbv1] + next if smb2 && !options[:smbv2] + next if smb3 && !options[:smbv3] + run_negotiation(options[:target], smb1, smb2, smb3, options[:netbios_name]) +end From f3807f18499d51aaf14ac2d715cbf58fc78e2e3b Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:18:27 -0400 Subject: [PATCH 12/21] Update examples/net_share_enum_all.rb to use OptionParser Replace raw ARGV access with OptionParser following the existing convention. Adds --[no-]smbv1/2/3, --username (with domain\username splitting), and --password flags. Prints the help banner when target is missing or when -h/--help is given. --- examples/net_share_enum_all.rb | 55 +++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/examples/net_share_enum_all.rb b/examples/net_share_enum_all.rb index 89a991a1f..839fc33ac 100644 --- a/examples/net_share_enum_all.rb +++ b/examples/net_share_enum_all.rb @@ -2,23 +2,63 @@ # This example script is used for testing NetShareEnumAll functionality # It will attempt to connect to a host and enumerate shares. -# Example usage: ruby net_share_enum_all.rb 192.168.172.138 msfadmin msfadmin +# Example usage: ruby net_share_enum_all.rb --username msfadmin --password msfadmin 192.168.172.138 # This will try to connect to \\192.168.172.138 with the msfadmin:msfadmin credentials require 'bundler/setup' +require 'optparse' require 'ruby_smb' -address = ARGV[0] -username = ARGV[1] -password = ARGV[2] -smb_versions = ARGV[3]&.split(',') || ['1','2','3'] +args = ARGV.dup +options = { + domain: '.', + username: '', + password: '', + smbv1: true, + smbv2: true, + smbv3: true, + target: nil +} +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target" + 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 + opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username| + if username.include?('\\') + options[:domain], options[:username] = username.split('\\', 2) + else + options[:username] = username + end + end + opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password| + options[:password] = password + end +end +optparser.parse!(args) + +if options[:target] == '-h' || options[:target] == '--help' + puts optparser.help + exit +end -path = "\\\\#{address}\\IPC$" +if options[:target].nil? + abort(optparser.help) +end + +address = options[:target] sock = TCPSocket.new address, 445 dispatcher = RubySMB::Dispatcher::Socket.new(sock, read_timeout: 60) -client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password) +client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain]) protocol = client.negotiate status = client.authenticate @@ -32,4 +72,3 @@ end client.disconnect! - From 9308c5bac241bd2f37a97aab06f1de9935797dc5 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:19:10 -0400 Subject: [PATCH 13/21] Update examples/pipes.rb to use OptionParser Replace raw ARGV access with OptionParser following the existing convention. Adds --[no-]smbv1/2/3, --username (with domain\username splitting), and --password flags. Prints the help banner when a required positional is missing or when -h/--help is given. --- examples/pipes.rb | 58 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/examples/pipes.rb b/examples/pipes.rb index e200feed0..b0653a0ad 100644 --- a/examples/pipes.rb +++ b/examples/pipes.rb @@ -4,22 +4,66 @@ # Example script for connecting to a named pipe and performing a peek operation. # This is used to demonstrate pipe operations. # -# Usage: ruby pipes.rb ADDRESS PIPENAME USER PASS 1|2 +# Usage: ruby pipes.rb --username USER --password PASS ADDRESS PIPENAME # require 'bundler/setup' +require 'optparse' require 'ruby_smb' -address = ARGV[0] -pipename = ARGV[1] -username = ARGV[2] -password = ARGV[3] -smb_versions = ARGV[4]&.split(',') || ['1','2','3'] +args = ARGV.dup +options = { + domain: '.', + username: '', + password: '', + smbv1: true, + smbv2: true, + smbv3: true, + target: nil, + pipename: nil +} +options[:pipename] = args.pop +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target pipename" + 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 + opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username| + if username.include?('\\') + options[:domain], options[:username] = username.split('\\', 2) + else + options[:username] = username + end + end + opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password| + options[:password] = password + end +end +optparser.parse!(args) + +if [options[:target], options[:pipename]].any? { |a| a == '-h' || a == '--help' } + puts optparser.help + exit +end + +if options[:target].nil? || options[:pipename].nil? + abort(optparser.help) +end + +address = options[:target] +pipename = options[:pipename] sock = TCPSocket.new(address, 445) dispatcher = RubySMB::Dispatcher::Socket.new(sock) -client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password) +client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain]) smbver = client.negotiate if smbver == 'SMB1' From 366b8a482c65adb20522634c6e0803f5f75c2f34 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:20:05 -0400 Subject: [PATCH 14/21] Update examples/query_service_status.rb to use OptionParser Replace raw ARGV access with OptionParser following the existing convention. Adds --[no-]smbv1/2/3, --username (with domain\username splitting), and --password flags. Prints the help banner when a required positional is missing or when -h/--help is given. --- examples/query_service_status.rb | 59 +++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/examples/query_service_status.rb b/examples/query_service_status.rb index 5fcba03ab..a00d05f35 100644 --- a/examples/query_service_status.rb +++ b/examples/query_service_status.rb @@ -2,22 +2,66 @@ # This example script is used for testing remote service status and start type query. # It will attempt to connect to a host and query the status and start type of the provided service. -# Example usage: ruby query_service_status.rb 192.168.172.138 msfadmin msfadmin "RemoteRegistry" +# Example usage: ruby query_service_status.rb --username msfadmin --password msfadmin 192.168.172.138 "RemoteRegistry" # This will try to connect to \\192.168.172.138 with the msfadmin:msfadmin credentialas and get the status and start type of the "RemoteRegistry" service. require 'bundler/setup' +require 'optparse' require 'ruby_smb' -address = ARGV[0] -username = ARGV[1] -password = ARGV[2] -service = ARGV[3] -smb_versions = ARGV[4]&.split(',') || ['1','2','3'] +args = ARGV.dup +options = { + domain: '.', + username: '', + password: '', + smbv1: true, + smbv2: true, + smbv3: true, + target: nil, + service: nil +} +options[:service] = args.pop +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target service" + 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 + opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username| + if username.include?('\\') + options[:domain], options[:username] = username.split('\\', 2) + else + options[:username] = username + end + end + opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password| + options[:password] = password + end +end +optparser.parse!(args) + +if [options[:target], options[:service]].any? { |a| a == '-h' || a == '--help' } + puts optparser.help + exit +end + +if options[:target].nil? || options[:service].nil? + abort(optparser.help) +end + +address = options[:target] +service = options[:service] sock = TCPSocket.new address, 445 dispatcher = RubySMB::Dispatcher::Socket.new(sock, read_timeout: 60) -client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password) +client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain]) protocol = client.negotiate status = client.authenticate @@ -99,4 +143,3 @@ svcctl.close end client.disconnect! - From 8141da3f6305983264b427d50c3050d7282629ed Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:21:01 -0400 Subject: [PATCH 15/21] Update examples/read_file_encryption.rb to use OptionParser Replace raw ARGV access with OptionParser following the existing convention. Adds --[no-]smbv1/2/3 (defaults: SMB1/2 disabled, SMB3 enabled to force encryption-capable negotiation), --username (with domain\username splitting), and --password flags. Prints the help banner when a required positional is missing or when -h/--help is given. Also splat the client options hash so the call works on Ruby 3.x where non-keyword hash positionals are no longer auto-converted. --- examples/read_file_encryption.rb | 97 +++++++++++++++++++++++--------- 1 file changed, 71 insertions(+), 26 deletions(-) diff --git a/examples/read_file_encryption.rb b/examples/read_file_encryption.rb index 3cad641fa..bbccb9b80 100644 --- a/examples/read_file_encryption.rb +++ b/examples/read_file_encryption.rb @@ -1,24 +1,11 @@ #!/usr/bin/ruby -# This example script is used for testing the reading of a file. +# This example script is used for testing the reading of a file with SMBv3 encryption. # It will attempt to connect to a specific share and then read a specified file. -# Example usage: ruby read_file.rb 192.168.172.138 msfadmin msfadmin TEST_SHARE short.txt +# Example usage: ruby read_file_encryption.rb --username msfadmin --password msfadmin 192.168.172.138 TEST_SHARE short.txt # This will try to connect to \\192.168.172.138\TEST_SHARE with the msfadmin:msfadmin credentials # and read the file short.txt -require 'bundler/setup' -require 'ruby_smb' - -address = ARGV[0] -username = ARGV[1] -password = ARGV[2] -share = ARGV[3] -filename = ARGV[4] -path = "\\\\#{address}\\#{share}" - -sock = TCPSocket.new address, 445 -dispatcher = RubySMB::Dispatcher::Socket.new(sock) - # To require encryption on the server, run this in an elevated Powershell: # C:\> Set-SmbServerConfiguration -EncryptData $true @@ -26,20 +13,78 @@ # C:\ Set-SmbServerConfiguration -EncryptData $false # C:\ Set-SmbShare -Name -EncryptData 1 -# For this encryption to work, it has to be SMBv3. By only setting smb3 to true, -# we make sure the server will negotiate this version, if it supports it -opts = { - smb1: false, - smb2: false, - smb3: true, - username: username, - password: password, +# For this encryption to work, it has to be SMBv3. By default, SMBv1 and SMBv2 +# are disabled here so the server will negotiate SMBv3 if it supports it. + +require 'bundler/setup' +require 'optparse' +require 'ruby_smb' + +args = ARGV.dup +options = { + domain: '.', + username: '', + password: '', + smbv1: false, + smbv2: false, + smbv3: true, + target: nil, + share: nil, + file: nil } +options[:file] = args.pop +options[:share] = args.pop +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target share file" + 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 + opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username| + if username.include?('\\') + options[:domain], options[:username] = username.split('\\', 2) + else + options[:username] = username + end + end + opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password| + options[:password] = password + end +end +optparser.parse!(args) + +if [options[:target], options[:share], options[:file]].any? { |a| a == '-h' || a == '--help' } + puts optparser.help + exit +end + +if options[:target].nil? || options[:share].nil? || options[:file].nil? + abort(optparser.help) +end + +path = "\\\\#{options[:target]}\\#{options[:share]}" + +sock = TCPSocket.new options[:target], 445 +dispatcher = RubySMB::Dispatcher::Socket.new(sock) # By default, the client uses encryption even if it is not required by the server. Disable this by setting always_encrypt to false -#opts[:always_encrypt] = false +client_opts = { + smb1: options[:smbv1], + smb2: options[:smbv2], + smb3: options[:smbv3], + username: options[:username], + password: options[:password], + domain: options[:domain] +} +#client_opts[:always_encrypt] = false -client = RubySMB::Client.new(dispatcher, opts) +client = RubySMB::Client.new(dispatcher, **client_opts) protocol = client.negotiate status = client.authenticate @@ -49,7 +94,7 @@ puts "Failed to connect to #{path}: #{e.message}" end -file = tree.open_file(filename: filename) +file = tree.open_file(filename: options[:file]) data = file.read puts data From f6b3c2de8d59928c521eba097e9dcf6499bc6834 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:21:39 -0400 Subject: [PATCH 16/21] Update examples/read_registry_key_value.rb to use OptionParser Replace raw ARGV access with OptionParser following the existing convention. Adds --[no-]smbv1/2/3, --username (with domain\username splitting), and --password flags. Prints the help banner when a required positional is missing or when -h/--help is given. --- examples/read_registry_key_value.rb | 63 ++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/examples/read_registry_key_value.rb b/examples/read_registry_key_value.rb index bf03395ca..3dd9a3057 100644 --- a/examples/read_registry_key_value.rb +++ b/examples/read_registry_key_value.rb @@ -2,23 +2,69 @@ # This example script is used for testing the Winreg registry key value read functionality. # It will attempt to connect to a host and reads the value of a specified registry key. -# Example usage: ruby enum_registry_key.rb 192.168.172.138 msfadmin msfadmin HKLM\\My\\Key ValueName +# Example usage: ruby read_registry_key_value.rb --username msfadmin --password msfadmin 192.168.172.138 HKLM\\My\\Key ValueName # This will try to connect to \\192.168.172.138 with the msfadmin:msfadmin credentialas and reads the ValueName data corresponding to the HKLM\\My\\Key registry key. require 'bundler/setup' +require 'optparse' require 'ruby_smb' -address = ARGV[0] -username = ARGV[1] -password = ARGV[2] -registry_key = ARGV[3] -value_name = ARGV[4] -smb_versions = ARGV[5]&.split(',') || ['1','2','3'] +args = ARGV.dup +options = { + domain: '.', + username: '', + password: '', + smbv1: true, + smbv2: true, + smbv3: true, + target: nil, + registry_key: nil, + value_name: nil +} +options[:value_name] = args.pop +options[:registry_key] = args.pop +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target registry_key value_name" + 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 + opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username| + if username.include?('\\') + options[:domain], options[:username] = username.split('\\', 2) + else + options[:username] = username + end + end + opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password| + options[:password] = password + end +end +optparser.parse!(args) + +if [options[:target], options[:registry_key], options[:value_name]].any? { |a| a == '-h' || a == '--help' } + puts optparser.help + exit +end + +if options[:target].nil? || options[:registry_key].nil? || options[:value_name].nil? + abort(optparser.help) +end + +address = options[:target] +registry_key = options[:registry_key] +value_name = options[:value_name] sock = TCPSocket.new address, 445 dispatcher = RubySMB::Dispatcher::Socket.new(sock, read_timeout: 60) -client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password) +client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain]) protocol = client.negotiate status = client.authenticate @@ -30,4 +76,3 @@ puts key_value client.disconnect! - From 6be1ee021887f9fdb12ac1a925879fe0bf6beb2e Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:22:31 -0400 Subject: [PATCH 17/21] Update examples/rename_file.rb to use OptionParser Replace raw ARGV access with OptionParser following the existing convention. Adds --[no-]smbv1/2/3, --username (with domain\username splitting), and --password flags. Prints the help banner when a required positional is missing or when -h/--help is given. --- examples/rename_file.rb | 73 ++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/examples/rename_file.rb b/examples/rename_file.rb index c5d85929d..048ae038d 100644 --- a/examples/rename_file.rb +++ b/examples/rename_file.rb @@ -1,28 +1,71 @@ #!/usr/bin/ruby -# This example script is used for testing the deleting of a file. +# This example script is used for testing the renaming of a file. # It will attempt to connect to a specific share and then rename a specified file. -# Example usage: ruby rename_file.rb 192.168.172.138 msfadmin msfadmin TEST_SHARE short.txt shortrenamed.txt +# Example usage: ruby rename_file.rb --username msfadmin --password msfadmin 192.168.172.138 TEST_SHARE short.txt shortrenamed.txt # This will try to connect to \\192.168.172.138\TEST_SHARE with the msfadmin:msfadmin credentials -# and rename the file short.txt +# and rename the file short.txt to shortrenamed.txt require 'bundler/setup' +require 'optparse' require 'ruby_smb' -address = ARGV[0] -username = ARGV[1] -password = ARGV[2] -share = ARGV[3] -file = ARGV[4] -new_name = ARGV[5] -smb_versions = ARGV[6]&.split(',') || ['1','2','3'] +args = ARGV.dup +options = { + domain: '.', + username: '', + password: '', + smbv1: true, + smbv2: true, + smbv3: true, + target: nil, + share: nil, + file: nil, + new_name: nil +} +options[:new_name] = args.pop +options[:file] = args.pop +options[:share] = args.pop +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target share file new_name" + 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 + opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username| + if username.include?('\\') + options[:domain], options[:username] = username.split('\\', 2) + else + options[:username] = username + end + end + opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password| + options[:password] = password + end +end +optparser.parse!(args) + +if [options[:target], options[:share], options[:file], options[:new_name]].any? { |a| a == '-h' || a == '--help' } + puts optparser.help + exit +end + +if options[:target].nil? || options[:share].nil? || options[:file].nil? || options[:new_name].nil? + abort(optparser.help) +end -path = "\\\\#{address}\\#{share}" +path = "\\\\#{options[:target]}\\#{options[:share]}" -sock = TCPSocket.new address, 445 +sock = TCPSocket.new options[:target], 445 dispatcher = RubySMB::Dispatcher::Socket.new(sock) -client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password) +client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain]) protocol = client.negotiate status = client.authenticate @@ -36,8 +79,8 @@ puts "Failed to connect to #{path}: #{e.message}" end -file = tree.open_file(filename: file, write: true, delete: true) +file = tree.open_file(filename: options[:file], write: true, delete: true) -data = file.rename(new_name) +data = file.rename(options[:new_name]) puts data file.close From e96489d5d888fb122993c78ba9b17746c312c79c Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:23:13 -0400 Subject: [PATCH 18/21] Update examples/write_file.rb to use OptionParser Replace raw ARGV access with OptionParser following the existing convention. Adds --[no-]smbv1/2/3, --username (with domain\username splitting), and --password flags. Prints the help banner when a required positional is missing or when -h/--help is given. --- examples/write_file.rb | 71 +++++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/examples/write_file.rb b/examples/write_file.rb index a15cc6139..58f1105e1 100644 --- a/examples/write_file.rb +++ b/examples/write_file.rb @@ -2,27 +2,70 @@ # This example script is used for testing the writing to a file. # It will attempt to connect to a specific share and then write to a specified file. -# Example usage: ruby write_file.rb 192.168.172.138 msfadmin msfadmin TEST_SHARE test.txt "data to write" +# Example usage: ruby write_file.rb --username msfadmin --password msfadmin 192.168.172.138 TEST_SHARE test.txt "data to write" # This will try to connect to \\192.168.172.138\TEST_SHARE with the msfadmin:msfadmin credentials -# and write "data to write" the file test.txt +# and write "data to write" to the file test.txt require 'bundler/setup' +require 'optparse' require 'ruby_smb' -address = ARGV[0] -username = ARGV[1] -password = ARGV[2] -share = ARGV[3] -file = ARGV[4] -data = ARGV[5] -smb_versions = ARGV[6]&.split(',') || ['1','2','3'] +args = ARGV.dup +options = { + domain: '.', + username: '', + password: '', + smbv1: true, + smbv2: true, + smbv3: true, + target: nil, + share: nil, + file: nil, + data: nil +} +options[:data] = args.pop +options[:file] = args.pop +options[:share] = args.pop +options[:target] = args.pop +optparser = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] target share file data" + 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 + opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username| + if username.include?('\\') + options[:domain], options[:username] = username.split('\\', 2) + else + options[:username] = username + end + end + opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password| + options[:password] = password + end +end +optparser.parse!(args) + +if [options[:target], options[:share], options[:file], options[:data]].any? { |a| a == '-h' || a == '--help' } + puts optparser.help + exit +end + +if options[:target].nil? || options[:share].nil? || options[:file].nil? || options[:data].nil? + abort(optparser.help) +end -path = "\\\\#{address}\\#{share}" +path = "\\\\#{options[:target]}\\#{options[:share]}" -sock = TCPSocket.new address, 445 +sock = TCPSocket.new options[:target], 445 dispatcher = RubySMB::Dispatcher::Socket.new(sock) -client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password) +client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain]) protocol = client.negotiate status = client.authenticate @@ -35,8 +78,8 @@ puts "Failed to connect to #{path}: #{e.message}" end -file = tree.open_file(filename: file, write: true, disposition: RubySMB::Dispositions::FILE_OVERWRITE_IF) +file = tree.open_file(filename: options[:file], write: true, disposition: RubySMB::Dispositions::FILE_OVERWRITE_IF) -result = file.write(data: data) +result = file.write(data: options[:data]) puts result.to_s file.close From 5dc2515063ffe0eee6a47cd9fd17b5c48cffe0fa Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 28 Apr 2026 16:25:01 -0400 Subject: [PATCH 19/21] Print help when -h/--help is the only argument to anonymous_auth.rb Without this, the positional pop consumed -h/--help as the target and the script then tried to open a TCP socket to that string. Now matches the behavior added to authenticate.rb and the other examples. --- examples/anonymous_auth.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/anonymous_auth.rb b/examples/anonymous_auth.rb index 2c8a4a2b1..e6ed147aa 100644 --- a/examples/anonymous_auth.rb +++ b/examples/anonymous_auth.rb @@ -45,6 +45,11 @@ def run_authentication(address, smb1, smb2, smb3) end optparser.parse!(args) +if options[:target] == '-h' || options[:target] == '--help' + puts optparser.help + exit +end + if options[:target].nil? abort(optparser.help) end From 1090e5b91fe5d091061136354af58bb33f3fe61d Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Thu, 21 May 2026 09:52:38 -0400 Subject: [PATCH 20/21] Don't continue if the tree isn't connected --- examples/write_file.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/write_file.rb b/examples/write_file.rb index 58f1105e1..e7ead99b5 100644 --- a/examples/write_file.rb +++ b/examples/write_file.rb @@ -75,7 +75,7 @@ tree = client.tree_connect(path) puts "Connected to #{path} successfully!" rescue StandardError => e - puts "Failed to connect to #{path}: #{e.message}" + abort("Failed to connect to #{path}: #{e.message}") end file = tree.open_file(filename: options[:file], write: true, disposition: RubySMB::Dispositions::FILE_OVERWRITE_IF) From 4108ff8d0b040a65e5d26c72591c8b885805b7d9 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Thu, 21 May 2026 09:53:10 -0400 Subject: [PATCH 21/21] Remove the stale usage reference --- examples/negotiate_with_netbios_service.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/negotiate_with_netbios_service.rb b/examples/negotiate_with_netbios_service.rb index 1be8ab741..c01db3b89 100644 --- a/examples/negotiate_with_netbios_service.rb +++ b/examples/negotiate_with_netbios_service.rb @@ -1,7 +1,6 @@ #!/usr/bin/ruby # This script is for testing the NetBIOS Session Service Request on port 139/tcp. -# Example usage: ruby negotiate_with_netbios_service.rb 192.168.172.138 NBNAME # This will connect to 192.168.172.138 (139/TCP) and request a NetBIOS session with NBNAME as the called name. # If successful, a SMB negotiation is performed using this NetBIOS session. # The default *SMBSERVER name is used if the NetBIOS name is not provided.