ruby_smb 2.0.2 → 2.0.3

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.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/examples/anonymous_auth.rb +3 -3
  5. data/examples/append_file.rb +10 -8
  6. data/examples/authenticate.rb +9 -5
  7. data/examples/delete_file.rb +8 -6
  8. data/examples/enum_registry_key.rb +5 -4
  9. data/examples/enum_registry_values.rb +5 -4
  10. data/examples/list_directory.rb +8 -6
  11. data/examples/negotiate_with_netbios_service.rb +9 -5
  12. data/examples/net_share_enum_all.rb +6 -4
  13. data/examples/pipes.rb +11 -12
  14. data/examples/query_service_status.rb +64 -0
  15. data/examples/read_file.rb +8 -6
  16. data/examples/read_registry_key_value.rb +6 -5
  17. data/examples/rename_file.rb +9 -7
  18. data/examples/tree_connect.rb +7 -5
  19. data/examples/write_file.rb +9 -7
  20. data/lib/ruby_smb/client.rb +72 -43
  21. data/lib/ruby_smb/client/negotiation.rb +1 -0
  22. data/lib/ruby_smb/dcerpc.rb +2 -0
  23. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  24. data/lib/ruby_smb/dcerpc/ndr.rb +209 -44
  25. data/lib/ruby_smb/dcerpc/request.rb +13 -0
  26. data/lib/ruby_smb/dcerpc/rpc_security_attributes.rb +34 -0
  27. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +9 -6
  28. data/lib/ruby_smb/dcerpc/svcctl.rb +479 -0
  29. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request.rb +48 -0
  30. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response.rb +26 -0
  31. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request.rb +25 -0
  32. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response.rb +26 -0
  33. data/lib/ruby_smb/dcerpc/svcctl/control_service_request.rb +26 -0
  34. data/lib/ruby_smb/dcerpc/svcctl/control_service_response.rb +26 -0
  35. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request.rb +35 -0
  36. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response.rb +23 -0
  37. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_request.rb +31 -0
  38. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_response.rb +23 -0
  39. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request.rb +25 -0
  40. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response.rb +44 -0
  41. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_request.rb +23 -0
  42. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_response.rb +27 -0
  43. data/lib/ruby_smb/dcerpc/svcctl/service_status.rb +25 -0
  44. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_request.rb +27 -0
  45. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_response.rb +25 -0
  46. data/lib/ruby_smb/dcerpc/winreg.rb +98 -17
  47. data/lib/ruby_smb/dcerpc/winreg/create_key_request.rb +73 -0
  48. data/lib/ruby_smb/dcerpc/winreg/create_key_response.rb +36 -0
  49. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +1 -1
  50. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +1 -1
  51. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +1 -1
  52. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +4 -4
  53. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +1 -1
  54. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +7 -6
  55. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +10 -10
  56. data/lib/ruby_smb/dcerpc/winreg/save_key_request.rb +37 -0
  57. data/lib/ruby_smb/dcerpc/winreg/save_key_response.rb +23 -0
  58. data/lib/ruby_smb/dispatcher/base.rb +1 -1
  59. data/lib/ruby_smb/dispatcher/socket.rb +1 -1
  60. data/lib/ruby_smb/field/stringz16.rb +17 -1
  61. data/lib/ruby_smb/nbss/session_header.rb +4 -4
  62. data/lib/ruby_smb/smb1/file.rb +2 -10
  63. data/lib/ruby_smb/smb1/pipe.rb +2 -0
  64. data/lib/ruby_smb/smb2/file.rb +25 -17
  65. data/lib/ruby_smb/smb2/pipe.rb +3 -0
  66. data/lib/ruby_smb/smb2/tree.rb +9 -3
  67. data/lib/ruby_smb/version.rb +1 -1
  68. data/spec/lib/ruby_smb/client_spec.rb +161 -60
  69. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +1396 -77
  70. data/spec/lib/ruby_smb/dcerpc/rpc_security_attributes_spec.rb +161 -0
  71. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +49 -12
  72. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request_spec.rb +191 -0
  73. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response_spec.rb +38 -0
  74. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request_spec.rb +30 -0
  75. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response_spec.rb +38 -0
  76. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_request_spec.rb +39 -0
  77. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_response_spec.rb +38 -0
  78. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request_spec.rb +78 -0
  79. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response_spec.rb +38 -0
  80. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_request_spec.rb +59 -0
  81. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_response_spec.rb +38 -0
  82. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request_spec.rb +38 -0
  83. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response_spec.rb +152 -0
  84. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_request_spec.rb +30 -0
  85. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_response_spec.rb +38 -0
  86. data/spec/lib/ruby_smb/dcerpc/svcctl/service_status_spec.rb +72 -0
  87. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_request_spec.rb +46 -0
  88. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_response_spec.rb +30 -0
  89. data/spec/lib/ruby_smb/dcerpc/svcctl_spec.rb +512 -0
  90. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_request_spec.rb +110 -0
  91. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_response_spec.rb +44 -0
  92. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +0 -4
  93. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +2 -2
  94. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +2 -2
  95. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +9 -4
  96. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +0 -4
  97. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +17 -17
  98. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +11 -23
  99. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_request_spec.rb +57 -0
  100. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_response_spec.rb +22 -0
  101. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +215 -41
  102. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +10 -10
  103. data/spec/lib/ruby_smb/field/stringz16_spec.rb +12 -0
  104. data/spec/lib/ruby_smb/nbss/session_header_spec.rb +4 -11
  105. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +7 -0
  106. data/spec/lib/ruby_smb/smb2/file_spec.rb +60 -6
  107. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +7 -0
  108. data/spec/lib/ruby_smb/smb2/tree_spec.rb +35 -1
  109. metadata +72 -2
  110. metadata.gz.sig +0 -0
