ruby_smb 1.1.0 → 2.0.4

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 (163) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.travis.yml +3 -5
  5. data/Gemfile +6 -2
  6. data/examples/anonymous_auth.rb +3 -3
  7. data/examples/append_file.rb +10 -8
  8. data/examples/authenticate.rb +9 -5
  9. data/examples/delete_file.rb +8 -6
  10. data/examples/enum_registry_key.rb +5 -4
  11. data/examples/enum_registry_values.rb +5 -4
  12. data/examples/list_directory.rb +8 -6
  13. data/examples/negotiate.rb +51 -8
  14. data/examples/negotiate_with_netbios_service.rb +9 -5
  15. data/examples/net_share_enum_all.rb +6 -4
  16. data/examples/pipes.rb +11 -12
  17. data/examples/query_service_status.rb +64 -0
  18. data/examples/read_file.rb +8 -6
  19. data/examples/read_file_encryption.rb +56 -0
  20. data/examples/read_registry_key_value.rb +6 -5
  21. data/examples/rename_file.rb +9 -7
  22. data/examples/tree_connect.rb +7 -5
  23. data/examples/write_file.rb +9 -7
  24. data/lib/ruby_smb.rb +4 -0
  25. data/lib/ruby_smb/client.rb +246 -26
  26. data/lib/ruby_smb/client/authentication.rb +32 -18
  27. data/lib/ruby_smb/client/echo.rb +2 -4
  28. data/lib/ruby_smb/client/encryption.rb +62 -0
  29. data/lib/ruby_smb/client/negotiation.rb +156 -16
  30. data/lib/ruby_smb/client/signing.rb +19 -0
  31. data/lib/ruby_smb/client/tree_connect.rb +6 -8
  32. data/lib/ruby_smb/client/utils.rb +24 -17
  33. data/lib/ruby_smb/client/winreg.rb +1 -1
  34. data/lib/ruby_smb/crypto.rb +30 -0
  35. data/lib/ruby_smb/dcerpc.rb +2 -0
  36. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  37. data/lib/ruby_smb/dcerpc/ndr.rb +209 -44
  38. data/lib/ruby_smb/dcerpc/request.rb +13 -0
  39. data/lib/ruby_smb/dcerpc/rpc_security_attributes.rb +34 -0
  40. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +9 -6
  41. data/lib/ruby_smb/dcerpc/svcctl.rb +479 -0
  42. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request.rb +48 -0
  43. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response.rb +26 -0
  44. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request.rb +25 -0
  45. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response.rb +26 -0
  46. data/lib/ruby_smb/dcerpc/svcctl/control_service_request.rb +26 -0
  47. data/lib/ruby_smb/dcerpc/svcctl/control_service_response.rb +26 -0
  48. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request.rb +35 -0
  49. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response.rb +23 -0
  50. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_request.rb +31 -0
  51. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_response.rb +23 -0
  52. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request.rb +25 -0
  53. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response.rb +44 -0
  54. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_request.rb +23 -0
  55. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_response.rb +27 -0
  56. data/lib/ruby_smb/dcerpc/svcctl/service_status.rb +25 -0
  57. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_request.rb +27 -0
  58. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_response.rb +25 -0
  59. data/lib/ruby_smb/dcerpc/winreg.rb +98 -17
  60. data/lib/ruby_smb/dcerpc/winreg/create_key_request.rb +73 -0
  61. data/lib/ruby_smb/dcerpc/winreg/create_key_response.rb +36 -0
  62. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +1 -1
  63. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +1 -1
  64. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +1 -1
  65. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +4 -4
  66. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +1 -1
  67. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +7 -6
  68. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +10 -10
  69. data/lib/ruby_smb/dcerpc/winreg/save_key_request.rb +37 -0
  70. data/lib/ruby_smb/dcerpc/winreg/save_key_response.rb +23 -0
  71. data/lib/ruby_smb/dispatcher/base.rb +1 -1
  72. data/lib/ruby_smb/dispatcher/socket.rb +5 -4
  73. data/lib/ruby_smb/error.rb +49 -6
  74. data/lib/ruby_smb/field/stringz16.rb +17 -1
  75. data/lib/ruby_smb/generic_packet.rb +11 -1
  76. data/lib/ruby_smb/nbss/session_header.rb +4 -4
  77. data/lib/ruby_smb/smb1/commands.rb +1 -1
  78. data/lib/ruby_smb/smb1/file.rb +13 -28
  79. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
  80. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
  81. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  82. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
  83. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
  84. data/lib/ruby_smb/smb1/pipe.rb +8 -8
  85. data/lib/ruby_smb/smb1/tree.rb +25 -12
  86. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  87. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  88. data/lib/ruby_smb/smb2/file.rb +59 -77
  89. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  90. data/lib/ruby_smb/smb2/packet.rb +2 -0
  91. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  92. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
  93. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +50 -4
  94. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  95. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
  96. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
  97. data/lib/ruby_smb/smb2/pipe.rb +8 -20
  98. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  99. data/lib/ruby_smb/smb2/tree.rb +44 -28
  100. data/lib/ruby_smb/version.rb +1 -1
  101. data/ruby_smb.gemspec +3 -1
  102. data/spec/lib/ruby_smb/client_spec.rb +1408 -70
  103. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  104. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +1396 -77
  105. data/spec/lib/ruby_smb/dcerpc/rpc_security_attributes_spec.rb +161 -0
  106. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +49 -12
  107. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request_spec.rb +191 -0
  108. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response_spec.rb +38 -0
  109. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request_spec.rb +30 -0
  110. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response_spec.rb +38 -0
  111. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_request_spec.rb +39 -0
  112. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_response_spec.rb +38 -0
  113. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request_spec.rb +78 -0
  114. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response_spec.rb +38 -0
  115. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_request_spec.rb +59 -0
  116. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_response_spec.rb +38 -0
  117. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request_spec.rb +38 -0
  118. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response_spec.rb +152 -0
  119. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_request_spec.rb +30 -0
  120. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_response_spec.rb +38 -0
  121. data/spec/lib/ruby_smb/dcerpc/svcctl/service_status_spec.rb +72 -0
  122. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_request_spec.rb +46 -0
  123. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_response_spec.rb +30 -0
  124. data/spec/lib/ruby_smb/dcerpc/svcctl_spec.rb +512 -0
  125. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_request_spec.rb +110 -0
  126. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_response_spec.rb +44 -0
  127. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +0 -4
  128. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +2 -2
  129. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +2 -2
  130. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +9 -4
  131. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +0 -4
  132. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +17 -17
  133. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +11 -23
  134. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_request_spec.rb +57 -0
  135. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_response_spec.rb +22 -0
  136. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +227 -41
  137. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +12 -12
  138. data/spec/lib/ruby_smb/error_spec.rb +88 -0
  139. data/spec/lib/ruby_smb/field/stringz16_spec.rb +12 -0
  140. data/spec/lib/ruby_smb/generic_packet_spec.rb +7 -0
  141. data/spec/lib/ruby_smb/nbss/session_header_spec.rb +4 -11
  142. data/spec/lib/ruby_smb/smb1/file_spec.rb +1 -3
  143. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  144. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  145. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  146. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  147. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +30 -5
  148. data/spec/lib/ruby_smb/smb1/tree_spec.rb +22 -0
  149. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  150. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  151. data/spec/lib/ruby_smb/smb2/file_spec.rb +147 -71
  152. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  153. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  154. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  155. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  156. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  157. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  158. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
  159. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +9 -45
  160. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  161. data/spec/lib/ruby_smb/smb2/tree_spec.rb +111 -9
  162. metadata +194 -75
  163. metadata.gz.sig +2 -1
