ruby_smb 2.0.2 → 2.0.3

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