lib/ruby_smb/dcerpc/ndr.rb in ruby_smb-3.2.3 vs lib/ruby_smb/dcerpc/ndr.rb in ruby_smb-3.2.4
- old
+ new
@@ -1313,7 +1313,125 @@
read(handle.to_s)
end
end
end
+ # (IDL/NDR) Pickles as defined in
+ # [(IDL/NDR) # Pickles](https://pubs.opengroup.org/onlinepubs/9668899/chap2.htm#tagcjh_05_01_07)
+ # and
+ # [2.2.6 Type Serialization Version # 1](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/9a1d0f97-eac0-49ab-a197-f1a581c2d6a0)
+
+ # [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)
+ class TypeSerialization1CommonTypeHeader < BinData::Record
+ default_parameter byte_align: 8
+ endian :little
+
+ uint8 :version, initial_value: 1
+ uint8 :endianness, initial_value: 0x10
+ uint16 :common_header_length, initial_value: 8
+ uint32 :filler, initial_value: 0xCCCCCCCC
+ end
+
+ # [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)
+ class TypeSerialization1PrivateHeader < BinData::Record
+ default_parameter byte_align: 8
+ endian :little
+
+ uint32 :object_buffer_length, initial_value: -> { buffer_length(@obj) }
+ uint32 :filler, initial_value: 0x00000000
+
+ def buffer_length(obj)
+ parent.respond_to?(:field_length) ? parent.field_length(obj.parent) : 0
+ end
+ end
+
+ # [2.2.6 Type Serialization Version 1](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/9a1d0f97-eac0-49ab-a197-f1a581c2d6a0)
+ #
+ # This structure is not meant to be instantiated directly. Instead, the
+ # structure with the fields to be serialized needs to inherit from
+ # TypeSerialization1. This class will take care of adding the
+ # TypeSerialization1PrivateHeader fields in front of any NDR constructed type
+ # structures and setting the buffer length field (:object_buffer_length) to
+ # the correct value.
+ #
+ # Example:
+ #
+ # class TestStruct < RubySMB::Dcerpc::Ndr::NdrStruct
+ # default_parameters byte_align: 8
+ # endian :little
+
+ # rpc_unicode_string :full_name
+ # ndr_uint32 :user_id
+ # end
+
+ # class TestTypeSerialization1 < RubySMB::Dcerpc::Ndr::TypeSerialization1
+ # default_parameter byte_align: 8
+ # endian :little
+ #
+ # test_struct :data1
+ # uint32 :value1, initial_value: 5
+ # uint32 :value2, initial_value: 6
+ # test_struct :data2
+ # uint32 :value3, initial_value: 7
+ # test_struct :data3
+ # end
+ #
+ # This will result in the following structure:
+ # {
+ # :common_header => {:version=>1, :endianness=>16, :common_header_length=>8, :filler=>3435973836},
+ # :private_header1 => {:object_buffer_length=>12, :filler=>0},
+ # :data1 => {:full_name=>{:buffer_length=>0, :maximum_length=>0, :buffer=>:null}, :user_id=>0},
+ # :value1 => 5,
+ # :value2 => 6,
+ # :private_header2 => {:object_buffer_length=>12, :filler=>0},
+ # :data2 => {:full_name=>{:buffer_length=>0, :maximum_length=>0, :buffer=>:null}, :user_id=>0},
+ # :value3 => 7,
+ # :private_header3 => {:object_buffer_length=>12, :filler=>0},
+ # :data3 => {:full_name=>{:buffer_length=>0, :maximum_length=>0, :buffer=>:null}, :user_id=>0}
+ # }
+ #
+ class TypeSerialization1 < BinData::Record
+ PRIVATE_HEADER_BASE_NAME = 'private_header'.freeze
+
+ default_parameter byte_align: 8
+ endian :little
+ search_prefix :type_serialization1
+
+ common_type_header :common_header
+
+ def field_length(obj)
+ length = 0
+ index = find_index_of(obj)
+ if index
+ each_pair {|n, o| length = o.num_bytes if n == field_names[index + 1]}
+ end
+ length
+ end
+
+ def self.method_missing(symbol, *args, &block)
+ return super if dsl_parser.respond_to?(symbol)
+
+ klass = BinData::RegisteredClasses.lookup(symbol, {endian: dsl_parser.endian, search_prefix: dsl_parser.search_prefix})
+ if klass.new.is_a?(ConstructedTypePlugin)
+ # We cannot have two fields with the same name. So, here we look for
+ # existing TypeSerialization1PrivateHeader fields and just increment
+ # the ending number of the last TypeSerialization1PrivateHeader field
+ # to make the new name unique.
+ names = dsl_parser.fields.find_all do |field|
+ field.prototype.instance_variable_get(:@obj_class) == TypeSerialization1PrivateHeader
+ end.map(&:name).sort
+ if names.empty?
+ new_name = "#{PRIVATE_HEADER_BASE_NAME}1"
+ else
+ num = names.last.match(/#{PRIVATE_HEADER_BASE_NAME}(\d)$/)[1].to_i
+ new_name = "#{PRIVATE_HEADER_BASE_NAME}#{num + 1}"
+ end
+
+ super(:private_header, new_name.to_sym)
+ end
+
+ super
+ end
+ end
+
end