diff --git a/lib/rex/proto/acpp/message.rb b/lib/rex/proto/acpp/message.rb index 177e4afba1..f797078167 100644 --- a/lib/rex/proto/acpp/message.rb +++ b/lib/rex/proto/acpp/message.rb @@ -79,33 +79,84 @@ module ACPP attr_accessor :status def initialize - @message_checksum = 0 @payload = '' @type = 0 @status = 0 - @password = 'public' + @password = '' end - def encode + # Get this Message as a String + # + # @return [String] the string representation of this Message + def to_s 'acpp' + [ 1, # unknown1 - @message_checksum, - Zlib::adler32(@payload), + message_checksum, + payload_checksum, @payload.size, 0, 0, # unknown2 @type, @status, - 0,0,0 # unknown3 + 0, 0, 0 # unknown3 ].pack('NNNNN2NNN3') + - Rex::Encoding::Xor::Generic.encode([@password].pack('a32'), XOR_KEY).first + + Rex::Encoding::Xor::Generic.encode([@password].pack('a32').slice(0, 32), XOR_KEY).first + ([0] * 12).pack('N12') + # unknown4 payload end - def to_s - @message_checksum = 0 - @message_checksum = Zlib::adler32(encode) - encode + # Compares this Message and another Message for equality + # + # @param other [Message] the Message to compare + # @return [Boolean] true iff the two messages have equal String representations, false otherwise + def ==(other) + to_s == other.to_s + end + + private + + # compute the 32-bit checksum of the payload + def payload_checksum + Zlib::adler32(@payload) + end + + # compute the 32-bit checksum of the entire message and payload + def message_checksum + Zlib::adler32( + 'acpp' + [ + 1, # unknown1 + 0, # message checksum is set to 0 during checksum calculation + payload_checksum, + @payload.size, + 0, 0, # unknown2 + @type, + @status, + 0, 0, 0 # unknown3 + ].pack('NNNNN2NNN3') + + Rex::Encoding::Xor::Generic.encode([@password].pack('a32').slice(0, 32), XOR_KEY).first + + ([0] * 12).pack('N12') + # unknown4 + payload + ) + end + + def self.decode(data, validate_checksum = true) + fail "Incorrect ACPP message size #{data.size}" unless data.size == 128 + fail 'Unexpected header' unless 'acpp' == data.slice!(0, 4) + unknown1 = data.slice!(0, 4) + read_message_checksum = data.slice!(0, 4).unpack('N').first + read_payload_checksum = data.slice!(0, 4).unpack('N').first + read_payload_size = data.slice!(0, 4).unpack('N').first + unknown2 = data.slice!(0, 8) + type = data.slice!(0, 4).unpack('N').first + status = data.slice!(0, 4).unpack('N').first + unknown3 = data.slice!(0, 12) + password = Rex::Encoding::Xor::Generic.encode(data.slice!(0, 32), XOR_KEY).first.strip + unknown4 = data.slice!(0, 48) + m = self.new + m.type = type + m.password = password + m.status = status + m.payload = data + m end end end diff --git a/spec/lib/rex/proto/acpp/message_spec.rb b/spec/lib/rex/proto/acpp/message_spec.rb index 1d5fe778f7..edbfff960e 100644 --- a/spec/lib/rex/proto/acpp/message_spec.rb +++ b/spec/lib/rex/proto/acpp/message_spec.rb @@ -9,18 +9,28 @@ describe Rex::Proto::ACPP::Message do described_class.new end - describe '#initialize' do - it 'constructs properly' do + # public.bin has the contents of a message type 20, password public message + # with a 612-byte retrieve payload that happens to be in a different packet + let(:retrieve_public_bin) do + IO.read(File.join(File.dirname(__FILE__), 'retrieve_public.bin')) + end + + let(:retrieve_public_message) do + m = described_class.new + m.password = 'public' + m.type = 20 + m + end + + describe '#to_s' do + it 'encodes properly' do + expect(retrieve_public_bin).to eq(retrieve_public_message.to_s) end end - describe '#encode' do - it 'constructs properly' do - message.password = 'public' - message.type = 20 - - File.open("/tmp/f", "wb") { |f| f.print(message.to_s) } - expect(IO.read(File.join(File.dirname(__FILE__), 'public.bin'))).to eq(message.to_s) + describe '#decode' do + it 'decodes properly' do + expect(retrieve_public_message).to eq(described_class.decode(retrieve_public_bin)) end end end diff --git a/spec/lib/rex/proto/acpp/public.bin b/spec/lib/rex/proto/acpp/retrieve_public.bin similarity index 100% rename from spec/lib/rex/proto/acpp/public.bin rename to spec/lib/rex/proto/acpp/retrieve_public.bin