ruby_smb 1.0.2 → 2.0.0

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 (200) 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 -2
  5. data/Gemfile +6 -2
  6. data/README.md +35 -47
  7. data/examples/enum_registry_key.rb +28 -0
  8. data/examples/enum_registry_values.rb +30 -0
  9. data/examples/negotiate.rb +51 -8
  10. data/examples/pipes.rb +2 -1
  11. data/examples/read_file_encryption.rb +56 -0
  12. data/examples/read_registry_key_value.rb +32 -0
  13. data/lib/ruby_smb.rb +4 -1
  14. data/lib/ruby_smb/client.rb +200 -20
  15. data/lib/ruby_smb/client/authentication.rb +70 -33
  16. data/lib/ruby_smb/client/echo.rb +20 -2
  17. data/lib/ruby_smb/client/encryption.rb +62 -0
  18. data/lib/ruby_smb/client/negotiation.rb +160 -24
  19. data/lib/ruby_smb/client/signing.rb +19 -0
  20. data/lib/ruby_smb/client/tree_connect.rb +24 -18
  21. data/lib/ruby_smb/client/utils.rb +8 -7
  22. data/lib/ruby_smb/client/winreg.rb +46 -0
  23. data/lib/ruby_smb/crypto.rb +30 -0
  24. data/lib/ruby_smb/dcerpc.rb +38 -0
  25. data/lib/ruby_smb/dcerpc/bind.rb +2 -2
  26. data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
  27. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  28. data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
  29. data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
  30. data/lib/ruby_smb/dcerpc/request.rb +28 -9
  31. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
  32. data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
  33. data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
  34. data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
  35. data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
  36. data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
  37. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
  38. data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
  39. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
  40. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
  41. data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
  42. data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
  43. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
  44. data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
  45. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
  46. data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
  47. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
  48. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
  49. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
  50. data/lib/ruby_smb/dispatcher/socket.rb +5 -3
  51. data/lib/ruby_smb/error.rb +68 -2
  52. data/lib/ruby_smb/generic_packet.rb +33 -4
  53. data/lib/ruby_smb/smb1/commands.rb +1 -1
  54. data/lib/ruby_smb/smb1/file.rb +66 -15
  55. data/lib/ruby_smb/smb1/packet/close_request.rb +2 -5
  56. data/lib/ruby_smb/smb1/packet/close_response.rb +2 -1
  57. data/lib/ruby_smb/smb1/packet/echo_request.rb +2 -4
  58. data/lib/ruby_smb/smb1/packet/echo_response.rb +2 -1
  59. data/lib/ruby_smb/smb1/packet/empty_packet.rb +10 -1
  60. data/lib/ruby_smb/smb1/packet/logoff_request.rb +2 -4
  61. data/lib/ruby_smb/smb1/packet/logoff_response.rb +2 -1
  62. data/lib/ruby_smb/smb1/packet/negotiate_request.rb +2 -5
  63. data/lib/ruby_smb/smb1/packet/negotiate_response.rb +3 -7
  64. data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +4 -4
  65. data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +2 -4
  66. data/lib/ruby_smb/smb1/packet/nt_create_andx_response.rb +2 -1
  67. data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +2 -1
  68. data/lib/ruby_smb/smb1/packet/nt_trans/create_response.rb +2 -1
  69. data/lib/ruby_smb/smb1/packet/nt_trans/request.rb +2 -4
  70. data/lib/ruby_smb/smb1/packet/nt_trans/response.rb +2 -1
  71. data/lib/ruby_smb/smb1/packet/read_andx_request.rb +2 -5
  72. data/lib/ruby_smb/smb1/packet/read_andx_response.rb +2 -1
  73. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +2 -1
  74. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +3 -2
  75. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +2 -5
  76. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +3 -2
  77. data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_request.rb +0 -1
  78. data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_response.rb +3 -2
  79. data/lib/ruby_smb/smb1/packet/trans/request.rb +2 -5
  80. data/lib/ruby_smb/smb1/packet/trans/response.rb +2 -1
  81. data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_request.rb +1 -1
  82. data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_response.rb +1 -1
  83. data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +2 -1
  84. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +8 -2
  85. data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +2 -1
  86. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +8 -2
  87. data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +2 -1
  88. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +2 -1
  89. data/lib/ruby_smb/smb1/packet/trans2/request.rb +2 -4
  90. data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +2 -4
  91. data/lib/ruby_smb/smb1/packet/trans2/response.rb +2 -1
  92. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +2 -1
  93. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +2 -1
  94. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +2 -4
  95. data/lib/ruby_smb/smb1/packet/tree_connect_response.rb +13 -3
  96. data/lib/ruby_smb/smb1/packet/tree_disconnect_request.rb +2 -4
  97. data/lib/ruby_smb/smb1/packet/tree_disconnect_response.rb +2 -1
  98. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +3 -6
  99. data/lib/ruby_smb/smb1/packet/write_andx_response.rb +2 -1
  100. data/lib/ruby_smb/smb1/pipe.rb +87 -6
  101. data/lib/ruby_smb/smb1/tree.rb +41 -3
  102. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  103. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  104. data/lib/ruby_smb/smb2/file.rb +103 -25
  105. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  106. data/lib/ruby_smb/smb2/packet.rb +2 -0
  107. data/lib/ruby_smb/smb2/packet/close_request.rb +2 -4
  108. data/lib/ruby_smb/smb2/packet/close_response.rb +2 -1
  109. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  110. data/lib/ruby_smb/smb2/packet/create_request.rb +2 -4
  111. data/lib/ruby_smb/smb2/packet/create_response.rb +2 -1
  112. data/lib/ruby_smb/smb2/packet/echo_request.rb +2 -4
  113. data/lib/ruby_smb/smb2/packet/echo_response.rb +2 -1
  114. data/lib/ruby_smb/smb2/packet/error_packet.rb +15 -3
  115. data/lib/ruby_smb/smb2/packet/ioctl_request.rb +2 -5
  116. data/lib/ruby_smb/smb2/packet/ioctl_response.rb +2 -1
  117. data/lib/ruby_smb/smb2/packet/logoff_request.rb +2 -4
  118. data/lib/ruby_smb/smb2/packet/logoff_response.rb +2 -1
  119. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -17
  120. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +51 -4
  121. data/lib/ruby_smb/smb2/packet/query_directory_request.rb +2 -4
  122. data/lib/ruby_smb/smb2/packet/query_directory_response.rb +8 -2
  123. data/lib/ruby_smb/smb2/packet/read_request.rb +2 -4
  124. data/lib/ruby_smb/smb2/packet/read_response.rb +2 -1
  125. data/lib/ruby_smb/smb2/packet/session_setup_request.rb +2 -5
  126. data/lib/ruby_smb/smb2/packet/session_setup_response.rb +2 -1
  127. data/lib/ruby_smb/smb2/packet/set_info_request.rb +2 -4
  128. data/lib/ruby_smb/smb2/packet/set_info_response.rb +2 -1
  129. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  130. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +93 -10
  131. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +10 -22
  132. data/lib/ruby_smb/smb2/packet/tree_disconnect_request.rb +2 -4
  133. data/lib/ruby_smb/smb2/packet/tree_disconnect_response.rb +2 -1
  134. data/lib/ruby_smb/smb2/packet/write_request.rb +2 -4
  135. data/lib/ruby_smb/smb2/packet/write_response.rb +2 -1
  136. data/lib/ruby_smb/smb2/pipe.rb +86 -12
  137. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  138. data/lib/ruby_smb/smb2/tree.rb +65 -21
  139. data/lib/ruby_smb/version.rb +1 -1
  140. data/ruby_smb.gemspec +5 -3
  141. data/spec/lib/ruby_smb/client_spec.rb +1563 -104
  142. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  143. data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
  144. data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
  145. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
  146. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
  147. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
  148. data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
  149. data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
  150. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
  151. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
  152. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
  153. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
  154. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
  155. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
  156. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
  157. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
  158. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
  159. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
  160. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
  161. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
  162. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
  163. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
  164. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
  165. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
  166. data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
  167. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +3 -1
  168. data/spec/lib/ruby_smb/error_spec.rb +59 -0
  169. data/spec/lib/ruby_smb/generic_packet_spec.rb +52 -4
  170. data/spec/lib/ruby_smb/smb1/file_spec.rb +191 -2
  171. data/spec/lib/ruby_smb/smb1/packet/empty_packet_spec.rb +68 -0
  172. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  173. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  174. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  175. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  176. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +11 -2
  177. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +11 -2
  178. data/spec/lib/ruby_smb/smb1/packet/tree_connect_response_spec.rb +40 -0
  179. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +272 -149
  180. data/spec/lib/ruby_smb/smb1/tree_spec.rb +44 -7
  181. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  182. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  183. data/spec/lib/ruby_smb/smb2/file_spec.rb +323 -6
  184. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  185. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  186. data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +78 -0
  187. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  188. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  189. data/spec/lib/ruby_smb/smb2/packet/query_directory_response_spec.rb +8 -0
  190. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  191. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  192. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -22
  193. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +286 -149
  194. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  195. data/spec/lib/ruby_smb/smb2/tree_spec.rb +261 -2
  196. metadata +191 -83
  197. metadata.gz.sig +0 -0
  198. data/lib/ruby_smb/smb1/dcerpc.rb +0 -67
  199. data/lib/ruby_smb/smb2/dcerpc.rb +0 -70
  200. data/spec/lib/ruby_smb/smb1/packet/error_packet_spec.rb +0 -37
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/ruby
2
+
3
+ # This example script is used for testing the Winreg registry key value read functionality.
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
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
+
8
+ require 'bundler/setup'
9
+ require 'ruby_smb'
10
+
11
+ address = ARGV[0]
12
+ username = ARGV[1]
13
+ password = ARGV[2]
14
+ registry_key = ARGV[3]
15
+ value_name = ARGV[4]
16
+
17
+ sock = TCPSocket.new address, 445
18
+ dispatcher = RubySMB::Dispatcher::Socket.new(sock, read_timeout: 60)
19
+
20
+ client = RubySMB::Client.new(dispatcher, smb1: true, smb2: true, username: username, password: password)
21
+ protocol = client.negotiate
22
+ status = client.authenticate
23
+
24
+ puts "#{protocol}: #{status}"
25
+ puts "Key: #{registry_key}"
26
+ puts "Value: #{value_name}"
27
+
28
+ key_value = client.read_registry_key_value(address, registry_key, value_name)
29
+ puts key_value
30
+
31
+ client.disconnect!
32
+
@@ -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
@@ -15,7 +18,6 @@ module RubySMB
15
18
  require 'ruby_smb/field'
