ruby_smb 3.1.7 → 3.2.1

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.
@@ -247,12 +247,22 @@ module RubySMB::Dcerpc::Ndr
247
247
 
248
248
  def do_read(io)
249
249
  if is_a?(ConfPlugin) && should_process_max_count?
250
- set_max_count(io.readbytes(4).unpack('L<').first)
250
+ max_count = io.readbytes(4).unpack1('L<')
251
+ BinData.trace_message do |tracer|
252
+ tracer.trace_obj("#{debug_name}.max_count", max_count.to_s)
253
+ end
254
+ set_max_count(max_count)
251
255
  end
252
256
 
253
257
  if is_a?(VarPlugin)
254
258
  @offset = io.readbytes(4).unpack('L<').first
259
+ BinData.trace_message do |tracer|
260
+ tracer.trace_obj("#{debug_name}.offset", @offset.to_s)
261
+ end
255
262
  @actual_count = @read_until_index = io.readbytes(4).unpack('L<').first
263
+ BinData.trace_message do |tracer|
264
+ tracer.trace_obj("#{debug_name}.actual_count", @actual_count.to_s)
265
+ end
256
266
  end
257
267
 
258
268
  if has_elements_to_read?
@@ -523,7 +533,11 @@ module RubySMB::Dcerpc::Ndr
523
533
 
524
534
  def do_read(io)
525
535
  if should_process_max_count?
526
- set_max_count(io.readbytes(4).unpack('L<').first)
536
+ max_count = io.readbytes(4).unpack1('L<')
537
+ BinData.trace_message do |tracer|
538
+ tracer.trace_obj("#{debug_name}.max_count", max_count.to_s)
539
+ end
540
+ set_max_count(max_count)
527
541
  end
528
542
  super
529
543
  end
@@ -582,7 +596,13 @@ module RubySMB::Dcerpc::Ndr
582
596
 
583
597
  def do_read(io)
584
598
  @offset = io.readbytes(4).unpack('L<').first
599
+ BinData.trace_message do |tracer|
600
+ tracer.trace_obj("#{debug_name}.offset", @offset.to_s)
601
+ end
585
602
  @actual_count = io.readbytes(4).unpack('L<').first
603
+ BinData.trace_message do |tracer|
604
+ tracer.trace_obj("#{debug_name}.actual_count", @actual_count.to_s)
605
+ end
586
606
  super if @actual_count > 0
587
607
  end
588
608
 
@@ -702,7 +722,11 @@ module RubySMB::Dcerpc::Ndr
702
722
 
703
723
  def do_read(io)
704
724
  if should_process_max_count?
705
- set_max_count(io.readbytes(4).unpack('L<').first)
725
+ max_count = io.readbytes(4).unpack1('L<')
726
+ BinData.trace_message do |tracer|
727
+ tracer.trace_obj("#{debug_name}.max_count", max_count.to_s)
728
+ end
729
+ set_max_count(max_count)
706
730
 
707
731
  # Align the structure according to the alignment rules for the structure
708
732
  if respond_to?(:referent_bytes_align)
@@ -1034,6 +1058,9 @@ module RubySMB::Dcerpc::Ndr
1034
1058
  end
1035
1059
  else
1036
1060
  @ref_id = io.readbytes(4).unpack('L<').first
1061
+ BinData.trace_message do |tracer|
1062
+ tracer.trace_obj("#{debug_name}.ref_id", @ref_id.to_s)
1063
+ end
1037
1064
  parent_obj = nil
1038
1065
  if parent&.is_a?(ConstructedTypePlugin)
1039
1066
  parent_obj = parent.get_top_level_constructed_type
@@ -1208,14 +1235,50 @@ module RubySMB::Dcerpc::Ndr
1208
1235
  extend PointerClassPlugin
1209
1236
  end
1210
1237
 
