ruby_smb 1.0.4 → 2.0.2

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 (130) 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 +207 -18
  15. data/lib/ruby_smb/client/authentication.rb +27 -8
  16. data/lib/ruby_smb/client/encryption.rb +62 -0
  17. data/lib/ruby_smb/client/negotiation.rb +153 -12
  18. data/lib/ruby_smb/client/signing.rb +19 -0
  19. data/lib/ruby_smb/client/tree_connect.rb +4 -4
  20. data/lib/ruby_smb/client/utils.rb +8 -7
  21. data/lib/ruby_smb/client/winreg.rb +46 -0
  22. data/lib/ruby_smb/crypto.rb +30 -0
  23. data/lib/ruby_smb/dcerpc.rb +38 -0
  24. data/lib/ruby_smb/dcerpc/bind.rb +2 -2
  25. data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
  26. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  27. data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
  28. data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
  29. data/lib/ruby_smb/dcerpc/request.rb +28 -9
  30. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
  31. data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
  32. data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
  33. data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
  34. data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
  35. data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
  36. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
  37. data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
  38. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
  39. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
  40. data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
  41. data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
  42. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
  43. data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
  44. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
  45. data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
  46. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
  47. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
  48. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
  49. data/lib/ruby_smb/dispatcher/socket.rb +4 -3
  50. data/lib/ruby_smb/error.rb +28 -1
  51. data/lib/ruby_smb/smb1/commands.rb +1 -1
  52. data/lib/ruby_smb/smb1/file.rb +6 -4
  53. data/lib/ruby_smb/smb1/packet/empty_packet.rb +4 -2
  54. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
  55. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
  56. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  57. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
  58. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
  59. data/lib/ruby_smb/smb1/pipe.rb +79 -3
  60. data/lib/ruby_smb/smb1/tree.rb +12 -3
  61. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  62. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  63. data/lib/ruby_smb/smb2/file.rb +25 -43
  64. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  65. data/lib/ruby_smb/smb2/packet.rb +2 -0
  66. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  67. data/lib/ruby_smb/smb2/packet/error_packet.rb +9 -4
  68. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
  69. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +50 -4
  70. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  71. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
  72. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
  73. data/lib/ruby_smb/smb2/pipe.rb +77 -3
  74. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  75. data/lib/ruby_smb/smb2/tree.rb +23 -17
  76. data/lib/ruby_smb/version.rb +1 -1
  77. data/ruby_smb.gemspec +5 -3
  78. data/spec/lib/ruby_smb/client_spec.rb +1441 -61
  79. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  80. data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
  81. data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
  82. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
  83. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
  84. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
  85. data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
  86. data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
  87. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
  88. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
  89. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
  90. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
  91. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
  92. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
  93. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
  94. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
  95. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
  96. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
  97. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
  98. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
  99. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
  100. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
  101. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
  102. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
  103. data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
  104. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +2 -2
  105. data/spec/lib/ruby_smb/error_spec.rb +59 -0
  106. data/spec/lib/ruby_smb/smb1/file_spec.rb +9 -1
  107. data/spec/lib/ruby_smb/smb1/packet/empty_packet_spec.rb +10 -0
  108. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  109. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  110. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  111. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  112. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +210 -148
  113. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  114. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  115. data/spec/lib/ruby_smb/smb2/file_spec.rb +86 -62
  116. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  117. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  118. data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +29 -2
  119. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  120. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  121. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  122. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  123. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
  124. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +220 -149
  125. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  126. data/spec/lib/ruby_smb/smb2/tree_spec.rb +53 -8
  127. metadata +187 -81
  128. metadata.gz.sig +0 -0
  129. data/lib/ruby_smb/smb1/dcerpc.rb +0 -72
  130. data/lib/ruby_smb/smb2/dcerpc.rb +0 -75
