# External to limit 4.2 release evasions with mongbat to sane values

class Conficker42Proc < Validator
  S_IPV4_FRAG = "ipv4_frag"
  S_TCP_SEG = "tcp_seg"
  
  S_IPV4_FRAG_EV = "ipv4_frag"
  S_IPV4_FRAG_SIZE = "IPv4 fragment size"  
  S_IPV4_ORDER_EV = "ipv4_order"
  S_IPV4_ORDER_ORDER = "IPv4 segment order"
  S_TCP_SEG_EV = "tcp_seg"
  S_TCP_SEG_SIZE = "TCP segment size"
  S_TCP_ORDER_EV = "tcp_order"
  S_TCP_OVERLAP_EV = "tcp_overlap"
  S_TCP_OVERLAP_AMOUNT = "Overlap amount"
  S_TCP_OVERLAP_DIRECTION = "Overlap direction"
  S_TCP_BOGUSSYNSENTACKS_EV = "tcp_bogussynsentacks"
  S_TCP_BOGUSSYNSENTACKS_OFFSET = "TCP ACK number offset"
  S_TCP_BOGUSSYNSENTACKS_DELAY = "Delay in milliseconds"
  S_TCP_BOGUSSYNSENTACKS_NUMBER = "Number of bogus ACKs"
  S_TCP_NUMRETRANS_EV = "tcp_numretrans"
  S_TCP_NUMRETRANS_NUMBER = "Number of segment retransmits"
  S_TCP_NUMRETRANS_TIMEOUT = "TCP user timeout in seconds"
  S_TCP_INITTSOPT_EV = "tcp_inittsopt"
  S_TCP_INITTSOPT_ENABLE = "Enable/Disable"
  S_TCP_PAWS_EV = "tcp_paws"
  S_TCP_PAWS_PROBABILITY = "Probability"
  S_TCP_PAWS_OFFSET = "Timestamp offset"
  S_TCP_PAWS_DATA = "Payload data"
  S_TCP_TIMEWAIT_EV = "tcp_timewait"
  S_TCP_TIMEWAIT_NUMBER = "Number of decoy connections"
  S_TCP_TIMEWAIT_DATATYPE = "Type of decoy data"
  S_TCP_SEGVAR_EV = "tcp_segvar"
  S_TCP_MINIMUM_SEGMENT_SIZE = "TCP minimum segment size"
  S_TCP_MAXIMUM_SEGMENT_SIZE = "TCP maximum segment size"
  S_SMB_SEG_EV = "smb_seg"
  S_SMB_SEG_SIZE = "SMB segment size"
  S_MSRPC_SEG_EV = "msrpc_seg"
  S_MSRPC_SEG_SIZE = "MSRPC fragment size"
  S_MSRPC_GROUPSENDS_EV = "msrpc_groupsends"
  S_MSRPC_GROUPSENDS_NUMBER = "Number of fragments to group"
  S_TCP_PAYLOADACKSLEEP_EV = "tcp_payloadacksleep"
  S_TCP_PAYLOADACKSLEEP_NUMBER = "Delay in milliseconds"
  S_TCP_SYNSENTSLEEP_EV = "tcp_synsentsleep"
  S_TCP_SYNSENTSLEEP_NUMBER = "Delay in milliseconds"

  def initialize()
    @stage_order = {}
    @stage_order[ "begin" ] = 0
    @stage_order[ "netbios_connect" ] = 1
    @stage_order[ "smb_connect" ] = 2
    @stage_order[ "smb_opentree" ] = 3
    @stage_order[ "smb_openpipe" ] = 4
    @stage_order[ "msrpc_bind" ] = 5
    @stage_order[ "msrpc_req" ] = 6
    @stage_order[ "end" ] = 7
      
    # Define maximum segment sizes between named stages. Values means the maximum segment size between current and following stage
    @tcp_max_seg = []
    @tcp_max_seg[ 0 ] = @tcp_max_seg[ 1 ] = @tcp_max_seg[ 2 ] = 103
    @tcp_max_seg[ 3 ] = 75 
    @tcp_max_seg[ 4 ] = 96
    @tcp_max_seg[ 5 ] = 140
    @tcp_max_seg[ 6 ] = 790
    @tcp_max_seg[ 7 ] = 0
      
    @smb_max_seg = []
    @smb_max_seg[ 0 ] = @smb_max_seg[ 1 ] = @smb_max_seg[ 2 ] = @smb_max_seg[ 3 ] = @smb_max_seg[ 4 ] = 0
    @smb_max_seg[ 5 ] = 72
    @smb_max_seg[ 6 ] = 720
    @smb_max_seg[ 7 ] = 0
  end
  
  def stage_id( name )
    id = @stage_order[ name ]
    if id.nil?
      puts @stage_order.inspect
      puts name.class
      raise "Stage name '#{name}' unhandled!"
      
    end
    return id
  end
  
  def max_tcp_seg_between( begin_stage_name, end_stage_name )
    begin_id = stage_id( begin_stage_name )
    end_id = stage_id( end_stage_name )
            
    max_seg = @tcp_max_seg[ stage_id( begin_stage_name ) ]
    begin_id.upto end_id do |id|
      if @tcp_max_seg[ id ] > max_seg
        max_seg = @tcp_max_seg[ id ]
      end
    end
    
    return max_seg
  end
  
  def max_smb_seg_between( begin_stage_name, end_stage_name )
    begin_id = stage_id( begin_stage_name )
    end_id = stage_id( end_stage_name )
           
    max_seg = @smb_max_seg[ stage_id( begin_stage_name ) ]
    begin_id.upto end_id do |id|
      if @smb_max_seg[ id ] > max_seg
        max_seg = @smb_max_seg[ id ]
      end
    end
    
    return max_seg
  end
  
  def valid_evasion?(evasion, option_hash)
    evasion_name = nil
        
    if evasion.instance_of? Predator4Module::StageEvasion
      # Do stage-specific validation
      if evasion.base_name == S_TCP_SEG_EV
        seg_size = option_hash[S_TCP_SEG_SIZE]
        max_tcp_seg = max_tcp_seg_between( evasion.begin.name, evasion.end.name )
        if seg_size > max_tcp_seg
          return false  # TCP segments larger than payload
        end
      elsif evasion.base_name == S_SMB_SEG_EV
        seg_size = option_hash[S_SMB_SEG_SIZE]
        max_smb_seg = max_smb_seg_between( evasion.begin.name, evasion.end.name )
        if seg_size > max_smb_seg
          return false  # SMB writes larger than payload
        end
      end
      
      evasion_name = evasion.base_name
    else
      evasion_name = evasion.name      
    end
    
    # Do general validation, does not depend on stages
    if evasion_name == S_TCP_OVERLAP_EV
      if option_hash[ S_TCP_OVERLAP_DIRECTION ] == "old"
        return false  # Non-windows tcp overlap style
      end
    elsif evasion_name == S_MSRPC_SEG_EV
      if option_hash[S_MSRPC_SEG_SIZE] > 720
        return false  # Larger segments than payload
      end
    elsif evasion_name == S_TCP_BOGUSSYNSENTACKS_EV
      if option_hash[S_TCP_BOGUSSYNSENTACKS_DELAY] * option_hash[S_TCP_BOGUSSYNSENTACKS_NUMBER] > 10000
        return false  # > 10s handshake, will timeout on windows
      end
    elsif evasion_name == S_TCP_NUMRETRANS_EV
      if option_hash[S_TCP_NUMRETRANS_NUMBER] > 10 or option_hash[S_TCP_NUMRETRANS_TIMEOUT] > 20
        return false  # Allow only slightly larger retransmission/timeout values
      end
    elsif evasion_name == S_TCP_TIMEWAIT_EV
      if option_hash[S_TCP_TIMEWAIT_NUMBER] > 16
        return false # Allow only a small number of timewait decoys
      end
    elsif evasion_name == S_TCP_PAYLOADACKSLEEP_EV
      if option_hash[S_TCP_PAYLOADACKSLEEP_NUMBER] > 80000
        return false
      end
    elsif evasion_name == S_TCP_SYNSENTSLEEP_EV
      if option_hash[S_TCP_SYNSENTSLEEP_NUMBER] > 24000
        return false
      end
    elsif evasion_name == S_TCP_SEGVAR_EV
      if option_hash[S_TCP_MINIMUM_SEGMENT_SIZE] >= option_hash[S_TCP_MAXIMUM_SEGMENT_SIZE]
        return false
      end
    end           
   
    
    return true
  end

  def valid_combo?(configured_evasions)
    if not super(configured_evasions)
      return false
    end
    evasion_by_name = {}
    configured_evasions.each_key do |evasion|
      if evasion.instance_of? Predator4Module::StageEvasion
        evasion_by_name[evasion.base_name] = evasion
      else
        evasion_by_name[evasion.name] = evasion
      end
    end
    
    # Extract evasions 
    ipv4_frag_size = ipv4_order = nil
    tcp_seg_size = tcp_overlap = tcp_inittsopt = tcp_paws = tcp_order = nil
    smb_seg_size = nil 
    msrpc_seg_size = msrpc_group_sends = nil

    if evasion_by_name.has_key? S_IPV4_FRAG_EV
      ipv4_frag_size = configured_evasions[ evasion_by_name[ S_IPV4_FRAG_EV ] ].values.first
    end
    if evasion_by_name.has_key? S_IPV4_ORDER_EV
      ipv4_order = configured_evasions[ evasion_by_name[ S_IPV4_ORDER_EV ] ].values.first
    end
    
    if evasion_by_name.has_key? S_TCP_SEG_EV
      tcp_seg_size = configured_evasions[ evasion_by_name[ S_TCP_SEG_EV ] ].values.first
    end
    if evasion_by_name.has_key? S_TCP_OVERLAP_EV
      tcp_overlap = configured_evasions[ evasion_by_name[ S_TCP_OVERLAP_EV ] ].values.first
    end
    if evasion_by_name.has_key? S_TCP_INITTSOPT_EV
      tcp_inittsopt = configured_evasions[ evasion_by_name[ S_TCP_INITTSOPT_EV ] ].values
    end    
    if evasion_by_name.has_key? S_TCP_PAWS_EV
      tcp_paws = configured_evasions[ evasion_by_name[ S_TCP_PAWS_EV ] ].values
    end
    if evasion_by_name.has_key? S_TCP_ORDER_EV
      tcp_order = configured_evasions[ evasion_by_name[ S_TCP_ORDER_EV ] ].values
    end
    
    if evasion_by_name.has_key? S_SMB_SEG_EV
      smb_seg_size = configured_evasions[ evasion_by_name[ S_SMB_SEG_EV ] ].values.first
    end
    if evasion_by_name.has_key? S_MSRPC_SEG_EV
      msrpc_seg_size = configured_evasions[ evasion_by_name[ S_MSRPC_SEG_EV ] ].values.first
    end    
    if evasion_by_name.has_key? S_MSRPC_GROUPSENDS_EV
      msrpc_group_sends = configured_evasions[ evasion_by_name[ S_MSRPC_GROUPSENDS_EV ] ].values.first
    end
   
            
    if not ipv4_frag_size.nil?
      if not tcp_seg_size.nil?
        if ipv4_frag_size > tcp_seg_size + 32
          return false  # Larger IPv4 fragments than TCP packets
        end
      end
    elsif not ipv4_order.nil?
      # IPv4 not fragmented, no need to reorder
      return false
    end
    
    if not tcp_overlap.nil?
      if not tcp_seg_size.nil?
        if tcp_overlap > tcp_seg_size
          return false
        end
      end
    end
    
    if not tcp_order.nil?
      if tcp_seg_size.nil?
        return false    # No point in changing TCP order without segmentation ( baseline attack doesn't segment )
      end
    end
    
    if not msrpc_group_sends.nil?
      if msrpc_seg_size.nil?
        return false    # Cannot group what does no exist
      else
        if msrpc_group_sends > ( 720 / msrpc_seg_size )
          return false # Not enough fragments to group
        end 
      end
    end
    
    if not tcp_paws.nil?
      if not tcp_inittsopt.nil?
        if( tcp_inittsopt[ 0 ] == "disable" )
          return false  # Cannot disable timestamps and do PAWS
        end
      end
    end        
    
    return true
  end

  def to_s
    return "#{__FILE__}: Validate Conficker against Windows XP SP2"
  end
end

register_external(Conficker42Proc)