@@ -9,17 +9,19 @@
9
9
  require 'bundler/setup'
10
10
  require 'ruby_smb'
11
11
 
12
- address = ARGV[0]
13
- username = ARGV[1]
14
- password = ARGV[2]
15
- share = ARGV[3]
16
- file = ARGV[4]
12
+ address = ARGV[0]
13
+ username = ARGV[1]
14
+ password = ARGV[2]
15
+ share = ARGV[3]
16
+ file = ARGV[4]
17
+ smb_versions = ARGV[5]&.split(',') || ['1','2','3']
18
+
17
19
  path = "\\\\#{address}\\#{share}"
18
20
 
19
21
  sock = TCPSocket.new address, 445
20
22
  dispatcher = RubySMB::Dispatcher::Socket.new(sock)
21
23
 
22
- client = RubySMB::Client.new(dispatcher, smb1: true, smb2: true, username: username, password: password)
24
+ client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password)
23
25
  protocol = client.negotiate
24
26
  status = client.authenticate
25
27
 
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/ruby
2
+
3
+ # This example script is used for testing the reading of a file.
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
6
+ # This will try to connect to \\192.168.172.138\TEST_SHARE with the msfadmin:msfadmin credentials
7
+ # and read the file short.txt
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
+ # To require encryption on the server, run this in an elevated Powershell:
23
+ # C:\> Set-SmbServerConfiguration -EncryptData $true
24
+
25
+ # To enable per-share encryption on the server, run this in an elevated Powershell:
26
+ # C:\ Set-SmbServerConfiguration -EncryptData $false
27
+ # C:\ Set-SmbShare -Name <share name> -EncryptData 1
28
+
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,
37
+ }
38
+
39
+ # 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
41
+
42
+ client = RubySMB::Client.new(dispatcher, opts)
43
+ protocol = client.negotiate
44
+ status = client.authenticate
45
+
46
+ begin
47
+ tree = client.tree_connect(path)
48
+ rescue StandardError => e
49
+ puts "Failed to connect to #{path}: #{e.message}"
50
+ end
51
+
52
+ file = tree.open_file(filename: filename)
53
+
54
+ data = file.read
55
+ puts data
56
+ file.close
@@ -8,16 +8,17 @@
8
8
  require 'bundler/setup'
