#!/usr/bin/env ruby
# -*- coding: binary -*-

require 'rex/machparsey'
require 'rex/machscan'
require 'rex/arch/x86'
require 'optparse'


def opt2i(o)
    o.index("0x")==0 ? o.hex : o.to_i
end

opt = OptionParser.new

opt.banner = "Usage: #{$PROGRAM_NAME} [mode] <options> [targets]"
opt.separator('')
opt.separator('Modes:')

worker = nil
param = {}

opt.on('-j', '--jump [regA,regB,regC]', 'Search for jump equivalent instructions') do |t|
  # take csv of register names (like eax,ebx) and convert
  # them to an array of register numbers
  regnums = t.split(',').collect { |o|
    begin
      Rex::Arch::X86.reg_number(o)
    rescue
      puts "Invalid register \"#{o}\""
      exit(1)
    end
  }
  worker = Rex::MachScan::Scanner::JmpRegScanner
  param['args'] = regnums
end

opt.on('-p', '--poppopret', 'Search for pop+pop+ret combinations') do |t|
  worker = Rex::MachScan::Scanner::PopPopRetScanner
  param['args'] = t
end

opt.on('-r', '--regex [regex]', 'Search for regex match') do |t|
  worker = Rex::MachScan::Scanner::RegexScanner
  param['args'] = t
end

opt.separator('')
opt.separator('Options:')

opt.on('-A', '--after [bytes]', 'Number of bytes to show after match (-a/-b)') do |t|
  param['after'] = opt2i(t)
end

opt.on('-B', '--before [bytes]', 'Number of bytes to show before match (-a/-b)') do |t|
  param['before'] = opt2i(t)
end

opt.on('-I', '--image-base [address]', 'Specify an alternate ImageBase') do |t|
  param['imagebase'] = opt2i(t)
end

opt.on_tail("-h", "--help", "Show this message") do
  puts opt
  exit
end

begin
  opt.parse!
rescue OptionParser::InvalidOption
  puts "Invalid option, try -h for usage"
  exit(1)
end

if (! worker)
  puts opt
  exit(1)
end

ARGV.each do |file|

  param['file'] = file

  begin
    mach = Rex::MachParsey::Mach.new_from_file(file, true)
    o = worker.new(mach)
    o.scan(param)
    mach.close
  rescue Rex::MachParsey::MachHeaderError
    $stderr.puts("File is not a Mach-O binary, trying Fat..\n")
    fat = Rex::MachParsey::Fat.new_from_file(file, true)
    o = worker.new(fat)
    o.scan(param)
    fat.close
  rescue Errno::ENOENT
    $stderr.puts("File does not exist: #{file}")
    next
  end
end