16
19
  require 'ruby_smb/nbss'
17
20
  require 'ruby_smb/fscc'
18
- require 'ruby_smb/dcerpc'
19
21
  require 'ruby_smb/generic_packet'
20
22
  require 'ruby_smb/dispatcher'
21
23
  require 'ruby_smb/version'
@@ -23,4 +25,5 @@ module RubySMB
23
25
  require 'ruby_smb/smb2'
24
26
  require 'ruby_smb/smb1'
25
27
  require 'ruby_smb/client'
28
+ require 'ruby_smb/crypto'
26
29
  end
@@ -8,6 +8,8 @@ module RubySMB
8
8
  require 'ruby_smb/client/tree_connect'
9
9
  require 'ruby_smb/client/echo'
10
10
  require 'ruby_smb/client/utils'
11
+ require 'ruby_smb/client/winreg'
12
+ require 'ruby_smb/client/encryption'
11
13
 
12
14
  include RubySMB::Client::Negotiation
13
15
  include RubySMB::Client::Authentication
@@ -15,13 +17,21 @@ module RubySMB
15
17
  include RubySMB::Client::TreeConnect
16
18
  include RubySMB::Client::Echo
17
19
  include RubySMB::Client::Utils
20
+ include RubySMB::Client::Winreg
21
+ include RubySMB::Client::Encryption
18
22
 
