ruby_smb 3.2.0 → 3.3.0

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 (71) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/verify.yml +8 -9
  4. data/README.md +1 -0
  5. data/cortex.yaml +15 -0
  6. data/examples/dump_secrets_from_sid.rb +2 -1
  7. data/lib/ruby_smb/client.rb +6 -4
  8. data/lib/ruby_smb/dcerpc/alter_context.rb +30 -0
  9. data/lib/ruby_smb/dcerpc/alter_context_resp.rb +42 -0
  10. data/lib/ruby_smb/dcerpc/bind.rb +3 -35
  11. data/lib/ruby_smb/dcerpc/bind_ack.rb +0 -31
  12. data/lib/ruby_smb/dcerpc/client.rb +10 -4
  13. data/lib/ruby_smb/dcerpc/drsr.rb +13 -13
  14. data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_decrypt_file_srv_request.rb +22 -0
  15. data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_decrypt_file_srv_response.rb +21 -0
  16. data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_query_recover_agents_request.rb +20 -0
  17. data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_query_recover_agents_response.rb +21 -0
  18. data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_query_users_on_file_request.rb +20 -0
  19. data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_query_users_on_file_response.rb +21 -0
  20. data/lib/ruby_smb/dcerpc/encrypting_file_system.rb +52 -0
  21. data/lib/ruby_smb/dcerpc/icpr.rb +3 -2
  22. data/lib/ruby_smb/dcerpc/ndr.rb +148 -3
  23. data/lib/ruby_smb/dcerpc/p_cont_list_t.rb +37 -0
  24. data/lib/ruby_smb/dcerpc/p_result_list_t.rb +13 -0
  25. data/lib/ruby_smb/dcerpc/p_result_t.rb +15 -0
  26. data/lib/ruby_smb/dcerpc/port_any_t.rb +11 -0
  27. data/lib/ruby_smb/dcerpc/request.rb +8 -3
  28. data/lib/ruby_smb/dcerpc/response.rb +6 -1
  29. data/lib/ruby_smb/dcerpc/samr.rb +4 -2
  30. data/lib/ruby_smb/dcerpc.rb +165 -122
  31. data/lib/ruby_smb/dispatcher/socket.rb +1 -1
  32. data/lib/ruby_smb/field/file_time.rb +2 -2
  33. data/lib/ruby_smb/field/utime.rb +2 -2
  34. data/lib/ruby_smb/generic_packet.rb +2 -2
  35. data/lib/ruby_smb/gss/provider/ntlm.rb +12 -5
  36. data/lib/ruby_smb/ntlm/client.rb +4 -0
  37. data/lib/ruby_smb/ntlm/custom/string_encoder.rb +22 -0
  38. data/lib/ruby_smb/ntlm.rb +37 -0
  39. data/lib/ruby_smb/server/server_client/share_io.rb +2 -2
  40. data/lib/ruby_smb/server/server_client/tree_connect.rb +2 -2
  41. data/lib/ruby_smb/server/share/provider/disk.rb +2 -2
  42. data/lib/ruby_smb/server/share/provider/processor.rb +25 -0
  43. data/lib/ruby_smb/server/share/provider/virtual_disk.rb +3 -3
  44. data/lib/ruby_smb/server/share/provider.rb +48 -1
  45. data/lib/ruby_smb/smb1/data_block.rb +2 -2
  46. data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +1 -1
  47. data/lib/ruby_smb/smb1/parameter_block.rb +2 -2
  48. data/lib/ruby_smb/smb1/pipe.rb +4 -0
  49. data/lib/ruby_smb/smb2/create_context.rb +5 -2
  50. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +5 -5
  51. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +1 -1
  52. data/lib/ruby_smb/smb2/pipe.rb +4 -0
  53. data/lib/ruby_smb/smb2/tree.rb +3 -1
  54. data/lib/ruby_smb/utils.rb +15 -0
  55. data/lib/ruby_smb/version.rb +1 -1
  56. data/lib/ruby_smb.rb +2 -0
  57. data/spec/lib/ruby_smb/client_spec.rb +4 -4
  58. data/spec/lib/ruby_smb/dcerpc/client_spec.rb +31 -16
  59. data/spec/lib/ruby_smb/dcerpc/drsr_spec.rb +4 -1
  60. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +198 -0
  61. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +0 -6
  62. data/spec/lib/ruby_smb/dcerpc/response_spec.rb +4 -12
  63. data/spec/lib/ruby_smb/dcerpc/sec_trailer_spec.rb +0 -14
  64. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +2 -2
  65. data/spec/lib/ruby_smb/smb1/packet/read_andx_request_spec.rb +2 -2
  66. data/spec/lib/ruby_smb/smb1/packet/write_andx_request_spec.rb +1 -1
  67. data/spec/lib/ruby_smb/smb2/create_context/create_context_request_spec.rb +35 -0
  68. data/spec/lib/ruby_smb/smb2/tree_spec.rb +5 -0
  69. data.tar.gz.sig +0 -0
  70. metadata +41 -86
  71. metadata.gz.sig +0 -0