@@ -0,0 +1,62 @@
1
+ module RubySMB
2
+ class Client
3
+ # Contains the methods for handling encryption / decryption
4
+ module Encryption
5
+ def smb3_encrypt(data)
6
+ unless @client_encryption_key
7
+ case @dialect
8
+ when '0x0300', '0x0302'
9
+ @client_encryption_key = RubySMB::Crypto::KDF.counter_mode(
10
+ @session_key,
11
+ "SMB2AESCCM\x00",
12
+ "ServerIn \x00"
13
+ )
14
+ when '0x0311'
15
+ @client_encryption_key = RubySMB::Crypto::KDF.counter_mode(
16
+ @session_key,
17
+ "SMBC2SCipherKey\x00",
18
+ @preauth_integrity_hash_value
19
+ )
20
+ else
21
+ raise RubySMB::Error::EncryptionError.new('Dialect is incompatible with SMBv3 encryption')
22
+ end
23
+ ######
24
+ # DEBUG
25
+ #puts "Client encryption key = #{@client_encryption_key.each_byte.map {|e| '%02x' % e}.join}"
26
+ ######
27
+ end
28
+
29
+ th = RubySMB::SMB2::Packet::TransformHeader.new(flags: 1, session_id: @session_id)
30
+ th.encrypt(data, @client_encryption_key, algorithm: @encryption_algorithm)
31
+ th
32
+ end
33
+
34
+ def smb3_decrypt(th)
35
+ unless @server_encryption_key
36
+ case @dialect
37
+ when '0x0300', '0x0302'
38
+ @server_encryption_key = RubySMB::Crypto::KDF.counter_mode(
39
+ @session_key,
40
+ "SMB2AESCCM\x00",
41
+ "ServerOut\x00"
42
+ )
43
+ when '0x0311'
44
+ @server_encryption_key = RubySMB::Crypto::KDF.counter_mode(
45
+ @session_key,
46
+ "SMBS2CCipherKey\x00",
47
+ @preauth_integrity_hash_value
48
+ )
49
+ else
50
+ raise RubySMB::Error::EncryptionError.new('Dialect is incompatible with SMBv3 decryption')
51
+ end
52
+ ######
53
+ # DEBUG
54
+ #puts "Server encryption key = #{@server_encryption_key.each_byte.map {|e| '%02x' % e}.join}"
55
+ ######
56
+ end
57
+
58
+ th.decrypt(@server_encryption_key, algorithm: @encryption_algorithm)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -17,10 +17,28 @@ module RubySMB
17
17
  # internally to be able to retrieve the negotiated dialect later on.
18
18
  # This is only valid for SMB1.
19
19
  response_packet.dialects = request_packet.dialects if response_packet.respond_to? :dialects=
20
- parse_negotiate_response(response_packet)
21
- rescue RubySMB::Error::InvalidPacket, Errno::ECONNRESET
22
- error = 'Unable to Negotiate with remote host'
23
- error << ', SMB1 may be disabled' if smb1 && !smb2
20
+ version = parse_negotiate_response(response_packet)
21
+ if @dialect == '0x0311'
22
+ update_preauth_hash(request_packet)
23
+ update_preauth_hash(response_packet)
24
+ end
25
+
26
+ # If the response contains the SMB2 wildcard revision number dialect;
27
+ # it indicates that the server implements SMB 2.1 or future dialect
28
+ # revisions and expects the client to send a subsequent SMB2 Negotiate
29
+ # request to negotiate the actual SMB 2 Protocol revision to be used.
30
+ # The wildcard revision number is sent only in response to a
31
+ # multi-protocol negotiate request with the "SMB 2.???" dialect string.
32
+ if @dialect == '0x02ff'
33
+ self.smb2_message_id += 1
34
+ version = negotiate
35
+ end
36
+ version
37
+ rescue RubySMB::Error::InvalidPacket, Errno::ECONNRESET, RubySMB::Error::CommunicationError => e
38
+ version = request_packet.packet_smb_version
39
+ version = 'SMB3' if version == 'SMB2' && !@smb2 && @smb3
40
+ version = 'SMB2 or SMB3' if version == 'SMB2' && @smb2 && @smb3
41
+ error = "Unable to negotiate #{version} with the remote host: #{e.message}"
24
42
  raise RubySMB::Error::NegotiationFailure, error
25
43
  end
26
44
 
