133 lines
5.4 KiB
Ruby
133 lines
5.4 KiB
Ruby
#!/usr/bin/env ruby
|
|
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
#
|
|
# Licence is LGPL, see LICENCE in the top-level directory
|
|
|
|
|
|
#
|
|
# this script disassembles an executable (elf/pe) using the GTK front-end
|
|
# use live:bla to open a running process whose filename contains 'bla'
|
|
#
|
|
# key binding (non exhaustive):
|
|
# Enter to follow a label (the current hilighted word)
|
|
# Esc to return to the previous position
|
|
# Space to switch between listing and graph views
|
|
# Tab to decompile (on already disassembled code)
|
|
# 'c' to start disassembling from the cursor position
|
|
# 'g' to go to a specific address (label/042h)
|
|
# 'l' to list known labels
|
|
# 'f' to list known functions
|
|
# 'x' to list xrefs to current address
|
|
# 'n' to rename a label (current word or current address)
|
|
# ctrl+'r' to run arbitrary ruby code in the context of the Gui objet (access to 'dasm', 'curaddr')
|
|
# ctrl+mousewheel to zoom in graph view ; also doubleclick on the background ('fit to window'/'reset zoom')
|
|
#
|
|
|
|
require 'metasm'
|
|
require 'optparse'
|
|
|
|
$VERBOSE = true
|
|
|
|
# parse arguments
|
|
opts = { :sc_cpu => 'Ia32' }
|
|
OptionParser.new { |opt|
|
|
opt.banner = 'Usage: disassemble-gtk.rb [options] <executable> [<entrypoints>]'
|
|
opt.on('--no-data-trace', 'do not backtrace memory read/write accesses') { opts[:nodatatrace] = true }
|
|
opt.on('--debug-backtrace', 'enable backtrace-related debug messages (very verbose)') { opts[:debugbacktrace] = true }
|
|
opt.on('-P <plugin>', '--plugin <plugin>', 'load a metasm disassembler/debugger plugin') { |h| (opts[:plugin] ||= []) << h }
|
|
opt.on('-e <code>', '--eval <code>', 'eval a ruby code') { |h| (opts[:hookstr] ||= []) << h }
|
|
opt.on('--map <mapfile>', 'load a map file (addr <-> name association)') { |f| opts[:map] = f }
|
|
opt.on('--fast', 'dasm cli args with disassemble_fast_deep') { opts[:fast] = true }
|
|
opt.on('--decompile') { opts[:decompile] = true }
|
|
opt.on('--gui <gtk|win32|qt>') { |g| ENV['METASM_GUI'] = g }
|
|
opt.on('--cpu <cpu>', 'the CPU class to use for a shellcode (Ia32, X64, ...)') { |c| opts[:sc_cpu] = c }
|
|
opt.on('--exe <exe_fmt>', 'the executable file format to use (PE, ELF, ...)') { |c| opts[:exe_fmt] = c }
|
|
opt.on('--rebase <addr>', 'rebase the loaded file to <addr>') { |a| opts[:rebase] = Integer(a) }
|
|
opt.on('-c <header>', '--c-header <header>', 'read C function prototypes (for external library functions)') { |h| opts[:cheader] = h }
|
|
opt.on('-a', '--autoload', 'loads all relevant files with same filename (.h, .map..)') { opts[:autoload] = true }
|
|
opt.on('-v', '--verbose') { $VERBOSE = true } # default
|
|
opt.on('-q', '--no-verbose') { $VERBOSE = false }
|
|
opt.on('-d', '--debug') { $DEBUG = $VERBOSE = true }
|
|
opt.on('-S <file>', '--session <sessionfile>', 'save user actions in this session file') { |a| opts[:session] = a }
|
|
opt.on('-N', '--new-session', 'start new session, discard old one') { opts[:newsession] = true }
|
|
}.parse!(ARGV)
|
|
|
|
case exename = ARGV.shift
|
|
when /^live:(.*)/
|
|
t = $1
|
|
t = t.to_i if $1 =~ /^[0-9]+$/
|
|
os = Metasm::OS.current
|
|
raise 'no such target' if not target = os.find_process(t) || os.create_process(t)
|
|
p target if $VERBOSE
|
|
w = Metasm::Gui::DbgWindow.new(target.debugger, "#{target.pid}:#{target.modules[0].path rescue nil} - metasm debugger")
|
|
dbg = w.dbg_widget.dbg
|
|
when /^(tcp:|udp:)?..+:/
|
|
dbg = Metasm::GdbRemoteDebugger.new(exename, opts[:sc_cpu])
|
|
w = Metasm::Gui::DbgWindow.new(dbg, "remote - metasm debugger")
|
|
else
|
|
w = Metasm::Gui::DasmWindow.new("#{exename + ' - ' if exename}metasm disassembler")
|
|
if exename
|
|
opts[:sc_cpu] = eval(opts[:sc_cpu]) if opts[:sc_cpu] =~ /[.(\s:]/
|
|
opts[:exe_fmt] = eval(opts[:exe_fmt]) if opts[:exe_fmt] =~ /[.(\s:]/
|
|
exe = w.loadfile(exename, opts[:sc_cpu], opts[:exe_fmt])
|
|
exe.disassembler.rebase(opts[:rebase]) if opts[:rebase]
|
|
if opts[:autoload]
|
|
basename = exename.sub(/\.\w\w?\w?$/, '')
|
|
opts[:map] ||= basename + '.map' if File.exist?(basename + '.map')
|
|
opts[:cheader] ||= basename + '.h' if File.exist?(basename + '.h')
|
|
(opts[:plugin] ||= []) << (basename + '.rb') if File.exist?(basename + '.rb')
|
|
opts[:session] ||= basename + '.metasm-session'
|
|
end
|
|
end
|
|
end
|
|
|
|
ep = ARGV.map { |arg| (?0..?9).include?(arg[0]) ? Integer(arg) : arg }
|
|
|
|
if exe
|
|
dasm = exe.disassembler
|
|
|
|
dasm.load_map opts[:map] if opts[:map]
|
|
dasm.parse_c_file opts[:cheader] if opts[:cheader]
|
|
dasm.backtrace_maxblocks_data = -1 if opts[:nodatatrace]
|
|
dasm.debug_backtrace = true if opts[:debugbacktrace]
|
|
dasm.callback_finished = lambda { w.dasm_widget.focus_addr w.dasm_widget.curaddr, :decompile ; dasm.decompiler.finalize } if opts[:decompile]
|
|
dasm.disassemble_fast_deep(*ep) if opts[:fast]
|
|
elsif dbg
|
|
dbg.load_map opts[:map] if opts[:map]
|
|
dbg.disassembler.parse_c_file opts[:cheader] if opts[:cheader]
|
|
opts[:plugin].to_a.each { |p|
|
|
begin
|
|
dbg.load_plugin(p)
|
|
rescue ::Exception
|
|
puts "Error with plugin #{p}: #{$!.class} #{$!}"
|
|
end
|
|
}
|
|
end
|
|
if dasm
|
|
w.display(dasm, ep)
|
|
opts[:plugin].to_a.each { |p|
|
|
begin
|
|
dasm.load_plugin(p)
|
|
rescue ::Exception
|
|
puts "Error with plugin #{p}: #{$!.class} #{$!}"
|
|
end
|
|
}
|
|
|
|
if opts[:session]
|
|
if File.exist?(opts[:session])
|
|
if opts[:newsession]
|
|
File.unlink(opts[:session])
|
|
else
|
|
puts "replaying session #{opts[:session]}"
|
|
w.widget.replay_session(opts[:session])
|
|
end
|
|
end
|
|
w.widget.save_session opts[:session]
|
|
end
|
|
end
|
|
|
|
opts[:hookstr].to_a.each { |f| eval f }
|
|
|
|
Metasm::Gui.main
|