1238
+
1239
+ # ArrayPtr definitions:
1240
+ # If the type is Ndr*ArrayPtr, it's a ConfVarArray (Uni-dimensional Conformant-varying Array)
1241
+ # If the type is Ndr*ConfArrayPtr, it's a ConfArray (Uni-dimensional Conformant Array)
1211
1242
  class NdrByteArrayPtr < NdrConfVarArray
1212
1243
  default_parameters type: :ndr_uint8
1213
1244
  extend PointerClassPlugin
1245
+
1246
+ def assign(val)
1247
+ val = val.bytes if val.is_a?(String)
1248
+ super(val.to_ary)
1249
+ end
1214
1250
  end
1215
1251
 
1216
- class NdrUint16ArrayPtr < NdrConfVarArray
1217
- default_parameters type: :ndr_uint16
1252
+ class NdrByteConfArrayPtr < NdrConfArray
1253
+ default_parameters type: :ndr_uint8
1218
1254
  extend PointerClassPlugin
1255
+
1256
+ def assign(val)
1257
+ val = val.bytes if val.is_a?(String)
1258
+ super(val.to_ary)
1259
+ end
1260
+ end
1261
+
1262
+ %i[ Uint8 Uint16 Uint32 Uint64 ].each do |klass|
1263
+ new_klass_name = "Ndr#{klass}ArrayPtr"
1264
+ unless self.const_defined?(new_klass_name)
1265
+ new_klass = Class.new(NdrConfVarArray) do
1266
+ default_parameters type: "ndr_#{klass}".downcase.to_sym
1267
+ extend PointerClassPlugin
1268
+ end
1269
+ self.const_set(new_klass_name, new_klass)
1270
+ BinData::RegisteredClasses.register(new_klass_name, new_klass)
1271
+ end
1272
+
1273
+ new_klass_name = "Ndr#{klass}ConfArrayPtr"
1274
+ unless self.const_defined?(new_klass_name)
1275
+ new_klass = Class.new(NdrConfArray) do
1276
+ default_parameters type: "ndr_#{klass}".downcase.to_sym
1277
+ extend PointerClassPlugin
1278
+ end
1279
+ self.const_set(new_klass_name, new_klass)
1280
+ BinData::RegisteredClasses.register(new_klass_name, new_klass)
1281
+ end
1219
1282
  end
1220
1283
 
1221
1284
  class NdrFileTimePtr < NdrFileTime
@@ -97,6 +97,10 @@ module RubySMB
97
97
  netr_dfs_remove_std_root_request Dfsnm::NETR_DFS_REMOVE_STD_ROOT
98
98
  string :default
99
99
  end
100
+ choice 'Icpr', selection: -> { opnum } do
101
+ cert_server_request_request Icpr::CERT_SERVER_REQUEST
102
+ string :default
103
+ end
100
104
  string :default
101
105
  end
102
106
  string :auth_pad,
@@ -320,7 +320,7 @@ module RubySMB
320
320
 
321
321
  def self.encrypt_password(password, key)
322
322
  # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/5fe3c4c4-e71b-440d-b2fd-8448bfaf6e04
323
- password = password.encode('UTF-16LE').force_encoding('ASCII-8bit')
323
+ password = RubySMB::Utils.safe_encode(password, 'UTF-16LE').force_encoding('ASCII-8bit')
324
324
  buffer = password.rjust(512, "\x00") + [ password.length ].pack('V')
325
325
  salt = SecureRandom.random_bytes(16)
326
326
  key = OpenSSL::Digest::MD5.new(salt + key).digest
@@ -47,6 +47,7 @@ module RubySMB
47
47
  require 'ruby_smb/dcerpc/drsr'
48
48
  require 'ruby_smb/dcerpc/sec_trailer'
49
49
  require 'ruby_smb/dcerpc/dfsnm'
50
+ require 'ruby_smb/dcerpc/icpr'
50
51
  require 'ruby_smb/dcerpc/request'
51
52
  require 'ruby_smb/dcerpc/response'
