Add initial support for Java serialization
parent
394d132d33
commit
5f11c70d7f
|
@ -0,0 +1,34 @@
|
|||
module Rex
|
||||
module Java
|
||||
# Include constants defining terminal and constant
|
||||
# values expected in a stream.
|
||||
module Serialization
|
||||
STREAM_MAGIC = 0xaced
|
||||
STREAM_VERSION = 5
|
||||
TC_NULL = 0x70
|
||||
TC_REFERENCE = 0x71
|
||||
TC_CLASSDESC = 0x72
|
||||
TC_OBJECT = 0x73
|
||||
TC_STRING = 0x74
|
||||
TC_ARRAY = 0x75
|
||||
TC_CLASS = 0x76
|
||||
TC_BLOCKDATA = 0x77
|
||||
TC_ENDBLOCKDATA = 0x78
|
||||
TC_RESET = 0x79
|
||||
TC_BLOCKDATALONG = 0x7A
|
||||
TC_EXCEPTION = 0x7B
|
||||
TC_LONGSTRING = 0x7C
|
||||
TC_PROXYCLASSDESC = 0x7D
|
||||
TC_ENUM = 0x7E
|
||||
BASE_WIRE_HANDLE = 0x7E0000
|
||||
|
||||
SC_WRITE_METHOD = 0x01 # if SC_SERIALIZABLE
|
||||
SC_BLOCK_DATA = 0x08 # if SC_EXTERNALIZABLE
|
||||
SC_SERIALIZABLE = 0x02
|
||||
SC_EXTERNALIZABLE = 0x04
|
||||
SC_ENUM = 0x10
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'rex/java/serialization/model'
|
|
@ -0,0 +1,5 @@
|
|||
require 'rex/java/serialization/model/element'
|
||||
require 'rex/java/serialization/model/end_block_data'
|
||||
require 'rex/java/serialization/model/null_reference'
|
||||
require 'rex/java/serialization/model/utf'
|
||||
require 'rex/java/serialization/model/field'
|
|
@ -0,0 +1,26 @@
|
|||
module Rex
|
||||
module Java
|
||||
module Serialization
|
||||
module Model
|
||||
class Element
|
||||
def self.decode(io)
|
||||
elem = self.new
|
||||
elem.decode(io)
|
||||
end
|
||||
|
||||
def initialize
|
||||
|
||||
end
|
||||
|
||||
def decode(io)
|
||||
self
|
||||
end
|
||||
|
||||
def encode
|
||||
''
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
module Rex
|
||||
module Java
|
||||
module Serialization
|
||||
module Model
|
||||
class EndBlockData < Element
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,167 @@
|
|||
module Rex
|
||||
module Java
|
||||
module Serialization
|
||||
module Model
|
||||
# This class provides a field description representation (fieldDesc). It's used for
|
||||
# both primitive descriptions (primitiveDesc) and object descriptions (objectDesc).
|
||||
class Field < Element
|
||||
|
||||
PRIMITIVE_TYPE_CODES = {
|
||||
'B' => 'byte',
|
||||
'C' => 'char',
|
||||
'D' => 'double',
|
||||
'F' => 'float',
|
||||
'I' => 'integer',
|
||||
'J' => 'long',
|
||||
'S' => 'short',
|
||||
'Z' => 'boolean',
|
||||
}
|
||||
|
||||
OBJECT_TYPE_CODES = {
|
||||
'[' => 'array',
|
||||
'L' => 'object'
|
||||
}
|
||||
|
||||
TYPE_CODES = PRIMITIVE_TYPE_CODES.merge(OBJECT_TYPE_CODES)
|
||||
|
||||
# @!attribute type
|
||||
# @return [String] The type of the field.
|
||||
attr_accessor :type
|
||||
# @!attribute name
|
||||
# @return [Java::Serialization::Model::Utf] The name of the field.
|
||||
attr_accessor :name
|
||||
# @!attribute field_type
|
||||
# @return [Java::Serialization::Model::Utf] The type of the field on object types.
|
||||
attr_accessor :field_type
|
||||
|
||||
# Unserializes a Java::Serialization::Field
|
||||
#
|
||||
# @param io [IO] the io to read from
|
||||
# @return [Java::Serialization::Model::Field] if deserialization is possible
|
||||
# @return [nil] if deserialization isn't possible
|
||||
def self.decode(io)
|
||||
elem = self.new
|
||||
|
||||
elem.decode(io)
|
||||
end
|
||||
|
||||
def initialize
|
||||
self.type = ''
|
||||
self.name = nil
|
||||
self.field_type = nil
|
||||
end
|
||||
|
||||
# Unserializes a Java::Serialization::Model::Field
|
||||
#
|
||||
# @param io [IO] the io to read from
|
||||
# @return [self] if deserialization is possible
|
||||
# @return [nil] if deserialization isn't possible
|
||||
def decode(io)
|
||||
code = io.read(1)
|
||||
return nil unless code && is_valid?(code)
|
||||
self.type = TYPE_CODES[code]
|
||||
|
||||
self.name = Utf.decode(io)
|
||||
return nil if name.nil?
|
||||
|
||||
if is_object?
|
||||
self.field_type = decode_field_type(io)
|
||||
return nil if field_type.nil?
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
# Serializes the Java::Serialization::Model::Field
|
||||
#
|
||||
# @return [String] if serialization is possible
|
||||
# @return [nil] if serialization isn't possible
|
||||
def encode
|
||||
unless is_type_valid?
|
||||
return nil
|
||||
end
|
||||
|
||||
encoded = ''
|
||||
encoded << TYPE_CODES.key(type)
|
||||
encoded << name.encode
|
||||
|
||||
if is_object?
|
||||
encoded << encode_field_type
|
||||
end
|
||||
|
||||
encoded
|
||||
end
|
||||
|
||||
# Whether the field type is valid.
|
||||
#
|
||||
# @return [Boolean]
|
||||
def is_type_valid?
|
||||
if TYPE_CODES.values.include?(type)
|
||||
return true
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
# Whether the field type is a primitive one.
|
||||
#
|
||||
# @return [Boolean]
|
||||
def is_primitive?
|
||||
if PRIMITIVE_TYPE_CODES.values.include?(type)
|
||||
return true
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
# Whether the field type is an object one.
|
||||
#
|
||||
# @return [Boolean]
|
||||
def is_object?
|
||||
if OBJECT_TYPE_CODES.values.include?(type)
|
||||
return true
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Whether the type opcode is a valid one.
|
||||
#
|
||||
# @param code [String] A type opcode
|
||||
# @return [Boolean]
|
||||
def is_valid?(code)
|
||||
if TYPE_CODES.keys.include?(code)
|
||||
return true
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
# Serializes the `field_type` attribute.
|
||||
#
|
||||
# @return [String]
|
||||
def encode_field_type
|
||||
encoded = [Java::Serialization::TC_STRING].pack('C')
|
||||
encoded << field_type.encode
|
||||
|
||||
encoded
|
||||
end
|
||||
|
||||
# Unserializes the `field_type` value.
|
||||
#
|
||||
# @param io [IO] the io to read from
|
||||
# @return [Java::Serialization::Model::Utf]
|
||||
def decode_field_type(io)
|
||||
opcode = io.read(1)
|
||||
return nil unless opcode && opcode == [Java::Serialization::TC_STRING].pack('C')
|
||||
type = Utf.decode(io)
|
||||
|
||||
type
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
module Rex
|
||||
module Java
|
||||
module Serialization
|
||||
module Model
|
||||
class NullReference < Element
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,66 @@
|
|||
module Rex
|
||||
module Java
|
||||
module Serialization
|
||||
module Model
|
||||
# This class provides a Utf string representation
|
||||
class Utf < Element
|
||||
|
||||
# @!attribute length
|
||||
# @return [Integer] the length of the string
|
||||
attr_accessor :length
|
||||
# @!attribute contents
|
||||
# @return [String] the contents of the string
|
||||
attr_accessor :contents
|
||||
|
||||
# Userializes a Java::Serialization::Model::Utf
|
||||
#
|
||||
# @param io [IO] the io to read from
|
||||
# @return [Java::Serialization::Model::Utf] if deserialization is possible
|
||||
# @return [nil] if deserialization isn't possible
|
||||
def self.decode(io)
|
||||
elem = self.new
|
||||
|
||||
elem.decode(io)
|
||||
end
|
||||
|
||||
# @param contents [String] the contents of the utf string
|
||||
def initialize(contents = '')
|
||||
self.contents = contents
|
||||
self.length = contents.length
|
||||
end
|
||||
|
||||
# Userializes a Java::Serialization::Model::Utf
|
||||
#
|
||||
# @param io [IO] the io to read from
|
||||
# @return [self] if deserialization is possible
|
||||
# @return [nil] if deserialization isn't possible
|
||||
def decode(io)
|
||||
raw_length = io.read(2)
|
||||
return nil if raw_length.nil?
|
||||
self.length = raw_length.unpack('n')[0]
|
||||
|
||||
if length == 0
|
||||
self.contents = ''
|
||||
else
|
||||
self.contents = io.read(length)
|
||||
return nil if contents.nil? || contents.length != length
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
# Serializes the Java::Serialization::Model::Utf
|
||||
#
|
||||
# @return [String] if serialization is possible
|
||||
# @return [nil] if serialization isn't possible
|
||||
def encode
|
||||
encoded = [length].pack('n')
|
||||
encoded << contents
|
||||
|
||||
encoded
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,132 @@
|
|||
require 'rex/java'
|
||||
require 'stringio'
|
||||
|
||||
describe Rex::Java::Serialization::Model::Field do
|
||||
subject(:field) do
|
||||
described_class.new
|
||||
end
|
||||
|
||||
let(:sample_primitive) { "I\x00\x06number" }
|
||||
let(:sample_primitive_io) { StringIO.new(sample_primitive) }
|
||||
let(:sample_object) { "[\x00\x0atest_arrayt\x00\x0b[LEmployee;" }
|
||||
let(:sample_object_io) { StringIO.new(sample_object) }
|
||||
|
||||
describe ".new" do
|
||||
it "Rex::Java::Serialization::Model::Field" do
|
||||
expect(field).to be_a(Rex::Java::Serialization::Model::Field)
|
||||
end
|
||||
|
||||
it "initializes code with empty string" do
|
||||
expect(field.type).to be_empty
|
||||
end
|
||||
|
||||
it "initializes name with nil" do
|
||||
expect(field.name).to be_nil
|
||||
end
|
||||
|
||||
it "initializes field_type with nil" do
|
||||
expect(field.field_type).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "#encode" do
|
||||
context "when empty field" do
|
||||
it { expect(field.encode).to be_nil }
|
||||
end
|
||||
|
||||
context "when primitive field" do
|
||||
it do
|
||||
field.type = 'integer'
|
||||
field.name = Rex::Java::Serialization::Model::Utf.new('number')
|
||||
expect(field.encode).to eq(sample_primitive)
|
||||
end
|
||||
end
|
||||
|
||||
context "when object field" do
|
||||
it do
|
||||
field.type = 'array'
|
||||
field.name = Rex::Java::Serialization::Model::Utf.new('test_array')
|
||||
field.field_type = Rex::Java::Serialization::Model::Utf.new('[LEmployee;')
|
||||
expect(field.encode).to eq(sample_object)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#decode" do
|
||||
context "when stream contains a primitive field" do
|
||||
it "returns a Rex::Java::Serialization::Model::Field" do
|
||||
expect(field.decode(sample_primitive_io)).to be_a(Rex::Java::Serialization::Model::Field)
|
||||
end
|
||||
|
||||
it "decodes field type" do
|
||||
field.decode(sample_primitive_io)
|
||||
expect(field.type).to eq('integer')
|
||||
end
|
||||
|
||||
it "decodes field name as Utf" do
|
||||
field.decode(sample_primitive_io)
|
||||
expect(field.name.contents).to eq('number')
|
||||
end
|
||||
end
|
||||
|
||||
context "when stream contains an object field" do
|
||||
it "returns a Rex::Java::Serialization::Model::Field" do
|
||||
expect(field.decode(sample_object_io)).to be_a(Rex::Java::Serialization::Model::Field)
|
||||
end
|
||||
|
||||
it "decodes field type" do
|
||||
field.decode(sample_object_io)
|
||||
expect(field.type).to eq('array')
|
||||
end
|
||||
|
||||
it "decodes field name" do
|
||||
field.decode(sample_object_io)
|
||||
expect(field.name.contents).to eq('test_array')
|
||||
end
|
||||
|
||||
it "decodes field_type string" do
|
||||
field.decode(sample_object_io)
|
||||
expect(field.field_type.contents).to eq('[LEmployee;')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".decode" do
|
||||
context "when stream contains a primitive field" do
|
||||
it "returns a Rex::Java::Serialization::Model::Field" do
|
||||
expect(described_class.decode(sample_primitive_io)).to be_a(Rex::Java::Serialization::Model::Field)
|
||||
end
|
||||
|
||||
it "decodes field type" do
|
||||
field = described_class.decode(sample_primitive_io)
|
||||
expect(field.type).to eq('integer')
|
||||
end
|
||||
|
||||
it "decodes field name as Utf" do
|
||||
field = described_class.decode(sample_primitive_io)
|
||||
expect(field.name.contents).to eq('number')
|
||||
end
|
||||
end
|
||||
|
||||
context "when stream contains an object field" do
|
||||
it "returns a Rex::Java::Serialization::Model::Field" do
|
||||
expect(described_class.decode(sample_object_io)).to be_a(Rex::Java::Serialization::Model::Field)
|
||||
end
|
||||
|
||||
it "decodes field type" do
|
||||
field = described_class.decode(sample_object_io)
|
||||
expect(field.type).to eq('array')
|
||||
end
|
||||
|
||||
it "decodes field name" do
|
||||
field = described_class.decode(sample_object_io)
|
||||
expect(field.name.contents).to eq('test_array')
|
||||
end
|
||||
|
||||
it "decodes field_type string" do
|
||||
field = described_class.decode(sample_object_io)
|
||||
expect(field.field_type.contents).to eq('[LEmployee;')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,137 @@
|
|||
require 'rex/java'
|
||||
require 'stringio'
|
||||
|
||||
describe Rex::Java::Serialization::Model::Utf do
|
||||
subject(:utf) do
|
||||
described_class.new
|
||||
end
|
||||
|
||||
let(:sample_utf) { "\x00\x10java.lang.Number" }
|
||||
let(:sample_utf_io) { StringIO.new(sample_utf) }
|
||||
let(:empty_utf) { "\x00\x00" }
|
||||
let(:empty_utf_io) { StringIO.new(empty_utf) }
|
||||
let(:incomplete_utf) { "\x00\x10java.lang.Numb" }
|
||||
let(:incomplete_utf_io) { StringIO.new(incomplete_utf) }
|
||||
let(:empty_io) { StringIO.new('') }
|
||||
|
||||
describe ".new" do
|
||||
it "Rex::Java::Serialization::Model::Utf" do
|
||||
expect(utf).to be_a(Rex::Java::Serialization::Model::Utf)
|
||||
end
|
||||
|
||||
it "initializes length to 0" do
|
||||
expect(utf.length).to eq(0)
|
||||
end
|
||||
|
||||
it "initializes contents with empty string" do
|
||||
expect(utf.contents).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "#encode" do
|
||||
context "when empty utf" do
|
||||
it { expect(utf.encode).to eq(empty_utf) }
|
||||
end
|
||||
|
||||
context "when filled utf" do
|
||||
it do
|
||||
utf.length = 16
|
||||
utf.contents = 'java.lang.Number'
|
||||
expect(utf.encode).to eq(sample_utf)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#decode" do
|
||||
context "when stream contains empty string" do
|
||||
it "returns nil" do
|
||||
expect(utf.decode(empty_io)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when stream contains empty utf" do
|
||||
it "returns a Rex::Java::Serialization::Model::Utf" do
|
||||
expect(utf.decode(empty_utf_io)).to be_a(Rex::Java::Serialization::Model::Utf)
|
||||
end
|
||||
|
||||
it "sets length to 0" do
|
||||
utf.decode(empty_utf_io)
|
||||
expect(utf.length).to eq(0)
|
||||
end
|
||||
|
||||
it "sets contents to empty string" do
|
||||
utf.decode(empty_utf_io)
|
||||
expect(utf.contents).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context "when stream contains incomplete utf" do
|
||||
it "returns nil" do
|
||||
expect(utf.decode(incomplete_utf_io)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when stream contains correct utf" do
|
||||
|
||||
it "returns a Rex::Java::Serialization::Model::Utf" do
|
||||
expect(utf.decode(sample_utf_io)).to be_a(Rex::Java::Serialization::Model::Utf)
|
||||
end
|
||||
|
||||
it "sets length to 0" do
|
||||
utf.decode(sample_utf_io)
|
||||
expect(utf.length).to eq(16)
|
||||
end
|
||||
|
||||
it "sets contents to sample string" do
|
||||
utf.decode(sample_utf_io)
|
||||
expect(utf.contents).to eq('java.lang.Number')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".decode" do
|
||||
context "when stream contains empty string" do
|
||||
it "returns nil" do
|
||||
expect(described_class.decode(empty_io)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when stream contains empty utf" do
|
||||
it "returns a Rex::Java::Serialization::Model::Utf" do
|
||||
expect(described_class.decode(empty_utf_io)).to be_a(Rex::Java::Serialization::Model::Utf)
|
||||
end
|
||||
|
||||
it "sets length to 0" do
|
||||
utf = described_class.decode(empty_utf_io)
|
||||
expect(utf.length).to eq(0)
|
||||
end
|
||||
|
||||
it "sets contents to empty string" do
|
||||
utf = described_class.decode(empty_utf_io)
|
||||
expect(utf.contents).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context "when stream contains incomplete utf" do
|
||||
it "returns nil" do
|
||||
expect(described_class.decode(incomplete_utf_io)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when stream contains correct utf" do
|
||||
it "returns a Rex::Java::Serialization::Model::Utf" do
|
||||
expect(described_class.decode(sample_utf_io)).to be_a(Rex::Java::Serialization::Model::Utf)
|
||||
end
|
||||
|
||||
it "sets length to 0" do
|
||||
utf = described_class.decode(sample_utf_io)
|
||||
expect(utf.length).to eq(16)
|
||||
end
|
||||
|
||||
it "sets contents to sample string" do
|
||||
utf = described_class.decode(sample_utf_io)
|
||||
expect(utf.contents).to eq('java.lang.Number')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue