Class: Msf::Exploit::Git::Packfile

Inherits:
Object
  • Object
show all
Defined in:
lib/msf/core/exploit/git/packfile.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(version = nil, objs) ⇒ Packfile

Returns a new instance of Packfile.


25
26
27
28
29
30
31
32
# File 'lib/msf/core/exploit/git/packfile.rb', line 25

def initialize(version = nil, objs)
  @version = version.nil? ? VERSION : version.to_i
  @git_objects = objs.kind_of?(Array) ? objs : [ objs ]

  pre_check_data = header + format_objects
  @checksum = Digest::SHA1.hexdigest(pre_check_data)
  @data = pre_check_data + [ @checksum ].pack('H*')
end

Instance Attribute Details

#checksumObject (readonly)

Returns the value of attribute checksum.


23
24
25
# File 'lib/msf/core/exploit/git/packfile.rb', line 23

def checksum
  @checksum
end

#dataObject (readonly)

Returns the value of attribute data.


23
24
25
# File 'lib/msf/core/exploit/git/packfile.rb', line 23

def data
  @data
end

#git_objectsObject (readonly)

Returns the value of attribute git_objects.


23
24
25
# File 'lib/msf/core/exploit/git/packfile.rb', line 23

def git_objects
  @git_objects
end

#versionObject (readonly)

Returns the value of attribute version.


23
24
25
# File 'lib/msf/core/exploit/git/packfile.rb', line 23

def version
  @version
end

Class Method Details

.apply_delta(delta, git_objects) ⇒ Object


285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/msf/core/exploit/git/packfile.rb', line 285

def self.apply_delta(delta, git_objects)
  target = nil

  case delta[:inst]
  when 'copy'
    base_obj = GitObject.find_object(delta[:base], git_objects)
    return nil unless base_obj

    offset = delta[:offset]
    size = delta[:size]
    type = base_obj.type

    content = base_obj.content
    content = content[offset..offset + size - 1]
    sha1, compressed = GitObject.build_object(type, content)
    target = GitObject.new(type, content, sha1, compressed)
  when 'insert'
    size = delta[:size]
    base_obj = GitObject.find_object(delta[:base], git_objects)
    type = base_obj.type
    sha1, compressed = GitObject.build_object(type, delta[:data])
    target = GitObject.new(type, delta[:data], sha1, compressed)
  end

  target
end

.get_variable_len_num(data, curr_pos) ⇒ Object


312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/msf/core/exploit/git/packfile.rb', line 312

def self.get_variable_len_num(data, curr_pos)
  shift = 7
  curr_byte = data[curr_pos].unpack('C').first
  offset = curr_byte & 0b01111111
  curr_pos += 1

  while curr_byte >= 128
    curr_byte = data[curr_pos].unpack('C').first
    offset = (offset << shift) | (curr_byte & 0b01111111)
    shift += 7
    curr_pos += 1
  end
  new_pos = curr_pos

  return offset, new_pos
end

.read_delta(type, content, base_obj_sha) ⇒ Object


196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/msf/core/exploit/git/packfile.rb', line 196

def self.read_delta(type, content, base_obj_sha)
  source_len = 0
  target_len = 0

  delta = { type: type, base: base_obj_sha }

  start = 0
  base_len, start = get_variable_len_num(content, start)
  target_len, start = get_variable_len_num(content, start)

  inst_type = ''
  inst = content[start].unpack('C').first
  start += 1
  num_bytes = 0
  if inst >= 128
    inst_type = 'copy'
    # now determine the offset
    shift = 0
    offset_mask = []
    off_bits = inst & 0b1111
    (0..3).each do |idx|
      if (off_bits >> idx) & 1 == 1
        num_bytes += 1
        offset_mask.prepend(0b11111111)
      else
        offset_mask.prepend(0b00000000)
      end
    end

    offset = 0
    unless num_bytes == 0
      shift = 0
      byte_idx = 0
      off_bytes = content[start].unpack("C#{num_bytes}")

      (0..3).each do |idx|
        if offset_mask[3 - idx] == 255
          offset |= ((off_bytes[byte_idx] & offset_mask[3 - idx]) << shift)
          byte_idx += 1
        else
          offset |= (0 << shift)
        end
        shift += 7
      end
    end

    delta[:offset] = offset
    size = 0
    num_bytes = 0
    size_mask = []
    size_bits = (inst & 0b01110000) >> 4
    start += num_bytes
    if size_bits == 0
      size = 0x10000
    else
      (0..2).each do |idx|
        if (size_bits >> idx) & 1 == 1
          num_bytes += 1
          size_mask.prepend(0b11111111)
        else
          size_mask.prepend(0b00000000)
        end
      end

      shift = 0
      byte_num = 0
      size_bytes = content[start].unpack("C#{num_bytes}")
      start += num_bytes
      (0..2).each do |idx|
        if size_mask[2 - idx] == 255
          size |= ((size_bytes[byte_num] & size_mask[2 - idx]) << shift)
          byte_num += 1
        else
          size |= (0 << shift)
        end
        shift += 7
      end
    end
  else
    inst_type = 'insert'
    size = inst & 0b0111111
    delta[:data] = content[start..start + size - 1]
  end
  delta[:size] = size
  delta[:inst] = inst_type

  delta
end

.read_packfile(data) ⇒ Object

Read the contents of the packfile and constructs the objects found return Array of GitObjects found in the packfile

Parameters:

  • the (String)

    packfile data


112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/msf/core/exploit/git/packfile.rb', line 112

def self.read_packfile(data)
  return nil unless data
  return nil if 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
  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
    if curr_byte >= 128
      keep_reading = true
    end

    curr_pos += 1
    while keep_reading
      curr_byte = data[curr_pos].unpack('C').first
      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'
      # get negative offset
      offset, curr_pos = get_variable_len_num(data, curr_pos)
      base_start = curr_pos - offset
      base_obj_sha = data[base_start..base_start+19].unpack('H*').first
    when 'ref-delta'
      base_obj_sha = data[curr_pos..curr_pos+19].unpack('H*').first
      curr_pos += 20
    end

    content = Rex::Text.zlib_inflate(data[curr_pos..-1])

    # delta objects are object types specific to packfile
    # and do not follow same format as other Git objects
    if type == 'ofs-delta' || type == 'ref-delta'
      delta_obj = read_delta(type, content, base_obj_sha)
      pack_objs << apply_delta(delta_obj, pack_objs)
    else
      sha1, compressed = GitObject.build_object(type, content)
      pack_objs << GitObject.new(type, content, sha1, compressed)
    end

    # update curr_pos to point to next obj header
    compressed_len = Rex::Text.zlib_deflate(content, Zlib::DEFAULT_COMPRESSION).length
    curr_pos = curr_pos + compressed_len
  end

  pack_objs
end

Instance Method Details

#format_objectsObject

Each object has a variable-sized header, with the size being determined by the length of the object's original, uncompressed content


42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/msf/core/exploit/git/packfile.rb', line 42

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

#headerObject


34
35
36
# File 'lib/msf/core/exploit/git/packfile.rb', line 34

def header
  SIGNATURE + [ @version ].pack('N') + [ @git_objects.length ].pack('N')
end