19
23
  # The Default SMB1 Dialect string used in an SMB1 Negotiate Request
20
24
  SMB1_DIALECT_SMB1_DEFAULT = 'NT LM 0.12'.freeze
21
25
  # The Default SMB2 Dialect string used in an SMB1 Negotiate Request
22
26
  SMB1_DIALECT_SMB2_DEFAULT = 'SMB 2.002'.freeze
23
- # Dialect value for SMB2 Default (Version 2.02)
24
- 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']
25
35
  # The default maximum size of a SMB message that the Client accepts (in bytes)
26
36
  MAX_BUFFER_SIZE = 64512
27
37
  # The default maximum size of a SMB message that the Server accepts (in bytes)
@@ -133,6 +143,11 @@ module RubySMB
133
143
  # @return [Boolean]
134
144
  attr_accessor :smb2
135
145
 
146
+ # Whether or not the Client should support SMB3
147
+ # @!attribute [rw] smb3
148
+ # @return [Boolean]
149
+ attr_accessor :smb3
150
+
136
151
  # Tracks the current SMB2 Message ID that keeps communication in sync
137
152
  # @!attribute [rw] smb2_message_id
138
153
  # @return [Integer]
@@ -176,12 +191,61 @@ module RubySMB
176
191
  # @return [Integer]
177
192
  attr_accessor :server_max_transact_size
178
193
 