@@ -32,8 +50,8 @@ module RubySMB
32
50
  def negotiate_request
33
51
  if smb1
34
52
  smb1_negotiate_request
35
- elsif smb2
36
- smb2_negotiate_request
53
+ else
54
+ smb2_3_negotiate_request
37
55
  end
38
56
  end
39
57
 
@@ -50,7 +68,7 @@ module RubySMB
50
68
  packet = RubySMB::SMB1::Packet::NegotiateResponseExtended.read raw_data
51
69
  response = packet if packet.valid?
52
70
  end
53
- if smb2 && response.nil?
71
+ if (smb2 || smb3) && response.nil?
54
72
  packet = RubySMB::SMB2::Packet::NegotiateResponse.read raw_data
55
73
  response = packet if packet.valid?
56
74
  end
@@ -95,26 +113,93 @@ module RubySMB
95
113
  when RubySMB::SMB1::Packet::NegotiateResponseExtended
96
114
  self.smb1 = true
97
115
  self.smb2 = false
116
+ self.smb3 = false
98
117
  self.signing_required = packet.parameter_block.security_mode.security_signatures_required == 1
99
118
  self.dialect = packet.negotiated_dialect.to_s
100
119
  # MaxBufferSize is largest message server will receive, measured from start of the SMB header. Subtract 260
101
120
  # for protocol overhead. Then this value can be used for max read/write size without having to factor in
102
121
  # protocol overhead every time.
103
122
  self.server_max_buffer_size = packet.parameter_block.max_buffer_size - 260
123
+ self.negotiated_smb_version = 1
124
+ self.session_encrypt_data = false
104
125
  'SMB1'
105
126
  when RubySMB::SMB2::Packet::NegotiateResponse
106
127
  self.smb1 = false
107
- self.smb2 = true
108
- self.signing_required = packet.security_mode.signing_required == 1
128
+ unless packet.dialect_revision.to_i == 0x02ff
129
+ self.smb2 = packet.dialect_revision.to_i >= 0x0200 && packet.dialect_revision.to_i < 0x0300
130
+ self.smb3 = packet.dialect_revision.to_i >= 0x0300 && packet.dialect_revision.to_i < 0x0400
131
+ end
132
+ self.signing_required = packet.security_mode.signing_required == 1 if self.smb2 || self.smb3
109
133
  self.dialect = "0x%04x" % packet.dialect_revision
110
134
  self.server_max_read_size = packet.max_read_size
111
135
  self.server_max_write_size = packet.max_write_size
112
136
  self.server_max_transact_size = packet.max_transact_size
113
137
  # This value is used in SMB1 only but calculate a valid value anyway
114
138
  self.server_max_buffer_size = [self.server_max_read_size, self.server_max_write_size, self.server_max_transact_size].min
115
- 'SMB2'
139
+ self.negotiated_smb_version = self.smb2 ? 2 : 3
140
+ self.server_guid = packet.server_guid
141
+ self.server_start_time = packet.server_start_time.to_time if packet.server_start_time != 0
142
+ self.server_system_time = packet.system_time.to_time if packet.system_time != 0
143
+ case self.dialect
144
+ when '0x02ff'
145
+ when '0x0300', '0x0302'
146
+ if packet&.capabilities&.encryption == 1
147
+ self.encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM]
148
+ end
149
+ self.session_encrypt_data = self.session_encrypt_data && !self.encryption_algorithm.nil?
150
+ when '0x0311'
151
+ parse_smb3_capabilities(packet)
152
+ self.session_encrypt_data = self.session_encrypt_data && !self.encryption_algorithm.nil?
153
+ else
154
+ self.session_encrypt_data = false
155
+ end
156
+ return "SMB#{self.negotiated_smb_version}"
157
+ else
158
+ error = 'Unable to negotiate with remote host'
159
+ if packet.status_code == WindowsError::NTStatus::STATUS_NOT_SUPPORTED
160
+ error << ", SMB2" if @smb2
161
+ error << ", SMB3" if @smb3
162
+ error << ' not supported'
163
+ end
164
+ raise RubySMB::Error::NegotiationFailure, error
165
+ end
166
+ end
167
+
168
+ def parse_smb3_capabilities(response_packet)
169
+ nc = response_packet.find_negotiate_context(
170
+ RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
171
+ )
172
+ @preauth_integrity_hash_algorithm = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[nc&.data&.hash_algorithms&.first]
173
+ unless @preauth_integrity_hash_algorithm
174
+ raise RubySMB::Error::EncryptionError.new(
175
+ 'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
176
+ )
177
+ end
178
+ # Set the encryption the client will use, prioritizing AES_128_GCM over AES_128_CCM
179
+ nc = response_packet.find_negotiate_context(
180
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
181
+ )
182
+ @server_encryption_algorithms = nc&.data&.ciphers&.to_ary
183
+ if @server_encryption_algorithms.nil? || @server_encryption_algorithms.empty?
184
+ raise RubySMB::Error::EncryptionError.new(
185
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
186
+ )
187
+ end
188
+ if @server_encryption_algorithms.include?(RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM)
189
+ @encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM]
190
+ else
191
+ @encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[@server_encryption_algorithms.first]
192
+ end
193
+ unless @encryption_algorithm
194
+ raise RubySMB::Error::EncryptionError.new(
195
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
196
+ )
116
197
  end
