ruby_smb 3.1.7 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2709740a324a2a43861b70704a20c07ad3302390e40ad2cb2b19aa4574e1718c
4
- data.tar.gz: 9a4e4d4e59ae52a67b02f098c4610137182e5ff2cb4106a5d710c5b9e5d0de44
3
+ metadata.gz: 9ce8d4ea99335efaf6bbc1ef003231ffa8a809382760e54dc26be04b76e53529
4
+ data.tar.gz: f284dabf7fe032ae02933a517abb2371f7c7c37eb641876f1cbf2b8cc0e4c2ac
5
5
  SHA512:
6
- metadata.gz: 00004e2a2b38ebdf190ec39bdf6abc27607ac227d705b4e5f7943737a3119ad13102ddf3884bfb697b3f7bca9ec29b45db2a0554811fd41f8e67a9bd96463244
7
- data.tar.gz: 677c779a105a0a8c478923e784500c0b9ec1710d9d332a87e4967a372987c3abb86a054254b0ec4ed2e7b4ad11527fa4c3fcc9fb233df92108f8700d3bb21d93
6
+ metadata.gz: 5cf115ffd4c634f593bceef2bb503d2c0a2b19c5cfa73391887d35ce2691472fabda03e47462b716f8c870d7dedbc5dc815f644887b259d1268658752396ea17
7
+ data.tar.gz: 46ba6053ca246692ef29c545c8a9682b6675c4bf50d10d3611107bf537470226986aeece854516d59d99afa1642ee2d72fed99f39c1731112292fcbb6d8138d2
checksums.yaml.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -186,7 +186,7 @@ Example:
186
186
  ```
187
187
  ### Using the Client
188
188
 
189
- Sitting on top of the packet layer in RubySMB is the RubySMB::Client class. This is the abstraction that most users of RubySMB will interact with. It provides simple conveience methods for performing SMB actions. It handles the creation, sending and receiving of packets for the user, providing reasonable defaults in many cases.
189
+ Sitting on top of the packet layer in RubySMB is the RubySMB::Client class. This is the abstraction that most users of RubySMB will interact with. It provides simple convenience methods for performing SMB actions. It handles the creation, sending and receiving of packets for the user, providing reasonable defaults in many cases.
190
190
 
191
191
  #### Negotiation
192
192
 
@@ -1,7 +1,11 @@
1
+ require 'ruby_smb/peer_info'
2
+
1
3
  module RubySMB
2
4
  class Client
3
5
  # This module holds all the backend client methods for authentication.
4
6
  module Authentication
7
+ include RubySMB::PeerInfo
8
+
5
9
  # Responsible for handling Authentication and Session Setup for
6
10
  # the SMB Client. It returns the final Status code from the authentication
7
11
  # exchange.
@@ -356,36 +360,6 @@ module RubySMB
356
360
  packet.security_mode.signing_enabled = 1
357
361
  packet
358
362
  end
359
-
360
- # Extract and store useful information about the peer/server from the
361
- # NTLM Type 2 (challenge) TargetInfo fields.
362
- #
363
- # @param target_info_str [String] the Target Info string
364
- def store_target_info(target_info_str)
365
- target_info = Net::NTLM::TargetInfo.new(target_info_str)
366
- {
367
- Net::NTLM::TargetInfo::MSV_AV_NB_COMPUTER_NAME => :@default_name,
368
- Net::NTLM::TargetInfo::MSV_AV_NB_DOMAIN_NAME => :@default_domain,
369
- Net::NTLM::TargetInfo::MSV_AV_DNS_COMPUTER_NAME => :@dns_host_name,
370
- Net::NTLM::TargetInfo::MSV_AV_DNS_DOMAIN_NAME => :@dns_domain_name,
371
- Net::NTLM::TargetInfo::MSV_AV_DNS_TREE_NAME => :@dns_tree_name
372
- }.each do |constant, attribute|
373
- if target_info.av_pairs[constant]
374
- value = target_info.av_pairs[constant].dup
375
- value.force_encoding('UTF-16LE')
376
- instance_variable_set(attribute, value.encode('UTF-8'))
377
- end
378
- end
379
- end
380
-
381
- # Extract the peer/server version number from the NTLM Type 2 (challenge)
382
- # Version field.
383
- #
384
- # @param version [String] the version number as a binary string
385
- # @return [String] the formated version number (<major>.<minor>.<build>)
386
- def extract_os_version(version)
387
- version.unpack('CCS').join('.')
388
- end
389
363
  end
390
364
  end
391
365
  end
@@ -8,8 +8,11 @@ module RubySMB
8
8
  require 'net/ntlm'
9
9
  require 'ruby_smb/dcerpc'
10
10
  require 'ruby_smb/gss'
11
+ require 'ruby_smb/peer_info'
11
12
 
13
+ include Dcerpc
12
14
  include Epm
15
+ include PeerInfo
13
16
 
14
17
  # The default maximum size of a RPC message that the Client accepts (in bytes)
15
18
  MAX_BUFFER_SIZE = 64512
@@ -134,11 +137,11 @@ module RubySMB
134
137
  end
135
138
 
136
139
  # Connect to the RPC endpoint. If a TCP socket was not provided, it takes
137
- # care of asking the Enpoint Mapper Interface the port used by the given
140
+ # care of asking the Endpoint Mapper Interface the port used by the given
138
141
  # endpoint provided in #initialize and connect a TCP socket
139
142
  #
140
143
  # @param port [Integer] An optional port number to connect to. If
141
- # provided, it will not ask the Enpoint Mapper Interface for a port
144
+ # provided, it will not ask the Endpoint Mapper Interface for a port
142
145
  # number.
143
146
  # @return [TcpSocket] The connected TCP socket
144
147
  def connect(port: nil)
@@ -172,218 +175,14 @@ module RubySMB
172
175
  @tcp_socket.close if @tcp_socket && !@tcp_socket.closed?
173
176
  end
174
177
 
175
- # Add the authentication verifier to the packet. This includes a sec
176
- # trailer and the actual authentication data.
177
- #
178
- # @param req [BinData::Record] the request to be updated
179
- # @param auth [String] the authentication data
180
- # @param auth_type [Integer] the authentication type
181
- # @param auth_level [Integer] the authentication level
182
- def add_auth_verifier(req, auth, auth_type, auth_level)
183
- req.sec_trailer = {
184
- auth_type: auth_type,
185
- auth_level: auth_level,
186
- auth_context_id: @ctx_id + @auth_ctx_id_base
187
- }
188
- req.auth_value = auth
189
- req.pdu_header.auth_length = auth.length
190
-
191
- nil
192
- end
193
-
194
178
  def process_ntlm_type2(type2_message)
195
- ntlmssp_offset = type2_message.index('NTLMSSP')
196
- type2_blob = type2_message.slice(ntlmssp_offset..-1)
197
- type2_b64_message = [type2_blob].pack('m')
198
- type3_message = @ntlm_client.init_context(type2_b64_message)
199
- auth3 = type3_message.serialize
200
-
201
- @session_key = @ntlm_client.session_key
179
+ auth3 = super
202
180
  challenge_message = @ntlm_client.session.challenge_message
203
181
  store_target_info(challenge_message.target_info) if challenge_message.has_flag?(:TARGET_INFO)
204
182
  @os_version = extract_os_version(challenge_message.os_version.to_s) unless challenge_message.os_version.empty?
205
183
  auth3
206
184
  end
207
185
 
208
- # Send a rpc_auth3 PDU that ends the authentication handshake.
209
- #
210
- # @param response [BindAck] the BindAck response packet
211
- # @param auth_type [Integer] the authentication type
212
- # @param auth_level [Integer] the authentication level
213
- # @raise [ArgumentError] if `:auth_type` is unknown
214
- # @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
215
- def send_auth3(response, auth_type, auth_level)
216
- case auth_type
217
- when RPC_C_AUTHN_NONE
218
- when RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT
219
- auth3 = process_ntlm_type2(response.auth_value)
220
- when RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_GSS_SCHANNEL, RPC_C_AUTHN_GSS_KERBEROS
221
- # TODO
222
- raise NotImplementedError
223
- else
224
- raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
225
- end
226
-
227
- rpc_auth3 = RpcAuth3.new
228
- add_auth_verifier(rpc_auth3, auth3, auth_type, auth_level)
229
- rpc_auth3.pdu_header.call_id = @call_id
230
-
231
- # The server should not respond
232
- send_packet(rpc_auth3)
233
- @call_id += 1
234
-
235
- nil
236
- end
237
-
238
- # Bind to the remote server interface endpoint. It takes care of adding
239
- # the necessary authentication verifier if `:auth_level` is set to
240
- # anything different than RPC_C_AUTHN_LEVEL_NONE
241
- #
242
- # @param endpoint [Module] the endpoint to bind to. This must be a Dcerpc
243
- # class with UUID, VER_MAJOR and VER_MINOR constants defined.
244
- # @param auth_level [Integer] the authentication level
245
- # @param auth_type [Integer] the authentication type
246
- # @return [BindAck] the BindAck response packet
247
- # @raise [Error::InvalidPacket] if an invalid packet is received
248
- # @raise [Error::BindError] if the response is not a BindAck packet or if the Bind result code is not ACCEPTANCE
249
- # @raise [ArgumentError] if `:auth_type` is unknown
250
- # @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
251
- def bind(endpoint: @endpoint, auth_level: RPC_C_AUTHN_LEVEL_NONE, auth_type: nil)
252
- bind_req = Bind.new(endpoint: endpoint)
253
- bind_req.pdu_header.call_id = @call_id
254
- # TODO: evasion: generate random UUIDs for bogus binds
255
-
256
- if auth_level && auth_level != RPC_C_AUTHN_LEVEL_NONE
257
- case auth_type
258
- when RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT
259
- raise ArgumentError, "NTLM Client not initialized. Username and password must be provided" unless @ntlm_client
260
- type1_message = @ntlm_client.init_context
261
- auth = type1_message.serialize
262
- when RPC_C_AUTHN_GSS_KERBEROS, RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE
263
- when RPC_C_AUTHN_GSS_KERBEROS, RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_GSS_SCHANNEL
264
- # TODO
265
- raise NotImplementedError
266
- else
267
- raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
268
- end
269
- add_auth_verifier(bind_req, auth, auth_type, auth_level)
270
- end
271
-
272
- send_packet(bind_req)
273
- bindack_response = recv_struct(BindAck)
274
- # TODO: see if BindNack response should be handled too
275
-
276
- res_list = bindack_response.p_result_list
277
- if res_list.n_results == 0 ||
278
- res_list.p_results[0].result != BindAck::ACCEPTANCE
279
- raise Error::BindError,
280
- "Bind Failed (Result: #{res_list.p_results[0].result}, Reason: #{res_list.p_results[0].reason})"
281
- end
282
-
283
- @max_buffer_size = bindack_response.max_xmit_frag
284
- @call_id = bindack_response.pdu_header.call_id
285
-
286
- if auth_level && auth_level != RPC_C_AUTHN_LEVEL_NONE
287
- # The number of legs needed to build the security context is defined
288
- # by the security provider
289
- # (see [2.2.1.1.7 Security Providers](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/d4097450-c62f-484b-872f-ddf59a7a0d36))
290
- case auth_type
291
- when RPC_C_AUTHN_WINNT
292
- send_auth3(bindack_response, auth_type, auth_level)
293
- when RPC_C_AUTHN_GSS_KERBEROS, RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE
294
- # TODO
295
- raise NotImplementedError
296
- end
297
- end
298
-
299
- nil
300
- end
301
-
302
- # Extract and store useful information about the peer/server from the
303
- # NTLM Type 2 (challenge) TargetInfo fields.
304
- #
305
- # @param target_info_str [String] the Target Info string
306
- def store_target_info(target_info_str)
307
- target_info = Net::NTLM::TargetInfo.new(target_info_str)
308
- {
309
- Net::NTLM::TargetInfo::MSV_AV_NB_COMPUTER_NAME => :@default_name,
310
- Net::NTLM::TargetInfo::MSV_AV_NB_DOMAIN_NAME => :@default_domain,
311
- Net::NTLM::TargetInfo::MSV_AV_DNS_COMPUTER_NAME => :@dns_host_name,
312
- Net::NTLM::TargetInfo::MSV_AV_DNS_DOMAIN_NAME => :@dns_domain_name,
313
- Net::NTLM::TargetInfo::MSV_AV_DNS_TREE_NAME => :@dns_tree_name
314
- }.each do |constant, attribute|
315
- if target_info.av_pairs[constant]
316
- value = target_info.av_pairs[constant].dup
317
- value.force_encoding('UTF-16LE')
318
- instance_variable_set(attribute, value.encode('UTF-8'))
319
- end
320
- end
321
- end
322
-
323
- # Extract the peer/server version number from the NTLM Type 2 (challenge)
324
- # Version field.
325
- #
326
- # @param version [String] the version number as a binary string
327
- # @return [String] the formated version number (<major>.<minor>.<build>)
328
- def extract_os_version(version)
329
- #version.unpack('CCS').join('.')
330
- begin
331
- os_version = NTLM::OSVersion.read(version)
332
- rescue IOError
333
- return ''
334
- end
335
- return "#{os_version.major}.#{os_version.minor}.#{os_version.build}"
336
- end
337
-
338
- # Add the authentication verifier to a Request packet. This includes a
339
- # sec trailer and the signature of the packet. This also encrypts the
340
- # Request stub if privacy is required (`:auth_level` option is
341
- # RPC_C_AUTHN_LEVEL_PKT_PRIVACY).
342
- #
343
- # @param dcerpc_req [Request] the Request packet to be updated
344
- # @param opts [Hash] the authenticaiton options: `:auth_type` and `:auth_level`
345
- # @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
346
- # @raise [ArgumentError] if `:auth_type` is unknown
347
- def set_integrity_privacy(dcerpc_req, auth_level:, auth_type:)
348
- dcerpc_req.sec_trailer = {
349
- auth_type: auth_type,
350
- auth_level: auth_level,
351
- auth_context_id: @ctx_id + @auth_ctx_id_base
352
- }
353
- dcerpc_req.auth_value = ' ' * 16
354
- dcerpc_req.pdu_header.auth_length = 16
355
-
356
- data_to_sign = plain_stub = dcerpc_req.stub.to_binary_s + dcerpc_req.auth_pad.to_binary_s
357
- if @ntlm_client.flags & NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] != 0
358
- data_to_sign = dcerpc_req.to_binary_s[0..-(dcerpc_req.pdu_header.auth_length + 1)]
359
- end
360
-
361
- encrypted_stub = ''
362
- if auth_level == RPC_C_AUTHN_LEVEL_PKT_PRIVACY
363
- case auth_type
364
- when RPC_C_AUTHN_NONE
365
- when RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT
366
- encrypted_stub = @ntlm_client.session.seal_message(plain_stub)
367
- when RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_GSS_SCHANNEL, RPC_C_AUTHN_GSS_KERBEROS
368
- # TODO
369
- raise NotImplementedError
370
- else
371
- raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
372
- end
373
- end
374
-
375
- signature = @ntlm_client.session.sign_message(data_to_sign)
376
-
377
- unless encrypted_stub.empty?
378
- pad_length = dcerpc_req.sec_trailer.auth_pad_length.to_i
379
- dcerpc_req.enable_encrypted_stub
380
- dcerpc_req.stub = encrypted_stub[0..-(pad_length + 1)]
381
- dcerpc_req.auth_pad = encrypted_stub[-(pad_length)..-1]
382
- end
383
- dcerpc_req.auth_value = signature
384
- dcerpc_req.pdu_header.auth_length = signature.size
385
- end
386
-
387
186
  # Send a DCERPC request with the provided stub packet.
388
187
  #
389
188
  # @param stub_packet [BinData::Record] the stub packet to be sent as
@@ -482,60 +281,6 @@ module RubySMB
482
281
  raise Error::CommunicationError, "An error occurred reading from the Socket: #{e.message}"
483
282
  end
484
283
 
485
- # Process the security context received in a response. It decrypts the
486
- # encrypted stub if `:auth_level` is set to anything different than
487
- # RPC_C_AUTHN_LEVEL_PKT_PRIVACY. It also checks the packet signature and
488
- # raises an InvalidPacket error if it fails. Note that the exception is
489
- # disabled by default and can be enabled with the
490
- # `:raise_signature_error` option
491
- #
492
- # @param dcerpc_response [Response] the Response packet
493
- # containing the security context to process
494
- # @param opts [Hash] the authenticaiton options: `:auth_type` and
495
- # `:auth_level`. To enable errors when signature check fails, set the
496
- # `:raise_signature_error` option to true
497
- # @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
498
- # @raise [Error::CommunicationError] if socket-related error occurs
499
- def handle_integrity_privacy(dcerpc_response, auth_level:, auth_type:, raise_signature_error: false)
500
- decrypted_stub = ''
501
- if auth_level == RPC_C_AUTHN_LEVEL_PKT_PRIVACY
502
- encrypted_stub = dcerpc_response.stub.to_binary_s + dcerpc_response.auth_pad.to_binary_s
503
- case auth_type
504
- when RPC_C_AUTHN_NONE
505
- when RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT
506
- decrypted_stub = @ntlm_client.session.unseal_message(encrypted_stub)
507
- when RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_GSS_SCHANNEL, RPC_C_AUTHN_GSS_KERBEROS
508
- # TODO
509
- raise NotImplementedError
510
- else
511
- raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
512
- end
513
- end
514
-
515
- unless decrypted_stub.empty?
516
- pad_length = dcerpc_response.sec_trailer.auth_pad_length.to_i
517
- dcerpc_response.stub = decrypted_stub[0..-(pad_length + 1)]
518
- dcerpc_response.auth_pad = decrypted_stub[-(pad_length)..-1]
519
- end
520
-
521
- signature = dcerpc_response.auth_value
522
- data_to_check = dcerpc_response.stub.to_binary_s
523
- if @ntlm_client.flags & NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] != 0
524
- data_to_check = dcerpc_response.to_binary_s[0..-(dcerpc_response.pdu_header.auth_length + 1)]
525
- end
526
- unless @ntlm_client.session.verify_signature(signature, data_to_check)
527
- if raise_signature_error
528
- raise Error::InvalidPacket.new(
529
- "Wrong packet signature received (set `raise_signature_error` to false to ignore)"
530
- )
531
- end
532
- end
533
-
534
- @call_id += 1
535
-
536
- nil
537
- end
538
-
539
284
  end
540
285
  end
541
286
  end
@@ -57,6 +57,16 @@ module RubySMB
57
57
  super(msg)
58
58
  end
59
59
  end
60
+
61
+ class IcprError < DcerpcError
62
+ include RubySMB::Error::UnexpectedStatusCode::Mixin
63
+
64
+ def initialize(msg, status_code: nil)
65
+ self.status_code = status_code unless status_code.nil?
66
+
67
+ super(msg)
68
+ end
69
+ end
60
70
  end
61
71
  end
62
72
  end
@@ -0,0 +1,27 @@
1
+ require 'ruby_smb/dcerpc/ndr'
2
+
3
+ module RubySMB
4
+ module Dcerpc
5
+ module Icpr
6
+
7
+ # [3.2.4.1.1 CertServerRequest (Opnum 0)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-icpr/0c6f150e-3ead-4006-b37f-ebbf9e2cf2e7)
8
+ class CertServerRequestRequest < BinData::Record
9
+ attr_reader :opnum
10
+
11
+ endian :little
12
+
13
+ ndr_uint32 :dw_flags
14
+ ndr_wide_stringz_ptr :pwsz_authority
15
+ ndr_uint32 :pdw_request_id
16
+ cert_trans_blob :pctb_attribs
17
+ cert_trans_blob :pctb_request
18
+
19
+ def initialize_instance
20
+ super
21
+ @opnum = CERT_SERVER_REQUEST
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ require 'ruby_smb/dcerpc/ndr'
2
+
3
+ module RubySMB
4
+ module Dcerpc
5
+ module Icpr
6
+
7
+ # [3.2.4.1.1 CertServerRequest (Opnum 0)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-icpr/0c6f150e-3ead-4006-b37f-ebbf9e2cf2e7)
8
+ class CertServerRequestResponse < BinData::Record
9
+ attr_reader :opnum
10
+
11
+ endian :little
12
+
13
+ ndr_uint32 :pdw_request_id
14
+ ndr_uint32 :pdw_disposition
15
+ cert_trans_blob :pctb_cert
16
+ cert_trans_blob :pctb_encoded_cert
17
+ cert_trans_blob :pctb_disposition_message
18
+ ndr_uint32 :error_status
19
+
20
+ def initialize_instance
21
+ super
22
+ @opnum = CERT_SERVER_REQUEST
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,84 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ module Icpr
4
+
5
+ # [3.2.4.1 ICertPassage Interface](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-icpr/d98e6cfb-87ba-4915-b3ec-a1b7c6129a53)
6
+ UUID = '91ae6020-9e3c-11cf-8d7c-00aa00c091be'
7
+ VER_MAJOR = 0
8
+ VER_MINOR = 0
9
+
10
+ # Operation numbers
11
+ CERT_SERVER_REQUEST = 0x0000
12
+
13
+ # Disposition constants, see
14
+ # [3.2.1.4.2.1 ICertRequestD::Request (Opnum 3)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/dbb2e78f-7630-4615-92c4-6734fccfc5a6)
15
+ CR_DISP_ISSUED = 0x0003
16
+ CR_DISP_UNDER_SUBMISSION = 0x0005
17
+
18
+ # [2.2.2.2 CERTTRANSBLOB](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/d6bee093-d862-4122-8f2b-7b49102097dc)
19
+ # (actually defined in MS-WCCE)
20
+ class CertTransBlob < Ndr::NdrStruct
21
+ endian :little
22
+ default_parameter byte_align: 4
23
+
24
+ ndr_uint32 :cb, initial_value: -> { pb.length }
25
+ ndr_byte_conf_array_ptr :pb
26
+
27
+ def buffer
28
+ pb.to_ary.pack('C*')
29
+ end
30
+ end
31
+
32
+ require 'ruby_smb/dcerpc/icpr/cert_server_request_request'
33
+ require 'ruby_smb/dcerpc/icpr/cert_server_request_response'
34
+
35
+ def cert_server_request(attributes:, authority:, csr:)
36
+ cert_server_request_request = CertServerRequestRequest.new(
37
+ pwsz_authority: authority,
38
+ pctb_attribs: { pb: (attributes.map { |k,v| "#{k}:#{v}" }.join("\n").encode('UTF-16le').force_encoding('ASCII-8bit') + "\x00\x00".b) },
39
+ pctb_request: { pb: csr.to_der }
40
+ )
41
+
42
+ response = dcerpc_request(
43
+ cert_server_request_request,
44
+ auth_level: RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
45
+ auth_type: RubySMB::Dcerpc::RPC_C_AUTHN_WINNT
46
+ )
47
+ begin
48
+ cert_server_request_response = CertServerRequestResponse.read(response)
49
+ rescue IOError
50
+ raise RubySMB::Dcerpc::Error::InvalidPacket, 'Error reading CertServerRequestResponse'
51
+ end
52
+
53
+ ret = {
54
+ disposition: cert_server_request_response.pdw_disposition.value,
55
+ disposition_message: cert_server_request_response.pctb_disposition_message.buffer.chomp("\x00\x00").force_encoding('utf-16le').encode,
56
+ status: {
57
+ CR_DISP_ISSUED => :issued,
58
+ CR_DISP_UNDER_SUBMISSION => :submitted,
59
+ }.fetch(cert_server_request_response.pdw_disposition.value, :error)
60
+ }
61
+
62
+ # note: error_status == RPC_S_BINDING_HAS_NO_AUTH when not properly bound
63
+ if ret[:status] == :error
64
+ unless cert_server_request_response.error_status == WindowsError::NTStatus::STATUS_SUCCESS
65
+ error_status = cert_server_request_response.error_status.value
66
+ status_code = WindowsError::Win32.find_by_retval(error_status).first
67
+ if status_code.nil? && (fault_name = RubySMB::Dcerpc::Fault::Status.name(error_status))
68
+ status_code = WindowsError::ErrorCode.new(fault_name.to_s, error_status, 'DCERPC fault')
69
+ end
70
+ raise RubySMB::Dcerpc::Error::IcprError.new(
71
+ "Error returned with cert_server_request: #{status_code || "0x#{error_status.to_s(16).rjust(8, '0')}"}",
72
+ status_code: status_code
73
+ )
74
+ end
75
+ else
76
+ ret[:certificate] = OpenSSL::X509::Certificate.new(cert_server_request_response.pctb_encoded_cert.buffer)
77
+ end
78
+
79
+ ret
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -1208,14 +1208,50 @@ module RubySMB::Dcerpc::Ndr
1208
1208
  extend PointerClassPlugin
1209
1209
  end
1210
1210
 
1211
+
1212
+ # ArrayPtr definitions:
1213
+ # If the type is Ndr*ArrayPtr, it's a ConfVarArray (Uni-dimensional Conformant-varying Array)
1214
+ # If the type is Ndr*ConfArrayPtr, it's a ConfArray (Uni-dimensional Conformant Array)
1211
1215
  class NdrByteArrayPtr < NdrConfVarArray
1212
1216
  default_parameters type: :ndr_uint8
1213
1217
  extend PointerClassPlugin
1218
+
1219
+ def assign(val)
1220
+ val = val.bytes if val.is_a?(String)
1221
+ super(val.to_ary)
1222
+ end
1214
1223
  end
1215
1224
 
1216
- class NdrUint16ArrayPtr < NdrConfVarArray
1217
- default_parameters type: :ndr_uint16
1225
+ class NdrByteConfArrayPtr < NdrConfArray
1226
+ default_parameters type: :ndr_uint8
1218
1227
  extend PointerClassPlugin
1228
+
1229
+ def assign(val)
1230
+ val = val.bytes if val.is_a?(String)
1231
+ super(val.to_ary)
1232
+ end
1233
+ end
1234
+
1235
+ %i[ Uint8 Uint16 Uint32 Uint64 ].each do |klass|
1236
+ new_klass_name = "Ndr#{klass}ArrayPtr"
1237
+ unless self.const_defined?(new_klass_name)
1238
+ new_klass = Class.new(NdrConfVarArray) do
1239
+ default_parameters type: "ndr_#{klass}".downcase.to_sym
1240
+ extend PointerClassPlugin
1241
+ end
1242
+ self.const_set(new_klass_name, new_klass)
1243
+ BinData::RegisteredClasses.register(new_klass_name, new_klass)
1244
+ end
1245
+
1246
+ new_klass_name = "Ndr#{klass}ConfArrayPtr"
1247
+ unless self.const_defined?(new_klass_name)
1248
+ new_klass = Class.new(NdrConfArray) do
1249
+ default_parameters type: "ndr_#{klass}".downcase.to_sym
1250
+ extend PointerClassPlugin
1251
+ end
1252
+ self.const_set(new_klass_name, new_klass)
1253
+ BinData::RegisteredClasses.register(new_klass_name, new_klass)
1254
+ end
1219
1255
  end
1220
1256
 
1221
1257
  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,