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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2709740a324a2a43861b70704a20c07ad3302390e40ad2cb2b19aa4574e1718c
4
- data.tar.gz: 9a4e4d4e59ae52a67b02f098c4610137182e5ff2cb4106a5d710c5b9e5d0de44
3
+ metadata.gz: 7b1e12b49f282f15459e21c5f16c6a6fc0f1074b107b97d85395d75a8531df1c
4
+ data.tar.gz: bccbfe2ef2823c390974786165e822eaa2fb7d1e1a96e8171daabbe871c6b324
5
5
  SHA512:
6
- metadata.gz: 00004e2a2b38ebdf190ec39bdf6abc27607ac227d705b4e5f7943737a3119ad13102ddf3884bfb697b3f7bca9ec29b45db2a0554811fd41f8e67a9bd96463244
7
- data.tar.gz: 677c779a105a0a8c478923e784500c0b9ec1710d9d332a87e4967a372987c3abb86a054254b0ec4ed2e7b4ad11527fa4c3fcc9fb233df92108f8700d3bb21d93
6
+ metadata.gz: fc4ba500076138ce97e1dc369733675600c4892669d9b5db815b4d09c1dc1b47efd0068da6cb7530fafdcdfc326d00b42b91937e44ed5fe2fdb9b4e5b37ffd9a
7
+ data.tar.gz: b851e6cc8f2846b7b6f7c36b01b14adb089c5d75257315940a282793bb62cce5bdf1f944c7ca3c95f75464914e26753f4ce1577ac446deea2c42c5a84986f82e
checksums.yaml.gz.sig CHANGED
Binary file
@@ -33,17 +33,15 @@ jobs:
33
33
  fail-fast: true
34
34
  matrix:
35
35
  ruby:
36
- - 2.6
37
36
  - 2.7
38
37
  - 3.0
39
38
  - 3.1
40
39
  os:
41
- - ubuntu-18.04
42
- - ubuntu-22.04
40
+ - ubuntu-20.04
41
+ - ubuntu-latest
43
42
  exclude:
44
- - { os: ubuntu-22.04, ruby: 2.6 }
45
- - { os: ubuntu-22.04, ruby: 2.7 }
46
- - { os: ubuntu-22.04, ruby: 3.0 }
43
+ - { os: ubuntu-latest, ruby: 2.7 }
44
+ - { os: ubuntu-latest, ruby: 3.0 }
47
45
  test_cmd:
48
46
  - bundle exec rspec
49
47
 
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
@@ -4,6 +4,7 @@ module RubySMB
4
4
  class Client
5
5
  require 'ruby_smb/ntlm'
6
6
  require 'ruby_smb/signing'
7
+ require 'ruby_smb/utils'
7
8
  require 'ruby_smb/client/negotiation'
8
9
  require 'ruby_smb/client/authentication'
9
10
  require 'ruby_smb/client/tree_connect'
@@ -13,6 +14,7 @@ module RubySMB
13
14
  require 'ruby_smb/client/encryption'
14
15
 
15
16
  include RubySMB::Signing
17
+ include RubySMB::NTLM
16
18
  include RubySMB::Client::Negotiation
17
19
  include RubySMB::Client::Authentication
18
20
  include RubySMB::Client::TreeConnect
@@ -322,7 +324,7 @@ module RubySMB
322
324
  @pid = rand(0xFFFF)
323
325
  @domain = domain
324
326
  @local_workstation = local_workstation
325
- @password = password.encode('utf-8') || ''.encode('utf-8')
327
+ @password = RubySMB::Utils.safe_encode((password||''), 'utf-8')
326
328
  @sequence_counter = 0
327
329
  @session_id = 0x00
328
330
  @session_key = ''
@@ -332,7 +334,7 @@ module RubySMB
332
334
  @smb1 = smb1
333
335
  @smb2 = smb2
334
336
  @smb3 = smb3
335
- @username = username.encode('utf-8') || ''.encode('utf-8')
337
+ @username = RubySMB::Utils.safe_encode((username||''), 'utf-8')
336
338
  @max_buffer_size = MAX_BUFFER_SIZE
337
339
  # These sizes will be modified during negotiation