194
+ # The algorithm to compute the preauthentication integrity hash (SMB 3.1.1).
195
+ # @!attribute [rw] preauth_integrity_hash_algorithm
196
+ # @return [String]
197
+ attr_accessor :preauth_integrity_hash_algorithm
198
+
199
+ # The preauthentication integrity hash value (SMB 3.1.1).
200
+ # @!attribute [rw] preauth_integrity_hash_value
201
+ # @return [String]
202
+ attr_accessor :preauth_integrity_hash_value
203
+
204
+ # The algorithm for encryption (SMB 3.x).
205
+ # @!attribute [rw] encryption_algorithm
206
+ # @return [String]
207
+ attr_accessor :encryption_algorithm
208
+
209
+ # The client encryption key (SMB 3.x).
210
+ # @!attribute [rw] client_encryption_key
211
+ # @return [String]
212
+ attr_accessor :client_encryption_key
213
+
214
+ # The server encryption key (SMB 3.x).
215
+ # @!attribute [rw] server_encryption_key
216
+ # @return [String]
217
+ attr_accessor :server_encryption_key
218
+
219
+ # Whether or not encryption is required (SMB 3.x)
220
+ # @!attribute [rw] encryption_required
221
+ # @return [Boolean]
222
+ attr_accessor :encryption_required
223
+
224
+ # The encryption algorithms supported by the server (SMB 3.x).
225
+ # @!attribute [rw] server_encryption_algorithms
226
+ # @return [Array<Integer>] list of supported encryption algorithms
227
+ # (constants defined in RubySMB::SMB2::EncryptionCapabilities)
228
+ attr_accessor :server_encryption_algorithms
229
+
230
+ # The compression algorithms supported by the server (SMB 3.x).
231
+ # @!attribute [rw] server_compression_algorithms
232
+ # @return [Array<Integer>] list of supported compression algorithms
233
+ # (constants defined in RubySMB::SMB2::CompressionCapabilities)
234
+ attr_accessor :server_compression_algorithms
235
+
236
+ # The SMB version that has been successfully negotiated. This value is only
237
+ # set after the NEGOTIATE handshake has been performed.
238
+ # @!attribute [rw] negotiated_smb_version
239
+ # @return [Integer] the negotiated SMB version
240
+ attr_accessor :negotiated_smb_version
241
+
179
242
  # @param dispatcher [RubySMB::Dispatcher::Socket] the packet dispatcher to use
180
243
  # @param smb1 [Boolean] whether or not to enable SMB1 support
181
244
  # @param smb2 [Boolean] whether or not to enable SMB2 support
182
- def initialize(dispatcher, smb1: true, smb2: true, username:, password:, domain: '.', local_workstation: 'WORKSTATION')
245
+ # @param smb3 [Boolean] whether or not to enable SMB3 support
246
+ def initialize(dispatcher, smb1: true, smb2: true, smb3: true, username:, password:, domain: '.', local_workstation: 'WORKSTATION', always_encrypt: true)
183
247
  raise ArgumentError, 'No Dispatcher provided' unless dispatcher.is_a? RubySMB::Dispatcher::Base
184
- if smb1 == false && smb2 == false
248
+ if smb1 == false && smb2 == false && smb3 == false
185
249
  raise ArgumentError, 'You must enable at least one Protocol'
186
250
  end
187
251
  @dispatcher = dispatcher
@@ -194,6 +258,7 @@ module RubySMB
194
258
  @signing_required = false
195
259
  @smb1 = smb1
196
260
  @smb2 = smb2
261
+ @smb3 = smb3
197
262
  @username = username.encode('utf-8') || ''.encode('utf-8')
198
263
  @max_buffer_size = MAX_BUFFER_SIZE
199
264
  # These sizes will be modifed during negotiation
@@ -202,6 +267,9 @@ module RubySMB
202
267
  @server_max_write_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
203
268
  @server_max_transact_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
204
269
 
270
+ # SMB 3.x options
271
+ @encryption_required = always_encrypt
272
+
205
273
  negotiate_version_flag = 0x02000000
206
274
  flags = Net::NTLM::Client::DEFAULT_FLAGS |
207
275
  Net::NTLM::FLAGS[:TARGET_INFO] |
@@ -241,7 +309,7 @@ module RubySMB
241
309
  # @param data [String] the data the server should echo back (ignored in SMB2)
242
310
  # @return [WindowsError::ErrorCode] the NTStatus of the last response received
243
311
  def echo(count: 1, data: '')
244
- response = if smb2
312
+ response = if smb2 || smb3
245
313
  smb2_echo
246
314
  else
247
315
  smb1_echo(count: count, data: data)
@@ -256,10 +324,8 @@ module RubySMB
256
324
  # @param packet [RubySMB::GenericPacket] the packet to set the message id for
257
325
  # @return [RubySMB::GenericPacket] the modified packet
258
326
  def increment_smb_message_id(packet)
259
- if packet.smb2_header.message_id.zero? && smb2_message_id != 0
260
- packet.smb2_header.message_id = smb2_message_id
261
- self.smb2_message_id += 1
262
- end
327
+ packet.smb2_header.message_id = smb2_message_id
328
+ self.smb2_message_id += 1
263
329
  packet
264
330
  end
265
331
 
@@ -297,15 +363,32 @@ module RubySMB
297
363
  # Sends a LOGOFF command to the remote server to terminate the session