52
53
  require 'ruby_smb/dcerpc/rpc_auth3'
@@ -55,25 +56,50 @@ module RubySMB
55
56
  require 'ruby_smb/dcerpc/print_system'
56
57
  require 'ruby_smb/dcerpc/encrypting_file_system'
57
58
 
58
- # Bind to the remote server interface endpoint.
59
+ # Bind to the remote server interface endpoint. It takes care of adding
60
+ # the necessary authentication verifier if `:auth_level` is set to
61
+ # anything different than RPC_C_AUTHN_LEVEL_NONE
59
62
  #
60
- # @param options [Hash] the options to pass to the Bind request packet. At least, :endpoint must but provided with an existing Dcerpc class
63
+ # @param [Hash] options
64
+ # @option options [Module] :endpoint the endpoint to bind to. This must be a Dcerpc
65
+ # class with UUID, VER_MAJOR and VER_MINOR constants defined.
66
+ # @option options [Integer] :auth_level the authentication level
67
+ # @option options [Integer] :auth_type the authentication type
61
68
  # @return [BindAck] the BindAck response packet
62
69
  # @raise [Error::InvalidPacket] if an invalid packet is received
63
70
  # @raise [Error::BindError] if the response is not a BindAck packet or if the Bind result code is not ACCEPTANCE
71
+ # @raise [ArgumentError] if `:auth_type` is unknown
72
+ # @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
64
73
  def bind(options={})
74
+ @call_id ||= 1
65
75
  bind_req = Bind.new(options)
66
- write(data: bind_req.to_binary_s)
67
- @size = 1024
68
- dcerpc_raw_response = read()
69
- begin
70
- dcerpc_response = BindAck.read(dcerpc_raw_response)
71
- rescue IOError
72
- raise Error::InvalidPacket, "Error reading the DCERPC response"
76
+ bind_req.pdu_header.call_id = @call_id
77
+
78
+ if options[:auth_level] && options[:auth_level] != RPC_C_AUTHN_LEVEL_NONE
79
+ case options[:auth_type]
80
+ when RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT
81
+ @ctx_id = 0
82
+ @auth_ctx_id_base = rand(0xFFFFFFFF)
83
+ raise ArgumentError, "NTLM Client not initialized. Username and password must be provided" unless @ntlm_client
84
+ type1_message = @ntlm_client.init_context
85
+ auth = type1_message.serialize
86
+ when RPC_C_AUTHN_GSS_KERBEROS, RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE
87
+ when RPC_C_AUTHN_GSS_KERBEROS, RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_GSS_SCHANNEL
88
+ # TODO
89
+ raise NotImplementedError
90
+ else
91
+ raise ArgumentError, "Unsupported Auth Type: #{options[:auth_type]}"
92
+ end
93
+ add_auth_verifier(bind_req, auth, options[:auth_type], options[:auth_level])
73
94
  end
74
- unless dcerpc_response.pdu_header.ptype == PTypes::BIND_ACK
75
- raise Error::BindError, "Not a BindAck packet"
95
+
96
+ send_packet(bind_req)
97
+ begin
98
+ dcerpc_response = recv_struct(BindAck)
99
+ rescue Error::InvalidPacket
100
+ raise Error::BindError # raise the more context-specific BindError
76
101
  end
102
+ # TODO: see if BindNack response should be handled too
77
103
 
78
104
  res_list = dcerpc_response.p_result_list
79
105
  if res_list.n_results == 0 ||
@@ -81,9 +107,216 @@ module RubySMB
81
107
  raise Error::BindError,
82
108
  "Bind Failed (Result: #{res_list.p_results[0].result}, Reason: #{res_list.p_results[0].reason})"
83
109
  end
