# This file is part of Metasm, the Ruby assembly manipulation suite # Copyright (C) 2007 Yoann GUILLOT # Original script and idea by Alexandre GAZET # # Licence is LGPL, see LICENCE in the top-level directory # # this script will load an upx-packed windows executable, find its # original entrypoint by disassembling the UPX stub, set breakpoint on it, # run the program, and dump the loaded image to an executable PE. # # usage: dump_upx.rb [] # require 'metasm' include Metasm class UPXUnpacker < WinDbg # loads the file # find the oep by disassembling # run it until the oep # dump the memory image def initialize(file, dumpfile, iat_rva=nil) @dumpfile = dumpfile || 'upx-dumped.exe' @iat = iat_rva pe = PE.decode_file(file) puts 'disassembling UPX loader...' @oep = find_oep(pe) puts "oep found at #{@oep.to_s 16}" @baseaddr = pe.optheader.image_base super(file.dup) puts 'running...' debugloop end # disassemble the upx stub to find a cross-section jump (to the real entrypoint) def find_oep(pe) dasm = pe.init_disassembler dasm.backtrace_maxblocks_data = -1 # speed up dasm dasm.disassemble 'entrypoint' jmp = dasm.decoded.find { |addr, di| di.instruction.opname == 'jmp' and s = dasm.get_section_at(di.instruction.args[0]) and s != dasm.get_section_at(addr) }[1].instruction dasm.normalize(jmp.args[0]) end # when the initial thread is created, set a hardware breakpoint to the entrypoint def handler_newthread(pid, tid, info) super(pid, tid, info) puts "oep breakpoint set..." ctx = get_context(pid, tid) ctx[:dr0] = @oep ctx[:dr6] = 0 ctx[:dr7] = 1 WinAPI::DBG_CONTINUE end # when our breakpoint hits, dump the file and terminate the process def handler_exception(pid, tid, info) if info.code == WinAPI::STATUS_SINGLE_STEP and get_context(pid, tid)[:eip] == @oep puts 'oep breakpoint hit !' puts 'dumping...' begin # dump the loaded pe to a genuine PE object dump = LoadedPE.memdump @mem[pid], @baseaddr, @oep, @iat # the UPX loader unpacks everything in the first section which is marked read-only in the PE header, we must make it writeable dump.sections.first.characteristics = %w[MEM_READ MEM_WRITE MEM_EXECUTE] # write the PE file dump.encode_file @dumpfile ensure # kill the process WinAPI.terminateprocess(@hprocess[pid], 0) end puts 'done.' WinAPI::DBG_CONTINUE else super(pid, tid, info) end end end if __FILE__ == $0 # packed [unpacked] [iat rva] UPXUnpacker.new(ARGV.shift, ARGV.shift, (Integer(ARGV.shift) rescue nil)) end