298
364
  #
299
365
  # @return [WindowsError::ErrorCode] the NTStatus of the response
366
+ # @raise [RubySMB::Error::InvalidPacket] if the response packet is not a LogoffResponse packet
300
367
  def logoff!
301
- if smb2
368
+ if smb2 || smb3
302
369
  request = RubySMB::SMB2::Packet::LogoffRequest.new
303
370
  raw_response = send_recv(request)
304
371
  response = RubySMB::SMB2::Packet::LogoffResponse.read(raw_response)
372
+ unless response.valid?
373
+ raise RubySMB::Error::InvalidPacket.new(
374
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
375
+ expected_cmd: RubySMB::SMB2::Packet::LogoffResponse::COMMAND,
376
+ received_proto: response.smb2_header.protocol,
377
+ received_cmd: response.smb2_header.command
378
+ )
379
+ end
305
380
  else
306
381
  request = RubySMB::SMB1::Packet::LogoffRequest.new
307
382
  raw_response = send_recv(request)
308
383
  response = RubySMB::SMB1::Packet::LogoffResponse.read(raw_response)
384
+ unless response.valid?
385
+ raise RubySMB::Error::InvalidPacket.new(
386
+ expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
387
+ expected_cmd: RubySMB::SMB1::Packet::LogoffResponse::COMMAND,
388
+ received_proto: response.smb_header.protocol,
389
+ received_cmd: response.smb_header.command
390
+ )
391
+ end
309
392
  end
310
393
  wipe_state!
311
394
  response.status_code
@@ -316,8 +399,9 @@ module RubySMB
316
399
  #
317
400
  # @param packet [RubySMB::GenericPacket] the request to be sent
318
401
  # @return [String] the raw response data received
319
- def send_recv(packet)
320
- case packet.packet_smb_version
402
+ def send_recv(packet, encrypt: false)
403
+ version = packet.packet_smb_version
404
+ case version
321
405
  when 'SMB1'
322
406
  packet.smb_header.uid = user_id if user_id
323
407
  packet = smb1_sign(packet)
@@ -325,25 +409,103 @@ module RubySMB
325
409
  packet = increment_smb_message_id(packet)
326
410
  packet.smb2_header.session_id = session_id
327
411
  unless packet.is_a?(RubySMB::SMB2::Packet::SessionSetupRequest)
328
- packet = smb2_sign(packet)
412
+ if self.smb2
413
+ packet = smb2_sign(packet)
414
+ elsif self.smb3
415
+ packet = smb3_sign(packet)
416
+ end
329
417
  end
330
418
  else
331
419
  packet = packet
332
420
  end
333
- dispatcher.send_packet(packet)
334
- raw_response = dispatcher.recv_packet
421
+
422
+ if can_be_encrypted?(packet) && encryption_supported? && (@encryption_required || encrypt)
423
+ send_encrypt(packet)
424
+ raw_response = recv_encrypt
425
+ loop do
426
+ break unless is_status_pending?(raw_response)
427
+ sleep 1
428
+ raw_response = recv_encrypt
429
+ end
430
+ else
431
+ dispatcher.send_packet(packet)
432
+ raw_response = dispatcher.recv_packet
433
+ loop do
434
+ break unless is_status_pending?(raw_response)
435
+ sleep 1
436
+ raw_response = dispatcher.recv_packet
437
+ end unless version == 'SMB1'
438
+ end
335
439
 
336
440
  self.sequence_counter += 1 if signing_required && !session_key.empty?
337
441
  raw_response
338
442
  end
339
443
 