84
- @tree.client.max_buffer_size = dcerpc_response.max_xmit_frag
110
+ self.max_buffer_size = dcerpc_response.max_xmit_frag
111
+ @call_id = dcerpc_response.pdu_header.call_id
112
+
113
+ if options[:auth_level] && options[:auth_level] != RPC_C_AUTHN_LEVEL_NONE
114
+ # The number of legs needed to build the security context is defined
115
+ # by the security provider
116
+ # (see [2.2.1.1.7 Security Providers](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/d4097450-c62f-484b-872f-ddf59a7a0d36))
117
+ case options[:auth_type]
118
+ when RPC_C_AUTHN_WINNT
119
+ send_auth3(dcerpc_response, options[:auth_type], options[:auth_level])
120
+ when RPC_C_AUTHN_GSS_KERBEROS, RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE
121
+ # TODO
122
+ raise NotImplementedError
123
+ end
124
+ end
125
+
85
126
  dcerpc_response
86
127
  end
87
128
 
129
+ def max_buffer_size=(value)
130
+ @tree.client.max_buffer_size = value
131
+ end
132
+
133
+ # Receive a packet from the remote host and parse it according to `struct`
134
+ #
135
+ # @param struct [Class] the structure class to parse the response with
136
+ def recv_struct(struct)
137
+ raw_response = read
138
+ begin
139
+ response = struct.read(raw_response)
140
+ rescue IOError
141
+ raise Error::InvalidPacket, "Error reading the #{struct} response"
142
+ end
143
+ unless response.pdu_header.ptype == struct::PTYPE
144
+ raise Error::InvalidPacket, "Not a #{struct} packet"
145
+ end
146
+
147
+ response
148
+ end
149
+
150
+ # Send a packet to the remote host
151
+ #
152
+ # @param packet [BinData::Record] the packet to send
153
+ # @raise [Error::CommunicationError] if socket-related error occurs
154
+ def send_packet(packet)
155
+ write(data: packet.to_binary_s)
156
+ nil
157
+ end
158
+
159
+ # Add the authentication verifier to a Request packet. This includes a
160
+ # sec trailer and the signature of the packet. This also encrypts the
161
+ # Request stub if privacy is required (`:auth_level` option is
162
+ # RPC_C_AUTHN_LEVEL_PKT_PRIVACY).
163
+ #
164
+ # @param dcerpc_req [Request] the Request packet to be updated
165
+ # @param opts [Hash] the authenticaiton options: `:auth_type` and `:auth_level`
166
+ # @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
167
+ # @raise [ArgumentError] if `:auth_type` is unknown
168
+ def set_integrity_privacy(dcerpc_req, auth_level:, auth_type:)
169
+ dcerpc_req.sec_trailer = {
170
+ auth_type: auth_type,
171
+ auth_level: auth_level,
172
+ auth_context_id: @ctx_id + @auth_ctx_id_base
173
+ }
174
+ dcerpc_req.auth_value = ' ' * 16
175
+ dcerpc_req.pdu_header.auth_length = 16
176
+
177
+ data_to_sign = plain_stub = dcerpc_req.stub.to_binary_s + dcerpc_req.auth_pad.to_binary_s
178
+ if @ntlm_client.flags & NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] != 0
179
+ data_to_sign = dcerpc_req.to_binary_s[0..-(dcerpc_req.pdu_header.auth_length + 1)]
180
+ end
181
+
182
+ encrypted_stub = ''
183
+ if auth_level == RPC_C_AUTHN_LEVEL_PKT_PRIVACY
184
+ case auth_type
185
+ when RPC_C_AUTHN_NONE
186
+ when RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT
187
+ encrypted_stub = @ntlm_client.session.seal_message(plain_stub)
188
+ when RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_GSS_SCHANNEL, RPC_C_AUTHN_GSS_KERBEROS
189
+ # TODO
190
+ raise NotImplementedError
191
+ else
192
+ raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
193
+ end
194
+ end
195
+
196
+ signature = @ntlm_client.session.sign_message(data_to_sign)
197
+
198
+ unless encrypted_stub.empty?
199
+ pad_length = dcerpc_req.sec_trailer.auth_pad_length.to_i
200
+ dcerpc_req.enable_encrypted_stub
201
+ dcerpc_req.stub = encrypted_stub[0..-(pad_length + 1)]
202
+ dcerpc_req.auth_pad = encrypted_stub[-(pad_length)..-1]
203
+ end
204
+ dcerpc_req.auth_value = signature
205
+ dcerpc_req.pdu_header.auth_length = signature.size
206
+ end
207
+
208
+ # Process the security context received in a response. It decrypts the
209
+ # encrypted stub if `:auth_level` is set to anything different than
210
+ # RPC_C_AUTHN_LEVEL_PKT_PRIVACY. It also checks the packet signature and
211
+ # raises an InvalidPacket error if it fails. Note that the exception is
212
+ # disabled by default and can be enabled with the
213
+ # `:raise_signature_error` option
214
+ #
215
+ # @param dcerpc_response [Response] the Response packet
216
+ # containing the security context to process
217
+ # @param opts [Hash] the authenticaiton options: `:auth_type` and
218
+ # `:auth_level`. To enable errors when signature check fails, set the
219
+ # `:raise_signature_error` option to true
220
+ # @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
221
+ # @raise [Error::CommunicationError] if socket-related error occurs
222
+ def handle_integrity_privacy(dcerpc_response, auth_level:, auth_type:, raise_signature_error: false)
223
+ decrypted_stub = ''
224
+ if auth_level == RPC_C_AUTHN_LEVEL_PKT_PRIVACY
225
+ encrypted_stub = dcerpc_response.stub.to_binary_s + dcerpc_response.auth_pad.to_binary_s
226
+ case auth_type
227
+ when RPC_C_AUTHN_NONE
228
+ when RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT
229
+ decrypted_stub = @ntlm_client.session.unseal_message(encrypted_stub)
230
+ when RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_GSS_SCHANNEL, RPC_C_AUTHN_GSS_KERBEROS
231
+ # TODO
232
+ raise NotImplementedError
233
+ else
234
+ raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
235
+ end
236
+ end
237
+
238
+ unless decrypted_stub.empty?
239
+ pad_length = dcerpc_response.sec_trailer.auth_pad_length.to_i
240
+ dcerpc_response.stub = decrypted_stub[0..-(pad_length + 1)]
241
+ dcerpc_response.auth_pad = decrypted_stub[-(pad_length)..-1]
242
+ end
243
+
244
+ signature = dcerpc_response.auth_value
245
+ data_to_check = dcerpc_response.stub.to_binary_s
246
+ if @ntlm_client.flags & NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] != 0
247
+ data_to_check = dcerpc_response.to_binary_s[0..-(dcerpc_response.pdu_header.auth_length + 1)]
248
+ end
249
+ unless @ntlm_client.session.verify_signature(signature, data_to_check)
250
+ if raise_signature_error
251
+ raise Error::InvalidPacket.new(
252
+ "Wrong packet signature received (set `raise_signature_error` to false to ignore)"
253
+ )
254
+ end
255
+ end
256
+
257
+ @call_id += 1
258
+
259
+ nil
260
+ end
261
+
262
+ # Add the authentication verifier to the packet. This includes a sec
263
+ # trailer and the actual authentication data.
264
+ #
265
+ # @param req [BinData::Record] the request to be updated
266
+ # @param auth [String] the authentication data
267
+ # @param auth_type [Integer] the authentication type
268
+ # @param auth_level [Integer] the authentication level
269
+ def add_auth_verifier(req, auth, auth_type, auth_level)
270
+ req.sec_trailer = {
271
+ auth_type: auth_type,
272
+ auth_level: auth_level,
273
+ auth_context_id: @ctx_id + @auth_ctx_id_base
274
+ }
275
+ req.auth_value = auth
276
+ req.pdu_header.auth_length = auth.length
277
+
278
+ nil
279
+ end
280
+
281
+ def process_ntlm_type2(type2_message)
282
+ ntlmssp_offset = type2_message.index('NTLMSSP')
283
+ type2_blob = type2_message.slice(ntlmssp_offset..-1)
284
+ type2_b64_message = [type2_blob].pack('m')
285
+ type3_message = @ntlm_client.init_context(type2_b64_message)
286
+ auth3 = type3_message.serialize
287
+
288
+ @session_key = @ntlm_client.session_key
289
+ auth3
290
+ end
291
+
292
+ # Send a rpc_auth3 PDU that ends the authentication handshake.
293
+ #
294
+ # @param response [BindAck] the BindAck response packet
295
+ # @param auth_type [Integer] the authentication type
296
+ # @param auth_level [Integer] the authentication level
297
+ # @raise [ArgumentError] if `:auth_type` is unknown
298
+ # @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
299
+ def send_auth3(response, auth_type, auth_level)
300
+ case auth_type
301
+ when RPC_C_AUTHN_NONE
302
+ when RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT
303
+ auth3 = process_ntlm_type2(response.auth_value)
304
+ when RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_GSS_SCHANNEL, RPC_C_AUTHN_GSS_KERBEROS
305
+ # TODO
306
+ raise NotImplementedError
307
+ else
308
+ raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
309
+ end
310
+
311
+ rpc_auth3 = RpcAuth3.new
312
+ add_auth_verifier(rpc_auth3, auth3, auth_type, auth_level)
313
+ rpc_auth3.pdu_header.call_id = @call_id
314
+
315
+ # The server should not respond
316
+ send_packet(rpc_auth3)
317
+ @call_id += 1
318
+
319
+ nil
320
+ end
88
321
  end
