samples/dump_upx.rb in metasm-1.0.1 vs samples/dump_upx.rb in metasm-1.0.2
- old
+ new
@@ -8,41 +8,46 @@
#
# 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 <packed.exe> [<dumped.exe>] [<rva iat>]
+# usage: dump_upx.rb <packed.exe> [<dumped.exe>] [<iat (r)va>] [--oep <rva>]
#
require 'metasm'
include Metasm
class UPXUnpacker
# 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
+ def initialize(file, oep, iat, dumpfile)
+ @dumpfile = dumpfile
+ @dumpfile ||= file.chomp('.exe') + '.dump.exe'
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
+ @oep = oep
+ @oep -= @baseaddr if @oep and @oep > @baseaddr # va => rva
+ @oep ||= find_oep(pe)
+ raise 'cant find oep...' if not @oep
+ puts "oep found at #{Expression[@oep]}" if not oep
+
+ @iat = iat
+ @iat -= @baseaddr if @iat and @iat > @baseaddr
+
@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'
+ dasm = pe.disassemble_fast_deep '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
@@ -78,18 +83,25 @@
# 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
+ # as UPX strips the relocation information, mark the exe to opt-out from ASLR
+ dump.encode_file @dumpfile, 'exe', false
puts 'dump complete'
ensure
# kill the process
@dbg.kill
end
end
if __FILE__ == $0
- # args: packed [unpacked] [iat rva]
- UPXUnpacker.new(ARGV.shift, ARGV.shift, (Integer(ARGV.shift) rescue nil))
+ fn = ARGV.shift
+ oep = Integer(ARGV.shift) unless ARGV.empty?
+ oep = nil if oep == -1
+ iat = Integer(ARGV.shift) unless ARGV.empty?
+ iat = nil if iat == -1
+ out = ARGV.shift
+ abort 'usage: dump <exe> [<oep>] [<iat>] [<outfile>]' if not File.exist?(fn)
+ UPXUnpacker.new(fn, oep, iat, out)
end