9
9
  require 'ruby_smb'
10
10
 
11
- address = ARGV[0]
12
- username = ARGV[1]
13
- password = ARGV[2]
11
+ address = ARGV[0]
12
+ username = ARGV[1]
13
+ password = ARGV[2]
14
14
  registry_key = ARGV[3]
15
- value_name = ARGV[4]
15
+ value_name = ARGV[4]
16
+ smb_versions = ARGV[5]&.split(',') || ['1','2','3']
16
17
 
17
18
  sock = TCPSocket.new address, 445
18
19
  dispatcher = RubySMB::Dispatcher::Socket.new(sock, read_timeout: 60)
19
20
 
20
- client = RubySMB::Client.new(dispatcher, smb1: true, smb2: true, username: username, password: password)
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)
21
22
  protocol = client.negotiate
22
23
  status = client.authenticate
23
24
 
@@ -9,18 +9,20 @@
9
9
  require 'bundler/setup'
10
10
  require 'ruby_smb'
11
11
 
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]
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']
19
+
18
20
  path = "\\\\#{address}\\#{share}"
19
21
 
20
22
  sock = TCPSocket.new address, 445
21
23
  dispatcher = RubySMB::Dispatcher::Socket.new(sock)
22
24
 
23
- client = RubySMB::Client.new(dispatcher, smb1: true, smb2: true, username: username, password: password)
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)
24
26
 
25
27
  protocol = client.negotiate
26
28
  status = client.authenticate
@@ -8,16 +8,18 @@
8
8
  require 'bundler/setup'
9
9
  require 'ruby_smb'
10
10
 
11
- address = ARGV[0]
12
- username = ARGV[1]
13
- password = ARGV[2]
14
- share = ARGV[3]
11
+ address = ARGV[0]
12
+ username = ARGV[1]
13
+ password = ARGV[2]
14
+ share = ARGV[3]
15
+ smb_versions = ARGV[4]&.split(',') || ['1','2','3']
16
+
15
17
  path = "\\\\#{address}\\#{share}"
16
18
 
17
19
  sock = TCPSocket.new address, 445
18
20
  dispatcher = RubySMB::Dispatcher::Socket.new(sock)
19
21
 
20
- client = RubySMB::Client.new(dispatcher, smb1: true, smb2: true, username: username, password: password)
22
+ client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password)
21
23
  protocol = client.negotiate
22
24
  status = client.authenticate
23
25
 
@@ -9,18 +9,20 @@
9
9
  require 'bundler/setup'
10
10
  require 'ruby_smb'
11
11
 
12
- address = ARGV[0]
13
- username = ARGV[1]
14
- password = ARGV[2]
15
- share = ARGV[3]
16
- file = ARGV[4]
17
- data = ARGV[5]
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']
19
+
18
20
  path = "\\\\#{address}\\#{share}"
19
21
 
20
22
  sock = TCPSocket.new address, 445
21
23
  dispatcher = RubySMB::Dispatcher::Socket.new(sock)
22
24
 
23
- client = RubySMB::Client.new(dispatcher, smb1: true, smb2: true, username: username, password: password)
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)
24
26
  protocol = client.negotiate
