196 lines
5.2 KiB
Ruby
196 lines
5.2 KiB
Ruby
module Msf
|
|
|
|
module Exploit::Git
|
|
|
|
SIGNATURE = 'PACK'
|
|
VERSION = 2
|
|
|
|
##
|
|
# object types
|
|
##
|
|
OBJ_COMMIT = 1
|
|
OBJ_TREE = 2
|
|
OBJ_BLOB = 3
|
|
OBJ_TAG = 4
|
|
# ? = 5
|
|
# type 5 is reserved
|
|
# see: https://git-scm.com/docs/pack-format
|
|
OBJ_OFS_DELTA = 6
|
|
OBJ_REF_DELTA = 7
|
|
|
|
class Packfile
|
|
|
|
attr_reader :version, :git_objects, :data, :checksum
|
|
|
|
def initialize(version = nil, objs)
|
|
@version = version.nil? ? VERSION : version.to_i
|
|
@git_objects = objs.kind_of?(Array) ? objs : [ objs ]
|
|
|
|
@data = header
|
|
@data << format_objects
|
|
@checksum = Digest::SHA1.hexdigest(@data)
|
|
@data << [ @checksum ].pack('H*')
|
|
end
|
|
|
|
def header
|
|
hdr = SIGNATURE
|
|
hdr << [ @version ].pack('N')
|
|
hdr << [ @git_objects.length ].pack('N')
|
|
hdr
|
|
end
|
|
|
|
# Each object has a variable-sized
|
|
# header, with the size being determined
|
|
# by the length of the object's original,
|
|
# uncompressed content
|
|
def format_objects
|
|
type = 0
|
|
obj_stream = []
|
|
|
|
@git_objects.each do |obj|
|
|
byte_amt = 1
|
|
obj_data_size = obj.content.length
|
|
case obj.type
|
|
when 'blob'
|
|
type = OBJ_BLOB
|
|
when 'tree'
|
|
type = OBJ_TREE
|
|
when 'commit'
|
|
type = OBJ_COMMIT
|
|
end
|
|
|
|
num_bits = 0
|
|
num = obj_data_size
|
|
while num != 0
|
|
num /= 2
|
|
num_bits += 1
|
|
end
|
|
|
|
# the first byte can only hold
|
|
# four bits of the size of the
|
|
# object's content since the
|
|
# leading bits are reserved for
|
|
# value of MSB and object type
|
|
if num_bits > 4
|
|
if num_bits > 11
|
|
byte_amt = num_bits / 7
|
|
byte_amt += 1 if (num_bits % 7 > 0)
|
|
else
|
|
byte_amt = 2
|
|
end
|
|
end
|
|
|
|
shift = 0
|
|
(1..byte_amt).each do |byte|
|
|
curr_byte = 0
|
|
# set msb if needed
|
|
if byte < byte_amt
|
|
curr_byte |= 128
|
|
end
|
|
|
|
# set the object type
|
|
# set last four bits for content size
|
|
if byte == 1
|
|
curr_byte |= (type << 4)
|
|
curr_byte |= (obj_data_size & 15)
|
|
else
|
|
curr_byte = (obj_data_size >> 4 >> shift) & 127
|
|
shift += 7
|
|
end
|
|
|
|
obj_stream << [ curr_byte ].pack('C*')
|
|
end
|
|
|
|
# Since the object type is denoted in the preceding
|
|
# info, we only store the compressed object data
|
|
obj_stream << Rex::Text.zlib_deflate(obj.content, Zlib::DEFAULT_COMPRESSION)
|
|
end
|
|
|
|
obj_stream = obj_stream.join
|
|
end
|
|
|
|
def self.read_packfile(data)
|
|
return nil unless data && !data.empty?
|
|
|
|
pack_start = data.index('PACK')
|
|
return nil unless pack_start
|
|
|
|
data = data[pack_start..-1]
|
|
version = data[4..7].unpack('N').first
|
|
obj_count = data[8..11].unpack('N').first
|
|
puts("Reading packfile containing #{obj_count} objects")
|
|
curr_pos = 12
|
|
|
|
type = ''
|
|
pack_objs = []
|
|
(1..obj_count).each do |obj_index|
|
|
# determine the current object's type first
|
|
first_byte = data[curr_pos].unpack('C').first
|
|
num_type = (first_byte & 0b01110000) >> 4
|
|
case num_type
|
|
when OBJ_COMMIT
|
|
type = 'commit'
|
|
when OBJ_TREE
|
|
type = 'tree'
|
|
when OBJ_BLOB
|
|
type = 'blob'
|
|
when OBJ_OFS_DELTA
|
|
type = 'ofs-delta'
|
|
when OBJ_REF_DELTA
|
|
type = 'ref-delta'
|
|
end
|
|
|
|
# now determine the size of the object's uncompressed data
|
|
shift = 4
|
|
curr_byte = first_byte
|
|
size = curr_byte & 0b00001111
|
|
keep_reading = false
|
|
puts "Current byte: #{curr_byte}"
|
|
if curr_byte >= 128
|
|
puts 'KEEP READING'
|
|
keep_reading = true
|
|
end
|
|
|
|
curr_pos += 1
|
|
while keep_reading
|
|
curr_byte = data[curr_pos].unpack('C').first
|
|
puts "Current byte: #{curr_byte}"
|
|
if curr_byte < 128
|
|
keep_reading = false
|
|
end
|
|
|
|
size = (curr_byte << shift) | size
|
|
shift += 7
|
|
curr_pos += 1
|
|
end
|
|
|
|
# now decompress content and create Git object
|
|
case type
|
|
when 'ofs-delta'
|
|
require 'pry';binding.pry
|
|
when 'ref-delta'
|
|
#require 'pry';binding.pry
|
|
base_obj_sha = data[curr_pos..curr_pos+19].unpack('H*').first
|
|
curr_pos += 20
|
|
end
|
|
|
|
puts "Decompressing content, size #{size}, for object: #{obj_index}"
|
|
puts "Position number: #{curr_pos}"
|
|
puts "Byte at current position: #{data[curr_pos].unpack('C').first}"
|
|
content = Rex::Text.zlib_inflate(data[curr_pos..-1])
|
|
puts "Decompressed content: #{content}, length: #{content.length}"
|
|
sha1, compressed = GitObject.build_object(type, content)
|
|
pack_objs << GitObject.new(type, content, sha1, compressed)
|
|
|
|
# update curr_pos to point to next obj header
|
|
puts "Compressing object ##{obj_index} content in order to find next object"
|
|
compressed_len = Rex::Text.zlib_deflate(content, Zlib::DEFAULT_COMPRESSION).length
|
|
curr_pos = curr_pos + compressed_len
|
|
end
|
|
|
|
pack_objs
|
|
end
|
|
end
|
|
end
|
|
end
|