2009-12-30 22:24:22 +00:00
|
|
|
# This file is part of Metasm, the Ruby assembly manipulation suite
|
2010-09-09 18:19:35 +00:00
|
|
|
# Copyright (C) 2006-2009 Yoann GUILLOT
|
2009-12-30 22:24:22 +00:00
|
|
|
# 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.
|
|
|
|
#
|
2011-04-06 17:40:01 +00:00
|
|
|
# usage: dump_upx.rb <packed.exe> [<dumped.exe>] [<rva iat>]
|
2009-12-30 22:24:22 +00:00
|
|
|
#
|
|
|
|
|
|
|
|
require 'metasm'
|
|
|
|
include Metasm
|
|
|
|
|
2010-09-09 18:19:35 +00:00
|
|
|
class UPXUnpacker
|
2013-08-30 21:28:33 +00:00
|
|
|
# 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
|
|
|
|
|
|
|
|
puts 'disassembling UPX loader...'
|
|
|
|
pe = PE.decode_file(file)
|
|
|
|
@oep = find_oep(pe)
|
|
|
|
raise 'cant find oep...' if not @oep
|
|
|
|
puts "oep found at #{Expression[@oep]}"
|
|
|
|
@baseaddr = pe.optheader.image_base
|
|
|
|
@iat -= @baseaddr if @iat > @baseaddr # va => rva
|
|
|
|
|
|
|
|
@dbg = OS.current.create_process(file).debugger
|
|
|
|
puts 'running...'
|
|
|
|
debugloop
|
|
|
|
end
|
|
|
|
|
|
|
|
# disassemble the upx stub to find a cross-section jump (to the real entrypoint)
|
|
|
|
def find_oep(pe)
|
|
|
|
dasm = pe.disassemble_fast 'entrypoint'
|
|
|
|
|
|
|
|
return if not jmp = dasm.decoded.find { |addr, di|
|
|
|
|
# check only once per basic block
|
|
|
|
next if not di.block_head?
|
|
|
|
b = di.block
|
|
|
|
# our target has only one follower
|
|
|
|
next if b.to_subfuncret.to_a.length != 0 or b.to_normal.to_a.length != 1
|
|
|
|
to = b.to_normal.first
|
|
|
|
# ignore jump to unmmaped address
|
|
|
|
next if not s = dasm.get_section_at(to)
|
|
|
|
# ignore jump to same section
|
|
|
|
next if dasm.get_section_at(di.address) == s
|
|
|
|
|
|
|
|
# gotcha !
|
|
|
|
true
|
|
|
|
}
|
|
|
|
|
|
|
|
# now jmp is a couple [addr, di], we extract and normalize the oep from there
|
|
|
|
dasm.normalize(jmp[1].block.to_normal.first)
|
|
|
|
end
|
|
|
|
|
|
|
|
def debugloop
|
|
|
|
# set up a oneshot breakpoint on oep
|
|
|
|
@dbg.hwbp(@oep, :x, 1, true) { breakpoint_callback }
|
|
|
|
@dbg.run_forever
|
|
|
|
puts 'done'
|
|
|
|
end
|
|
|
|
|
|
|
|
def breakpoint_callback
|
|
|
|
puts 'breakpoint hit !'
|
|
|
|
|
|
|
|
# dump the process
|
|
|
|
# create a genuine PE object from the memory image
|
|
|
|
dump = LoadedPE.memdump @dbg.memory, @baseaddr, @oep, @iat
|
|
|
|
|
|
|
|
# the UPX loader unpacks everything in sections marked read-only in the PE header, make them writeable
|
|
|
|
dump.sections.each { |s| s.characteristics |= ['MEM_WRITE'] }
|
|
|
|
|
|
|
|
# write the PE file to disk
|
|
|
|
dump.encode_file @dumpfile
|
|
|
|
|
|
|
|
puts 'dump complete'
|
|
|
|
ensure
|
|
|
|
# kill the process
|
|
|
|
@dbg.kill
|
|
|
|
end
|
2009-12-30 22:24:22 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
if __FILE__ == $0
|
2013-08-30 21:28:33 +00:00
|
|
|
# args: packed [unpacked] [iat rva]
|
|
|
|
UPXUnpacker.new(ARGV.shift, ARGV.shift, (Integer(ARGV.shift) rescue nil))
|
2009-12-30 22:24:22 +00:00
|
|
|
end
|