25
27
  status = client.authenticate
26
28
 
@@ -1,6 +1,9 @@
1
1
  require 'bindata'
2
2
  require 'net/ntlm'
3
3
  require 'net/ntlm/client'
4
+ require 'openssl'
5
+ require 'openssl/ccm'
6
+ require 'openssl/cmac'
4
7
  require 'windows_error'
5
8
  require 'windows_error/nt_status'
6
9
  # A packet parsing and manipulation library for the SMB1 and SMB2 protocols
@@ -22,4 +25,5 @@ module RubySMB
22
25
  require 'ruby_smb/smb2'
23
26
  require 'ruby_smb/smb1'
24
27
  require 'ruby_smb/client'
28
+ require 'ruby_smb/crypto'
25
29
  end
@@ -9,6 +9,7 @@ module RubySMB
9
9
  require 'ruby_smb/client/echo'
10
10
  require 'ruby_smb/client/utils'
11
11
  require 'ruby_smb/client/winreg'
12
+ require 'ruby_smb/client/encryption'
12
13
 
13
14
  include RubySMB::Client::Negotiation
14
15
  include RubySMB::Client::Authentication
@@ -17,13 +18,20 @@ module RubySMB
17
18
  include RubySMB::Client::Echo
18
19
  include RubySMB::Client::Utils
19
20
  include RubySMB::Client::Winreg
21
+ include RubySMB::Client::Encryption
20
22
 
21
23
  # The Default SMB1 Dialect string used in an SMB1 Negotiate Request
22
24
  SMB1_DIALECT_SMB1_DEFAULT = 'NT LM 0.12'.freeze
23
25
  # The Default SMB2 Dialect string used in an SMB1 Negotiate Request
24
26
  SMB1_DIALECT_SMB2_DEFAULT = 'SMB 2.002'.freeze
25
- # Dialect value for SMB2 Default (Version 2.02)
26
- SMB2_DIALECT_DEFAULT = 0x0202
27
+ # The SMB2 wildcard revision number Dialect string used in an SMB1 Negotiate Request
28
+ # It indicates that the server implements SMB 2.1 or future dialect revisions
29
+ # Note that this must be used for SMB3
30
+ SMB1_DIALECT_SMB2_WILDCARD = 'SMB 2.???'.freeze
31
+ # Dialect values for SMB2
32
+ SMB2_DIALECT_DEFAULT = ['0x0202', '0x0210']
33
+ # Dialect values for SMB3
34
+ SMB3_DIALECT_DEFAULT = ['0x0300', '0x0302', '0x0311']
27
35
  # The default maximum size of a SMB message that the Client accepts (in bytes)
28
36
  MAX_BUFFER_SIZE = 64512
29
37
  # The default maximum size of a SMB message that the Server accepts (in bytes)
@@ -135,6 +143,11 @@ module RubySMB
135
143
  # @return [Boolean]
136
144
  attr_accessor :smb2
137
145
 
146
+ # Whether or not the Client should support SMB3
147
+ # @!attribute [rw] smb3
148
+ # @return [Boolean]
149
+ attr_accessor :smb3
150
+
138
151
  # Tracks the current SMB2 Message ID that keeps communication in sync
139
152
  # @!attribute [rw] smb2_message_id
140
153
  # @return [Integer]
@@ -147,9 +160,16 @@ module RubySMB
147
160
 
148
161
  # The UID set in SMB1
149
162
  # @!attribute [rw] user_id
150
- # @return [String]
163
+ # @return [Integer]
151
164
  attr_accessor :user_id
152
165
 
166
+ # The Process ID set in SMB1
167
+ # It is randomly generated during the client initialization, but can
168
+ # be modified using the accessor.
169
+ # @!attribute [rw] pid
170
+ # @return [Integer]
171
+ attr_accessor :pid
172
+
153
173
  # The maximum size SMB message that the Client accepts (in bytes)
154
174
  # The default value is equal to {MAX_BUFFER_SIZE}.
155
175
  # @!attribute [rw] max_buffer_size
@@ -178,15 +198,92 @@ module RubySMB
178
198
  # @return [Integer]
179
199
  attr_accessor :server_max_transact_size
180
200
 
