diff --git a/lib/rex/java/serialization/model/new_object.rb b/lib/rex/java/serialization/model/new_object.rb new file mode 100644 index 0000000000..b223501ee0 --- /dev/null +++ b/lib/rex/java/serialization/model/new_object.rb @@ -0,0 +1,170 @@ +module Rex + module Java + module Serialization + module Model + # This class provides a NewObject (Java Object) representation + class NewObject < Element + + include Rex::Java::Serialization + + # @!attribute array_description + # @return [Java::Serialization::Model::ClassDescription] The description of the object + attr_accessor :class_desc + attr_accessor :class_data + + def initialize + self.class_desc = nil + self.class_data = [] + end + + # Deserializes a Java::Serialization::Model::NewObject + # + # @param io [IO] the io to read from + # @return [self] if deserialization succeeds + # @raise [RuntimeError] if deserialization doesn't succeed + def decode(io) + self.class_desc = ClassDesc.decode(io) + + unless class_desc.description.super_class.description.class == Rex::Java::Serialization::Model::NullReference + raise ::RuntimeError, 'Deserialization of objects with super classes not supported' + end + + self.class_data = decode_class_data(io) + + self + end + + # Serializes the Java::Serialization::Model::NewArray + # + # @return [String] if serialization succeeds + # @raise [RuntimeError] if serialization doesn't succeed + def encode + unless class_desc.class == Rex::Java::Serialization::Model::ClassDesc + raise ::RuntimeError, 'Failed to serialize NewObject' + end + + encoded = '' + encoded << class_desc.encode + + class_data.each do |value| + encoded << encode_value(value) + end + + encoded + end + + private + + def decode_class_data(io) + values = [] + + class_desc.description.fields.each do |field| + unless field.is_primitive? + raise ::RuntimeError, 'Deserialization of objects with complex fields not supported' + end + + values << decode_value(io, field.type) + end + + values + end + + # Deserializes a NewArray value + # + # @param io [IO] the io to read from + # @return [Fixnum] if deserialization succeeds + # @return [Float] if deserialization succeeds + # @raise [RuntimeError] if deserialization fails + def decode_value(io, type) + value = [] + + case type + when 'byte' + value_raw = io.read(1) + raise ::RuntimeError, 'Failed to deserialize NewArray value' if value_raw.nil? + value.push('byte', value_raw.unpack('c')[0]) + when 'char' + value_raw = io.read(2) + unless value_raw && value_raw.length == 2 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value.push('char', value_raw.unpack('s>')[0]) + when 'double' + value_raw = io.read(8) + unless value_raw && value_raw.length == 8 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value.push('double', value = value_raw.unpack('G')[0]) + when 'float' + value_raw = io.read(4) + unless value_raw && value_raw.length == 4 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value.push('float', value_raw.unpack('g')[0]) + when 'int' + value_raw = io.read(4) + unless value_raw && value_raw.length == 4 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value.push('int', value_raw.unpack('l>')[0]) + when 'long' + value_raw = io.read(8) + unless value_raw && value_raw.length == 8 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value.push('long', value_raw.unpack('q>')[0]) + when 'short' + value_raw = io.read(2) + unless value_raw && value_raw.length == 2 + raise ::RuntimeError, 'Failed to deserialize NewArray value' + end + value.push('short', value_raw.unpack('s>')[0]) + when 'boolean' + value_raw = io.read(1) + raise ::RuntimeError, 'Failed to deserialize NewArray value' if value_raw.nil? + value.push('boolean', value_raw.unpack('c')[0]) + else + raise ::RuntimeError, 'Unsupported NewArray type' + end + + value + end + + # Serializes an NewArray value + # + # @param value [Fixnum] the value to serialize + # @param value [Float] the value to serialize + # @return [String] the serialized value + # @raise [RuntimeError] if serialization fails + def encode_value(value) + res = '' + + case value[0] + when 'byte' + res = [value[1]].pack('c') + when 'char' + res = [value[1]].pack('s>') + when 'double' + res = [value[1]].pack('G') + when 'float' + res = [value[1]].pack('g') + when 'int' + res = [value[1]].pack('l>') + when 'long' + res = [value[1]].pack('q>') + when 'short' + res = [value[1]].pack('s>') + when 'boolean' + res = [value[1]].pack('c') + else + raise ::RuntimeError, 'Unsupported NewArray type' + end + + res + end + + end + end + end + end +end \ No newline at end of file diff --git a/spec/lib/rex/java/serialization/model/new_object_spec.rb b/spec/lib/rex/java/serialization/model/new_object_spec.rb new file mode 100644 index 0000000000..2aaa279a68 --- /dev/null +++ b/spec/lib/rex/java/serialization/model/new_object_spec.rb @@ -0,0 +1,68 @@ +require 'rex/java' +require 'stringio' + +describe Rex::Java::Serialization::Model::NewObject do + + subject(:new_object) do + described_class.new + end + + let(:easy_object) do + "\x72\x00\x04\x45\x61\x73\x79\x74" + + "\x1d\xe1\xbc\xbb\x2f\xcb\xaa\x02" + + "\x00\x01\x49\x00\x03\x53\x53\x4e" + + "\x78\x70\x41\x42\x43\x44" + end + + let(:easy_object_io) { StringIO.new(easy_object) } + + describe ".new" do + it "Rex::Java::Serialization::Model::NewObject" do + expect(new_object).to be_a(Rex::Java::Serialization::Model::NewObject) + end + + it "initializes class_desc with nil" do + expect(new_object.class_desc).to be_nil + end + end + + describe "#decode" do + it "deserializes an object" do + expect(new_object.decode(easy_object_io)).to be_a(Rex::Java::Serialization::Model::NewObject) + end + + it "deserializes the object class fields correctly" do + new_object.decode(easy_object_io) + expect(new_object.class_desc.description.fields.length).to eq(1) + end + + it "deserializes the object class data correctly" do + new_object.decode(easy_object_io) + expect(new_object.class_data).to eq([['int', 0x41424344]]) + end + end + + + describe "#encode" do + it "serializes an Object" do + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new('Easy') + new_class_desc.serial_version = 0x741de1bcbb2fcbaa + new_class_desc.flags = 2 + field = Rex::Java::Serialization::Model::Field.new + field.type = 'int' + field.name = Rex::Java::Serialization::Model::Utf.new('SSN') + new_class_desc.fields << field + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new + new_object.class_desc.description = new_class_desc + new_object.class_data = [['int', 0x41424344]] + + expect(new_object.encode.unpack("C*")).to eq(easy_object.unpack("C*")) + end + end + +end \ No newline at end of file