#
#   Copyright (C) Stonesoft Corporation 2010 - 2013.
#   All rights reserved.
#
#   The StoneGate software, manuals, and technical
#   literature may not be reproduced in any form or
#   by any means except by permission in writing from
#   Stonesoft Corporation.
#
# maps evasions from predator4

module Predator4Module
  S_OPTION = "Option"
  S_INTEGER = "integer"
  # external codes (negative to avoid overlapping with predator4 codes)
  EVASION_TIMED_OUT = -1
  EVASION_SEGMENTATION_FAULT = -2
  EVASION_COULD_NOT_INTERPRET = -3

  S_0_SUCCESS = "0: Exploit succeeded."
  R_LF = /\n/
  R_SEGMENTATION_FAULT = /Segmentation fault/m

  $verify_delay = 200

  $port_array = (10000..65535).to_a.shuffle!
  $port_mutex = Mutex.new
  $port_point = 0
  $port_array_size = $port_array.size
  def Predator4Module::get_free_port()
    port = nil
    $port_mutex.synchronize do
      port = $port_array[$port_point%$port_array_size]
      $port_point+=1
    end
    return port
  end

  R_SPACE = /\s+/
  R_SPACESPACE = /\s\s+/
  R_TERMINATED = /Terminated/

  S_NO_EVASION = "No Evasions"
  S_MULTILAYER = "Multilayer"
  S_APPLICATION = "Application"
  H_EVASION_LAYER = {
    "ipv4" => "Internet",
    "ipv6" => "Internet",
    "tcp" => "Transport",
    "netbios" => "Application Protocol",
    "smb" => "Application Protocol",
    "http" => "Application",
    "msrpc" => "Application",
  }

  R_EVASION = /^--evasion=/
  R_EVASION_WITH_STAGES = /^--evasion=\[([^\]])+\](.+)$/
  R_EVASION_WITHOUT_STAGES = /^--evasion=(.+)$/
  R_COMMA = /,/
  R_COLON = /:/
  R_ONLY_DEC = /^\d+$/
  
  R_UNDERSCORE = /_/

  def Predator4Module::get_evasion_layers_from_attack(attack)
    layers = {}
    attack.split(R_SPACE).each do |arg|
      if arg =~ R_EVASION
        if arg =~ R_EVASION_WITH_STAGES
          evasion_with_params = $2
        elsif arg =~ R_EVASION_WITHOUT_STAGES
          evasion_with_params = $1
        else
          raise "Unhandled #{arg}"
        end
        evasion_name = evasion_with_params.split(R_COMMA, 2)[0]
        protocol_name = evasion_name.split(R_UNDERSCORE, 2)[0]
        layers[protocol_name] = true
      end
    end
    if layers.size == 0
      return S_NO_EVASION
    elsif layers.size == 1
      protocol_name = layers.keys.first
      if H_EVASION_LAYER.has_key?(protocol_name)
        return H_EVASION_LAYER[protocol_name]
      else
        return S_APPLICATION
      end
    else
      return S_MULTILAYER
    end
  end

  class Attack
    attr_accessor :short_name, :name, :desc, :cve, :bid, :ms, :evasions, :evasions_by_name, :extra_options, :extra_options_by_name, :stages, :payloads
    attr_reader :payload_to_guiname, :payload_to_description
    def initialize( short_name )
      @short_name = short_name
      @name = name
      @desc = nil
      @cve = nil
      @bid = nil
      @ms = nil
      @evasions = []
      @evasions_by_name = {}

      @extra_options = []
      @extra_options_by_name = {}
      @stages = []
      @payloads = [] # attack payloads like 'shell', 'bsod' of which the first shall be the default
      @payload_to_guiname = {}
      @payload_to_description = {}
    end
    def to_s
      "#{@name}"
    end

    def add_evasion( ev )
      @evasions.push ev
      @evasions_by_name[ ev.name ] = ev
    end
  end

  class Evasion
    attr_reader :name, :comment
    attr_accessor :options, :options_by_name
    def initialize(name, comment)
      @name = name
      @comment = comment
      @options = []
      @options_by_name = {}
    end
    def base_name
      return @name
    end
    def to_s
      "#{@name}"
    end

    def option_index( option )
      @options.size.times do |i|
        if @options[ i ] == option
          return i
        end
      end

      return nil
    end
  end

  class StageEvasion < Evasion
    attr_accessor :parent, :begin, :end
    def base_name
      return @parent.name
    end
  end

  class OriginalEvasion < Evasion
    attr_reader :supported_stages, :supported_stages_by_name, :stage_index
    def initialize(name, comment)
      super(name, comment)
      @supported_stages = []
      @supported_stages_by_name = {}
    end
    def overlap?(first, second)
      # overlaps if first evasions end is inside second or
      # second evasions end is inside first
      return ((@stage_index[first.begin] <= @stage_index[second.begin] and @stage_index[second.begin] < @stage_index[first.end]) or
              (@stage_index[second.begin] <= @stage_index[first.begin] and @stage_index[first.begin] < @stage_index[second.end]))
    end
    def generate_all_evasions
      evasions = []
      @stage_index = {}
      index = 0
      if @supported_stages.size > 0
        # check that the end stage is present
        last_stage = @supported_stages[-1]
        if last_stage.name != "end"
          @supported_stages.push SupportedStage.new("end", "autogenerated end")
        end
      end
      @supported_stages.each do |begin_stage|
        zone = false
        @supported_stages.each do |end_stage|
          if begin_stage == end_stage
            zone = true
            next
          end
          next if not zone
          name = "[" + begin_stage.name + "," + end_stage.name + "]" + @name
          evasion = StageEvasion.new(name, @comment)
          evasion.options = @options
          evasion.options_by_name = @options_by_name
          evasion.parent = self
          evasion.begin = begin_stage
          evasion.end = end_stage
          evasions.push evasion
        end
        @stage_index[begin_stage] = index
        index += 1
      end
      if @supported_stages.size == 0
        evasions.push self
      end
      return evasions
    end
  end

  class ExploitOption
    attr_accessor :name, :comment, :type

    def initialize( name, comment, type )
      @name = name
      @comment = comment
      @type = type
    end
  end

  class Option
    attr_reader :name, :comment
    def initialize(name, comment)
      @name = name
      @comment = comment
    end
    def to_s
      "#{@name}"
    end
    def get_value(index)
      return @enumerated_options[index]
    end
    def get_enum_size
      if @enumerated_options.size == 0
        raise "Enum size is nil for #{self.class}::#{self}"
      end
      return @enumerated_options.size
    end
  end

  class Preset
    attr_reader :name, :comment, :attacks
    def initialize(name, comment, attacks)
      @name = name
      @comment = comment
      @attacks = attacks
    end
    def to_s
      "#{@name}"
    end
  end

  class SupportedStage
    attr_reader :name, :comment
    def initialize(name, comment)
      @name = name
      @comment = comment
    end
    def to_s
      "#{name}"
    end
  end

  R_INTEGER_OPTION = /^\[ (\d+), (\d+) \] with step size (\d+)$/
  class IntegerOption < Option
    attr_reader :min, :max, :step
    # in a perfect world we'd like all of these
    # but there are situations where:
    # 10 from the beginning and 5 from the middle and 3 from the end will have to suffice
    MAX_ENTRIES = 18
    START_ENTRIES = 10
    END_ENTRIES = 3
    def initialize(name, comment, rest, all_options)
      super(name, comment)
      @all_options = all_options
      if rest.strip =~ R_INTEGER_OPTION
        @min = $1.to_i
        @max = $2.to_i
        @step = $3.to_i
      else
        raise "Unhandled IntegerOption rest #{rest}"
      end
      enum()
    end
    def to_s
      super + " [#{@min},#{@max}](#{@step})"
    end
    def get_value(index)
      if @all_options
        return @min + index*@step
      else
        value = @enumerated_options[index]
        if value.nil?
          value = rand(@area)*@step + @indent
        end
        return value
      end
    end

    def get_enum_size
      if @all_options
        return (@max-@min)/@step
      else
        super()
      end
    end

    def enum
      return nil if @all_options # do not attempt to map them
      if (@max-@min)/@step > MAX_ENTRIES
        result = []
        current = @min
        START_ENTRIES.times do |i|
          result.push current
          current += @step
        end
        current = @max
        END_ENTRIES.times do |i|
          result.push current
          current -= @step
        end
        (MAX_ENTRIES-result.size).times do
          result.push nil
        end
        @area = (@max/@step - END_ENTRIES) - (@min/@step + START_ENTRIES) + 1
        @indent = @min + START_ENTRIES*@step
      else
        result = []
        current = @min
        while(current < @max)
          result.push current
          current += @step
          break if result.size > MAX_ENTRIES
        end
      end
      @enumerated_options = result
      return nil
    end
  end
  R_CHOICE_SINGLE = /^\( single valid \):$/
  R_CHOICE_MULTIPLE = /^\( multiple valid \):$/
  class ChoiceOption < Option
    attr_reader :single_valid, :choices

    def initialize(name, comment, rest)
      super(name, comment)
      if rest.strip =~ R_CHOICE_SINGLE
        @single_valid = true
      elsif rest.strip =~ R_CHOICE_MULTIPLE
        @single_valid = false
      else
        raise "Unhandled ChoiceOption #{rest}"
      end
      @choices = {}
      enum()
    end

    def to_s
      super + " (#{@choices.keys.join("|")}) (single: #{@single_valid})"
    end

    def enum()
      if @single_valid
        @enumerated_options = @choices.keys
      else
        total = []
        ignore_first = true # it seems multiple valid does not mean empty
        (2**@choices.size).times do |i|
          if ignore_first
            ignore_first = false
            next
          end
          result = []
          @choices.size.times do |j|
            if(((i & (2**j))>>j) == 1)
              result.push @choices.keys[j]
            end
          end
          total.push result.join("|")
        end
        @enumerated_options = total
      end
      return nil
    end
  end

  A_PROPABILITY = ["25%", "50%", "75%", "1", "2", "3", "5", "8", "13", "21"]
  class PropabilityOption < Option
    def initialize(name, comment, all_options)
      super(name, comment)
      @all_options = all_options
      enum()
    end
    def enum()
      if @all_options
        @enumerated_options = ["1"]
      else
        @enumerated_options = A_PROPABILITY
      end
      return nil
    end
  end

  class Parser
    def Parser.get_attacks(predator4 = "predator4")
      attack_names = []
      zone = false
      `#{predator4} --attacks 2>&1`.split(/\n/).each do |line|
        if line =~ /Available exploits:/
          zone = true
        elsif zone
          name, comment = line.strip.split(/-/, 2)
          attack_names.push name.strip
        end
      end
      return attack_names
    end
    def Parser.get_presets(predator4 = "predator4")
      zone = false
      presets = []
      `#{predator4} --presets 2>&1`.split(/\n/).each do |line|
        if line =~ /Available presets:/
          zone = true
        elsif zone
          name, comment = line.strip.split(R_SPACE, 2)
          comment =~ /(\d+)\sattacks$/
          presets.push Preset.new(name.strip, comment.strip, $1.to_i)
        end
      end
      return presets
    end

    R_AVAILABLE_EXPLOITS = /Available exploits:/
    R_CVE = /^\w*CVE/
    R_BID = /^\w*BID/
    R_MS = /^\w*MS/
    R_PAYLOAD_SUPPORT = /Payload support:/
    R_EXPLOIT_EXTRA_OPTIONS = /Exploit extra options:/
    R_EXPLOIT_STAGES = /Exploit stages:/
    S_CLEAN = "clean"
    R_PAYLOAD_NAME = /^\s*(\S+)\s+(.+)$/
    R_EXPLOIT_OPTION = /([^\x20]*)\x20(.*)\x20-\x20\[([^\]]*)\]/
    R_STAGE_NAME = /^\s*(\S+)\s/
    def Parser.parse(selected_attack = nil, use_stages = true, predator4 = "predator4", all_options = false)
      attacks = []
      zone = false
      `#{predator4} --attacks 2>&1`.split(R_LF).each do |line|
        if line =~ R_AVAILABLE_EXPLOITS
          zone = true
        elsif zone
          name, comment = line.strip.split(R_DASH, 2)
          if selected_attack.nil? or name.strip == selected_attack
            attacks.push Attack.new( name.strip )
          end
        end
      end

      # Extract extra info about attack
      zone = false
      zone2 = false
      zone3 = false

      attacks.each do |attack|
        name_zone_regexp = /Attack \"#{attack.short_name}\":/
        zone_name = zone_id = zone_desc = zone_payload = zone_extraopt = zone_stages = false
        `#{predator4} --info=#{attack.short_name}`.split( R_LF ).each do |line|

          if line =~ name_zone_regexp
            zone_name = true
          elsif zone_name
            attack.name = line
            zone_name = false
            zone_id = true
          elsif zone_id
            line.strip!
            if line.size() == 0
              next
            end

            if line =~ R_CVE
              attack.cve = line
            elsif line =~ R_BID
              attack.bid = line
            elsif line =~ R_MS
              attack.ms = line
            else
              attack.desc = line
              zone_id = false
              zone_desc = true
            end
          elsif zone_desc
            if line =~ R_PAYLOAD_SUPPORT
              zone_desc = false
              zone_payload = true
            elsif line =~ R_EXPLOIT_EXTRA_OPTIONS
              zone_desc = false
              zone_extraopt = true
            elsif line =~ R_EXPLOIT_STAGES
              zone_desc = false
              zone_stages = true
            else
              attack.desc << "\n#{line}"
            end
          elsif zone_payload
            if line =~ R_EXPLOIT_EXTRA_OPTIONS
              zone_payload = false
              zone_extraopt = true
            elsif line =~ R_EXPLOIT_STAGES
              zone_payload = false
              zone_stages = true
            else
              if line =~ R_PAYLOAD_NAME
                payload = $1
                payload_guiname = $2.strip
                if not payload == S_CLEAN
                  attack.payloads.push payload
                  attack.payload_to_guiname[payload] = payload_guiname
                  attack.payload_to_description[payload] = payload_guiname
                end
              end
            end
          elsif zone_extraopt
            if line =~ R_EXPLOIT_STAGES
              zone_extraopt = false
              zone_stages = true
            else
              line.strip!
              if line.size() == 0
                next
              end

              if line =~ R_EXPLOIT_OPTION
                exploit_opt = ExploitOption.new( $1, $2, $3 )
                attack.extra_options.push exploit_opt
                attack.extra_options_by_name[ $1 ] = exploit_opt
              else
                puts "Illegal extra option line: \"#{line}\""
              end
            end
          elsif zone_stages
            if line =~ R_STAGE_NAME
              attack.stages.push $1
            end
          end
        end
      end

      attacks = Parser::parse_evasions(attacks, predator4, use_stages, all_options)
      return attacks
    end

    R_LF = /\n/
    R_PARAMETERS = /Parameters:/
    R_SUPPORTED_STAGES = /Supported stages:/
    R_AVAILABLE_EVASIONS = /Available evasions:/
    S_INVALID_PARAMS = "100: Invalid parameters."
    S_NO_IFACES = "Info: No interfaces given, printing evasion info"
    S_PROPABILITY_OPTION = "Append '%' for probability (n%), otherwise run for every 'n'th invocation"
    S_INTEGER = "integer,"
    S_OPTION = "Option"
    S_PROPABILITY = "Probability"
    S_APPEND = "Append"
    R_DASH = /-/
    R_SPACE_PLUS = /\s+/

    def Parser::parse_evasions(attacks, predator4, use_stages = true, all_options = false)
      # Extract evasions
      zone = false
      blind = false
      if attacks.nil?
        attacks = [ Attack.new("blind") ]
        blind = true
      end
      attacks.each do |attack|
        if blind
          evasion_list = `#{predator4} --evasions 2>&1`
        else
          evasion_list = `#{predator4} --attack=#{attack.short_name} --evasions 2>&1`
        end
        evasion_list.split(R_LF).each do |line|
          line.strip!
          next if line.size == 0
          next if line == S_INVALID_PARAMS
          if line =~ R_AVAILABLE_EVASIONS
            zone = true
          elsif zone
            name, comment = line.strip.split(R_DASH, 2)
            if comment.nil?
              puts "comment is nil for #{attack.short_name} from #{line}"
              comment=""
            end
            attack.add_evasion(OriginalEvasion.new(name.strip, comment.strip))
          end
        end
      end

      attacks.each do |attack|
        attack.evasions.each do |evasion|
          current_option = nil
          current_type = nil
          param_zone = false
          supported_stages_zone = false
          if blind
            evasion_config = `#{predator4} --evasion=#{evasion.name} 2>&1`
          else
            evasion_config = `#{predator4} --attack=#{attack.short_name} --evasion=#{evasion.name} 2>&1`
          end
          evasion_config.split(R_LF).each do |line|
            line.strip!
            next if line == S_NO_IFACES
            next if line == S_INVALID_PARAMS
            if line =~ R_PARAMETERS
              param_zone = true
            elsif line =~ R_SUPPORTED_STAGES
              param_zone = false
              supported_stages_zone = true
            elsif param_zone
              if line.strip.size == 0
                current_option = nil
                current_type = nil
              elsif current_option.nil? and current_type.nil?
                name, comment = line.strip.split(R_SPACESPACE, 2)
                if comment.nil?
                  comment = ""
                  #puts "#{attack.name} #{evasion.name} line #{line} does not have tabs"
                end
                current_option = [name.strip, comment.strip]
              elsif not current_option.nil? and current_type.nil?
                type, rest = line.strip.split(R_SPACE, 2)
                if type == S_INTEGER
                  option = IntegerOption.new(current_option[0], current_option[1], rest, all_options)
                elsif type == S_OPTION
                  option = ChoiceOption.new(current_option[0], current_option[1], rest)
                elsif current_option[0] == S_PROPABILITY and type == S_APPEND
                  option = PropabilityOption.new(current_option[0], current_option[1], all_options)
                elsif type == S_APPEND
                  puts "name has failed me >#{current_option[0]}<"
                else
                  raise "Unsupported option #{type}"
                end
                evasion.options.push option
                if evasion.options_by_name.has_key?(option.name)
                  raise "Evasion #{evasion.name} already has option #{option.name}"
                else
                  evasion.options_by_name[option.name] = option
                end
                current_type = true
              elsif evasion.options.last.class == IntegerOption
                raise "Unparsed line after integer option #{line}"
              elsif evasion.options.last.class == ChoiceOption
                name, comment = line.strip.split(R_DASH, 2 )
                name.strip!
                comment.strip!
                if evasion.options.last.choices.has_key?(name)
                  raise "Evasion #{evasion} option #{evasion.options.last} already has key #{name}"
                else
                  evasion.options.last.choices[name] = comment
                end
                evasion.options.last.enum
              elsif evasion.options.last.class == ProbabilityOption
                if line.strip == S_PROPABILITY_OPTION
                else
                  raise "Unhandled probability option content #{line}"
                end
              else
                raise "Unsupported option type #{line}"
              end
            elsif use_stages and supported_stages_zone
              if not line.strip.size == 0
                name, comment = line.strip.split(R_SPACE_PLUS, 2)
                supported_stage = SupportedStage.new(name, comment)
                evasion.supported_stages.push supported_stage
                evasion.supported_stages_by_name[name] = supported_stage
              end
            end
          end
        end
      end
      attacks.each do |attack|
        original_evasions = attack.evasions
        attack.evasions = []
        original_evasions.each do |original_evasion|
          original_evasion.generate_all_evasions.each do |evasion|
            attack.evasions.push evasion
          end
        end
      end
      return attacks
    end
  end

  class HostInterface
    attr_reader :name, :ipv4_addresses, :ipv6_addresses
    attr_accessor :carrier, :up
    def initialize(name)
      @name = name
      @carrier = true
      @up = false
      @ipv4_addresses = []
      @ipv6_addresses = []
    end
  end

  class HostConfig
    attr_reader :interfaces
    def initialize()
      @interfaces = {}
    end
    def parse(ip_ad = `ip ad`)
      iface = nil
      ip_ad.split(/\n/).each do |line|
        if line =~ /^\d+:\s+(\S+):/
          if not iface.nil?
            @interfaces[iface.name] = iface
          end
          iface = HostInterface.new($1)
          iface.carrier = false if line =~ /NO-CARRIER/
          iface.up = true if line =~ /[<,]UP[,>]/
        elsif line =~ /^\s+inet (\d+\.\d+\.\d+\.\d+)\/\d+ brd/
          iface.ipv4_addresses.push IPForge::IPv4Address.new($1)
        elsif line =~ /^\s+inet6 (\S+)\/\d+ scope/
          iface.ipv6_addresses.push IPForge::IPv6Address.new($1)
        end
      end
      if not iface.nil?
        @interfaces[iface.name] = iface
      end
      if @interfaces.has_key?("lo")
        @interfaces.delete("lo")
      end
    end
  end

  class NetworkConfig
    attr_accessor :iface, :src_ip, :dst_ip, :gw_ip, :mask, :dst_port
    def to_s
      str = "--if=#{@iface} --src_ip=#{@src_ip}"
      if defined?(@mask) and not @mask.nil?
        str += "/#{@mask}"
      end
      str += " --dst_ip=#{@dst_ip}"
      if defined?(@gw_ip) and not @gw_ip.nil?
        str += " --gw=#{@gw_ip}"
      end
      if defined?(@dst_port) and not @dst_port.nil?
        str += " --dst_port=#{@dst_port}"
      end
      return str
    end
  end

  # control interface
  class Interface
    def initialize(binary)
      @binary = binary
    end
    def get_options(attack, use_stages = true, all_options = false)
      return Parser.parse(attack, use_stages, @binary, all_options)[0]
    end
    def get_blind_options(use_stages = true, all_options = false)
      return Parser.parse_evasions(nil, @binary, use_stages, all_options)
    end
    def get_attacks()
      return Parser.get_attacks(@binary)
    end
    def clean(attack, network_config, port_usage)
      # test clean
      repeat = port_usage - 1
      result = ""
      cmd = ""
      code = 0
      explanation, result, cmd = nil
      repeat.times do |i|
        cmd = "#{@binary} --uid=#{$attacker_uid} #{network_config} --autoclose --attack=#{attack} --payload=clean --src_port=#{Predator4Module::get_free_port()} 2>&1"
        code, explanation, result, cmd = command(cmd, 3, network_config.src_ip)
        log "#{i}/#{repeat}: cmd is #{cmd}", DEBUG
        log "#{i}/#{repeat}: result is #{result}", DEBUG
        if code == 0
          break
        end
        sleep 1 # this will throttle things
      end
    
      return code, explanation, result, cmd
    end
    R_DOT = /./
    R_SEGFAULT = /Segmentation fault/
    R_HTTP_SERVER_STOPPED = /(HTTP server stopped|Segmentation fault)/
    R_BODY_OPEN = /<body>/
    R_BODY_CLOSE = /<\/body>/
    R_SERVER_STARTED = /(server started, visit (\S+) to get|Segmentation fault)/
    R_SHASUM = /(SHA-512 of content: (\S+)|Segmentation fault)/
    def server_attack(attack, network_config, evasions, src_port, recdir, timeout, driver, randseed = nil, passthrough = [])
      extra_pem = ""
      if attack == "http_eicar_server"
        is_eicar = true
      elsif attack == "https_eicar_server"
        is_eicar = true
        if not $predefined_pemfile.nil?
          extra_pem = " --extra=keypath=#{$predefined_pemfile}"
        end
      else
        is_eicar = false
      end
      is_eicar = (attack == "http_eicar_server" or attack == "https_eicar_server")

      cmd = "#{@binary} --uid=#{$attacker_uid} #{network_config} --autoclose --attack=#{attack}"
      if not src_port.nil?
        cmd += " --src_port=#{src_port}"
      end
      cmd += " --verifydelay=#{$verify_delay}"
      if $payload_obfuscation
        cmd += " --obfuscate"
      end
      if is_eicar
        cmd += " --extra=stay_open=true"
      end
      cmd += extra_pem
      if not randseed.nil?
        cmd += " --randseed=#{randseed}"
      end
      predator_output = ""
      recname = nil
      if not recdir.nil?
        recname = "#{recdir}/#{network_config.src_ip}:#{src_port}_#{time.hour}:#{time.min}:#{time.sec}:#{time.usec}.pcap"
        cmd += " --record=#{recname}"
      end
      
      evasions.each do |evasion, options|
        cmd += " --evasion=#{evasion.name}"
        evasion.options.each do |option|
          cmd += ",\"#{options[option.name]}\""
        end
      end
      if not passthrough.nil? and passthrough.size > 0
        passthrough.each do |pt|
          cmd += " #{pt}"
        end
      end

      log("predator server: #{cmd}", DEBUG)
      p4serv = ATFExpect.new(cmd)
      str = p4serv.expect_line(R_SERVER_STARTED)
      predator_output += str
      if str =~ R_SEGFAULT
        code = EVASION_SEGMENTATION_FAULT
        explanation = "Segmentation Fault"
        return code, explanation, str, cmd, recname
      elsif str =~ R_SERVER_STARTED
        url = $2
      end
      if is_eicar
        str = p4serv.expect_line(R_SHASUM)
        predator_output += str
        if str =~ R_SEGFAULT
          code = EVASION_SEGMENTATION_FAULT
          explanation = "Segmentation Fault"
          return code, explanation, str, cmd, recname
        elsif str =~ R_SHASUM
          sha = $2
        end
      end
      code = 299 # Timeout
      start_time = Time.new
      explanation = ""
      begin
        source = nil
        timeout(timeout) do
          if driver.nil?
            #source = `/root/wget-1.13.4/src/wget --tries=1 --quiet --timeout=#{(timeout/2).to_i} --output-document=- #{url} 2>&1`
            wget_cmd = "wget --tries=1 --quiet --timeout=#{timeout - 2} --output-document=- #{url} --no-check-certificate 2>&1"
            log("wget: #{wget_cmd}", DEBUG)
            source = `#{wget_cmd}`
            source.force_encoding "ASCII-8BIT"
            if source =~ R_SEGFAULT
              code = -4
              explanation = "WGET SEGFAULT: #{cmd}"
            end
          elsif driver.instance_of?(DRbObject)
            source = driver.go_to_url(url)
          else
            driver.navigate.to url
            source = driver.page_source
            source = source.split(R_BODY_CLOSE, 2)[0].to_s.split(R_BODY_OPEN, 2)[1].to_s
          end
        end
        if is_eicar
          if code > 0
            browser_sha = Digest::SHA512.hexdigest(source)
            if browser_sha == sha
              code = 0
              explanation = "Successful exploit"
            else
              code = 200
              explanation = "Different sha: #{sha} vs browser got #{browser_sha}"
            end
          end
        else
          puts "TODO: check for shell"
        end
      rescue Exception => e
        if e.class != Timeout::Error
          puts "#{e}\n#{e.backtrace.join("\n")}"
        end
      end
      str = predator_close(p4serv, cmd) # check for segfault
      if str =~ R_SEGFAULT
        code = EVASION_SEGMENTATION_FAULT
        explanation = "Segmentation Fault"
        return code, explanation, predator_output + "\n" + str, cmd, recname
      end
      return code, explanation, predator_output, cmd, recname
    end

    def predator_close(p4serv, cmd)
      str = ""
      begin
        timeout(2) do
          begin
            p4serv.send("Q\n")
            p4serv.flush
            str = p4serv.expect_line(R_HTTP_SERVER_STOPPED)
          rescue Errno::EIO => f
          rescue Timeout::Error => f
          end
        end
      rescue Exception => e
        if e.class != Timeout::Error
          puts "#{e.class}:#{e}\n#{e.backtrace.join("\n")}"
        end
      end
      p4serv.close
      return str
    end

    def attack(attack, network_config, evasions, src_port, attacker_shell_port, port_usage, recdir, timeout, randseed = nil, passthrough = [])
      # test clean
      code, explanation, result, cmd = clean(attack, network_config, port_usage)
      if code != 0
        return code + 1000, explanation, result, cmd
      end

      time = Time.new
      attack_src_port = Predator4Module::get_free_port()
      
      if attacker_shell_port.nil?
        cmd = "#{@binary} --uid=#{$attacker_uid} #{network_config} --autoclose --attack=#{attack} --src_port=#{attack_src_port} --verifydelay=#{$verify_delay}"
      else
        cmd = "#{@binary} --uid=#{$attacker_uid} #{network_config} --autoclose --attack=#{attack} --src_port=#{attack_src_port} --extra=bindport=#{attacker_shell_port} --verifydelay=#{$verify_delay}"
      end
      if $payload_obfuscation
        cmd += " --obfuscate"
      end
      if not randseed.nil?
        cmd += " --randseed=#{randseed}"
      end
      recname = nil
      if not recdir.nil?
        recname = "#{recdir}/#{network_config.src_ip}:#{attack_src_port}_#{time.hour}:#{time.min}:#{time.sec}:#{time.usec}.pcap"
        cmd += " --record=#{recname}"
      end
      
      evasions.each do |evasion, options|
        cmd += " --evasion=#{evasion.name}"
        evasion.options.each do |option|
          cmd += ",\"#{options[option.name]}\""
        end
      end
      if not passthrough.nil? and passthrough.size > 0
        passthrough.each do |pt|
          cmd += " #{pt}"
        end
      end

      code, explanation, result, cmd = command(cmd, timeout, network_config.src_ip)
      if not recname.nil?
        if File.exists?(recname)
          # add the code to the beginning of the filename
          dirname, basename = File.split(recname)
          new_recname = dirname + File::SEPARATOR + code.to_s + "_" + basename
          FileUtils.mv(recname, new_recname)
          recname = new_recname
        else
          recname = nil
        end
      end

      return code, explanation, result, cmd, recname
    end


    $slayer_thread = Thread.new do
      # skeletons of society
      $slayer_hash = {}
      $slayer_mutex = Mutex.new
      while not defined? $predator4_basename
        sleep 0.1
      end
      while not defined? $attacker_uid
        sleep 0.1
      end
      $slayer_search_left = "ps -afe | grep \"#{$predator4_basename} \" | grep -E \"src_ip="
      $slayer_search_right = "(/| )\" | grep \"uid=#{$attacker_uid} \" | grep -v 'sh -c' | grep -v grep | grep -v ruby"
      while(true) do
        time = Time.new
        if $slayer_hash.size > 0
          a = []
          $slayer_mutex.synchronize do
            $slayer_hash.each do |attacker, slaying_time|
              if time > slaying_time
                a.push attacker
                $slayer_hash.delete(attacker)
              end
            end
          end
          a.each do |attacker|
            #puts "#{$slayer_search_left}#{attacker}#{$slayer_search_right}"
            psout = `#{$slayer_search_left}#{attacker}#{$slayer_search_right}`
            if psout.size > 0
              pid = psout.split(/\n/).first.split(/\s+/)[1] + " "
              puts "Pid #{pid.strip} timed out - killed"
              `kill #{pid.strip} 2>&1`
            else
              puts "Could not find pid for timed out.. this will cause problems for worker #{attacker}."
            end
          end
        end
        sleep 1
      end
    end

    def register_slayer(attacker, timeout)
      # seasons in the abyss
      slaying_time = Time.new + timeout
      $slayer_mutex.synchronize do
        $slayer_hash[attacker] = slaying_time
      end
    end
    def unregister_slayer(attacker)
      $slayer_mutex.synchronize do
        $slayer_hash.delete(attacker)
      end
    end

    def command(cmd, timeout, attacker)
      log "cmd is #{cmd}", DEBUG
      register_slayer(attacker, timeout)
      result = `#{cmd} 2>&1`
      unregister_slayer(attacker)
      if result =~ R_TERMINATED
        code = EVASION_TIMED_OUT
        explanation = "Timed out"
      elsif result =~ R_SEGMENTATION_FAULT
        # and not the good tcp segmentation kind
        code = EVASION_SEGMENTATION_FAULT
        explanation = "Segmentation Fault"
      else
        if result.to_s.size > 0
          code, explanation = result.split(R_LF).last.split(R_COLON)
          if code =~ R_ONLY_DEC and not explanation.nil?
            code = code.to_i
            explanation.strip!
          else
            code = EVASION_COULD_NOT_INTERPRET
            explanation = "Could not interpret #{$predator4_basename} output"
          end
        else
          code = EVASION_COULD_NOT_INTERPRET
          explanation = "Empty #{$predator4_basename} output"
        end
      end
      log "result #{code}/#{explanation} from #{result}", DEBUG
      
      return code, explanation, result, cmd
    end
    def running?
      return (`ps -e | grep #{$predator4_basename} | grep -v grep`.strip.size > 0)
    end
  end