@@ -9,18 +9,20 @@
9
9
  require 'bundler/setup'
10
10
  require 'ruby_smb'
11
11
 
12
- address = ARGV[0]
13
- username = ARGV[1]
14
- password = ARGV[2]
15
- share = ARGV[3]
16
- file = ARGV[4]
17
- data = ARGV[5]
12
+ address = ARGV[0]
13
+ username = ARGV[1]
14
+ password = ARGV[2]
15
+ share = ARGV[3]
16
+ file = ARGV[4]
17
+ data = ARGV[5]
18
+ smb_versions = ARGV[6]&.split(',') || ['1','2','3']
19
+
18
20
  path = "\\\\#{address}\\#{share}"
19
21
 
20
22
  sock = TCPSocket.new address, 445
21
23
  dispatcher = RubySMB::Dispatcher::Socket.new(sock)
22
24
 
23
- client = RubySMB::Client.new(dispatcher, smb1: true, smb2: true, username: username, password: password)
25
+ client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password)
24
26
  protocol = client.negotiate
25
27
  status = client.authenticate
26
28
 
@@ -160,9 +160,16 @@ module RubySMB
160
160
 
161
161
  # The UID set in SMB1
162
162
  # @!attribute [rw] user_id
163
- # @return [String]
163
+ # @return [Integer]
164
164
  attr_accessor :user_id
165
165
 
166
+ # The Process ID set in SMB1
167
+ # It is randomly generated during the client initialization, but can
168
+ # be modified using the accessor.
169
+ # @!attribute [rw] pid
170
+ # @return [Integer]
171
+ attr_accessor :pid
172
+
166
173
  # The maximum size SMB message that the Client accepts (in bytes)
167
174
  # The default value is equal to {MAX_BUFFER_SIZE}.
168
175
  # @!attribute [rw] max_buffer_size
@@ -258,6 +265,14 @@ module RubySMB
258
265
  # @return [Integer] the negotiated SMB version
259
266
  attr_accessor :negotiated_smb_version
260
267
 
268
+ # Whether or not the server supports multi-credit operations. It is
269
+ # reported by the LARGE_MTU capabiliy as part of the negotiation process
270
+ # (SMB 2.x and 3.x).
271
+ # @!attribute [rw] server_supports_multi_credit
272
+ # @return [Boolean] true if the server supports multi-credit operations,
273
+ # false otherwise
274
+ attr_accessor :server_supports_multi_credit
275
+
261
276
  # @param dispatcher [RubySMB::Dispatcher::Socket] the packet dispatcher to use
262
277
  # @param smb1 [Boolean] whether or not to enable SMB1 support
263
278
  # @param smb2 [Boolean] whether or not to enable SMB2 support
@@ -268,6 +283,7 @@ module RubySMB
268
283
  raise ArgumentError, 'You must enable at least one Protocol'
269
284
  end
270
285
  @dispatcher = dispatcher
286
+ @pid = rand(0xFFFF)
271
287
  @domain = domain