@@ -247,12 +247,22 @@ module RubySMB::Dcerpc::Ndr
247
247
 
248
248
  def do_read(io)
249
249
  if is_a?(ConfPlugin) && should_process_max_count?
250
- set_max_count(io.readbytes(4).unpack('L<').first)
250
+ max_count = io.readbytes(4).unpack1('L<')
251
+ BinData.trace_message do |tracer|
252
+ tracer.trace_obj("#{debug_name}.max_count", max_count.to_s)
253
+ end
254
+ set_max_count(max_count)
251
255
  end
252
256
 
253
257
  if is_a?(VarPlugin)
254
258
  @offset = io.readbytes(4).unpack('L<').first
259
+ BinData.trace_message do |tracer|
260
+ tracer.trace_obj("#{debug_name}.offset", @offset.to_s)
261
+ end
255
262
  @actual_count = @read_until_index = io.readbytes(4).unpack('L<').first
263
+ BinData.trace_message do |tracer|
264
+ tracer.trace_obj("#{debug_name}.actual_count", @actual_count.to_s)
265
+ end
256
266
  end
257
267
 
258
268
  if has_elements_to_read?
@@ -523,7 +533,11 @@ module RubySMB::Dcerpc::Ndr
523
533
 
524
534
  def do_read(io)
525
535
  if should_process_max_count?
526
- set_max_count(io.readbytes(4).unpack('L<').first)
536
+ max_count = io.readbytes(4).unpack1('L<')
537
+ BinData.trace_message do |tracer|
538
+ tracer.trace_obj("#{debug_name}.max_count", max_count.to_s)
539
+ end
540
+ set_max_count(max_count)
527
541
  end
528
542
  super
529
543
  end
@@ -582,7 +596,13 @@ module RubySMB::Dcerpc::Ndr
582
596
 
583
597
  def do_read(io)
584
598
  @offset = io.readbytes(4).unpack('L<').first
599
+ BinData.trace_message do |tracer|
600
+ tracer.trace_obj("#{debug_name}.offset", @offset.to_s)
601
+ end
585
602
  @actual_count = io.readbytes(4).unpack('L<').first
603
+ BinData.trace_message do |tracer|
604
+ tracer.trace_obj("#{debug_name}.actual_count", @actual_count.to_s)
605
+ end
586
606
  super if @actual_count > 0
587
607
  end
588
608
 
@@ -702,7 +722,11 @@ module RubySMB::Dcerpc::Ndr
702
722
 
703
723
  def do_read(io)
704
724
  if should_process_max_count?
705
- set_max_count(io.readbytes(4).unpack('L<').first)
725
+ max_count = io.readbytes(4).unpack1('L<')
726
+ BinData.trace_message do |tracer|
727
+ tracer.trace_obj("#{debug_name}.max_count", max_count.to_s)
728
+ end
729
+ set_max_count(max_count)
706
730
 
707
731
  # Align the structure according to the alignment rules for the structure
708
732
  if respond_to?(:referent_bytes_align)
@@ -1034,6 +1058,9 @@ module RubySMB::Dcerpc::Ndr
1034
1058
  end
1035
1059
  else
1036
1060
  @ref_id = io.readbytes(4).unpack('L<').first
1061
+ BinData.trace_message do |tracer|
1062
+ tracer.trace_obj("#{debug_name}.ref_id", @ref_id.to_s)
1063
+ end
1037
1064
  parent_obj = nil
1038
1065
  if parent&.is_a?(ConstructedTypePlugin)
1039
1066
  parent_obj = parent.get_top_level_constructed_type
@@ -1288,5 +1315,123 @@ module RubySMB::Dcerpc::Ndr
1288
1315
  end
1289
1316
  end
1290
1317
 
