#!/usr/bin/env ruby
#
# Searchsploit Ruby Edition
# By: Hood3dRob1n
#
# Exploit-db now on Github: 
# git clone https://github.com/offensive-security/exploit-database.git
#
# The original bash searchsploit script is too simple sometimes (and a little outdated)
# Plus, who doesn't like logging and a little color to mix things up ;p
#

######## Exploit-DB Files.csv Path ########
CSV='/home/clue/fun/exploit-db/files.csv' #
###########################################

###### STD GEMS ######
require 'optparse'   #
require 'fileutils'  #
#### NON-STD GEMS ####
require 'rubygems'   #
require 'colorize'   #
######################

# Catch System Interupts
trap("SIGINT") {
  puts "\n\nWARNING".light_red + "!".white + " CTRL".light_red + "+".white + "C Detected".light_red + ",".white + " Shutting things down".light_red + "....\n\n".white
  exit 666;
}

# 0x1337 Banner
def banner
  puts
  puts "Searchsploit-rb".light_red + ": An Exploit-DB Search Tool".white
end

# Clear
def cls
  if RUBY_PLATFORM =~ /win32|win64|\.NET|windows|W0W64/i
    system('cls')
  else
    system('clear')
  end
end

# Update to latest and greatest
# Leverages the new Github format
# Much better than older HTTP downloads!
def git_update(dir=nil)
  if dir.nil?
    directory = "#{CSV.split("/")[0..-2].join("/")}/"
  else
    directory = dir
  end
  Dir.chdir(directory) do 
    system('git pull')
  end
end

# Fetch fresh copy of exploit-db
# Git clones to local dir
# renames: exploit-db
def git_get(dir=nil)
  if dir.nil?
    directory = Dir.pwd
  else
    directory = dir
  end
  Dir.chdir(directory) do 
    system('git clone https://github.com/offensive-security/exploit-database.git exploit-db')
  end
end

