ruby_smb 3.1.7 → 3.2.1

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