93 lines
2.6 KiB
Ruby
93 lines
2.6 KiB
Ruby
# 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 <packed.exe> [<dumped.exe>]
|
|
#
|
|
|
|
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
|