272
288
  @local_workstation = local_workstation
273
289
  @password = password.encode('utf-8') || ''.encode('utf-8')
@@ -285,6 +301,7 @@ module RubySMB
285
301
  @server_max_read_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
286
302
  @server_max_write_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
287
303
  @server_max_transact_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
304
+ @server_supports_multi_credit = false
288
305
 
289
306
  # SMB 3.x options
290
307
  @session_encrypt_data = always_encrypt
@@ -344,7 +361,7 @@ module RubySMB
344
361
  # @param packet [RubySMB::GenericPacket] the packet to set the message id for
345
362
  # @return [RubySMB::GenericPacket] the modified packet
346
363
  def increment_smb_message_id(packet)
347
- packet.smb2_header.message_id = smb2_message_id
364
+ packet.smb2_header.message_id = self.smb2_message_id
348
365
  self.smb2_message_id += 1
349
366
  packet
350
367
  end
@@ -427,7 +444,8 @@ module RubySMB
427
444
  version = packet.packet_smb_version
428
445
  case version
429
446
  when 'SMB1'
430
- packet.smb_header.uid = user_id if user_id
447
+ packet.smb_header.uid = self.user_id if self.user_id
448
+ packet.smb_header.pid_low = self.pid if self.pid
431
449
  packet = smb1_sign(packet)
432
450
  when 'SMB2'
433
451
  packet = increment_smb_message_id(packet)
@@ -443,35 +461,32 @@ module RubySMB
443
461
  packet = packet
444
462
  end
445
463
 
464
+ encrypt_data = false
446
465
  if can_be_encrypted?(packet) && encryption_supported? && (@session_encrypt_data || encrypt)
447
- send_encrypt(packet)
448
- raw_response = recv_encrypt
449
- loop do
450
- break unless is_status_pending?(raw_response)
451
- sleep 1
452
- raw_response = recv_encrypt
453
- end
454
- else
455
- dispatcher.send_packet(packet)
456
- raw_response = dispatcher.recv_packet
457
- loop do
458
- break unless is_status_pending?(raw_response)
459
- sleep 1
460
- raw_response = dispatcher.recv_packet
461
- end unless version == 'SMB1'
466
+ encrypt_data = true
462
467
  end
468
+ send_packet(packet, encrypt: encrypt_data)
469
+ raw_response = recv_packet(encrypt: encrypt_data)
470
+ smb2_header = nil
471
+ loop do
472
+ smb2_header = RubySMB::SMB2::SMB2Header.read(raw_response)
473
+ break unless is_status_pending?(smb2_header)
474
+ sleep 1
475
+ raw_response = recv_packet(encrypt: encrypt_data)
476
+ end unless version == 'SMB1'
463
477
 
464
478
  self.sequence_counter += 1 if signing_required && !session_key.empty?
479
+ # update the SMB2 message ID according to the received Credit Charged
480
+ self.smb2_message_id += smb2_header.credit_charge - 1 if smb2_header && self.dialect != '0x0202'
465
481
  raw_response
466
482
  end
467
483
 
468
484
  # Check if the response is an asynchronous operation with STATUS_PENDING
469
485
  # status code.
470
486
  #
471
- # @param raw_response [String] the raw response packet
487
+ # @param smb2_header [String] the response packet SMB2 header
472
488
  # @return [Boolean] true if it is a status pending operation, false otherwise
473
- def is_status_pending?(raw_response)
474
- smb2_header = RubySMB::SMB2::SMB2Header.read(raw_response)
489
+ def is_status_pending?(smb2_header)
475
490
  value = smb2_header.nt_status.value
476
491
  status_code = WindowsError::NTStatus.find_by_retval(value).first
477
492
  status_code == WindowsError::NTStatus::STATUS_PENDING &&
@@ -497,37 +512,50 @@ module RubySMB
497
512
  ['0x0300', '0x0302', '0x0311'].include?(@dialect)
498
513
  end
499
514
 