444
+ # Check if the response is an asynchronous operation with STATUS_PENDING
445
+ # status code.
446
+ #
447
+ # @param raw_response [String] the raw response packet
448
+ # @return [Boolean] true if it is a status pending operation, false otherwise
449
+ def is_status_pending?(raw_response)
450
+ smb2_header = RubySMB::SMB2::SMB2Header.read(raw_response)
451
+ value = smb2_header.nt_status.value
452
+ status_code = WindowsError::NTStatus.find_by_retval(value).first
453
+ status_code == WindowsError::NTStatus::STATUS_PENDING &&
454
+ smb2_header.flags.async_command == 1
455
+ end
456
+
457
+ # Check if the request packet can be encrypted. Per the SMB spec,
458
+ # SessionSetupRequest and NegotiateRequest must not be encrypted.
459
+ #
460
+ # @param packet [RubySMB::GenericPacket] the request packet
461
+ # @return [Boolean] true if the packet can be encrypted
462
+ def can_be_encrypted?(packet)
463
+ [RubySMB::SMB2::Packet::SessionSetupRequest, RubySMB::SMB2::Packet::NegotiateRequest].none? do |klass|
464
+ packet.is_a?(klass)
465
+ end
466
+ end
467
+
468
+ # Check if the current dialect support encryption.
469
+ #
470
+ # @return [Boolean] true if encryption is supported
471
+ def encryption_supported?
472
+ ['0x0300', '0x0302', '0x0311'].include?(@dialect)
473
+ end
474
+
475
+ # Encrypt and send a packet
476
+ def send_encrypt(packet)
477
+ begin
478
+ transform_request = smb3_encrypt(packet.to_binary_s)
479
+ rescue RubySMB::Error::RubySMBError => e
480
+ raise RubySMB::Error::EncryptionError, "Error while encrypting #{packet.class.name} packet (SMB #{@dialect}): #{e}"
481
+ end
482
+ dispatcher.send_packet(transform_request)
483
+ end
484
+
485
+ # Receives the raw response through the Dispatcher and decrypt the packet.
486
+ #
487
+ # @return [String] the raw unencrypted packet
488
+ def recv_encrypt
489
+ raw_response = dispatcher.recv_packet
490
+ begin
491
+ transform_response = RubySMB::SMB2::Packet::TransformHeader.read(raw_response)
492
+ rescue IOError
493
+ raise RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet'
494
+ end
495
+ begin
496
+ smb3_decrypt(transform_response)
497
+ rescue RubySMB::Error::RubySMBError => e
498
+ raise RubySMB::Error::EncryptionError, "Error while decrypting #{transform_response.class.name} packet (SMB #@dialect}): #{e}"
499
+ end
500
+ end
501
+
340
502
  # Connects to the supplied share
341
503
  #
342
504
  # @param share [String] the path to the share in `\\server\share_name` format
343
505
  # @return [RubySMB::SMB1::Tree] if talking over SMB1
344
506
  # @return [RubySMB::SMB2::Tree] if talking over SMB2
345
507
  def tree_connect(share)
346
- connected_tree = if smb2
508
+ connected_tree = if smb2 || smb3
347
509
  smb2_tree_connect(share)
348
510
  else
349
511
  smb1_tree_connect(share)
@@ -373,6 +535,8 @@ module RubySMB
373
535
  self.session_key = ''
374
536
  self.sequence_counter = 0
375
537
  self.smb2_message_id = 0
538
+ self.client_encryption_key = nil
539
+ self.server_encryption_key = nil
376
540
  end
377
541
 
378
542
  # Requests a NetBIOS Session Service using the provided name.
@@ -380,14 +544,19 @@ module RubySMB
380
544
  # @param name [String] the NetBIOS name to request
381
545
  # @return [TrueClass] if session request is granted
382
546
  # @raise [RubySMB::Error::NetBiosSessionService] if session request is refused
547
+ # @raise [RubySMB::Error::InvalidPacket] if the response packet is not a NBSS packet
383
548
  def session_request(name = '*SMBSERVER')
384
549
  session_request = session_request_packet(name)
385
550
  dispatcher.send_packet(session_request, nbss_header: false)
386
551
  raw_response = dispatcher.recv_packet(full_response: true)
387
- session_header = RubySMB::Nbss::SessionHeader.read(raw_response)
388
- if session_header.session_packet_type == RubySMB::Nbss::NEGATIVE_SESSION_RESPONSE
389
- negative_session_response = RubySMB::Nbss::NegativeSessionResponse.read(raw_response)
390
- raise RubySMB::Error::NetBiosSessionService, "Session Request failed: #{negative_session_response.error_msg}"
552
+ begin
553
+ session_header = RubySMB::Nbss::SessionHeader.read(raw_response)
554
+ if session_header.session_packet_type == RubySMB::Nbss::NEGATIVE_SESSION_RESPONSE
555
+ negative_session_response = RubySMB::Nbss::NegativeSessionResponse.read(raw_response)
556
+ raise RubySMB::Error::NetBiosSessionService, "Session Request failed: #{negative_session_response.error_msg}"
557
+ end
558
+ rescue IOError
559
+ raise RubySMB::Error::InvalidPacket, 'Not a NBSS packet'
391
560
  end
392
561
 
393
562
  return true
@@ -410,5 +579,16 @@ module RubySMB
410
579
  session_request
411
580
  end
412
581
 
582
+ def update_preauth_hash(data)
583
+ unless @preauth_integrity_hash_algorithm
584
+ raise RubySMB::Error::EncryptionError.new(
585
+ 'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
586
+ )
587
+ end
588
+ @preauth_integrity_hash_value = OpenSSL::Digest.digest(
589
+ @preauth_integrity_hash_algorithm,
590
+ @preauth_integrity_hash_value + data.to_binary_s
591
+ )
592
+ end
413
593
  end
414
594
  end
@@ -54,14 +54,15 @@ module RubySMB
54
54
  end