338
340
  @server_max_buffer_size = SERVER_MAX_BUFFER_SIZE
@@ -415,8 +417,8 @@ module RubySMB
415
417
  local_workstation: self.local_workstation, ntlm_flags: NTLM::DEFAULT_CLIENT_FLAGS)
416
418
  @domain = domain
417
419
  @local_workstation = local_workstation
418
- @password = pass.encode('utf-8') || ''.encode('utf-8')
419
- @username = user.encode('utf-8') || ''.encode('utf-8')
420
+ @password = RubySMB::Utils.safe_encode((pass||''), 'utf-8')
421
+ @username = RubySMB::Utils.safe_encode((user||''), 'utf-8')
420
422
 
421
423
  @ntlm_client = RubySMB::NTLM::Client.new(
422
424
  @username,
@@ -5,11 +5,16 @@ module RubySMB
5
5
  class Client
6
6
  require 'bindata'
7
7
  require 'windows_error'
8
- require 'net/ntlm'
8
+ require 'ruby_smb/ntlm'
9
9
  require 'ruby_smb/dcerpc'
10
10
  require 'ruby_smb/gss'
11
+ require 'ruby_smb/peer_info'
12
+ require 'ruby_smb/utils'
11
13
 
14
+ include Dcerpc
12
15
  include Epm
16
+ include PeerInfo
17
+ include Utils
13
18
 
14
19
  # The default maximum size of a RPC message that the Client accepts (in bytes)
15
20
  MAX_BUFFER_SIZE = 64512
@@ -115,15 +120,15 @@ module RubySMB
115
120
  @read_timeout = read_timeout
116
121
  @domain = domain
117
122
  @local_workstation = local_workstation
118
- @username = username.encode('utf-8')
119
- @password = password.encode('utf-8')
123
+ @username = RubySMB::Utils.safe_encode(username, 'utf-8')
124
+ @password = RubySMB::Utils.safe_encode(password, 'utf-8')
120
125
  @max_buffer_size = MAX_BUFFER_SIZE
121
126
  @call_id = 1
122
127
  @ctx_id = 0
123
128
  @auth_ctx_id_base = rand(0xFFFFFFFF)
124
129
 
125
130
  unless username.empty? && password.empty?
126
- @ntlm_client = Net::NTLM::Client.new(
131
+ @ntlm_client = RubySMB::NTLM::Client.new(
127
132
  @username,
128
133
  @password,
129
134
  workstation: @local_workstation,
@@ -134,11 +139,11 @@ module RubySMB
134
139
  end
135
140
 
136
141
  # 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
142
+ # care of asking the Endpoint Mapper Interface the port used by the given
138
143
  # endpoint provided in #initialize and connect a TCP socket
139
144
  #
140
145
  # @param port [Integer] An optional port number to connect to. If
141
- # provided, it will not ask the Enpoint Mapper Interface for a port
146
+ # provided, it will not ask the Endpoint Mapper Interface for a port
142
147
  # number.
143
148
  # @return [TcpSocket] The connected TCP socket
144
149
  def connect(port: nil)
@@ -172,218 +177,14 @@ module RubySMB
172
177
  @tcp_socket.close if @tcp_socket && !@tcp_socket.closed?
173
178
  end
174
179
 
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
180
  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
181
+ auth3 = super
202
182
  challenge_message = @ntlm_client.session.challenge_message
203
183
  store_target_info(challenge_message.target_info) if challenge_message.has_flag?(:TARGET_INFO)
204
184
  @os_version = extract_os_version(challenge_message.os_version.to_s) unless challenge_message.os_version.empty?
205
185
  auth3
206
186
  end
207
187
 
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
188
  # Send a DCERPC request with the provided stub packet.
388
189
  #
389
190
  # @param stub_packet [BinData::Record] the stub packet to be sent as
@@ -482,60 +283,6 @@ module RubySMB
482
283
  raise Error::CommunicationError, "An error occurred reading from the Socket: #{e.message}"
483
284
  end
484
285
 
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
286
  end
540
287
  end
541
288
  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: (RubySMB::Utils.safe_encode(attributes.map { |k,v| "#{k}:#{v}" }.join("\n"), '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