89
322
  end
@@ -73,6 +73,10 @@ module RubySMB
73
73
  module Mixin
74
74
  attr_reader :status_code
75
75
 
76
+ def status_name
77
+ @status_code.name
78
+ end
79
+
76
80
  private
77
81
 
78
82
  def status_code=(status_code)
@@ -82,7 +86,7 @@ module RubySMB
82
86
  when Integer
83
87
  @status_code = WindowsError::NTStatus.find_by_retval(status_code).first
84
88
  if @status_code.nil?
85
- @status_code = WindowsError::ErrorCode.new("0x#{status_code.to_s(16).rjust(8, '0')}", status_code, "Unknown status: 0x#{status_code.to_s(16)}")
89
+ @status_code = WindowsError::ErrorCode.new("0x#{status_code.to_s(16).rjust(8, '0')}", status_code, "Unknown status: 0x#{status_code.to_s(16).rjust(8, '0')}")
86
90
  end
87
91
  else
88
92
  raise ArgumentError, "Status code must be a WindowsError::ErrorCode or an Integer, got #{status_code.class}"
@@ -18,6 +18,7 @@ module RubySMB
18
18
  end
19
19
 
20
20
  class Authenticator < Authenticator::Base
21
+
21
22
  def reset!
22
23
  super
23
24
  @server_challenge = nil