55
55
 
56
56
  def smb1_anonymous_auth_response(raw_response)
57
- begin
58
- packet = RubySMB::SMB1::Packet::SessionSetupLegacyResponse.read(raw_response)
59
- rescue
60
- packet = RubySMB::SMB1::Packet::EmptyPacket.read(raw_response)
61
- end
62
-
63
- unless packet.smb_header.command == RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP
64
- raise RubySMB::Error::InvalidPacket, "Command was #{packet.smb_header.command} and not #{RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP}"
57
+ packet = RubySMB::SMB1::Packet::SessionSetupLegacyResponse.read(raw_response)
58
+
59
+ unless packet.valid?
60
+ raise RubySMB::Error::InvalidPacket.new(
61
+ expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
62
+ expected_cmd: RubySMB::SMB1::Packet::SessionSetupLegacyResponse::COMMAND,
63
+ received_proto: packet.smb_header.protocol,
64
+ received_cmd: packet.smb_header.command
65
+ )
65
66
  end
66
67
  packet
67
68
  end
@@ -147,14 +148,15 @@ module RubySMB
147
148
 
148
149
  # Takes the raw binary string and returns a {RubySMB::SMB1::Packet::SessionSetupResponse}
149
150
  def smb1_ntlmssp_final_packet(raw_response)
150
- begin
151
- packet = RubySMB::SMB1::Packet::SessionSetupResponse.read(raw_response)
152
- rescue
153
- packet = RubySMB::SMB1::Packet::EmptyPacket.read(raw_response)
154
- end
151
+ packet = RubySMB::SMB1::Packet::SessionSetupResponse.read(raw_response)
155
152
 
156
- unless packet.smb_header.command == RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP
157
- raise RubySMB::Error::InvalidPacket, "Command was #{packet.smb_header.command} and not #{RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP}"
153
+ unless packet.valid?
154
+ raise RubySMB::Error::InvalidPacket.new(
155
+ expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
156
+ expected_cmd: RubySMB::SMB1::Packet::SessionSetupResponse::COMMAND,
157
+ received_proto: packet.smb_header.protocol,
158
+ received_cmd: packet.smb_header.command
159
+ )
158
160
  end
159
161
  packet
160
162
  end
@@ -162,15 +164,20 @@ module RubySMB
162
164
  # Takes the raw binary string and returns a {RubySMB::SMB1::Packet::SessionSetupResponse}
163
165
  def smb1_ntlmssp_challenge_packet(raw_response)
164
166
  packet = RubySMB::SMB1::Packet::SessionSetupResponse.read(raw_response)
165
- status_code = packet.status_code
167
+ unless packet.valid?
168
+ raise RubySMB::Error::InvalidPacket.new(
169
+ expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
170
+ expected_cmd: RubySMB::SMB1::Packet::SessionSetupResponse::COMMAND,
171
+ received_proto: packet.smb_header.protocol,
172
+ received_cmd: packet.smb_header.command
173
+ )
174
+ end
166
175
 
176
+ status_code = packet.status_code
167
177
  unless status_code.name == 'STATUS_MORE_PROCESSING_REQUIRED'
168
- raise RubySMB::Error::UnexpectedStatusCode, status_code.to_s
178
+ raise RubySMB::Error::UnexpectedStatusCode, status_code
169
179
  end
170
180
 
171
- unless packet.smb_header.command == RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP
172
- raise RubySMB::Error::InvalidPacket, "Command was #{packet.smb_header.command} and not #{RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP}"
173
- end
174
181
  packet
175
182
  end
176
183
 
@@ -194,6 +201,9 @@ module RubySMB
194
201
  def smb2_authenticate
195
202
  response = smb2_ntlmssp_negotiate
196
203
  challenge_packet = smb2_ntlmssp_challenge_packet(response)
204
+ if @dialect == '0x0311'
205
+ update_preauth_hash(challenge_packet)
206
+ end
197
207
  @session_id = challenge_packet.smb2_header.session_id
198
208
  type2_b64_message = smb2_type2_message(challenge_packet)
199
209
  type3_message = @ntlm_client.init_context(type2_b64_message)
@@ -206,28 +216,49 @@ module RubySMB
206
216
  raw = smb2_ntlmssp_authenticate(type3_message, @session_id)
207
217
  response = smb2_ntlmssp_final_packet(raw)
208
218
 
219
+ if @smb3 && !@encryption_required && response.session_flags.encrypt_data == 1
220
+ @encryption_required = true
221
+ end
222
+ ######
223
+ # DEBUG
224
+ #puts "Session ID = #{@session_id.to_binary_s.each_byte.map {|e| '%02x' % e}.join}"
225
+ #puts "Session key = #{@session_key.each_byte.map {|e| '%02x' % e}.join}"
226
+ #puts "PreAuthHash = #{@preauth_integrity_hash_value.each_byte.map {|e| '%02x' % e}.join}" if @preauth_integrity_hash_value
227
+ ######
228
+
209
229
  response.status_code
