#    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 is a simple executable tracer for Windows using the
# Metasm windows debug api abstraction
# all callbacks are full ruby, so this is extremely slow !
#

require 'metasm'
Metasm.require 'samples/metasm-shell'

class Tracer < Metasm::WinDbgAPI
	def initialize(*a)
		super(*a)
		@label = {}
		@prog = Metasm::ExeFormat.new(Metasm::Ia32.new)
		loop
		puts 'finished'
	end

	def handler_newprocess(pid, tid, info)
		hide_debugger(pid, tid, info)
		Metasm::WinAPI::DBG_CONTINUE
	end

	def handler_newthread(pid, tid, info)
		do_singlestep(pid, tid)
		Metasm::WinAPI::DBG_CONTINUE
	end

	def handler_exception(pid, tid, info)
		do_singlestep(pid, tid) if @hthread[pid] and @hthread[pid][tid]
		case info.code
		when Metasm::WinAPI::STATUS_SINGLE_STEP
			Metasm::WinAPI::DBG_CONTINUE
		else super(pid, tid, info)
		end
	end

	def handler_loaddll(pid, tid, info)
		# update @label with exported symbols
		pe = Metasm::LoadedPE.load(@mem[pid][info.imagebase, 0x1000000])
		pe.decode_header
		pe.decode_exports
		libname = read_str_indirect(pid, info.imagename, info.unicode)
		if pe.export
			libname = pe.export.libname if libname == ''
			pe.export.exports.each { |e|
				next if not r = pe.label_rva(e.target)
				@label[info.imagebase + r] = libname + '!' + (e.name || "ord_#{e.ordinal}")
			}
		end
		super(pid, tid, info)
	end

	# dumps the opcode at eip, sets the trace flag
	def do_singlestep(pid, tid)
		ctx = get_context(pid, tid)
		eip = ctx[:eip]

		if l = @label[eip]
			puts l + ':'
		end
		if $VERBOSE
		bin = @mem[pid][eip, 16]
		di = @prog.cpu.decode_instruction(Metasm::EncodedData.new(bin), eip)
		puts "#{'%08X' % eip} #{di.instruction}"
		end

		ctx[:eflags] |= 0x100
	end

	# resets the DebuggerPresent field of the PEB
	def hide_debugger(pid, tid, info)
		peb = @mem[pid][info.threadlocalbase + 0x30, 4].unpack('L').first
		@mem[pid][peb + 2, 2] = [0].pack('S')
	end
end

if $0 == __FILE__
	Metasm::WinOS.get_debug_privilege
	if ARGV.empty?
		# display list of running processes if no target found
		puts Metasm::WinOS.list_processes.sort_by { |pr_| pr_.pid }
		abort 'target needed'
	end
	Tracer.new ARGV.shift.dup
end