500
- # Encrypt and send a packet
501
- def send_encrypt(packet)
502
- begin
503
- transform_request = smb3_encrypt(packet.to_binary_s)
504
- rescue RubySMB::Error::RubySMBError => e
505
- raise RubySMB::Error::EncryptionError, "Error while encrypting #{packet.class.name} packet (SMB #{@dialect}): #{e}"
515
+ # Encrypt (if required) and send a packet.
516
+ #
517
+ # @param encrypt [Boolean] true if the packet should be encrypted, false
518
+ # otherwise
519
+ def send_packet(packet, encrypt: false)
520
+ if encrypt
521
+ begin
522
+ packet = smb3_encrypt(packet.to_binary_s)
523
+ rescue RubySMB::Error::RubySMBError => e
524
+ raise RubySMB::Error::EncryptionError, "Error while encrypting #{packet.class.name} packet (SMB #{@dialect}): #{e}"
525
+ end
506
526
  end
507
- dispatcher.send_packet(transform_request)
527
+ dispatcher.send_packet(packet)
508
528
  end
509
529
 
510
- # Receives the raw response through the Dispatcher and decrypt the packet.
530
+ # Receives the raw response through the Dispatcher and decrypt the packet (if required).
511
531
  #
532
+ # @param encrypt [Boolean] true if the packet is encrypted, false otherwise
512
533
  # @return [String] the raw unencrypted packet
513
- def recv_encrypt
534
+ def recv_packet(encrypt: false)
514
535
  begin
515
536
  raw_response = dispatcher.recv_packet
516
537
  rescue RubySMB::Error::CommunicationError => e
517
- raise RubySMB::Error::EncryptionError, "Communication error with the "\
518
- "remote host: #{e.message}. The server supports encryption but was "\
519
- "not able to handle the encrypted request."
520
- end
521
- begin
522
- transform_response = RubySMB::SMB2::Packet::TransformHeader.read(raw_response)
523
- rescue IOError
524
- raise RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet'
538
+ if encrypt
539
+ raise RubySMB::Error::EncryptionError, "Communication error with the "\
540
+ "remote host: #{e.message}. The server supports encryption but was "\
541
+ "not able to handle the encrypted request."
542
+ else
543
+ raise e
544
+ end
525
545
  end
526
- begin
527
- smb3_decrypt(transform_response)
528
- rescue RubySMB::Error::RubySMBError => e
529
- raise RubySMB::Error::EncryptionError, "Error while decrypting #{transform_response.class.name} packet (SMB #@dialect}): #{e}"
546
+ if encrypt
547
+ begin
548
+ transform_response = RubySMB::SMB2::Packet::TransformHeader.read(raw_response)
549
+ rescue IOError
550
+ raise RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet'
551
+ end
552
+ begin
553
+ raw_response = smb3_decrypt(transform_response)
554
+ rescue RubySMB::Error::RubySMBError => e
555
+ raise RubySMB::Error::EncryptionError, "Error while decrypting #{transform_response.class.name} packet (SMB #@dialect}): #{e}"
556
+ end
530
557
  end
558
+ raw_response
531
559
  end
532
560
 
533
561
  # Connects to the supplied share
@@ -568,6 +596,7 @@ module RubySMB
568
596
  self.smb2_message_id = 0
569
597
  self.client_encryption_key = nil
570
598
  self.server_encryption_key = nil
599
+ self.server_supports_multi_credit = false
571
600
  end
572
601
 
573
602
  # Requests a NetBIOS Session Service using the provided name.
@@ -605,7 +634,7 @@ module RubySMB
605
634
  session_request.session_header.session_packet_type = RubySMB::Nbss::SESSION_REQUEST
606
635
  session_request.called_name = called_name
607
636
  session_request.calling_name = calling_name
608
- session_request.session_header.packet_length =
637
+ session_request.session_header.stream_protocol_length =
609
638
  session_request.num_bytes - session_request.session_header.num_bytes
610
639
  session_request
611
640
  end
@@ -140,6 +140,7 @@ module RubySMB
140
140
  self.server_guid = packet.server_guid
141
141
  self.server_start_time = packet.server_start_time.to_time if packet.server_start_time != 0
142
142
  self.server_system_time = packet.system_time.to_time if packet.system_time != 0
143
+ self.server_supports_multi_credit = self.dialect != '0x0202' && packet&.capabilities&.large_mtu == 1
143
144
  case self.dialect
144
145
  when '0x02ff'
145
146
  when '0x0300', '0x0302'
@@ -10,9 +10,11 @@ module RubySMB
10
10
  require 'ruby_smb/dcerpc/ptypes'
11
11
  require 'ruby_smb/dcerpc/p_syntax_id_t'
12
12
  require 'ruby_smb/dcerpc/rrp_unicode_string'
