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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/examples/anonymous_auth.rb +5 -0
  3. data/examples/append_file.rb +57 -14
  4. data/examples/authenticate.rb +64 -16
  5. data/examples/delete_file.rb +53 -11
  6. data/examples/dump_secrets_from_sid.rb +43 -8
  7. data/examples/enum_domain_users.rb +51 -8
  8. data/examples/enum_registry_key.rb +51 -7
  9. data/examples/enum_registry_values.rb +51 -9
  10. data/examples/get_computer_info.rb +48 -8
  11. data/examples/list_directory.rb +54 -12
  12. data/examples/negotiate.rb +54 -42
  13. data/examples/negotiate_with_netbios_service.rb +55 -16
  14. data/examples/net_share_enum_all.rb +47 -8
  15. data/examples/pipes.rb +51 -7
  16. data/examples/query_service_status.rb +51 -8
  17. data/examples/read_file_encryption.rb +71 -26
  18. data/examples/read_registry_key_value.rb +54 -9
  19. data/examples/rename_file.rb +58 -15
  20. data/examples/write_file.rb +58 -15
  21. data/lib/ruby_smb/client/authentication.rb +53 -0
  22. data/lib/ruby_smb/client/negotiation.rb +10 -2
  23. data/lib/ruby_smb/client/tree_connect.rb +8 -1
  24. data/lib/ruby_smb/client.rb +16 -5
  25. data/lib/ruby_smb/rap/net_share_enum.rb +166 -0
  26. data/lib/ruby_smb/rap.rb +10 -0
  27. data/lib/ruby_smb/smb1/commands.rb +1 -0
  28. data/lib/ruby_smb/smb1/packet/negotiate_response.rb +11 -0
  29. data/lib/ruby_smb/smb1/packet/open_andx_request.rb +39 -0
  30. data/lib/ruby_smb/smb1/packet/open_andx_response.rb +40 -0
  31. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +2 -2
  32. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +11 -0
  33. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +53 -13
  34. data/lib/ruby_smb/smb1/packet/trans2/find_information_level/find_info_standard.rb +39 -0
  35. data/lib/ruby_smb/smb1/packet/trans2/find_information_level.rb +1 -0
  36. data/lib/ruby_smb/smb1/packet/trans2/win9x_framing.rb +68 -0
  37. data/lib/ruby_smb/smb1/packet/trans2.rb +1 -0
  38. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +1 -1
  39. data/lib/ruby_smb/smb1/packet/tree_connect_response.rb +10 -1
  40. data/lib/ruby_smb/smb1/packet.rb +2 -0
  41. data/lib/ruby_smb/smb1/pipe.rb +2 -0
  42. data/lib/ruby_smb/smb1/tree.rb +113 -9
  43. data/lib/ruby_smb/version.rb +1 -1
  44. data/lib/ruby_smb.rb +1 -0
  45. data/spec/lib/ruby_smb/client_spec.rb +2 -1
  46. data/spec/lib/ruby_smb/rap/net_share_enum_spec.rb +185 -0
  47. data/spec/lib/ruby_smb/smb1/packet/trans2/win9x_framing_spec.rb +113 -0
  48. data/spec/lib/ruby_smb/smb1/tree_spec.rb +188 -2
  49. 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 read_file.rb 192.168.172.138 msfadmin msfadmin TEST_SHARE short.txt
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 only setting smb3 to true,
30
- # we make sure the server will negotiate this version, if it supports it
31
- opts = {
32
- smb1: false,
33
- smb2: false,
34
- smb3: true,
35
- username: username,
36
- password: password,
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
- #opts[:always_encrypt] = false
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, opts)
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: 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 enum_registry_key.rb 192.168.172.138 msfadmin msfadmin HKLM\\My\\Key ValueName
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
- address = ARGV[0]
12
- username = ARGV[1]
13
- password = ARGV[2]
14
- registry_key = ARGV[3]
15
- value_name = ARGV[4]
16
- smb_versions = ARGV[5]&.split(',') || ['1','2','3']
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: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password)
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
-
@@ -1,28 +1,71 @@
1
1
  #!/usr/bin/ruby
2
2
 
3
- # This example script is used for testing the deleting of a file.
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 msfadmin msfadmin TEST_SHARE short.txt shortrenamed.txt
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
- address = ARGV[0]
13
- username = ARGV[1]
14
- password = ARGV[2]
15
- share = ARGV[3]
16
- file = ARGV[4]
17
- new_name = ARGV[5]
18
- smb_versions = ARGV[6]&.split(',') || ['1','2','3']
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 = "\\\\#{address}\\#{share}"
63
+ path = "\\\\#{options[:target]}\\#{options[:share]}"
21
64
 
22
- sock = TCPSocket.new address, 445
65
+ sock = TCPSocket.new options[:target], 445
23
66
  dispatcher = RubySMB::Dispatcher::Socket.new(sock)
24
67
 
25
- client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password)
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
@@ -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 msfadmin msfadmin TEST_SHARE test.txt "data to write"
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
- address = ARGV[0]
13
- username = ARGV[1]
14
- password = ARGV[2]
15
- share = ARGV[3]
16
- file = ARGV[4]
17
- data = ARGV[5]
18
- smb_versions = ARGV[6]&.split(',') || ['1','2','3']
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 = "\\\\#{address}\\#{share}"
63
+ path = "\\\\#{options[:target]}\\#{options[:share]}"
21
64
 
22
- sock = TCPSocket.new address, 445
65
+ sock = TCPSocket.new options[:target], 445
23
66
  dispatcher = RubySMB::Dispatcher::Socket.new(sock)
24
67
 
25
- client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password)
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
- puts "Failed to connect to #{path}: #{e.message}"
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::NegotiateResponseExtended
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.negotiation_security_buffer = packet.data_block.security_blob
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)
@@ -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 = RubySMB::SMB2::File::MAX_PACKET_SIZE
342
- @server_max_write_size = RubySMB::SMB2::File::MAX_PACKET_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 = "#{''.ljust(15)}\x00"
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