# Search based on initial full CSV results set against platform
# This initializes our array to perform further searchs
# We wind down narrowing results as we go
# Due to poor CSV consistency this is the best I could come up with
def search_platform(query)
  platform_results=[]
  IO.foreach(CSV) do |line|
    line = line.unpack('C*').pack('U*') if !line.valid_encoding? # Thanks Stackoverflow :)
    if line =~ /(\".+,.+\")/ # Deal with annoying commans within quotes as they shouldn't be used to split on (ahrg)
      crappy_csv_line = $1
      not_as_crappy_csv_line = crappy_csv_line.sub(",", "")
      workable_csv_line = line.sub!("#{crappy_csv_line}","#{not_as_crappy_csv_line}").split(",")
    else
      workable_csv_line = line.split(",")
    end
    foo = workable_csv_line - workable_csv_line.slice(0,5)
    foobar = foo - workable_csv_line.slice(-1, 1) - workable_csv_line.slice(-2, 1)
    if query == 'nil'
      platform_results << line
    else
      platform_results << line if "#{foobar.join(",")}" =~ /#{query}/i
    end
  end
  return platform_results
end

# Search based on TYPE
# Returns an array with the results
def search_type(exploits_array, query)
  search_results=[]
  exploits_array.each do |line|
    line = line.unpack('C*').pack('U*') if !line.valid_encoding?
    if line =~ /(\".+,.+\")/ 
      crappy_csv_line = $1
      not_as_crappy_csv_line = crappy_csv_line.sub(",", "")
      workable_csv_line = line.sub!("#{crappy_csv_line}","#{not_as_crappy_csv_line}").split(",")
    else
      workable_csv_line = line.split(",")
    end
    foo = workable_csv_line - workable_csv_line.slice(0,5)
    foobar = foo - workable_csv_line.slice(-1, 1) - workable_csv_line.slice(-2, 1)
    if query == 'nil'
      search_results << line
    else
      if not workable_csv_line[-2].nil?
        search_results << line if "#{workable_csv_line[-2].downcase}" =~ /#{query}/i
      end
    end
  end
  return search_results
end

# Search based on Author
# Returns an array with the results
def search_author(exploits_array, query)
  search_results=[]
  exploits_array.each do |line|
    line = line.unpack('C*').pack('U*') if !line.valid_encoding?
    if line =~ /(\".+,.+\")/ 
      crappy_csv_line = $1
      not_as_crappy_csv_line = crappy_csv_line.sub(",", "")
      workable_csv_line = line.sub!("#{crappy_csv_line}","#{not_as_crappy_csv_line}").split(",")
    else
      workable_csv_line = line.split(",")
    end
    foo = workable_csv_line - workable_csv_line.slice(0,5)
    foobar = foo - workable_csv_line.slice(-1, 1) - workable_csv_line.slice(-2, 1)
    if query == 'nil'
      search_results << line
    else
      if not workable_csv_line[4].nil?
        search_results << line if "#{workable_csv_line[4].downcase}" =~ /#{query}/i
      end
    end
  end
  return search_results
end

# Search based on PORT
# Returns an array with the results
def search_port(exploits_array, query)
  search_results=[]
  exploits_array.each do |line|
    line = line.unpack('C*').pack('U*') if !line.valid_encoding?
    if line =~ /(\".+,.+\")/ 
      crappy_csv_line = $1
      not_as_crappy_csv_line = crappy_csv_line.sub(",", "")
      workable_csv_line = line.sub!("#{crappy_csv_line}","#{not_as_crappy_csv_line}").split(",")
    else
      workable_csv_line = line.split(",")
    end
    foo = workable_csv_line - workable_csv_line.slice(0,5)
    foobar = foo - workable_csv_line.slice(-1, 1) - workable_csv_line.slice(-2, 1)
    if query == 'nil'
      search_results << line
    else
      if not workable_csv_line[-1].nil?
        search_results << line if "#{workable_csv_line[-1].downcase}" =~ /#{query}/i
      end
    end
  end
  return search_results
end

# Search based on SEARCH Term
# Returns an array with the results
def search_search(exploits_array, query)
  search_results=[]
  exploits_array.each do |line|
    line = line.unpack('C*').pack('U*') if !line.valid_encoding?
    if query == 'nil'
      search_results << line
    else
      search_results << line if line =~ /#{query}/i
    end
  end
  return search_results
end

# Parse remaining exploits in array
# Print out the search results for user
# log to file as well if requested
def display_results(remaining_exploits)
  rezsize = remaining_exploits.size
  puts "[".light_green + "*".white + "] ".light_green + "Saved #{rezsize} Result(s) to: #{@out}".white if @log
  puts "[".light_green + "*".white + "] ".light_green + "Displaying Result(s):".white if @verbose and @log
  puts "[".light_green + "*".white + "] ".light_green + "Found #{rezsize} Result(s):".white if @verbose and not @log
  remaining_exploits.each do |line|
    if line =~ /(\".+,.+\")/
      crappy_csv_line = $1
      not_as_crappy_csv_line = crappy_csv_line.sub(",", "")
      workable_csv_line = line.sub!("#{crappy_csv_line}","#{not_as_crappy_csv_line}").split(",")
    else
      workable_csv_line = line.split(",")
    end
    foo = workable_csv_line - workable_csv_line.slice(0,5)
    foobar = foo - workable_csv_line.slice(-1, 1) - workable_csv_line.slice(-2, 1)
    if @log
      f = File.open("#{@out}", 'a+')
      f.puts "Description: #{workable_csv_line[2]}"
      f.puts "Location: #{CSV.split("/")[0..-2].join("/")}/#{workable_csv_line[1]}"
      f.puts "Exploit ID: #{workable_csv_line[0]}"
      f.puts "Platform: #{foobar.join(",")}"
      f.puts "Type: #{workable_csv_line[-2]}"
      if not "#{workable_csv_line[-1].chomp}".to_i == 0
        f.puts "Port: #{workable_csv_line[-1].chomp}"
      end
      f.puts "Author: #{workable_csv_line[4]}"
      f.puts "Submit: #{workable_csv_line[3]}"
      f.puts
      f.close
    end
    if @verbose
      puts "[".light_green + "*".white + "] ".light_green + "Description: ".light_red + "#{workable_csv_line[2]}".white
      puts "[".light_green + "*".white + "] ".light_green + "Location: ".light_red + "#{CSV.split("/")[0..-2].join("/")}/#{workable_csv_line[1]}".white
      puts "[".light_green + "*".white + "] ".light_green + "Exploit ID: ".light_red + "#{workable_csv_line [0]}".white
      puts "[".light_green + "*".white + "] ".light_green + "Platform: ".light_red + "#{foobar.join(",")}".white
      puts "[".light_green + "*".white + "] ".light_green + "Type: ".light_red + "#{workable_csv_line[-2]}".white
      if not "#{workable_csv_line[-1].chomp}".to_i == 0
        "[".light_green + "*".white + "] ".light_green + "Port: ".light_red + "#{workable_csv_line[-1].chomp}".white
      end
      puts "[".light_green + "*".white + "] ".light_green + "Author: ".light_red + "#{workable_csv_line[4]}".white
      puts "[".light_green + "*".white + "] ".light_green + "Submit: ".light_red + "#{workable_csv_line[3]}\n".white
    end
  end
  puts "[".light_blue + "*".white + "] ".light_blue + "Search Complete!".white
  puts "[".light_blue + "*".white + "] ".light_blue + "Hope you found what you needed....".white
  puts "[".light_blue + "*".white + "] ".light_blue + "Good Bye!".white
end



### MAIN ###
options = {}
optparse = OptionParser.new do |opts| 
  opts.banner = "Usage:".light_green + " #{$0} ".white + "[".light_green + "OPTIONS".white + "]".light_green
  opts.separator ""
  opts.separator "EX:".light_green + " #{$0} --update".white
  opts.separator "EX:".light_green + " #{$0} -t webapps -s vBulletin".white
  opts.separator "EX:".light_green + " #{$0} --search=\"Linux Kernel 2.6\"".white
  opts.separator "EX:".light_green + " #{$0} -a \"JoinSe7en\" -s \"MyBB\"".white
  opts.separator "EX:".light_green + " #{$0} -t remote -s \"SQL Injection\"".white
  opts.separator "EX:".light_green + " #{$0} -p linux -t local -s UDEV -o search_results.txt".white
  opts.separator ""
  opts.separator "Options: ".light_green
  opts.on('-u', '--update', "\n\tUpdate Exploit-DB Working Archive to Latest & Greatest".white) do |host|
    options[:method] = 0
  end
  opts.on('-p', '--platform <PLATFORM>', "\n\tSystem Platform Type, options include:
sco, bsdi/x86, openbsd, lin/amd64, plan9, bsd/x86, openbsd/x86, hardware, bsd, unix, lin/x86-64, netbsd/x86, linux, solaris, ultrix, arm, php, solaris/sparc, osX, os-x/ppc, cfm, generator, freebsd/x86, bsd/ppc, minix, unixware, freebsd/x86-64, cgi, hp-ux, multiple, win64, tru64, jsp, novell, linux/mips, solaris/x86, aix, windows, linux/ppc, irix, QNX, lin/x86, win32, linux/sparc, freebsd, asp, sco/x86".white) do |platform|
    options[:platform] = platform.downcase.chomp
    options[:method] = 1
  end
  opts.on('-t', '--type <TYPE>', "\n\tType of Exploit, options include:\n\tDoS, Remote, Local, WebApps, Papers or Shellcode".white) do |type|
    options[:type] =  type.downcase.chomp
    options[:method] = 1
  end
  opts.on('-a', '--author <NAME>', "\n\tRun Lookup based on Author Username".white) do |author|
    options[:author] = author.downcase.chomp
    options[:method] = 1
  end
  opts.on('-s', '--search <SEARCH_TERM>', "\n\tSearch Term to look for in Exploit-DB Working Archive".white) do |search|
    options[:search] = search.downcase.chomp
    options[:method] = 1
  end
  opts.on('-o', '--output <OUTPUT_FILE>', "\n\tOutput File to Write Search Results to".white) do |output|
    @out = output.strip.chomp
    options[:log] = true
  end
  opts.on('-q', '--quiet', "\n\tSilence Output to Terminal for Search Results (when logging output)".white) do |output|
    options[:q] = true
  end
  opts.on('-h', '--help', "\n\tHelp Menu".white) do 
    banner
    puts
    puts opts
    puts
    exit 69;
  end
end
begin
  foo = ARGV[0] || ARGV[0] = "--help"
  optparse.parse!
  mandatory = [:method]
  missing = mandatory.select{ |param| options[param].nil? }
  if not missing.empty?
    cls
    banner
    puts
    puts "Missing options: ".light_red + " #{missing.join(', ')}".white  
    puts optparse
    exit 666;
  end
rescue OptionParser::InvalidOption, OptionParser::MissingArgument, OptionParser::AmbiguousOption
  cls
  banner
  puts
  puts $!.to_s.light_red
  puts
  puts optparse
  puts
  exit 666;   
end
#Make we have search values or set wildcards
options[:platform] = '' if not options[:platform]
options[:type]     = '' if not options[:type]
options[:author]   = '' if not options[:author]
options[:port]     = '' if not options[:port]
options[:search]   = '' if not options[:search]
if options[:log]
  @log=true
else
  @log=false
end
if options[:q]
  @verbose=false
else
  @verbose=true
end
cls
banner
puts
if File.exists?(CSV) and File.directory?("#{CSV.split("/")[0..-2].join("/")}/platforms")
  if options[:method].to_i == 0
    # Update
    puts "[".light_green + "*".white + "] ".light_green + "Found existing archive file: #{CSV}".white
    puts "[".light_green + "*".white + "] ".light_green + "Running update to get latest and greatest....".white
    git_update
  else
    puts "[".light_green + "*".white + "] ".light_green + "Found archive file: #{CSV}".white
    plresults = search_platform(options[:platform]) unless options[:platform].nil?
    tresults = search_type(plresults, options[:type]) unless options[:type].nil?
    aresults = search_author(tresults, options[:author]) unless options[:author].nil?
    presults = search_port(aresults, options[:port]) unless options[:port].nil?
    results = search_search(presults, options[:search]) unless options[:search].nil?
    display_results(results)
  end
else
  # Not finding fi.es.csv and ./platforms/
  puts "[".light_red + "X".white + "] ".light_red + "Problem locating archive file: #{CSV}!".white unless File.exists?(CSV)
  puts "[".light_red + "X".white + "] ".light_red + "Problem locating archives platforms dir: #{CSV.split("/")[0..-2].join("/")}/platforms!".white unless File.directory?("#{CSV.split("/")[0..-2].join("/")}/platforms")
  puts "[".light_red + "X".white + "] ".light_red + "Can't run searches without it...".white
  print "(".light_red + "Do You want to Git CLone it ".white + "(".light_yellow + "Y".white + "/".light_yellow + "N".white + ")".light_yellow + "?".white + ")> ".light_red
  answer = gets.chomp
  if answer.upcase == 'Y' or answer.upcase == 'YES'
    puts "[".light_blue + "*".white + "] ".light_blue + "Trying to git clone the exploit-db repo...".white
    if File.directory?("#{CSV.split("/")[0..-2].join("/")}/") and "#{CSV.split("/")[0..-2].join("/")}/" != '/'
      git_get("#{CSV.split("/")[0..-2].join("/")}/")
    else
      git_get
    end
    puts "[".light_blue + "*".white + "] ".light_blue + "Hopefully things went well with git clone installation...".white
    puts "[".light_blue + "*".white + "] ".light_blue + "Try editing source code to point to the new setup and run again...".white
  else
    puts "[".light_red + "X".white + "] ".light_red + "OK, try again when you get things figured out then.....".white
  end
end
puts "\n\n"
#EOF