210
230
  end
211
231
 
212
232
  # Takes the raw binary string and returns a {RubySMB::SMB2::Packet::SessionSetupResponse}
213
233
  def smb2_ntlmssp_final_packet(raw_response)
214
234
  packet = RubySMB::SMB2::Packet::SessionSetupResponse.read(raw_response)
215
- unless packet.smb2_header.command == RubySMB::SMB2::Commands::SESSION_SETUP
216
- raise RubySMB::Error::InvalidPacket, "Command was #{packet.smb2_header.command} and not #{RubySMB::SMB2::Commands::SESSION_SETUP}"
235
+ unless packet.valid?
236
+ raise RubySMB::Error::InvalidPacket.new(
237
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
238
+ expected_cmd: RubySMB::SMB2::Packet::SessionSetupResponse::COMMAND,
239
+ received_proto: packet.smb2_header.protocol,
240
+ received_cmd: packet.smb2_header.command
241
+ )
217
242
  end
243
+
218
244
  packet
219
245
  end
220
246
 
221
247
  # Takes the raw binary string and returns a {RubySMB::SMB2::Packet::SessionSetupResponse}
222
248
  def smb2_ntlmssp_challenge_packet(raw_response)
223
249
  packet = RubySMB::SMB2::Packet::SessionSetupResponse.read(raw_response)
224
- status_code = packet.status_code
225
- unless status_code.name == 'STATUS_MORE_PROCESSING_REQUIRED'
226
- raise RubySMB::Error::UnexpectedStatusCode, status_code.to_s
250
+ unless packet.valid?
251
+ raise RubySMB::Error::InvalidPacket.new(
252
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
253
+ expected_cmd: RubySMB::SMB2::Packet::SessionSetupResponse::COMMAND,
254
+ received_proto: packet.smb2_header.protocol,
255
+ received_cmd: packet.smb2_header.command
256
+ )
227
257
  end
228
258
 
229
- unless packet.smb2_header.command == RubySMB::SMB2::Commands::SESSION_SETUP
230
- raise RubySMB::Error::InvalidPacket, "Command was #{packet.smb2_header.command} and not #{RubySMB::SMB2::Commands::SESSION_SETUP}"
259
+ status_code = packet.status_code
260
+ unless status_code.name == 'STATUS_MORE_PROCESSING_REQUIRED'
261
+ raise RubySMB::Error::UnexpectedStatusCode, status_code
231
262
  end
232
263
  packet
233
264
  end
@@ -238,7 +269,11 @@ module RubySMB
238
269
  # @return [String] the binary string response from the server
239
270
  def smb2_ntlmssp_negotiate
240
271
  packet = smb2_ntlmssp_negotiate_packet
241
- send_recv(packet)
272
+ response = send_recv(packet)
273
+ if @dialect == '0x0311'
274
+ update_preauth_hash(packet)
275
+ end
276
+ response
242
277
  end
243
278
 
244
279
  # Creates the {RubySMB::SMB2::Packet::SessionSetupRequest} packet
@@ -250,10 +285,7 @@ module RubySMB
250
285
  type1_message = ntlm_client.init_context
251
286
  packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
252
287
  packet.set_type1_blob(type1_message.serialize)
253
- # This Message ID should always be 1, but thanks to Multi-Protocol Negotiation
254
- # the Message ID can be out of sync at this point so we re-synch it here.
255
- packet.smb2_header.message_id = 1
256
- self.smb2_message_id = 2
288
+ packet.security_mode.signing_enabled = 1
257
289
  packet
258
290
  end
259
291
 
@@ -276,7 +308,11 @@ module RubySMB
276
308
  # @return [String] the raw binary response from the server
277
309
  def smb2_ntlmssp_authenticate(type3_message, user_id)
278
310
  packet = smb2_ntlmssp_auth_packet(type3_message, user_id)
279
- send_recv(packet)
311
+ response = send_recv(packet)
312
+ if @dialect == '0x0311'
313
+ update_preauth_hash(packet)
314
+ end
315
+ response
280
316
  end
281
317
 
282
318
  # Generates the {RubySMB::SMB2::Packet::SessionSetupRequest} packet
@@ -289,6 +325,7 @@ module RubySMB
289
325
  packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
290
326
  packet.smb2_header.session_id = session_id
291
327
  packet.set_type3_blob(type3_message.serialize)
328
+ packet.security_mode.signing_enabled = 1
292
329
  packet
293
330
  end
294
331