1318
+ # (IDL/NDR) Pickles as defined in
1319
+ # [(IDL/NDR) # Pickles](https://pubs.opengroup.org/onlinepubs/9668899/chap2.htm#tagcjh_05_01_07)
1320
+ # and
1321
+ # [2.2.6 Type Serialization Version # 1](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/9a1d0f97-eac0-49ab-a197-f1a581c2d6a0)
1322
+
1323
+ # [2.2.6.1 Common Type Header for the Serialization Stream](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/6d75d40e-e2d2-4420-b9e9-8508a726a9ae)
1324
+ class TypeSerialization1CommonTypeHeader < BinData::Record
1325
+ default_parameter byte_align: 8
1326
+ endian :little
1327
+
1328
+ uint8 :version, initial_value: 1
1329
+ uint8 :endianness, initial_value: 0x10
1330
+ uint16 :common_header_length, initial_value: 8
1331
+ uint32 :filler, initial_value: 0xCCCCCCCC
1332
+ end
1333
+
1334
+ # [2.2.6.2 Private Header for Constructed Type](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/63949ba8-bc88-4c0c-9377-23f14b197827)
1335
+ class TypeSerialization1PrivateHeader < BinData::Record
1336
+ default_parameter byte_align: 8
1337
+ endian :little
1338
+
1339
+ uint32 :object_buffer_length, initial_value: -> { buffer_length(@obj) }
1340
+ uint32 :filler, initial_value: 0x00000000
1341
+
1342
+ def buffer_length(obj)
1343
+ parent.respond_to?(:field_length) ? parent.field_length(obj.parent) : 0
1344
+ end
1345
+ end
1346
+
1347
+ # [2.2.6 Type Serialization Version 1](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/9a1d0f97-eac0-49ab-a197-f1a581c2d6a0)
1348
+ #
1349
+ # This structure is not meant to be instantiated directly. Instead, the
1350
+ # structure with the fields to be serialized needs to inherit from
1351
+ # TypeSerialization1. This class will take care of adding the
1352
+ # TypeSerialization1PrivateHeader fields in front of any NDR constructed type
1353
+ # structures and setting the buffer length field (:object_buffer_length) to
1354
+ # the correct value.
1355
+ #
1356
+ # Example:
1357
+ #
1358
+ # class TestStruct < RubySMB::Dcerpc::Ndr::NdrStruct
1359
+ # default_parameters byte_align: 8
1360
+ # endian :little
1361
+
1362
+ # rpc_unicode_string :full_name
1363
+ # ndr_uint32 :user_id
1364
+ # end
1365
+
1366
+ # class TestTypeSerialization1 < RubySMB::Dcerpc::Ndr::TypeSerialization1
1367
+ # default_parameter byte_align: 8
1368
+ # endian :little
1369
+ #
1370
+ # test_struct :data1
1371
+ # uint32 :value1, initial_value: 5
1372
+ # uint32 :value2, initial_value: 6
1373
+ # test_struct :data2
1374
+ # uint32 :value3, initial_value: 7
1375
+ # test_struct :data3
1376
+ # end
1377
+ #
1378
+ # This will result in the following structure:
1379
+ # {
1380
+ # :common_header => {:version=>1, :endianness=>16, :common_header_length=>8, :filler=>3435973836},
1381
+ # :private_header1 => {:object_buffer_length=>12, :filler=>0},
1382
+ # :data1 => {:full_name=>{:buffer_length=>0, :maximum_length=>0, :buffer=>:null}, :user_id=>0},
1383
+ # :value1 => 5,
1384
+ # :value2 => 6,
1385
+ # :private_header2 => {:object_buffer_length=>12, :filler=>0},
1386
+ # :data2 => {:full_name=>{:buffer_length=>0, :maximum_length=>0, :buffer=>:null}, :user_id=>0},
1387
+ # :value3 => 7,
1388
+ # :private_header3 => {:object_buffer_length=>12, :filler=>0},
1389
+ # :data3 => {:full_name=>{:buffer_length=>0, :maximum_length=>0, :buffer=>:null}, :user_id=>0}
1390
+ # }
1391
+ #
1392
+ class TypeSerialization1 < BinData::Record
1393
+ PRIVATE_HEADER_BASE_NAME = 'private_header'.freeze
1394
+
1395
+ default_parameter byte_align: 8
1396
+ endian :little
1397
+ search_prefix :type_serialization1
1398
+
1399
+ common_type_header :common_header
1400
+
1401
+ def field_length(obj)
1402
+ length = 0
1403
+ index = find_index_of(obj)
1404
+ if index
1405
+ each_pair {|n, o| length = o.num_bytes if n == field_names[index + 1]}
1406
+ end
1407
+ length
1408
+ end
1409
+
1410
+ def self.method_missing(symbol, *args, &block)
1411
+ return super if dsl_parser.respond_to?(symbol)
1412
+
1413
+ klass = BinData::RegisteredClasses.lookup(symbol, {endian: dsl_parser.endian, search_prefix: dsl_parser.search_prefix})
1414
+ if klass.new.is_a?(ConstructedTypePlugin)
1415
+ # We cannot have two fields with the same name. So, here we look for
1416
+ # existing TypeSerialization1PrivateHeader fields and just increment
1417
+ # the ending number of the last TypeSerialization1PrivateHeader field
1418
+ # to make the new name unique.
1419
+ names = dsl_parser.fields.find_all do |field|
1420
+ field.prototype.instance_variable_get(:@obj_class) == TypeSerialization1PrivateHeader
1421
+ end.map(&:name).sort
1422
+ if names.empty?
1423
+ new_name = "#{PRIVATE_HEADER_BASE_NAME}1"
1424
+ else
1425
+ num = names.last.match(/#{PRIVATE_HEADER_BASE_NAME}(\d)$/)[1].to_i
1426
+ new_name = "#{PRIVATE_HEADER_BASE_NAME}#{num + 1}"
1427
+ end
1428
+
1429
+ super(:private_header, new_name.to_sym)
1430
+ end
1431
+
1432
+ super
1433
+ end
1434
+ end
1435
+
1291
1436
  end
1292
1437
 
@@ -0,0 +1,37 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ # The presentation context list and its element as defined in
4
+ # [Connection-oriented PDU Data Types - Declarations](https://pubs.opengroup.org/onlinepubs/9629399/chap12.htm#tagcjh_17_06_03_01)
5
+ class PContElemT < Ndr::NdrStruct
6
+ default_parameter byte_align: 4
7
+ endian :little
8
+
9
+ ndr_uint16 :p_cont_id, label: 'Context ID'
10
+ ndr_uint8 :n_transfer_syn, label: 'Number of transfer syntaxes', initial_value: 1
11
+ ndr_uint8 :reserved
12
+ p_syntax_id_t :abstract_syntax, label: 'Abstract syntax',
13
+ uuid: -> { endpoint::UUID },
14
+ ver_major: -> { endpoint::VER_MAJOR },
15
+ ver_minor: -> { endpoint::VER_MINOR }
16
+ array :transfer_syntaxes, label: 'Transfer syntax', type: :p_syntax_id_t,
17
+ initial_length: -> { n_transfer_syn },
18
+ uuid: -> { Ndr::UUID },
19
+ ver_major: -> { Ndr::VER_MAJOR },
20
+ ver_minor: -> { Ndr::VER_MINOR },
21
+ byte_align: 4
22
+ end
23
+
24
+ class PContListT < Ndr::NdrStruct
25
+ default_parameter byte_align: 4
26
+ endian :little
27
+
28
+ ndr_uint8 :n_context_elem, label: 'Number of context elements', initial_value: -> { 1 }
29
+ ndr_uint8 :reserved
30
+ ndr_uint16 :reserved2
31
+ array :p_cont_elem, label: 'Presentation context elements', type: :p_cont_elem_t,
32
+ initial_length: -> {n_context_elem},
33
+ endpoint: -> {endpoint},
34
+ byte_align: 4
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ class PResultListT < Ndr::NdrStruct
4
+ default_parameter byte_align: 4
5
+ endian :little
6
+
7
+ ndr_uint8 :n_results, label: 'Number of results', initial_value: -> { p_results.size }
8
+ ndr_uint8 :reserved
9
+ ndr_uint16 :reserved2
10
+ array :p_results, label: 'Results', type: :p_result_t, initial_length: -> { n_results }, byte_align: 4
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ class PResultT < Ndr::NdrStruct
4
+ default_parameter byte_align: 4
5
+ endian :little
6
+
7
+ ndr_uint16 :result, label: 'Presentation context negotiation results'
8
+ ndr_uint16 :reason, label: 'Rejection reason'
9
+ p_syntax_id_t :transfer_syntax, label: 'Presentation syntax ID',
10
+ uuid: -> { Ndr::UUID },
11
+ ver_major: -> { Ndr::VER_MAJOR },
12
+ ver_minor: -> { Ndr::VER_MINOR }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ class PortAnyT < Ndr::NdrStruct
4
+ default_parameter byte_align: 2
5
+ endian :little
6
+
7
+ ndr_uint16 :str_length, label: 'Length', initial_value: -> { port_spec.to_binary_s.size }
8
+ stringz :port_spec, label: 'Port string spec', byte_align: 2, onlyif: -> { str_length > 0 }
9
+ end
10
+ end
11
+ end
@@ -103,9 +103,10 @@ module RubySMB
103
103
  end
104
104
  string :default
105
105
  end
106
- string :auth_pad,
106
+
107
+ string :auth_pad,
107
108
  onlyif: -> { has_auth_verifier? },
108
- length: -> { (16 - (stub.num_bytes % 16)) % 16 }
109
+ length: -> { calculate_padding_size }
109
110
 
110
111
  # Auth Verifier
111
112
  sec_trailer :sec_trailer, onlyif: -> { has_auth_verifier? }
@@ -113,6 +114,11 @@ module RubySMB
113
114
  onlyif: -> { has_auth_verifier? },
114
115
  read_length: -> { pdu_header.auth_length }
115
116
 
117
+ # Per the spec (MS_RPCE 2.2.2.11): start of the trailer should be a multiple of 16 bytes offset from the start of the stub
118
+ def calculate_padding_size
119
+ (16 - (stub.num_bytes % 16)) % 16
120
+ end
121
+
116
122
  def initialize_instance
117
123
  super
118
124
  pdu_header.ptype = PTYPE
@@ -125,7 +131,6 @@ module RubySMB
125
131
  def has_auth_verifier?
126
132
  self.pdu_header.auth_length > 0
127
133
  end
128
-
129
134
  end
130
135
  end
131
136
  end
@@ -18,7 +18,7 @@ module RubySMB
18
18
  string :stub, label: 'Stub', read_length: -> { stub_length }
19
19
  string :auth_pad,
20
20
  onlyif: -> { has_auth_verifier? },
21
- length: -> { (16 - (stub.num_bytes % 16)) % 16 }
21
+ length: -> { calculate_padding_size }
22
22
 
23
23
  # Auth Verifier
24
24
  sec_trailer :sec_trailer, onlyif: -> { has_auth_verifier? }
@@ -26,6 +26,11 @@ module RubySMB
26
26
  onlyif: -> { has_auth_verifier? },
27
27
  read_length: -> { pdu_header.auth_length }
28
28
 
29
+ # Per the spec (MS_RPCE 2.2.2.11): start of the trailer should be a multiple of 16 bytes offset from the start of the stub
30
+ def calculate_padding_size
31
+ (16 - (stub.num_bytes % 16)) % 16
32
+ end
33
+
29
34
  def initialize_instance
30
35
  super
31
36
  pdu_header.ptype = PTYPE
@@ -259,6 +259,8 @@ module RubySMB
259
259
  3 => 'des-cbc-md5',
260
260
  17 => 'aes128-cts-hmac-sha1-96',
261
261
  18 => 'aes256-cts-hmac-sha1-96',
262
+ # Windows Server 2008 and later DC includes a KeyType of -140. Not present when the domain functional level is raised to DS_BEHAVIOR_WIN2008 or greater
263
+ # [Appendix_A_24](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/fa61e5fc-f8fb-4d5b-9695-c724af0c3829#Appendix_A_24)
262
264
  0xffffff74 => 'rc4_hmac'
263
265
  }