@@ -141,7 +142,10 @@ module RubySMB
141
142
  case type3_msg.ntlm_version
142
143
  when :ntlmv1
143
144
  my_ntlm_response = Net::NTLM::ntlm_response(
144
- ntlm_hash: Net::NTLM::ntlm_hash(account.password.encode('UTF-16LE'), unicode: true),
145
+ ntlm_hash: Net::NTLM::ntlm_hash(
146
+ RubySMB::Utils.safe_encode(account.password, 'UTF-16LE'),
147
+ unicode: true
148
+ ),
145
149
  challenge: @server_challenge
146
150
  )
147
151
  matches = my_ntlm_response == type3_msg.ntlm_response
@@ -151,9 +155,9 @@ module RubySMB
151
155
  their_blob = type3_msg.ntlm_response[digest.digest_length..-1]
152
156
 
153
157
  ntlmv2_hash = Net::NTLM.ntlmv2_hash(
154
- account.username.encode('UTF-16LE'),
155
- account.password.encode('UTF-16LE'),
156
- type3_msg.domain.encode('UTF-16LE'), # don't use the account domain because of the special '.' value
158
+ RubySMB::Utils.safe_encode(account.username, 'UTF-16LE'),
159
+ RubySMB::Utils.safe_encode(account.password, 'UTF-16LE'),
160
+ RubySMB::Utils.safe_encode(type3_msg.domain, 'UTF-16LE'), # don't use the account domain because of the special '.' value
157
161
  {client_challenge: their_blob[16...24], unicode: true}
158
162
  )