end

if __FILE__ == $0
  $:.push File.dirname(File.expand_path($0))

  require "util.rb"
  require "ipv4address.rb"
  require "ipv6address.rb"

  hc = Predator4Module::HostConfig.new()
  hc.parse()
  puts hc.interfaces

  # 
  attacks = Predator4Module::Parser.parse("conficker")
  puts attacks.first.short_name
  presets = Predator4Module::Parser.get_presets()
  puts presets
  combinations = 1

  1.times do |i|
    attack_cmd = "predator4 --attack=conficker "
    attacks[0].evasions.each do |evasion|
      if true
        if evasion.name =~ /msrpc_ndrflag/
          puts evasion.name
          puts evasion.options.inspect
        end
        attack_cmd += "--evasion=#{evasion.name}"
        # evasion on
        evasion.options.each do |option|
          value = option.get_value(rand(option.get_enum_size()))
          attack_cmd += ",\"#{value}\""
        end
        attack_cmd += " "
      end
      if i == 0
        evasion.options.each do |option|
          #puts "#{option.name} #{option.enum}"
          combinations *= option.get_enum_size()
        end
      end
    end
    puts "#{i} #{attack_cmd}"
  end

  puts "total combo: #{combinations} " + sprintf("%0b", combinations).size.to_s
end