117
198
 
199
+ nc = response_packet.find_negotiate_context(
200
+ RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
201
+ )
202
+ @server_compression_algorithms = nc&.data&.compression_algorithms&.to_ary || []
118
203
  end
119
204
 
120
205
  # Create a {RubySMB::SMB1::Packet::NegotiateRequest} packet with the
@@ -127,10 +212,17 @@ module RubySMB
127
212
  # while being guaranteed to work with any modern Windows system. We can get more sophisticated
128
213
  # with switching this on and off at a later date if the need arises.
129
214
  packet.smb_header.flags2.extended_security = 1
215
+ # Recent Mac OS X requires the unicode flag to be set on the Negotiate
216
+ # SMB Header request, even if this packet does not contain string fields
217
+ # (see Flags2 SMB_FLAGS2_UNICODE definition in "2.2.3.1 The SMB Header"
218
+ # documentation:
219
+ # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/69a29f73-de0c-45a6-a1aa-8ceeea42217f
220
+ packet.smb_header.flags2.unicode = 1
130
221
  # There is no real good reason to ever send an SMB1 Negotiate packet
131
222
  # to Negotiate strictly SMB2, but the protocol WILL support it
132
223
  packet.add_dialect(SMB1_DIALECT_SMB1_DEFAULT) if smb1
133
224
  packet.add_dialect(SMB1_DIALECT_SMB2_DEFAULT) if smb2
225
+ packet.add_dialect(SMB1_DIALECT_SMB2_WILDCARD) if smb2 || smb3
134
226
  packet
135
227
  end
136
228
 
@@ -139,11 +231,60 @@ module RubySMB
139
231
  # may want to communicate over SMB1
140
232
  #
141
233
  # @ return [RubySMB::SMB2::Packet::NegotiateRequest] a completed SMB2 Negotiate Request packet
142
- def smb2_negotiate_request
234
+ def smb2_3_negotiate_request
143
235
  packet = RubySMB::SMB2::Packet::NegotiateRequest.new
144
236
  packet.security_mode.signing_enabled = 1
145
- packet.add_dialect(SMB2_DIALECT_DEFAULT)
146
237
  packet.client_guid = SecureRandom.random_bytes(16)