159
163
 
@@ -305,7 +309,10 @@ module RubySMB
305
309
  username = username.downcase
306
310
  domain = @default_domain if domain.nil? || domain == '.'.encode(domain.encoding)
307
311
  domain = domain.downcase
308
- @accounts.find { |account| account.username.encode(username.encoding).downcase == username && account.domain.encode(domain.encoding).downcase == domain }
312
+ @accounts.find do |account|
313
+ RubySMB::Utils.safe_encode(account.username, username.encoding).downcase == username &&
314
+ RubySMB::Utils.safe_encode(account.domain, domain.encoding).downcase == domain
315
+ end
309
316
  end
310
317
 
311
318
  #
@@ -51,6 +51,10 @@ module RubySMB::NTLM
51
51
  !challenge_message.has_flag?(:UNICODE) && challenge_message.has_flag?(:OEM)
52
52
  end
53
53
 
54
+ def ntlmv2_hash
55
+ @ntlmv2_hash ||= RubySMB::NTLM.ntlmv2_hash(username, password, domain, {:client_challenge => client_challenge, :unicode => !use_oem_strings?})
56
+ end
57
+
54
58
  def calculate_user_session_key!
55
59
  if is_anonymous?
56
60
  # see MS-NLMP section 3.4
@@ -0,0 +1,19 @@
1
+ require 'net/ntlm'
2
+
3
+ module Custom
4
+ module NTLM
5
+
6
+ def self.prepended(base)
7
+ base.singleton_class.send(:prepend, ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def encode_utf16le(str)
12
+ str.dup.force_encoding('UTF-8').encode(Encoding::UTF_16LE, Encoding::UTF_8).force_encoding('ASCII-8BIT')
13
+ end
14
+ end
15
+
16
+ end
17
+ end
18
+
19
+ Net::NTLM::EncodeUtil.send(:prepend, Custom::NTLM)
data/lib/ruby_smb/ntlm.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'ruby_smb/ntlm/custom/ntlm'
2
+
1
3
  module RubySMB
2
4
  module NTLM
3
5
  # [[MS-NLMP] 2.2.2.5](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/99d90ff4-957f-4c8a-80e4-5bfe5a9a9832)
@@ -56,6 +58,41 @@ module RubySMB
56
58
  "Version #{major}.#{minor} (Build #{build}); NTLM Current Revision #{ntlm_revision}"
57
59
  end
58
60
  end
61
+
62
+ class << self
63
+
64
+ # Generate a NTLMv2 Hash
65
+ # @param [String] user The username
66
+ # @param [String] password The password
67
+ # @param [String] target The domain or workstation to authenticate to
68
+ # @option opt :unicode (false) Unicode encode the domain
69
+ def ntlmv2_hash(user, password, target, opt={})
70
+ if Net::NTLM.is_ntlm_hash? password
71
+ decoded_password = Net::NTLM::EncodeUtil.decode_utf16le(password)
72
+ ntlmhash = [decoded_password.upcase[33,65]].pack('H32')
73
+ else
74
+ ntlmhash = Net::NTLM.ntlm_hash(password, opt)
75
+ end
76
+
77
+ if opt[:unicode]
78
+ # Uppercase operation on username containing non-ASCII characters
79
+ # after being unicode encoded with `EncodeUtil.encode_utf16le`
80
+ # doesn't play well. Upcase should be done before encoding.
81
+ user_upcase = Net::NTLM::EncodeUtil.decode_utf16le(user).upcase
82
+ user_upcase = Net::NTLM::EncodeUtil.encode_utf16le(user_upcase)
83
+ else
84
+ user_upcase = user.upcase
85
+ end
86
+ userdomain = user_upcase + target
87
+
88
+ unless opt[:unicode]
89
+ userdomain = Net::NTLM::EncodeUtil.encode_utf16le(userdomain)
90
+ end
91
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
92
+ end
93
+
94
+ end
95
+
59
96
  end
60
97
  end
61
98
 
@@ -0,0 +1,39 @@
1
+ module RubySMB
2
+ module PeerInfo
3
+ # Extract and store useful information about the peer/server from the
4
+ # NTLM Type 2 (challenge) TargetInfo fields.
5
+ #
6
+ # @param target_info_str [String] the Target Info string
7
+ def store_target_info(target_info_str)
8
+ target_info = Net::NTLM::TargetInfo.new(target_info_str)
9
+ {
10
+ Net::NTLM::TargetInfo::MSV_AV_NB_COMPUTER_NAME => :@default_name,
11
+ Net::NTLM::TargetInfo::MSV_AV_NB_DOMAIN_NAME => :@default_domain,
12
+ Net::NTLM::TargetInfo::MSV_AV_DNS_COMPUTER_NAME => :@dns_host_name,
13
+ Net::NTLM::TargetInfo::MSV_AV_DNS_DOMAIN_NAME => :@dns_domain_name,
14
+ Net::NTLM::TargetInfo::MSV_AV_DNS_TREE_NAME => :@dns_tree_name
15
+ }.each do |constant, attribute|
16
+ if target_info.av_pairs[constant]
17
+ value = target_info.av_pairs[constant].dup
18
+ value.force_encoding('UTF-16LE')
19
+ instance_variable_set(attribute, value.encode('UTF-8'))
20
+ end
21
+ end
22
+ end
23
+
24
+ # Extract the peer/server version number from the NTLM Type 2 (challenge)
25
+ # Version field.
26
+ #
27
+ # @param version [String] the version number as a binary string
28
+ # @return [String] the formatted version number (<major>.<minor>.<build>)
29
+ def extract_os_version(version)
30
+ begin
31
+ os_version = RubySMB::NTLM::OSVersion.read(version)
32
+ rescue IOError
33
+ return ''
34
+ end
35
+
36
+ "#{os_version.major}.#{os_version.minor}.#{os_version.build}"
37
+ end
38
+ end
39
+ end
@@ -7,7 +7,7 @@ module RubySMB
7
7
  # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/b062f3e3-1b65-4a9a-854a-0ee432499d8f
8
8
  response = RubySMB::SMB1::Packet::TreeConnectResponse.new
9
9
 
10
- share_name = request.data_block.path.encode('UTF-8').split('\\', 4).last
10
+ share_name = RubySMB::Utils.safe_encode(request.data_block.path, 'UTF-8').split('\\', 4).last
11
11
  share_provider = @server.shares.transform_keys(&:downcase)[share_name.downcase]
12
12
  if share_provider.nil?
13
13
  logger.warn("Received TREE_CONNECT request for non-existent share: #{share_name}")
@@ -49,7 +49,7 @@ module RubySMB
49
49
  return response
50
50
  end
51
51
 
52
- share_name = request.path.encode('UTF-8').split('\\', 4).last
52
+ share_name = RubySMB::Utils.safe_encode(request.path, 'UTF-8').split('\\', 4).last
53
53
  share_provider = @server.shares.transform_keys(&:downcase)[share_name.downcase]
54
54
 
55
55
  if share_provider.nil?