ruby_smb 3.3.18 → 3.3.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/anonymous_auth.rb +5 -0
- data/examples/append_file.rb +57 -14
- data/examples/authenticate.rb +64 -16
- data/examples/delete_file.rb +53 -11
- data/examples/dump_secrets_from_sid.rb +43 -8
- data/examples/enum_domain_users.rb +51 -8
- data/examples/enum_registry_key.rb +51 -7
- data/examples/enum_registry_values.rb +51 -9
- data/examples/get_computer_info.rb +48 -8
- data/examples/list_directory.rb +54 -12
- data/examples/negotiate.rb +54 -42
- data/examples/negotiate_with_netbios_service.rb +55 -16
- data/examples/net_share_enum_all.rb +47 -8
- data/examples/pipes.rb +51 -7
- data/examples/query_service_status.rb +51 -8
- data/examples/read_file_encryption.rb +71 -26
- data/examples/read_registry_key_value.rb +54 -9
- data/examples/rename_file.rb +58 -15
- data/examples/write_file.rb +58 -15
- data/lib/ruby_smb/client/authentication.rb +53 -0
- data/lib/ruby_smb/client/negotiation.rb +10 -2
- data/lib/ruby_smb/client/tree_connect.rb +8 -1
- data/lib/ruby_smb/client.rb +16 -5
- data/lib/ruby_smb/rap/net_share_enum.rb +166 -0
- data/lib/ruby_smb/rap.rb +10 -0
- data/lib/ruby_smb/smb1/commands.rb +1 -0
- data/lib/ruby_smb/smb1/packet/negotiate_response.rb +11 -0
- data/lib/ruby_smb/smb1/packet/open_andx_request.rb +39 -0
- data/lib/ruby_smb/smb1/packet/open_andx_response.rb +40 -0
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +2 -2
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +11 -0
- data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +53 -13
- data/lib/ruby_smb/smb1/packet/trans2/find_information_level/find_info_standard.rb +39 -0
- data/lib/ruby_smb/smb1/packet/trans2/find_information_level.rb +1 -0
- data/lib/ruby_smb/smb1/packet/trans2/win9x_framing.rb +68 -0
- data/lib/ruby_smb/smb1/packet/trans2.rb +1 -0
- data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/tree_connect_response.rb +10 -1
- data/lib/ruby_smb/smb1/packet.rb +2 -0
- data/lib/ruby_smb/smb1/pipe.rb +2 -0
- data/lib/ruby_smb/smb1/tree.rb +113 -9
- data/lib/ruby_smb/version.rb +1 -1
- data/lib/ruby_smb.rb +1 -0
- data/spec/lib/ruby_smb/client_spec.rb +2 -1
- data/spec/lib/ruby_smb/rap/net_share_enum_spec.rb +185 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/win9x_framing_spec.rb +113 -0
- data/spec/lib/ruby_smb/smb1/tree_spec.rb +188 -2
- metadata +12 -2
|
@@ -1,24 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/ruby
|
|
2
2
|
|
|
3
|
-
# This example script is used for testing the reading of a file.
|
|
3
|
+
# This example script is used for testing the reading of a file with SMBv3 encryption.
|
|
4
4
|
# It will attempt to connect to a specific share and then read a specified file.
|
|
5
|
-
# Example usage: ruby
|
|
5
|
+
# Example usage: ruby read_file_encryption.rb --username msfadmin --password msfadmin 192.168.172.138 TEST_SHARE short.txt
|
|
6
6
|
# This will try to connect to \\192.168.172.138\TEST_SHARE with the msfadmin:msfadmin credentials
|
|
7
7
|
# and read the file short.txt
|
|
8
8
|
|
|
9
|
-
require 'bundler/setup'
|
|
10
|
-
require 'ruby_smb'
|
|
11
|
-
|
|
12
|
-
address = ARGV[0]
|
|
13
|
-
username = ARGV[1]
|
|
14
|
-
password = ARGV[2]
|
|
15
|
-
share = ARGV[3]
|
|
16
|
-
filename = ARGV[4]
|
|
17
|
-
path = "\\\\#{address}\\#{share}"
|
|
18
|
-
|
|
19
|
-
sock = TCPSocket.new address, 445
|
|
20
|
-
dispatcher = RubySMB::Dispatcher::Socket.new(sock)
|
|
21
|
-
|
|
22
9
|
# To require encryption on the server, run this in an elevated Powershell:
|
|
23
10
|
# C:\> Set-SmbServerConfiguration -EncryptData $true
|
|
24
11
|
|
|
@@ -26,20 +13,78 @@ dispatcher = RubySMB::Dispatcher::Socket.new(sock)
|
|
|
26
13
|
# C:\ Set-SmbServerConfiguration -EncryptData $false
|
|
27
14
|
# C:\ Set-SmbShare -Name <share name> -EncryptData 1
|
|
28
15
|
|
|
29
|
-
# For this encryption to work, it has to be SMBv3. By
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
16
|
+
# For this encryption to work, it has to be SMBv3. By default, SMBv1 and SMBv2
|
|
17
|
+
# are disabled here so the server will negotiate SMBv3 if it supports it.
|
|
18
|
+
|
|
19
|
+
require 'bundler/setup'
|
|
20
|
+
require 'optparse'
|
|
21
|
+
require 'ruby_smb'
|
|
22
|
+
|
|
23
|
+
args = ARGV.dup
|
|
24
|
+
options = {
|
|
25
|
+
domain: '.',
|
|
26
|
+
username: '',
|
|
27
|
+
password: '',
|
|
28
|
+
smbv1: false,
|
|
29
|
+
smbv2: false,
|
|
30
|
+
smbv3: true,
|
|
31
|
+
target: nil,
|
|
32
|
+
share: nil,
|
|
33
|
+
file: nil
|
|
37
34
|
}
|
|
35
|
+
options[:file] = args.pop
|
|
36
|
+
options[:share] = args.pop
|
|
37
|
+
options[:target] = args.pop
|
|
38
|
+
optparser = OptionParser.new do |opts|
|
|
39
|
+
opts.banner = "Usage: #{File.basename(__FILE__)} [options] target share file"
|
|
40
|
+
opts.on("--[no-]smbv1", "Enable or disable SMBv1 (default: #{options[:smbv1] ? 'Enabled' : 'Disabled'})") do |smbv1|
|
|
41
|
+
options[:smbv1] = smbv1
|
|
42
|
+
end
|
|
43
|
+
opts.on("--[no-]smbv2", "Enable or disable SMBv2 (default: #{options[:smbv2] ? 'Enabled' : 'Disabled'})") do |smbv2|
|
|
44
|
+
options[:smbv2] = smbv2
|
|
45
|
+
end
|
|
46
|
+
opts.on("--[no-]smbv3", "Enable or disable SMBv3 (default: #{options[:smbv3] ? 'Enabled' : 'Disabled'})") do |smbv3|
|
|
47
|
+
options[:smbv3] = smbv3
|
|
48
|
+
end
|
|
49
|
+
opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username|
|
|
50
|
+
if username.include?('\\')
|
|
51
|
+
options[:domain], options[:username] = username.split('\\', 2)
|
|
52
|
+
else
|
|
53
|
+
options[:username] = username
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password|
|
|
57
|
+
options[:password] = password
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
optparser.parse!(args)
|
|
61
|
+
|
|
62
|
+
if [options[:target], options[:share], options[:file]].any? { |a| a == '-h' || a == '--help' }
|
|
63
|
+
puts optparser.help
|
|
64
|
+
exit
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if options[:target].nil? || options[:share].nil? || options[:file].nil?
|
|
68
|
+
abort(optparser.help)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
path = "\\\\#{options[:target]}\\#{options[:share]}"
|
|
72
|
+
|
|
73
|
+
sock = TCPSocket.new options[:target], 445
|
|
74
|
+
dispatcher = RubySMB::Dispatcher::Socket.new(sock)
|
|
38
75
|
|
|
39
76
|
# By default, the client uses encryption even if it is not required by the server. Disable this by setting always_encrypt to false
|
|
40
|
-
|
|
77
|
+
client_opts = {
|
|
78
|
+
smb1: options[:smbv1],
|
|
79
|
+
smb2: options[:smbv2],
|
|
80
|
+
smb3: options[:smbv3],
|
|
81
|
+
username: options[:username],
|
|
82
|
+
password: options[:password],
|
|
83
|
+
domain: options[:domain]
|
|
84
|
+
}
|
|
85
|
+
#client_opts[:always_encrypt] = false
|
|
41
86
|
|
|
42
|
-
client = RubySMB::Client.new(dispatcher,
|
|
87
|
+
client = RubySMB::Client.new(dispatcher, **client_opts)
|
|
43
88
|
protocol = client.negotiate
|
|
44
89
|
status = client.authenticate
|
|
45
90
|
|
|
@@ -49,7 +94,7 @@ rescue StandardError => e
|
|
|
49
94
|
puts "Failed to connect to #{path}: #{e.message}"
|
|
50
95
|
end
|
|
51
96
|
|
|
52
|
-
file = tree.open_file(filename:
|
|
97
|
+
file = tree.open_file(filename: options[:file])
|
|
53
98
|
|
|
54
99
|
data = file.read
|
|
55
100
|
puts data
|
|
@@ -2,23 +2,69 @@
|
|
|
2
2
|
|
|
3
3
|
# This example script is used for testing the Winreg registry key value read functionality.
|
|
4
4
|
# It will attempt to connect to a host and reads the value of a specified registry key.
|
|
5
|
-
# Example usage: ruby
|
|
5
|
+
# Example usage: ruby read_registry_key_value.rb --username msfadmin --password msfadmin 192.168.172.138 HKLM\\My\\Key ValueName
|
|
6
6
|
# 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.
|
|
7
7
|
|
|
8
8
|
require 'bundler/setup'
|
|
9
|
+
require 'optparse'
|
|
9
10
|
require 'ruby_smb'
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
args = ARGV.dup
|
|
13
|
+
options = {
|
|
14
|
+
domain: '.',
|
|
15
|
+
username: '',
|
|
16
|
+
password: '',
|
|
17
|
+
smbv1: true,
|
|
18
|
+
smbv2: true,
|
|
19
|
+
smbv3: true,
|
|
20
|
+
target: nil,
|
|
21
|
+
registry_key: nil,
|
|
22
|
+
value_name: nil
|
|
23
|
+
}
|
|
24
|
+
options[:value_name] = args.pop
|
|
25
|
+
options[:registry_key] = args.pop
|
|
26
|
+
options[:target] = args.pop
|
|
27
|
+
optparser = OptionParser.new do |opts|
|
|
28
|
+
opts.banner = "Usage: #{File.basename(__FILE__)} [options] target registry_key value_name"
|
|
29
|
+
opts.on("--[no-]smbv1", "Enable or disable SMBv1 (default: #{options[:smbv1] ? 'Enabled' : 'Disabled'})") do |smbv1|
|
|
30
|
+
options[:smbv1] = smbv1
|
|
31
|
+
end
|
|
32
|
+
opts.on("--[no-]smbv2", "Enable or disable SMBv2 (default: #{options[:smbv2] ? 'Enabled' : 'Disabled'})") do |smbv2|
|
|
33
|
+
options[:smbv2] = smbv2
|
|
34
|
+
end
|
|
35
|
+
opts.on("--[no-]smbv3", "Enable or disable SMBv3 (default: #{options[:smbv3] ? 'Enabled' : 'Disabled'})") do |smbv3|
|
|
36
|
+
options[:smbv3] = smbv3
|
|
37
|
+
end
|
|
38
|
+
opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username|
|
|
39
|
+
if username.include?('\\')
|
|
40
|
+
options[:domain], options[:username] = username.split('\\', 2)
|
|
41
|
+
else
|
|
42
|
+
options[:username] = username
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password|
|
|
46
|
+
options[:password] = password
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
optparser.parse!(args)
|
|
50
|
+
|
|
51
|
+
if [options[:target], options[:registry_key], options[:value_name]].any? { |a| a == '-h' || a == '--help' }
|
|
52
|
+
puts optparser.help
|
|
53
|
+
exit
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if options[:target].nil? || options[:registry_key].nil? || options[:value_name].nil?
|
|
57
|
+
abort(optparser.help)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
address = options[:target]
|
|
61
|
+
registry_key = options[:registry_key]
|
|
62
|
+
value_name = options[:value_name]
|
|
17
63
|
|
|
18
64
|
sock = TCPSocket.new address, 445
|
|
19
65
|
dispatcher = RubySMB::Dispatcher::Socket.new(sock, read_timeout: 60)
|
|
20
66
|
|
|
21
|
-
client = RubySMB::Client.new(dispatcher, smb1:
|
|
67
|
+
client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain])
|
|
22
68
|
protocol = client.negotiate
|
|
23
69
|
status = client.authenticate
|
|
24
70
|
|
|
@@ -30,4 +76,3 @@ key_value = client.read_registry_key_value(address, registry_key, value_name)
|
|
|
30
76
|
puts key_value
|
|
31
77
|
|
|
32
78
|
client.disconnect!
|
|
33
|
-
|
data/examples/rename_file.rb
CHANGED
|
@@ -1,28 +1,71 @@
|
|
|
1
1
|
#!/usr/bin/ruby
|
|
2
2
|
|
|
3
|
-
# This example script is used for testing the
|
|
3
|
+
# This example script is used for testing the renaming of a file.
|
|
4
4
|
# It will attempt to connect to a specific share and then rename a specified file.
|
|
5
|
-
# Example usage: ruby rename_file.rb 192.168.172.138
|
|
5
|
+
# Example usage: ruby rename_file.rb --username msfadmin --password msfadmin 192.168.172.138 TEST_SHARE short.txt shortrenamed.txt
|
|
6
6
|
# This will try to connect to \\192.168.172.138\TEST_SHARE with the msfadmin:msfadmin credentials
|
|
7
|
-
# and rename the file short.txt
|
|
7
|
+
# and rename the file short.txt to shortrenamed.txt
|
|
8
8
|
|
|
9
9
|
require 'bundler/setup'
|
|
10
|
+
require 'optparse'
|
|
10
11
|
require 'ruby_smb'
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
args = ARGV.dup
|
|
14
|
+
options = {
|
|
15
|
+
domain: '.',
|
|
16
|
+
username: '',
|
|
17
|
+
password: '',
|
|
18
|
+
smbv1: true,
|
|
19
|
+
smbv2: true,
|
|
20
|
+
smbv3: true,
|
|
21
|
+
target: nil,
|
|
22
|
+
share: nil,
|
|
23
|
+
file: nil,
|
|
24
|
+
new_name: nil
|
|
25
|
+
}
|
|
26
|
+
options[:new_name] = args.pop
|
|
27
|
+
options[:file] = args.pop
|
|
28
|
+
options[:share] = args.pop
|
|
29
|
+
options[:target] = args.pop
|
|
30
|
+
optparser = OptionParser.new do |opts|
|
|
31
|
+
opts.banner = "Usage: #{File.basename(__FILE__)} [options] target share file new_name"
|
|
32
|
+
opts.on("--[no-]smbv1", "Enable or disable SMBv1 (default: #{options[:smbv1] ? 'Enabled' : 'Disabled'})") do |smbv1|
|
|
33
|
+
options[:smbv1] = smbv1
|
|
34
|
+
end
|
|
35
|
+
opts.on("--[no-]smbv2", "Enable or disable SMBv2 (default: #{options[:smbv2] ? 'Enabled' : 'Disabled'})") do |smbv2|
|
|
36
|
+
options[:smbv2] = smbv2
|
|
37
|
+
end
|
|
38
|
+
opts.on("--[no-]smbv3", "Enable or disable SMBv3 (default: #{options[:smbv3] ? 'Enabled' : 'Disabled'})") do |smbv3|
|
|
39
|
+
options[:smbv3] = smbv3
|
|
40
|
+
end
|
|
41
|
+
opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username|
|
|
42
|
+
if username.include?('\\')
|
|
43
|
+
options[:domain], options[:username] = username.split('\\', 2)
|
|
44
|
+
else
|
|
45
|
+
options[:username] = username
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password|
|
|
49
|
+
options[:password] = password
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
optparser.parse!(args)
|
|
53
|
+
|
|
54
|
+
if [options[:target], options[:share], options[:file], options[:new_name]].any? { |a| a == '-h' || a == '--help' }
|
|
55
|
+
puts optparser.help
|
|
56
|
+
exit
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
if options[:target].nil? || options[:share].nil? || options[:file].nil? || options[:new_name].nil?
|
|
60
|
+
abort(optparser.help)
|
|
61
|
+
end
|
|
19
62
|
|
|
20
|
-
path
|
|
63
|
+
path = "\\\\#{options[:target]}\\#{options[:share]}"
|
|
21
64
|
|
|
22
|
-
sock = TCPSocket.new
|
|
65
|
+
sock = TCPSocket.new options[:target], 445
|
|
23
66
|
dispatcher = RubySMB::Dispatcher::Socket.new(sock)
|
|
24
67
|
|
|
25
|
-
client = RubySMB::Client.new(dispatcher, smb1:
|
|
68
|
+
client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain])
|
|
26
69
|
|
|
27
70
|
protocol = client.negotiate
|
|
28
71
|
status = client.authenticate
|
|
@@ -36,8 +79,8 @@ rescue StandardError => e
|
|
|
36
79
|
puts "Failed to connect to #{path}: #{e.message}"
|
|
37
80
|
end
|
|
38
81
|
|
|
39
|
-
file = tree.open_file(filename: file, write: true, delete: true)
|
|
82
|
+
file = tree.open_file(filename: options[:file], write: true, delete: true)
|
|
40
83
|
|
|
41
|
-
data = file.rename(new_name)
|
|
84
|
+
data = file.rename(options[:new_name])
|
|
42
85
|
puts data
|
|
43
86
|
file.close
|
data/examples/write_file.rb
CHANGED
|
@@ -2,27 +2,70 @@
|
|
|
2
2
|
|
|
3
3
|
# This example script is used for testing the writing to a file.
|
|
4
4
|
# It will attempt to connect to a specific share and then write to a specified file.
|
|
5
|
-
# Example usage: ruby write_file.rb 192.168.172.138
|
|
5
|
+
# Example usage: ruby write_file.rb --username msfadmin --password msfadmin 192.168.172.138 TEST_SHARE test.txt "data to write"
|
|
6
6
|
# This will try to connect to \\192.168.172.138\TEST_SHARE with the msfadmin:msfadmin credentials
|
|
7
|
-
# and write "data to write" the file test.txt
|
|
7
|
+
# and write "data to write" to the file test.txt
|
|
8
8
|
|
|
9
9
|
require 'bundler/setup'
|
|
10
|
+
require 'optparse'
|
|
10
11
|
require 'ruby_smb'
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
args = ARGV.dup
|
|
14
|
+
options = {
|
|
15
|
+
domain: '.',
|
|
16
|
+
username: '',
|
|
17
|
+
password: '',
|
|
18
|
+
smbv1: true,
|
|
19
|
+
smbv2: true,
|
|
20
|
+
smbv3: true,
|
|
21
|
+
target: nil,
|
|
22
|
+
share: nil,
|
|
23
|
+
file: nil,
|
|
24
|
+
data: nil
|
|
25
|
+
}
|
|
26
|
+
options[:data] = args.pop
|
|
27
|
+
options[:file] = args.pop
|
|
28
|
+
options[:share] = args.pop
|
|
29
|
+
options[:target] = args.pop
|
|
30
|
+
optparser = OptionParser.new do |opts|
|
|
31
|
+
opts.banner = "Usage: #{File.basename(__FILE__)} [options] target share file data"
|
|
32
|
+
opts.on("--[no-]smbv1", "Enable or disable SMBv1 (default: #{options[:smbv1] ? 'Enabled' : 'Disabled'})") do |smbv1|
|
|
33
|
+
options[:smbv1] = smbv1
|
|
34
|
+
end
|
|
35
|
+
opts.on("--[no-]smbv2", "Enable or disable SMBv2 (default: #{options[:smbv2] ? 'Enabled' : 'Disabled'})") do |smbv2|
|
|
36
|
+
options[:smbv2] = smbv2
|
|
37
|
+
end
|
|
38
|
+
opts.on("--[no-]smbv3", "Enable or disable SMBv3 (default: #{options[:smbv3] ? 'Enabled' : 'Disabled'})") do |smbv3|
|
|
39
|
+
options[:smbv3] = smbv3
|
|
40
|
+
end
|
|
41
|
+
opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username|
|
|
42
|
+
if username.include?('\\')
|
|
43
|
+
options[:domain], options[:username] = username.split('\\', 2)
|
|
44
|
+
else
|
|
45
|
+
options[:username] = username
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password|
|
|
49
|
+
options[:password] = password
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
optparser.parse!(args)
|
|
53
|
+
|
|
54
|
+
if [options[:target], options[:share], options[:file], options[:data]].any? { |a| a == '-h' || a == '--help' }
|
|
55
|
+
puts optparser.help
|
|
56
|
+
exit
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
if options[:target].nil? || options[:share].nil? || options[:file].nil? || options[:data].nil?
|
|
60
|
+
abort(optparser.help)
|
|
61
|
+
end
|
|
19
62
|
|
|
20
|
-
path
|
|
63
|
+
path = "\\\\#{options[:target]}\\#{options[:share]}"
|
|
21
64
|
|
|
22
|
-
sock = TCPSocket.new
|
|
65
|
+
sock = TCPSocket.new options[:target], 445
|
|
23
66
|
dispatcher = RubySMB::Dispatcher::Socket.new(sock)
|
|
24
67
|
|
|
25
|
-
client = RubySMB::Client.new(dispatcher, smb1:
|
|
68
|
+
client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain])
|
|
26
69
|
protocol = client.negotiate
|
|
27
70
|
status = client.authenticate
|
|
28
71
|
|
|
@@ -32,11 +75,11 @@ begin
|
|
|
32
75
|
tree = client.tree_connect(path)
|
|
33
76
|
puts "Connected to #{path} successfully!"
|
|
34
77
|
rescue StandardError => e
|
|
35
|
-
|
|
78
|
+
abort("Failed to connect to #{path}: #{e.message}")
|
|
36
79
|
end
|
|
37
80
|
|
|
38
|
-
file = tree.open_file(filename: file, write: true, disposition: RubySMB::Dispositions::FILE_OVERWRITE_IF)
|
|
81
|
+
file = tree.open_file(filename: options[:file], write: true, disposition: RubySMB::Dispositions::FILE_OVERWRITE_IF)
|
|
39
82
|
|
|
40
|
-
result = file.write(data: data)
|
|
83
|
+
result = file.write(data: options[:data])
|
|
41
84
|
puts result.to_s
|
|
42
85
|
file.close
|
|
@@ -15,6 +15,10 @@ module RubySMB
|
|
|
15
15
|
if smb1
|
|
16
16
|
if username.empty? && password.empty?
|
|
17
17
|
smb1_anonymous_auth
|
|
18
|
+
elsif @smb1_negotiate_challenge
|
|
19
|
+
# Non-extended security negotiated (e.g. Windows 95/98). Use legacy
|
|
20
|
+
# LM/NTLM challenge-response rather than NTLMSSP.
|
|
21
|
+
smb1_legacy_authenticate
|
|
18
22
|
else
|
|
19
23
|
smb1_authenticate
|
|
20
24
|
end
|
|
@@ -200,6 +204,55 @@ module RubySMB
|
|
|
200
204
|
[type2_blob].pack('m')
|
|
201
205
|
end
|
|
202
206
|
|
|
207
|
+
# Handles SMB1 authentication against servers that negotiated non-extended
|
|
208
|
+
# (legacy) security — Windows 95/98/ME and old Samba builds. These hosts
|
|
209
|
+
# provide a raw 8-byte challenge in the Negotiate response and expect
|
|
210
|
+
# LM + NTLM hash responses in SessionSetupLegacyRequest.
|
|
211
|
+
def smb1_legacy_authenticate
|
|
212
|
+
challenge = @smb1_negotiate_challenge
|
|
213
|
+
lm_hash = Net::NTLM.lm_hash(@password)
|
|
214
|
+
ntlm_hash = Net::NTLM.ntlm_hash(@password)
|
|
215
|
+
lm_resp = Net::NTLM.lm_response(lm_hash: lm_hash, challenge: challenge)
|
|
216
|
+
ntlm_resp = Net::NTLM.ntlm_response(ntlm_hash: ntlm_hash, challenge: challenge)
|
|
217
|
+
|
|
218
|
+
packet = smb1_legacy_auth_request(lm_resp, ntlm_resp)
|
|
219
|
+
raw_response = send_recv(packet)
|
|
220
|
+
response = smb1_legacy_auth_response(raw_response)
|
|
221
|
+
response_code = response.status_code
|
|
222
|
+
|
|
223
|
+
if response_code == WindowsError::NTStatus::STATUS_SUCCESS
|
|
224
|
+
self.user_id = response.smb_header.uid
|
|
225
|
+
self.peer_native_os = response.data_block.native_os.to_s
|
|
226
|
+
self.peer_native_lm = response.data_block.native_lan_man.to_s
|
|
227
|
+
self.primary_domain = response.data_block.primary_domain.to_s
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
response_code
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def smb1_legacy_auth_request(lm_response, ntlm_response)
|
|
234
|
+
packet = RubySMB::SMB1::Packet::SessionSetupLegacyRequest.new
|
|
235
|
+
packet.parameter_block.max_buffer_size = self.max_buffer_size
|
|
236
|
+
packet.parameter_block.max_mpx_count = 50
|
|
237
|
+
packet.data_block.oem_password = lm_response
|
|
238
|
+
packet.data_block.unicode_password = ntlm_response
|
|
239
|
+
packet.data_block.account_name = @username.encode('ASCII', invalid: :replace, undef: :replace)
|
|
240
|
+
packet.data_block.primary_domain = @domain.encode('ASCII', invalid: :replace, undef: :replace)
|
|
241
|
+
packet
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def smb1_legacy_auth_response(raw_response)
|
|
245
|
+
packet = RubySMB::SMB1::Packet::SessionSetupLegacyResponse.read(raw_response)
|
|
246
|
+
unless packet.valid?
|
|
247
|
+
raise RubySMB::Error::InvalidPacket.new(
|
|
248
|
+
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
|
249
|
+
expected_cmd: RubySMB::SMB1::Packet::SessionSetupLegacyResponse::COMMAND,
|
|
250
|
+
packet: packet
|
|
251
|
+
)
|
|
252
|
+
end
|
|
253
|
+
packet
|
|
254
|
+
end
|
|
255
|
+
|
|
203
256
|
#
|
|
204
257
|
# SMB 2 Methods
|
|
205
258
|
#
|
|
@@ -106,7 +106,8 @@ module RubySMB
|
|
|
106
106
|
# @return [String] The SMB version as a string ('SMB1', 'SMB2')
|
|
107
107
|
def parse_negotiate_response(packet)
|
|
108
108
|
case packet
|
|
109
|
-
when RubySMB::SMB1::Packet::
|
|
109
|
+
when RubySMB::SMB1::Packet::NegotiateResponse,
|
|
110
|
+
RubySMB::SMB1::Packet::NegotiateResponseExtended
|
|
110
111
|
self.smb1 = true
|
|
111
112
|
self.smb2 = false
|
|
112
113
|
self.smb3 = false
|
|
@@ -118,7 +119,14 @@ module RubySMB
|
|
|
118
119
|
self.server_max_buffer_size = packet.parameter_block.max_buffer_size - 260
|
|
119
120
|
self.negotiated_smb_version = 1
|
|
120
121
|
self.session_encrypt_data = false
|
|
121
|
-
self.
|
|
122
|
+
self.server_supports_nt_smbs = packet.parameter_block.capabilities.nt_smbs != 0
|
|
123
|
+
if packet.is_a?(RubySMB::SMB1::Packet::NegotiateResponseExtended)
|
|
124
|
+
self.negotiation_security_buffer = packet.data_block.security_blob
|
|
125
|
+
else
|
|
126
|
+
# Non-extended security (e.g. Windows 95/98/ME, old Samba). Server provides a raw
|
|
127
|
+
# 8-byte challenge instead of a SPNEGO blob; store it so auth can compute LM/NTLM responses.
|
|
128
|
+
@smb1_negotiate_challenge = packet.data_block.challenge.to_s
|
|
129
|
+
end
|
|
122
130
|
'SMB1'
|
|
123
131
|
when RubySMB::SMB2::Packet::NegotiateResponse
|
|
124
132
|
self.smb1 = false
|
|
@@ -11,10 +11,17 @@ module RubySMB
|
|
|
11
11
|
# {RubySMB::SMB1::Tree}
|
|
12
12
|
#
|
|
13
13
|
# @param share [String] the share path to connect to
|
|
14
|
+
# @param password [String, nil] share-level password for servers using
|
|
15
|
+
# share-level authentication (e.g. Windows 95/98/ME)
|
|
14
16
|
# @return [RubySMB::SMB1::Tree] the connected Tree
|
|
15
|
-
def smb1_tree_connect(share)
|
|
17
|
+
def smb1_tree_connect(share, password: nil)
|
|
16
18
|
request = RubySMB::SMB1::Packet::TreeConnectRequest.new
|
|
17
19
|
request.smb_header.tid = 65_535
|
|
20
|
+
if password
|
|
21
|
+
pass_bytes = password + "\x00".b
|
|
22
|
+
request.parameter_block.password_length = pass_bytes.length
|
|
23
|
+
request.data_block.password = pass_bytes
|
|
24
|
+
end
|
|
18
25
|
request.data_block.path = share
|
|
19
26
|
raw_response = send_recv(request)
|
|
20
27
|
response = RubySMB::SMB1::Packet::TreeConnectResponse.read(raw_response)
|
data/lib/ruby_smb/client.rb
CHANGED
|
@@ -309,6 +309,14 @@ module RubySMB
|
|
|
309
309
|
# @return [String] The raw security buffer bytes
|
|
310
310
|
attr_accessor :negotiation_security_buffer
|
|
311
311
|
|
|
312
|
+
# Whether the negotiated SMB1 server supports the "NT SMBs" capability
|
|
313
|
+
# (i.e. SMB_COM_NT_CREATE_ANDX). Set false for Windows 95/98/ME and other
|
|
314
|
+
# LAN Manager-era servers, which must use SMB_COM_OPEN_ANDX instead.
|
|
315
|
+
# Always true for SMB2/3.
|
|
316
|
+
# @!attribute [rw] supports_nt_smbs
|
|
317
|
+
# @return [Boolean]
|
|
318
|
+
attr_accessor :server_supports_nt_smbs
|
|
319
|
+
|
|
312
320
|
# @param dispatcher [RubySMB::Dispatcher::Socket] the packet dispatcher to use
|
|
313
321
|
# @param smb1 [Boolean] whether or not to enable SMB1 support
|
|
314
322
|
# @param smb2 [Boolean] whether or not to enable SMB2 support
|
|
@@ -338,10 +346,11 @@ module RubySMB
|
|
|
338
346
|
@max_buffer_size = MAX_BUFFER_SIZE
|
|
339
347
|
# These sizes will be modified during negotiation
|
|
340
348
|
@server_max_buffer_size = SERVER_MAX_BUFFER_SIZE
|
|
341
|
-
@server_max_read_size
|
|
342
|
-
@server_max_write_size
|
|
349
|
+
@server_max_read_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
|
|
350
|
+
@server_max_write_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
|
|
343
351
|
@server_max_transact_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
|
|
344
352
|
@server_supports_multi_credit = false
|
|
353
|
+
@server_supports_nt_smbs = true
|
|
345
354
|
|
|
346
355
|
# SMB 3.x options
|
|
347
356
|
# this merely initializes the default value for session encryption, it may be changed as necessary when a
|
|
@@ -615,13 +624,15 @@ module RubySMB
|
|
|
615
624
|
# Connects to the supplied share
|
|
616
625
|
#
|
|
617
626
|
# @param share [String] the path to the share in `\\server\share_name` format
|
|
627
|
+
# @param password [String, nil] share-level password (SMB1 only, for
|
|
628
|
+
# servers using share-level auth such as Windows 95/98/ME)
|
|
618
629
|
# @return [RubySMB::SMB1::Tree] if talking over SMB1
|
|
619
630
|
# @return [RubySMB::SMB2::Tree] if talking over SMB2
|
|
620
|
-
def tree_connect(share)
|
|
631
|
+
def tree_connect(share, password: nil)
|
|
621
632
|
connected_tree = if smb2 || smb3
|
|
622
633
|
smb2_tree_connect(share)
|
|
623
634
|
else
|
|
624
|
-
smb1_tree_connect(share)
|
|
635
|
+
smb1_tree_connect(share, password: password)
|
|
625
636
|
end
|
|
626
637
|
@tree_connects << connected_tree
|
|
627
638
|
connected_tree
|
|
@@ -684,7 +695,7 @@ module RubySMB
|
|
|
684
695
|
# @return [RubySMB::Nbss::SessionRequest] the SessionRequest packet
|
|
685
696
|
def session_request_packet(name = '*SMBSERVER')
|
|
686
697
|
called_name = "#{name.upcase.ljust(15)}\x20"
|
|
687
|
-
calling_name = "#{
|
|
698
|
+
calling_name = "#{@local_workstation.upcase.ljust(15)}\x00"
|
|
688
699
|
|
|
689
700
|
session_request = RubySMB::Nbss::SessionRequest.new
|
|
690
701
|
session_request.session_header.session_packet_type = RubySMB::Nbss::SESSION_REQUEST
|