264
266
 
@@ -320,7 +322,7 @@ module RubySMB
320
322
 
321
323
  def self.encrypt_password(password, key)
322
324
  # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/5fe3c4c4-e71b-440d-b2fd-8448bfaf6e04
323
- password = password.encode('UTF-16LE').force_encoding('ASCII-8bit')
325
+ password = RubySMB::Utils.safe_encode(password, 'UTF-16LE').force_encoding('ASCII-8bit')
324
326
  buffer = password.rjust(512, "\x00") + [ password.length ].pack('V')
325
327
  salt = SecureRandom.random_bytes(16)
326
328
  key = OpenSSL::Digest::MD5.new(salt + key).digest
@@ -461,7 +463,7 @@ module RubySMB
461
463
  array :old_credentials, type: :kerb_key_data_new, initial_length: :old_credential_count
462
464
  array :older_credentials, type: :kerb_key_data_new, initial_length: :older_credential_count
463
465
  string :default_salt, read_length: -> { credentials.map { |e| e.key_offset }.min - @obj.abs_offset }
464
- string :key_values, read_length: -> { credentials.map { |e| e.key_length }.sum }
466
+ string :key_values, read_length: -> { credentials.map { |e| e.key_length }.sum(&:to_i) }
465
467
 
466
468
  def get_key_values
467
469
  credentials.map do |credential|