Add initial support for Java serialization

bug/bundler_fix
jvazquez-r7 2014-12-01 19:07:45 -06:00
parent 394d132d33
commit 5f11c70d7f
9 changed files with 587 additions and 0 deletions

View File

@ -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'

View File

@ -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'

View File

@ -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

View File

@ -0,0 +1,10 @@
module Rex
module Java
module Serialization
module Model
class EndBlockData < Element
end
end
end
end
end

View File

@ -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

View File

@ -0,0 +1,10 @@
module Rex
module Java
module Serialization
module Model
class NullReference < Element
end
end
end
end
end

View File

@ -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

View File

@ -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

View File

@ -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