13
+ require 'ruby_smb/dcerpc/rpc_security_attributes'
13
14
  require 'ruby_smb/dcerpc/pdu_header'
14
15
  require 'ruby_smb/dcerpc/srvsvc'
15
16
  require 'ruby_smb/dcerpc/winreg'
17
+ require 'ruby_smb/dcerpc/svcctl'
16
18
  require 'ruby_smb/dcerpc/request'
17
19
  require 'ruby_smb/dcerpc/response'
18
20
  require 'ruby_smb/dcerpc/bind'
@@ -13,6 +13,9 @@ module RubySMB
13
13
 
14
14
  # Raised when an error is returned during a Winreg operation
15
15
  class WinregError < DcerpcError; end
16
+
17
+ # Raised when an error is returned during a Svcctl operation
18
+ class SvcctlError < DcerpcError; end
16
19
  end
17
20
  end
18
21
  end
@@ -7,64 +7,88 @@ module RubySMB
7
7
  VER_MAJOR = 2
8
8
  VER_MINOR = 0
9
9
 
10
- # An NDR Top-level Full Pointers representation as defined in
11
- # [Transfer Syntax NDR - Top-level Full Pointers](http://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_11_01)
12
- # This class must be inherited and the subclass must have a #referent protperty
13
- class NdrTopLevelFullPointer < BinData::Primitive
10
+
11
+ # An NDR Conformant and Varying String representation as defined in
12
+ # [Transfer Syntax NDR - Conformant and Varying Strings](http://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_04_02)
13
+ # The string elements are Stringz16 (unicode)
14
+ class NdrString < BinData::Primitive
14
15
  endian :little
15
16
 
16
- uint32 :referent_identifier, initial_value: 0x00020000
17
+ uint32 :max_count
18
+ uint32 :offset, initial_value: 0
19
+ uint32 :actual_count
20
+ stringz16 :str, max_length: -> { actual_count * 2 }, onlyif: -> { actual_count > 0 }
17
21
 
18
22
  def get
19
- is_a_null_pointer? ? 0 : self.referent
23
+ self.actual_count == 0 ? 0 : self.str
20
24
  end
21
25
 
22
26
  def set(v)
23
- if v.is_a?(Integer) && v == 0
24
- self.referent_identifier = 0
27
+ if v == 0
28
+ self.str.clear
29
+ self.actual_count = 0
25
30
  else
26
- self.referent = v
31
+ v = v.str if v.is_a?(self.class)
32
+ unless self.str.equal?(v)
33
+ if v.empty?
34
+ self.actual_count = 0
35
+ else
36
+ self.actual_count = v.to_s.size + 1
37
+ self.max_count = self.actual_count
38
+ end
39
+ end
40
+ self.str = v.to_s
27
41
  end
28
42
  end
29
43
 
30
- def is_a_null_pointer?
31
- self.referent_identifier == 0
44
+ def clear
45
+ # Make sure #max_count and #offset are not cleared out
46
+ self.str.clear
47
+ self.actual_count.clear
48
+ end
49
+
50
+ def to_s
51
+ self.str.to_s
32
52
  end
33
53
  end
34
54
 
35
- # An NDR Conformant and Varying String representation as defined in
36
- # [Transfer Syntax NDR - Conformant and Varying Strings](http://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_04_02)
37
- # The string elements are Stringz16 (unicode)
38
- class NdrString < BinData::Primitive
55
+ # An NDR Uni-dimensional Conformant Array of Bytes representation as defined in
56
+ # [Transfer Syntax NDR - Uni-dimensional Conformant Arrays](https://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_03_02)
57
+ class NdrLpByte < BinData::Primitive
39
58
  endian :little
40
59
 
41
- uint32 :max_count
42
- uint32 :offset, initial_value: 0
43
- uint32 :actual_count
44
- stringz16 :str, read_length: -> { actual_count }, onlyif: -> { actual_count > 0 }
60
+ uint32 :max_count, initial_value: -> { self.elements.size }
61
+ array :elements, type: :uint8, read_until: -> { index == self.max_count - 1 }, onlyif: -> { self.max_count > 0 }
45
62
 
46
63
  def get
47
- self.actual_count == 0 ? 0 : self.str
64
+ self.elements
48
65
  end
49
66
 
50
67
  def set(v)
51
- if v.is_a?(Integer) && v == 0
52
- self.actual_count = 0
53
- else
54
- self.str = v
55
- self.max_count = self.actual_count = str.to_binary_s.size / 2
56
- end
68
+ v = v.elements if v.is_a?(self.class)
69
+ self.elements = v.to_ary
70
+ self.max_count = self.elements.size unless self.elements.equal?(v)
57
71
  end
58
72
  end
59
73
 
60
- # A pointer to a NdrString structure
61
- class NdrLpStr < NdrTopLevelFullPointer
74
+ # An NDR Uni-dimensional Conformant-varying Arrays of bytes representation as defined in:
75
+ # [Transfer Syntax NDR - NDR Constructed Types](http://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_03_04)
76
+ class NdrByteArray < BinData::Primitive
62
77
  endian :little
63
78
 
64
- ndr_string :referent, onlyif: -> { !is_a_null_pointer? }
79
+ uint32 :max_count, initial_value: -> { self.actual_count }
80
+ uint32 :offset, initial_value: 0
81
+ uint32 :actual_count, initial_value: -> { self.bytes.size }
82
+ array :bytes, :type => :uint8, initial_length: -> { self.actual_count }
65
83
 
66
- def to_s
67
- is_a_null_pointer? ? "\0" : self.referent
84
+ def get
85
+ self.bytes
86
+ end
87
+
88
+ def set(v)
89
+ v = v.bytes if v.is_a?(self.class)
90
+ self.bytes = v.to_ary
91
+ self.max_count = self.bytes.size unless self.bytes.equal?(v)
68
92
  end
69
93
  end
70
94
 
@@ -72,6 +96,7 @@ module RubySMB
72
96
  # [IDL Data Type Declarations - Basic Type Declarations](http://pubs.opengroup.org/onlinepubs/9629399/apdxn.htm#tagcjh_34_01)
73
97
  class NdrContextHandle < BinData::Primitive
74
98
  endian :little
99
+
75
100
  uint32 :context_handle_attributes
76
101
  uuid :context_handle_uuid
77
102
 
@@ -91,30 +116,170 @@ module RubySMB
91
116
  end
92
117
  end
93
118
 
94
- # A pointer to a DWORD
95
- class NdrLpDword < NdrTopLevelFullPointer
119
+ # An NDR Top-level Full Pointers representation as defined in
120
+ # [Transfer Syntax NDR - Top-level Full Pointers](http://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_11_01)
121
+ # This class must be inherited and the subclass must have a #referent property
122
+ class NdrPointer < BinData::Primitive
96
123
  endian :little
97
124
 
98
- uint32 :referent, onlyif: -> { !is_a_null_pointer? }
125
+ uint32 :referent_id, initial_value: 0
126
+
127
+ def do_read(io)
128
+ self.referent_id.do_read(io)
129
+ if process_referent?
130
+ self.referent.do_read(io) unless self.referent_id == 0
131
+ end
132
+ end
133
+
134
+ def do_write(io)
135
+ self.referent_id.do_write(io)
136
+ if process_referent?
137
+ self.referent.do_write(io) unless self.referent_id == 0
138
+ end
139
+ end
140
+
141
+ def set(v)
142
+ if v == :null
143
+ self.referent.clear
144
+ self.referent_id = 0
145
+ else
146
+ if self.referent.respond_to?(:set)
147
+ self.referent.set(v)
148
+ else
149
+ self.referent = v
150
+ end
151
+ self.referent_id = rand(0xFFFFFFFF) if self.referent_id == 0
152
+ end
153
+ end
154
+
155
+ def get
156
+ if self.referent_id == 0
157
+ :null
158
+ else
159
+ self.referent
160
+ end
161
+ end
162
+
163
+ def process_referent?
164
+ current_parent = parent
165
+ loop do
166
+ return true unless current_parent
167
+ return false if current_parent.is_a?(NdrStruct)
168
+ current_parent = current_parent.parent
169
+ end
170
+ end
99
171
  end
100
172
 
101
- # An NDR Uni-dimensional Conformant-varying Arrays representation as defined in:
102
- # [Transfer Syntax NDR - NDR Constructed Types](http://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_03_04)
103
- class NdrLpByte < BinData::Record
173
+ # A pointer to a NdrString structure
174
+ class NdrLpStr < NdrPointer
175
+ endian :little
176
+
177
+ ndr_string :referent, onlyif: -> { self.referent_id != 0 }
178
+ end
179
+
180
+ class NdrLpDword < NdrPointer
181
+ endian :little
182
+
183
+ uint32 :referent, onlyif: -> { self.referent_id != 0 }
184
+ end
185
+
186
+ # A pointer to an NDR Uni-dimensional Conformant-varying Arrays of bytes
187
+ class NdrLpByteArray < NdrPointer
104
188
  endian :little
