### 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.
### Portions Copyright 2013 BlackBerry Ltd. 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 ###
'''
Contains analyzers used to match rules that are used to classify the state
of a GDB inferior and some helper functions.
'''

import re
import signal

from lib.tools import memoized

class Analyzer(object):
    '''
    Contains methods that analyze a Target (a Linux GDB inferior state) to 
    determine properties of the Target (such as how the application crashed).
    The methods correspond to "rules" defined in rules.py.
    If an analyzer method returns True the rule is considered a match
    to the GDB inferior's state, otherwise the rule is not considered a match.
    This class contains logic for x86 platforms (the "original" target of the
    exploitable plugin), and is inherited to implement logic for other 
    platforms.
    '''

    def __init__(self, target):
        self.target = target

    # EXPLOITABLE matching methods follow

    @memoized
    def isBranchAvNotNearNull(self):
        return self.isBranchAv() and \
            not self.isNearNull(self.faultingAddress())

    @memoized
    def isReturnAv(self):
        rets = ["iret", "ret"]
        return self.isAccessViolationSignal() and \
            (self.target.current_instruction() and \
            (self.target.current_instruction().mnemonic in rets))

    @memoized
    def isSegFaultOnPcNotNearNull(self):
        return self.isSegFaultOnPc() and not self.isFaNearNull()

    @memoized
    def isErrorWhileExecutingFromStack(self):
        if self.isBenign():
            return False
        sect = self.target.procmaps().findByAddr(self.target.pc())
        if sect and sect.name == "[stack]": # maybe check threadstacks too?
            return True
        return False

    @memoized
    def isStackBufferOverflow(self):
        # Some versions of libc attempt to print a backtrace when
        # __stack_chk_fail is invoked, which may cause a SIGSEGV in the SIGABRT
        # handler. This is a a known issue, see CVE 2010-3192.
        # Thus the isAbortSignal(target) check must be omitted.
 
        sequences = [["__fortify_fail", "__stack_chk_fail"],
                     ["__GI___fortify_fail", "__stack_chk_fail"]]
        for seq in sequences:
            if self.isInBacktrace(seq, "/libc"):
                return True
        return False

    @memoized
    def isPossibleStackCorruption(self):
        if self.isBenign():
            return False
    
        if self.isStackOverflow():
            return False
    
        bt = self.target.backtrace()
        was_abnormal_termination = bt.abnormal_termination
    
        if was_abnormal_termination:
            for fr in bt:
                if fr.name() is None:
                    return True
    
        for fr in bt[1:]:
            if not fr.mapped_region:
                return True
    
            pm = self.target.procmaps().findByAddr(self.target.stack_pointer())
            if not pm or pm.name != "[stack]":
                return True
    
        return False

    @memoized
    def isDestAvNotNearNull(self):
        return self.isDestAv() and not self.isFaNearNull()

    @memoized
    def isHeapError(self):
        libc_bts = [ ["abort", "__libc_message", "malloc_printerr"], # mcheck_print
                     ["__GI_abort", "__libc_message", "malloc_printerr"],
                 ["abort", "malloc_printerr"], # mcheck_noprint
                 ["free"],
                 ["malloc"],
                 ["__malloc_assert"] ]
    
        for seq in libc_bts:
            if self.isInBacktrace(seq, "/libc"):
                return True
        return False

    # PROBABLY_EXPLOITABLE matching methods follow

    @memoized
    def isStackOverflow(self):
        if not self.isAccessViolationSignal() or not self.target.current_instruction():
            return False

        # verify this is a push* instruction or
        # a call instruction where the AV is due to the "push"
        if "push" not in self.target.current_instruction().mnemonic and not ( \
                self.target.current_instruction().mnemonic == "call" and \
                self.faultingAddress() != self.target.current_instruction().dest.eval() and \
                self.faultingAddress() + self.target.pointer_size() == self.target.stack_pointer()):
            return False

        # verify the stack pointer is outside the default stack region
        pm = self.target.procmaps().findByAddr(self.target.stack_pointer())
        if pm and pm.name == "[stack]":
            return False

        return True

    @memoized
    def isMalformedInstructionSignal(self):
        siglist = ["SIGILL", "SIGSYS"]
        return self.isSignalInList(siglist)

    @memoized
    def isSegFaultOnPcNearNull(self):
        return self.isSegFaultOnPc() and self.isFaNearNull()

    @memoized
    def isBranchAvNearNull(self):
        return self.isBranchAv() and self.isNearNull(self.faultingAddress())

    _re_blk_mov = re.compile("^rep.*mov")
    @memoized
    def isBlockMove(self):
        if self.isBenign() or not self.target.current_instruction():
            return False
        m = self.target.current_instruction().mnemonic
        if self._re_blk_mov.match(m):
            return True
        return False

    @memoized
    def isDestAvNearNull(self):
        return self.isDestAv() and self.isFaNearNull()

    # PROBABLY_NOT_EXPLOITABLE matching methods follow

    @memoized
    def isBenignSignal(self):
       siglist = ["SIGTERM", "SIGINT", "SIGQUIT", "SIGKILL", "SIGHUP",
              "SIGALRM", "SIGVTALRM", "SIGPROF", "SIGIO", "SIGURG",
              "SIGPOLL", "SIGUSR1", "SIGUSR2", "SIGWINCH", "SIGINFO",
              "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP"]
       return self.isSignalInList(siglist)

    @memoized
    def isSourceAvNotNearNull(self):
        return self.isSourceAv() and not self.isFaNearNull()

    @memoized
    def isFloatingPointException(self):
        siglist = ["SIGFPE"]
        return self.isSignalInList(siglist)

    # UNKNOWN matching methods follow

    @memoized
    def isSourceAvNearNull(self):
        return self.isSourceAv() and self.isFaNearNull()

    @memoized
    def isAbortSignal(self):
        return self.isOnSignal() and self.target.si_signo() == signal.SIGABRT

    @memoized
    def isAccessViolationSignal(self):
         return self.isSignalInList(["SIGSEGV", "SIGBUS"])

    @memoized
    def isUncategorizedSignal(self):
        return not (self.isAccessViolationSignal() or self.isAbortSignal() \
            or self.isBenignSignal() or self.isFloatingPointException() \
            or self.isMalformedInstructionSignal())

    '''
    Helpers
    The following functions are called by multiple analyzers and are not 
    directly associated with a rule.
    '''

    @memoized
    def isOnSignal(self):
        return bool(self.target.si_signo())

    @memoized
    def isSignalInList(self, siglist):
        '''
        Returns True if target's signo is in siglist, False otherwise
        '''
        if not self.isOnSignal():
            return False
        tsigno = self.target.si_signo() # only get this ONCE per function call
        for s in siglist:
            signo = getattr(signal, s, None) # not all sigs may be defined
            if signo and signo == tsigno:
                return True
        return False

    def isNearNull(self, addr):
        '''
        Returns True of addr is near NULL, False otherwise
        '''
        if addr < 64 * 1024: # same as !exploitable
            return True
        return False

    @memoized
    def isInBacktrace(self, fnames, region=None):
        i = 0
        for fr in self.target.backtrace():
            if fr.name() and fnames[i] == fr.name() and (not region or not fr.mapped_region or region in fr.mapped_region.name):
                i = i + 1
                if i == len(fnames):
                    return True
            else:
                i = 0
        return False

    @memoized
    def isFaNearNull(self):
        return self.isNearNull(self.faultingAddress())

    @memoized
    def isBenign(self):
        return self.isOnSignal() and self.isBenignSignal()

    @memoized
    def isJumpInstruction(self):
        ins = self.target.current_instruction()
        jumps = ["ja", "jae", "jb", "jbe", "jc", "jcxz", "je", "jecxz", "jg",
                "jge", "jl", "jle", "jmp", "jna", "jnae", "jnb", "jnbe", "jnc",
                "jne", "jng", "jnge", "jnl", "jnle", "jno", "jnp", "jns", "jp",
                "js", "jz"]
        return ins and ins.mnemonic in jumps

    @memoized
    def isBranchAv(self):
        if not self.isAccessViolationSignal():
            return False
        ins = self.target.current_instruction()
        calls = ["call", "callq"]
        return self.isJumpInstruction() or (ins and ins.mnemonic in calls)

    @memoized
    def faultingAddress(self):
        if self.isJumpInstruction():
            # si_addr does not always contain a valid faulting address, but
            # jump instructions always access the dest op and GDB always displays
            # the absolute addr, so we can use the dest op instead of si_addr here.
            return self.target.current_instruction().operands[0].eval()
        return self.target.si_addr()

    @memoized
    def isSegFaultOnPc(self):
        return self.isAccessViolationSignal() and \
           self.faultingAddress() == self.target.pc()

    @memoized
    def isDestAv(self):
        if not self.isAccessViolationSignal():
            return False
        dest_op = getattr(self.target.current_instruction(), "dest", False)
        return dest_op and dest_op.is_pointer and \
            dest_op.eval() == self.faultingAddress()

    @memoized
    def isSourceAv(self):
        if not self.isAccessViolationSignal():
            return False
        source_op = getattr(self.target.current_instruction(), "source", False)
        return source_op and source_op.is_pointer and \
            source_op.eval() == self.faultingAddress()

    @memoized
    def isUseAfterFree(self):
        return False