201
+ # The algorithm to compute the preauthentication integrity hash (SMB 3.1.1).
202
+ # @!attribute [rw] preauth_integrity_hash_algorithm
203
+ # @return [String]
204
+ attr_accessor :preauth_integrity_hash_algorithm
205
+
206
+ # The preauthentication integrity hash value (SMB 3.1.1).
207
+ # @!attribute [rw] preauth_integrity_hash_value
208
+ # @return [String]
209
+ attr_accessor :preauth_integrity_hash_value
210
+
211
+ # The algorithm for encryption (SMB 3.x).
212
+ # @!attribute [rw] encryption_algorithm
213
+ # @return [String]
214
+ attr_accessor :encryption_algorithm
215
+
216
+ # The client encryption key (SMB 3.x).
217
+ # @!attribute [rw] client_encryption_key
218
+ # @return [String]
219
+ attr_accessor :client_encryption_key
220
+
221
+ # The server encryption key (SMB 3.x).
222
+ # @!attribute [rw] server_encryption_key
223
+ # @return [String]
224
+ attr_accessor :server_encryption_key
225
+
226
+ # Whether or not the whole session needs to be encrypted (SMB 3.x)
227
+ # @!attribute [rw] session_encrypt_data
228
+ # @return [Boolean]
229
+ attr_accessor :session_encrypt_data
230
+
231
+ # The encryption algorithms supported by the server (SMB 3.x).
232
+ # @!attribute [rw] server_encryption_algorithms
233
+ # @return [Array<Integer>] list of supported encryption algorithms
234
+ # (constants defined in RubySMB::SMB2::EncryptionCapabilities)
235
+ attr_accessor :server_encryption_algorithms
236
+
237
+ # The compression algorithms supported by the server (SMB 3.x).
238
+ # @!attribute [rw] server_compression_algorithms
239
+ # @return [Array<Integer>] list of supported compression algorithms
240
+ # (constants defined in RubySMB::SMB2::CompressionCapabilities)
241
+ attr_accessor :server_compression_algorithms
242
+
243
+ # The GUID of the server (SMB 2.x and 3.x).
244
+ # @!attribute [rw] server_guid
245
+ # @return [String]
246
+ attr_accessor :server_guid
247
+
248
+ # The server's start time if it is reported as part of the negotiation
249
+ # process (SMB 2.x and 3.x). This value is nil if the server does not report
250
+ # it (reports a value of 0).
251
+ # @!attribute [rw] server_start_time
252
+ # @return [Time] the time that the server reports that it was started at
253
+ attr_accessor :server_start_time
254
+
255
+ # The server's current time if it is reported as part of the negotiation
256
+ # process (SMB 2.x and 3.x). This value is nil if the server does not report
257
+ # it (reports a value of 0).
258
+ # @!attribute [rw] server_system_time
259
+ # @return [Time] the time that the server reports as current
260
+ attr_accessor :server_system_time
261
+
262
+ # The SMB version that has been successfully negotiated. This value is only
263
+ # set after the NEGOTIATE handshake has been performed.
264
+ # @!attribute [rw] negotiated_smb_version
265
+ # @return [Integer] the negotiated SMB version
266
+ attr_accessor :negotiated_smb_version
267
+
268
+ # Whether or not the server supports multi-credit operations. It is
269
+ # reported by the LARGE_MTU capabiliy as part of the negotiation process
270
+ # (SMB 2.x and 3.x).
271
+ # @!attribute [rw] server_supports_multi_credit
272
+ # @return [Boolean] true if the server supports multi-credit operations,
273
+ # false otherwise
274
+ attr_accessor :server_supports_multi_credit
275
+
181
276
  # @param dispatcher [RubySMB::Dispatcher::Socket] the packet dispatcher to use
182
277
  # @param smb1 [Boolean] whether or not to enable SMB1 support
183
278
  # @param smb2 [Boolean] whether or not to enable SMB2 support
184
- def initialize(dispatcher, smb1: true, smb2: true, username:, password:, domain: '.', local_workstation: 'WORKSTATION')
279
+ # @param smb3 [Boolean] whether or not to enable SMB3 support
280
+ def initialize(dispatcher, smb1: true, smb2: true, smb3: true, username:, password:, domain: '.', local_workstation: 'WORKSTATION', always_encrypt: true)
185
281
  raise ArgumentError, 'No Dispatcher provided' unless dispatcher.is_a? RubySMB::Dispatcher::Base
186
- if smb1 == false && smb2 == false
282
+ if smb1 == false && smb2 == false && smb3 == false
187
283
  raise ArgumentError, 'You must enable at least one Protocol'
