ruby_smb 1.0.4 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
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