238
+ packet.set_dialects(SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)}) if smb2
239
+ packet = add_smb3_to_negotiate_request(packet) if smb3
240
+ packet
241
+ end
242
+
243
+ # This adds SMBv3 specific information: SMBv3 supported dialects,
244
+ # encryption capability, Negotiate Contexts if the dialect requires them
245
+ #
246
+ # @param packet [RubySMB::SMB2::Packet::NegotiateRequest] the NegotiateRequest
247
+ # to add SMB3 specific info to
248
+ # @param dialects [Array<String>] the dialects to negotiate. This must be
249
+ # an array of strings. Default is SMB3_DIALECT_DEFAULT
250
+ # @return [RubySMB::SMB2::Packet::NegotiateRequest] a completed SMB3 Negotiate Request packet
251
+ # @raise [ArgumentError] if dialects is not an array of strings
252
+ def add_smb3_to_negotiate_request(packet, dialects = SMB3_DIALECT_DEFAULT)
253
+ dialects.each do |dialect|
254
+ raise ArgumentError, 'Must be an array of strings' unless dialect.is_a? String
255
+ packet.add_dialect(dialect.to_i(16))
256
+ end
257
+ packet.capabilities.encryption = 1
258
+
259
+ if packet.dialects.include?(0x0311)
260
+ nc = RubySMB::SMB2::NegotiateContext.new(
261
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
262
+ )
263
+ nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
264
+ nc.data.salt = SecureRandom.random_bytes(32)
265
+ packet.add_negotiate_context(nc)
266
+
267
+ @preauth_integrity_hash_value = "\x00" * 64
268
+ nc = RubySMB::SMB2::NegotiateContext.new(
269
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
270
+ )
271
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
272
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
273
+ packet.add_negotiate_context(nc)
274
+
275
+ nc = RubySMB::SMB2::NegotiateContext.new(
276
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
277
+ )
278
+ # Adding all possible compression algorithm even if we don't support
279
+ # them yet. This will force the server to disclose the support
280
+ # algorithms in the repsonse.
281
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
282
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
283
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
284
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
285
+ packet.add_negotiate_context(nc)
286
+ end
287
+
147
288
  packet
148
289
  end
149
290
  end
@@ -40,6 +40,25 @@ module RubySMB
40
40
  end
41
41
  packet
42
42
  end
43
+
44
+ def smb3_sign(packet)
45
+ if !session_key.empty? && (signing_required || packet.is_a?(RubySMB::SMB2::Packet::TreeConnectRequest))
46
+ case @dialect
47
+ when '0x0300', '0x0302'
48
+ signing_key = RubySMB::Crypto::KDF.counter_mode(@session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
49
+ when '0x0311'
50
+ signing_key = RubySMB::Crypto::KDF.counter_mode(@session_key, "SMBSigningKey\x00", @preauth_integrity_hash_value)
51
+ else
52
+ raise RubySMB::Error::SigningError.new('Dialect is incompatible with SMBv3 signing')
53
+ end
54
+
55
+ packet.smb2_header.flags.signed = 1
56
+ packet.smb2_header.signature = "\x00" * 16
57
+ hmac = OpenSSL::CMAC.digest('AES', signing_key, packet.to_binary_s)
58
+ packet.smb2_header.signature = hmac[0, 16]
59
+ end
60
+ packet
61
+ end
43
62
  end
44
63
  end
45
64
  end
@@ -38,7 +38,7 @@ module RubySMB
38
38
  )
39
39
  end
40
40
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
41
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
41
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
42
42
  end
43
43
  RubySMB::SMB1::Tree.new(client: self, share: share, response: response)
44
44
  end
@@ -55,7 +55,7 @@ module RubySMB
55
55
  def smb2_tree_connect(share)
56
56
  request = RubySMB::SMB2::Packet::TreeConnectRequest.new
57
57
  request.smb2_header.tree_id = 65_535
58
- request.encode_path(share)
58
+ request.path = share
59
59
  raw_response = send_recv(request)
60
60
  response = RubySMB::SMB2::Packet::TreeConnectResponse.read(raw_response)
61
61
  smb2_tree_from_response(share, response)
@@ -78,9 +78,9 @@ module RubySMB
78
78
  )
79
79
  end
80
80
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
81
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
81
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
82
82
  end
83
- RubySMB::SMB2::Tree.new(client: self, share: share, response: response)
83
+ RubySMB::SMB2::Tree.new(client: self, share: share, response: response, encrypt: response.share_flags.encrypt == 1)
84
84
  end
85
85
  end
86
86
  end
@@ -40,27 +40,28 @@ module RubySMB
40
40
  end
41
41
 