188
284
  end
189
285
  @dispatcher = dispatcher
286
+ @pid = rand(0xFFFF)
190
287
  @domain = domain
191
288
  @local_workstation = local_workstation
192
289
  @password = password.encode('utf-8') || ''.encode('utf-8')
@@ -196,6 +293,7 @@ module RubySMB
196
293
  @signing_required = false
197
294
  @smb1 = smb1
198
295
  @smb2 = smb2
296
+ @smb3 = smb3
199
297
  @username = username.encode('utf-8') || ''.encode('utf-8')
200
298
  @max_buffer_size = MAX_BUFFER_SIZE
201
299
  # These sizes will be modifed during negotiation
@@ -203,11 +301,16 @@ module RubySMB
203
301
  @server_max_read_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
204
302
  @server_max_write_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
205
303
  @server_max_transact_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
304
+ @server_supports_multi_credit = false
305
+
306
+ # SMB 3.x options
307
+ @session_encrypt_data = always_encrypt
206
308
 
207
309
  negotiate_version_flag = 0x02000000
208
310
  flags = Net::NTLM::Client::DEFAULT_FLAGS |
209
311
  Net::NTLM::FLAGS[:TARGET_INFO] |
210
- negotiate_version_flag
312
+ negotiate_version_flag ^
313
+ Net::NTLM::FLAGS[:OEM]
211
314
 