105
189
 
106
- uint32 :referent_identifier, initial_value: 0x00020000
107
- uint32 :max_count, initial_value: -> { actual_count }, onlyif: -> { referent_identifier != 0 }
108
- uint32 :offset, initial_value: 0, onlyif: -> { referent_identifier != 0 }
109
- uint32 :actual_count, initial_value: -> { bytes.size }, onlyif: -> { referent_identifier != 0 }
110
- array :bytes, :type => :uint8, initial_length: -> { actual_count }, onlyif: -> { referent_identifier != 0 }
190
+ ndr_byte_array :referent, onlyif: -> { self.referent_id != 0 }
191
+
192
+ def set(v)
193
+ if v != :null && v.is_a?(NdrLpByteArray)
194
+ super(v.referent)
195
+ else
196
+ super(v)
197
+ end
198
+ end
111
199
  end
112
200
 
113
201
  # A pointer to a Windows FILETIME structure
114
- class NdrLpFileTime < NdrTopLevelFullPointer
202
+ class NdrLpFileTime < NdrPointer
115
203
  endian :little
116
204
 
117
- file_time :referent, onlyif: -> { !is_a_null_pointer? }
205
+ file_time :referent, onlyif: -> { self.referent_id != 0 }
206
+ end
207
+
208
+ # A generic NDR structure that implements logic to #read and #write
209
+ # (#to_binary_s) in case the structure contains BinData::Array or
210
+ # NdrPointer fields. This class must be inherited.
211
+ class NdrStruct < BinData::Record
212
+
213
+ def do_read(io)
214
+ super(io)
215
+ each_pair do |_name, field|
216
+ case field
217
+ when BinData::Array
218
+ field.each do |element|
219
+ next unless element.is_a?(NdrPointer)
220
+ next if element.referent_id == 0
221
+ pad = (4 - io.offset % 4) % 4
222
+ io.seekbytes(pad) if pad > 0
223
+ element.referent.do_read(io)
224
+ end
225
+ when NdrPointer
226
+ next if field.referent_id == 0
227
+ pad = (4 - io.offset % 4) % 4
228
+ io.seekbytes(pad) if pad > 0
229
+ field.referent.do_read(io)
230
+ end
231
+ end
232
+ end
233
+
234
+ def do_write(io)
235
+ super(io)
236
+ each_pair do |_name, field|
237
+ case field
238
+ when BinData::Array
239
+ field.each do |element|
240
+ next unless element.is_a?(NdrPointer)
241
+ next if element.referent_id == 0
242
+ pad = (4 - io.offset % 4) % 4
243
+ io.writebytes("\x00" * pad + element.referent.to_binary_s)
244
+ end
245
+ when NdrPointer
246
+ next if field.referent_id == 0
247
+ pad = (4 - io.offset % 4) % 4
248
+ io.writebytes("\x00" * pad + field.referent.to_binary_s)
249
+ end
250
+ end
251
+ end
252
+ end
253
+
254
+ class NdrStringPtrsw < NdrStruct
255
+ endian :little
256
+
257
+ uint32 :max_count, initial_value: -> { self.elements.size }
258
+ array :elements, type: :ndr_lp_str, read_until: -> { index == self.max_count - 1 }, onlyif: -> { self.max_count > 0 }
259
+
260
+ def get
261
+ self.elements
262
+ end
263
+
264
+ def set(v)
265
+ v = v.elements if v.is_a?(self.class)
266
+ self.elements = v.to_ary
267
+ self.max_count = self.elements.size unless self.elements.equal?(v)
268
+ end
269
+
270
+ def do_num_bytes
271
+ to_binary_s.size
272
+ end
273
+ end
274
+
275
+ class NdrLpStringPtrsw < NdrPointer
276
+ endian :little
277
+
278
+ ndr_string_ptrsw :referent, onlyif: -> { self.referent_id != 0 }
279
+
280
+ def set(v)
281
+ super(v.respond_to?(:to_ary) ? v.to_ary : v)
282
+ end
118
283
  end
119
284
  end
120
285
  end