### BEGIN LICENSE ###
### Use of the triage tools and related source code is subject to the terms
### of the license below.
###
### ------------------------------------------------------------------------
### Copyright (C) 2011 Carnegie Mellon University. All Rights Reserved.
### ------------------------------------------------------------------------
### Redistribution and use in source and binary forms, with or without
### modification, are permitted provided that the following conditions are
### met:
###
### 1. Redistributions of source code must retain the above copyright
###    notice, this list of conditions and the following acknowledgments
###    and disclaimers.
###
### 2. Redistributions in binary form must reproduce the above copyright
###    notice, this list of conditions and the following disclaimer in the
###    documentation and/or other materials provided with the distribution.
###
### 3. All advertising materials for third-party software mentioning
###    features or use of this software must display the following
###    disclaimer:
###
###    "Neither Carnegie Mellon University nor its Software Engineering
###     Institute have reviewed or endorsed this software"
###
### 4. The names "Department of Homeland Security," "Carnegie Mellon
###    University," "CERT" and/or "Software Engineering Institute" shall
###    not be used to endorse or promote products derived from this software
###    without prior written permission. For written permission, please
###    contact permission@sei.cmu.edu.
###
### 5. Products derived from this software may not be called "CERT" nor
###    may "CERT" appear in their names without prior written permission of
###    permission@sei.cmu.edu.
###
### 6. Redistributions of any form whatsoever must retain the following
###    acknowledgment:
###
###    "This product includes software developed by CERT with funding
###     and support from the Department of Homeland Security under
###     Contract No. FA 8721-05-C-0003."
###
### THIS SOFTWARE IS PROVIDED BY CARNEGIE MELLON UNIVERSITY ``AS IS'' AND
### CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
### EXPRESS OR IMPLIED, AS TO ANY MATTER, AND ALL SUCH WARRANTIES, INCLUDING
### WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE
### EXPRESSLY DISCLAIMED. WITHOUT LIMITING THE GENERALITY OF THE FOREGOING,
### CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND
### RELATING TO EXCLUSIVITY, INFORMATIONAL CONTENT, ERROR-FREE OPERATION,
### RESULTS TO BE OBTAINED FROM USE, FREEDOM FROM PATENT, TRADEMARK AND
### COPYRIGHT INFRINGEMENT AND/OR FREEDOM FROM THEFT OF TRADE SECRETS.
### END LICENSE ###
'''
A collection of objects used to classify GDB Inferiors (Targets).
'''

from tools import AttrDict
import rules
import versions
from copy import deepcopy
import warnings

class Tag(object):
    '''
    A Tag used for classification. A Tag is a partial description of the
    state of a GDB Inferior. Tags can be ordered by ranking.
    '''
    def __init__(self, tag_dict):
        self.__dict__ = tag_dict

    def __cmp__(self, other):
        if type(other) != type(self):
            raise TypeError("cannot compare type %s to type %s" % \
                            (type(other),type(self)))
        return self.ranking[0] - other.ranking[0]

    def __str__(self):
        return "%s (%d/%d)" % ((self.short_desc,) + self.ranking)

class Classification(AttrDict):
    '''
    A classification of a state of a GDB Inferior. Describes how exploitable
    the current state of the GDB Inferior is.

    An instance of this object is returned by a Classifier.
    '''
    version = versions.exploitable_version
    def __init__(self):
        self.tags = []

    def __add__(self, tag):
        if type(tag) != Tag:
            raise TypeError("cannot add type %s to type %s" % \
                            (type(tag), type(self)))
        self.tags.append(tag)
        self.tags.sort()
        for k, v in self.tags[0].__dict__.iteritems():
            self[k] = v

        return self

    def __cmp__(self, other):
        if type(other) != type(self):
            raise TypeError("cannot compare type %s to type %s" % \
                            (type(other),type(self)))

        if len(self.tags) == 0 or len(other.tags) == 0:
            return len(self.tags) - len(other.tags)

        i = 0
        while i < len(self.tags) and i < len(other.tags):
            result = cmp(self.tags[i], other.tags[i])
            if result:
                return result
            i += 1
        return result

    def __str__(self):
        if len(self.tags) == 0:
            return "No matches"

        result = "Description: %s\n" % self.desc
        result += "Short description: %s\n" % str(self.tags[0])
        result += "Hash: %s.%s\n" % (self.hash.major, self.hash.minor)
        result += "Exploitability Classification: %s\n" % self.category
        result += "Explanation: %s\n" % self.explanation
        if len(self.tags) > 1:
            result += "Other tags: %s" % \
                ", ".join([str(r) for r in self.tags[1:]])
        return result

class Classifier():
    '''
    A Classifier used for classifying the state of a Target (a Linux GDB
    Inferior).
    '''
    _major_hash_depth = 5

    def __init__(self):
        '''
        Organizes the nested list of rules (dicts) for classification

        The rules specified in rules.py are organized into AttrDicts ("rules").
        Each rule is composed of a tag and an analyzer.
        '''
        processed_rules = []
        num_rules = sum([len(rl) for c, rl in rules.rules])
        ranking = 1
        for cat, user_rule_list in rules.rules:
            for user_rule in user_rule_list:
                analyzer = user_rule['analyzer']
                tag_data = deepcopy(user_rule)
                del tag_data ['analyzer']
                tag_data['ranking'] = (ranking, num_rules)
                tag_data['category'] = cat
                rule = AttrDict(analyzer=analyzer, tag=Tag(tag_data))
                processed_rules.append(rule)
                ranking += 1

        self.rules = processed_rules

    def getClassification(self, target):
        '''
        Returns the Classification of target, which is a Classification of the
        exploitability of a Linux GDB Inferior.
        '''
        c = Classification()
        for rule in self.rules:
            try:
                match = rule.analyzer(target)
                if match:
                    c += rule.tag
            except RuntimeError as e:
                warnings.warn("Error while analyzing rule %s: %s"
                              % (rule.tag, str(e)))

        c.hash = target.hash()
        return c

def getClassification(target):
    '''
    Returns the exploitability Classification of target. target should be an
    instance of Target -- a representation of the state of the GDB Inferior.
    '''
    return Classifier().getClassification(target)