212
315
  @ntlm_client = Net::NTLM::Client.new(
213
316
  @username,
@@ -243,7 +346,7 @@ module RubySMB
243
346
  # @param data [String] the data the server should echo back (ignored in SMB2)
244
347
  # @return [WindowsError::ErrorCode] the NTStatus of the last response received
245
348
  def echo(count: 1, data: '')
246
- response = if smb2
349
+ response = if smb2 || smb3
247
350
  smb2_echo
248
351
  else
249
352
  smb1_echo(count: count, data: data)
@@ -258,10 +361,8 @@ module RubySMB
258
361
  # @param packet [RubySMB::GenericPacket] the packet to set the message id for
259
362
  # @return [RubySMB::GenericPacket] the modified packet
260
363
  def increment_smb_message_id(packet)
261
- if packet.smb2_header.message_id.zero? && smb2_message_id != 0
262
- packet.smb2_header.message_id = smb2_message_id
263
- self.smb2_message_id += 1
264
- end
364
+ packet.smb2_header.message_id = self.smb2_message_id
365
+ self.smb2_message_id += 1
265
366
  packet
266
367
  end
267
368
 
@@ -283,7 +384,8 @@ module RubySMB
283
384
  negotiate_version_flag = 0x02000000
284
385
  flags = Net::NTLM::Client::DEFAULT_FLAGS |
285
386
  Net::NTLM::FLAGS[:TARGET_INFO] |
286
- negotiate_version_flag
387
+ negotiate_version_flag ^
388
+ Net::NTLM::FLAGS[:OEM]
287
389
 
288
390
  @ntlm_client = Net::NTLM::Client.new(
289
391
  @username,
@@ -301,7 +403,7 @@ module RubySMB
301
403
  # @return [WindowsError::ErrorCode] the NTStatus of the response
302
404
  # @raise [RubySMB::Error::InvalidPacket] if the response packet is not a LogoffResponse packet
303
405
  def logoff!
304
- if smb2
406
+ if smb2 || smb3
305
407
  request = RubySMB::SMB2::Packet::LogoffRequest.new
306
408
  raw_response = send_recv(request)
307
409
  response = RubySMB::SMB2::Packet::LogoffResponse.read(raw_response)
@@ -309,8 +411,7 @@ module RubySMB
309
411
  raise RubySMB::Error::InvalidPacket.new(
310
412
  expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
311
413
  expected_cmd: RubySMB::SMB2::Packet::LogoffResponse::COMMAND,
312
- received_proto: response.smb2_header.protocol,
313
- received_cmd: response.smb2_header.command
414
+ packet: response
314
415
  )
315
416
  end
316
417
  else
@@ -321,8 +422,7 @@ module RubySMB
321
422
  raise RubySMB::Error::InvalidPacket.new(
322
423
  expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
323
424
  expected_cmd: RubySMB::SMB1::Packet::LogoffResponse::COMMAND,
324
- received_proto: response.smb_header.protocol,
325
- received_cmd: response.smb_header.command
425
+ packet: response
326
426
  )
327
427
  end
328
428
  end
@@ -334,25 +434,131 @@ module RubySMB
334
434
  # It will also sign the packet if neccessary.
335
435
  #
336
436
  # @param packet [RubySMB::GenericPacket] the request to be sent
437
+ # @param encrypt [Boolean] true if encryption has to be enabled for this transaction
438
+ # (note that if @session_encrypt_data is set, encryption will be enabled
439
+ # regardless of this parameter value)
337
440
  # @return [String] the raw response data received
338
- def send_recv(packet)
339
- case packet.packet_smb_version
441
+ def send_recv(packet, encrypt: false)
442
+ version = packet.packet_smb_version
443
+ case version
340
444
  when 'SMB1'
341
- packet.smb_header.uid = user_id if user_id
445
+ packet.smb_header.uid = self.user_id if self.user_id
446
+ packet.smb_header.pid_low = self.pid if self.pid
342
447
  packet = smb1_sign(packet)
343
448
  when 'SMB2'
344
449
  packet = increment_smb_message_id(packet)
345
450
  packet.smb2_header.session_id = session_id
346
451
  unless packet.is_a?(RubySMB::SMB2::Packet::SessionSetupRequest)
347
- packet = smb2_sign(packet)
452
+ if self.smb2
453
+ packet = smb2_sign(packet)
454
+ elsif self.smb3
455
+ packet = smb3_sign(packet)
456
+ end
348
457
  end
349
458
  else
350
459
  packet = packet
351
460
  end
352
- dispatcher.send_packet(packet)
353
- raw_response = dispatcher.recv_packet
461
+
462
+ encrypt_data = false
463
+ if can_be_encrypted?(packet) && encryption_supported? && (@session_encrypt_data || encrypt)
464
+ encrypt_data = true
465
+ end
466
+ send_packet(packet, encrypt: encrypt_data)
467
+ raw_response = recv_packet(encrypt: encrypt_data)
468
+ smb2_header = nil
469
+ loop do
470
+ smb2_header = RubySMB::SMB2::SMB2Header.read(raw_response)
471
+ break unless is_status_pending?(smb2_header)
472
+ sleep 1
473
+ raw_response = recv_packet(encrypt: encrypt_data)
474
+ rescue IOError
475
+ # We're expecting an SMB2 packet, but the server sent an SMB1 packet
476
+ # instead. This behavior has been observed with older versions of Samba
477
+ # when something goes wrong on the server side. So, we just ignore it
478
+ # and expect the caller to handle this wrong response packet.
479
+ break
480
+ end unless version == 'SMB1'
354
481
 
355
482
  self.sequence_counter += 1 if signing_required && !session_key.empty?
483
+ # update the SMB2 message ID according to the received Credit Charged
484
+ self.smb2_message_id += smb2_header.credit_charge - 1 if smb2_header && self.server_supports_multi_credit
485
+ raw_response
486
+ end
487
+
488
+ # Check if the response is an asynchronous operation with STATUS_PENDING
489
+ # status code.
490
+ #
491
+ # @param smb2_header [String] the response packet SMB2 header
492
+ # @return [Boolean] true if it is a status pending operation, false otherwise
493
+ def is_status_pending?(smb2_header)
494
+ value = smb2_header.nt_status.value
495
+ status_code = WindowsError::NTStatus.find_by_retval(value).first
496
+ status_code == WindowsError::NTStatus::STATUS_PENDING &&
497
+ smb2_header.flags.async_command == 1
498
+ end
499
+
500
+ # Check if the request packet can be encrypted. Per the SMB2 spec,
501
+ # SessionSetupRequest and NegotiateRequest must not be encrypted.
502
+ #
503
+ # @param packet [RubySMB::GenericPacket] the request packet
504
+ # @return [Boolean] true if the packet can be encrypted
505
+ def can_be_encrypted?(packet)
506
+ return false if packet.packet_smb_version == 'SMB1'
507
+ [RubySMB::SMB2::Packet::SessionSetupRequest, RubySMB::SMB2::Packet::NegotiateRequest].none? do |klass|
508
+ packet.is_a?(klass)
509
+ end
510
+ end
511
+
512
+ # Check if the current dialect supports encryption.
513
+ #
514
+ # @return [Boolean] true if encryption is supported
515
+ def encryption_supported?
516
+ ['0x0300', '0x0302', '0x0311'].include?(@dialect)
517
+ end
518
+
519
+ # Encrypt (if required) and send a packet.
520
+ #
521
+ # @param encrypt [Boolean] true if the packet should be encrypted, false
522
+ # otherwise
523
+ def send_packet(packet, encrypt: false)
524
+ if encrypt
525
+ begin
526
+ packet = smb3_encrypt(packet.to_binary_s)
527
+ rescue RubySMB::Error::RubySMBError => e
528
+ raise RubySMB::Error::EncryptionError, "Error while encrypting #{packet.class.name} packet (SMB #{@dialect}): #{e}"
529
+ end
530
+ end
531
+ dispatcher.send_packet(packet)
532
+ end
533
+
534
+ # Receives the raw response through the Dispatcher and decrypt the packet (if required).
535
+ #
536
+ # @param encrypt [Boolean] true if the packet is encrypted, false otherwise
537
+ # @return [String] the raw unencrypted packet
538
+ def recv_packet(encrypt: false)
539
+ begin
540
+ raw_response = dispatcher.recv_packet
541
+ rescue RubySMB::Error::CommunicationError => e
542
+ if encrypt
543
+ raise RubySMB::Error::EncryptionError, "Communication error with the "\
544
+ "remote host: #{e.message}. The server supports encryption but was "\
545
+ "not able to handle the encrypted request."
546
+ else
547
+ raise e
548
+ end
549
+ end
550
+ if encrypt
551
+ begin
552
+ transform_response = RubySMB::SMB2::Packet::TransformHeader.read(raw_response)
553
+ rescue IOError
554
+ raise RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet'
555
+ end
556
+ begin
557
+ raw_response = smb3_decrypt(transform_response)
558
+ rescue RubySMB::Error::RubySMBError => e
559
+ raise RubySMB::Error::EncryptionError, "Error while decrypting #{transform_response.class.name} packet (SMB #@dialect}): #{e}"
560
+ end
561
+ end
356
562
  raw_response
357
563
  end
358
564
 
@@ -362,7 +568,7 @@ module RubySMB
362
568
  # @return [RubySMB::SMB1::Tree] if talking over SMB1
363
569
  # @return [RubySMB::SMB2::Tree] if talking over SMB2
364
570
  def tree_connect(share)
365
- connected_tree = if smb2
571
+ connected_tree = if smb2 || smb3
366
572
  smb2_tree_connect(share)
367
573
  else
368
574
  smb1_tree_connect(share)
@@ -377,7 +583,7 @@ module RubySMB
377
583
  # @param [String] host
378
584
  def net_share_enum_all(host)
379
585
  tree = tree_connect("\\\\#{host}\\IPC$")
380
- named_pipe = tree.open_file(filename: "srvsvc", write: true, read: true)
586
+ named_pipe = tree.open_pipe(filename: "srvsvc", write: true, read: true)
381
587
  named_pipe.net_share_enum_all(host)
382
588
  end
383
589
 
@@ -392,6 +598,9 @@ module RubySMB
392
598
  self.session_key = ''
393
599
  self.sequence_counter = 0
394
600
  self.smb2_message_id = 0
601
+ self.client_encryption_key = nil
602
+ self.server_encryption_key = nil
603
+ self.server_supports_multi_credit = false
395
604
  end
396
605
 
397
606
  # Requests a NetBIOS Session Service using the provided name.
@@ -429,10 +638,21 @@ module RubySMB
429
638
  session_request.session_header.session_packet_type = RubySMB::Nbss::SESSION_REQUEST
430
639
  session_request.called_name = called_name
431
640
  session_request.calling_name = calling_name
432
- session_request.session_header.packet_length =
641
+ session_request.session_header.stream_protocol_length =
433
642
  session_request.num_bytes - session_request.session_header.num_bytes
434
643
  session_request
435
644
  end
436
645
 
646
+ def update_preauth_hash(data)
647
+ unless @preauth_integrity_hash_algorithm
648
+ raise RubySMB::Error::EncryptionError.new(
649
+ 'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
650
+ )
651
+ end
652
+ @preauth_integrity_hash_value = OpenSSL::Digest.digest(
653
+ @preauth_integrity_hash_algorithm,
654
+ @preauth_integrity_hash_value + data.to_binary_s
655
+ )
656
+ end
437
657
  end
438
658
  end