42
42
  #Writes data to an open file handle
43
- def write(file_id, offset = 0, data = '', do_recv = true)
43
+ def write(file_id = last_file_id, offset = 0, data = '', do_recv = true)
44
44
  @open_files[file_id].send_recv_write(data: data, offset: offset)
45
45
  end
46
46
 
47
- def read(file_id, offset = 0, length = last_file.size)
47
+ def read(file_id = last_file_id, offset = 0, length = last_file.size, do_recv = true)
48
48
  data = @open_files[file_id].send_recv_read(read_length: length, offset: offset)
49
49
  data.bytes
50
50
  end
51
51
 
52
- def delete(path)
53
- file = last_tree.open_file(filename: path.sub(/^\\/, ''), delete: true)
52
+ def delete(path, tree_id = last_tree_id, do_recv = true)
53
+ tree = @tree_connects.detect{ |tree| tree.id == tree_id }
54
+ file = tree.open_file(filename: path.sub(/^\\/, ''), delete: true)
54
55
  file.delete
55
56
  file.close
56
57
  end
57
58
 
58
- def close(file_id, tree_id)
59
+ def close(file_id = last_file_id, tree_id = last_tree_id, do_recv = true)
59
60
  @open_files[file_id].close
60
61
  end
61
62
 
62
- def tree_disconnect(share)
63
- @tree_connects.detect{|tree| tree.id == share }.disconnect!
63
+ def tree_disconnect(tree_id = last_tree_id, do_recv = true)
64
+ @tree_connects.detect{|tree| tree.id == tree_id }.disconnect!
64
65
  end
65
66
 
66
67
  def native_os
@@ -0,0 +1,46 @@
1
+ module RubySMB
2
+ class Client
3
+ module Winreg
4
+
5
+ def connect_to_winreg(host)
6
+ share = "\\\\#{host}\\IPC$"
7
+ tree = @tree_connects.find {|tree| tree.share == share}
8
+ tree = tree_connect(share) unless tree
9
+ named_pipe = tree.open_file(filename: "winreg", write: true, read: true)
10
+ if block_given?
11
+ res = yield named_pipe
12
+ named_pipe.close
13
+ res
14
+ else
15
+ named_pipe
16
+ end
17
+ end
18
+
19
+ def has_registry_key?(host, key)
20
+ connect_to_winreg(host) do |named_pipe|
21
+ named_pipe.has_registry_key?(key)
22
+ end
23
+ end
24
+
25
+ def read_registry_key_value(host, key, value_name)
26
+ connect_to_winreg(host) do |named_pipe|
27
+ named_pipe.read_registry_key_value(key, value_name)
28
+ end
29
+ end
30
+
31
+ def enum_registry_key(host, key)
32
+ connect_to_winreg(host) do |named_pipe|
33
+ named_pipe.enum_registry_key(key)
34
+ end
35
+ end
36
+
37
+ def enum_registry_values(host, key)
38
+ connect_to_winreg(host) do |named_pipe|
39
+ named_pipe.enum_registry_values(key)
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end
46
+
@@ -0,0 +1,30 @@
1
+ module RubySMB
2
+ module Crypto
3
+ module KDF
4
+ def self.counter_mode(ki, label, context, length: 128)
5
+ digest = OpenSSL::Digest.new('SHA256')
6
+ r = 32
7
+
8
+ n = length / 256
9
+ n = 1 if n == 0
10
+
11
+ raise ArgumentError if n > 2**r - 1
12
+ result = ""
13
+
14
+ n.times do |i|
15
+ input = [i + 1].pack('L>')
16
+ input << label
17
+ input << "\x00"
18
+ input << context
19
+ input << [length].pack('L>')
20
+ k = OpenSSL::HMAC.digest(digest, ki, input)
21
+ result << k
22
+ end
23
+
24
+ return result[0...(length / 8)]
25
+ rescue OpenSSL::OpenSSLError => e
26
+ raise RubySMB::Error::EncryptionError, "Crypto::KDF.counter_mode OpenSSL error: #{e.message}"
27
+ end
28
+ end
29
+ end
30
+ end