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

import signal
import re
from tools import *

'''
Analyzers
If an analyzer function returns True the rule is considered a match
to the GDB inferior's state, otherwise the rule is not considered a match.
'''

# EXPLOITABLE

def isBranchAvNotNearNull(target):
    return isBranchAv(target) and \
        not isNearNull(faultingAddress(target))

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

def isSegFaultOnPcNotNearNull(target):
    return isSegFaultOnPc(target) and not isFaNearNull(target)

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

def isStackBufferOverflow(target):
    frames = ["__fortify_fail",
              "__stack_chk_fail"]
    # 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.
    return isInBacktrace(target, frames)

def isPossibleStackCorruption(target):
    if isBenign(target):
        return False

    if isStackOverflow(target):
        return False

    bt = target.backtrace()
    if bt.abnormal_termination:
        return True

    for fr in bt[1:]:
        if not fr.mapped_region:
            return True

    pm = target.procmaps().findByAddr(target.stack_pointer())
    if not pm or pm.name != "[stack]":
        return True

    return False

def isDestAvNotNearNull(target):
    return isDestAv(target) and not isFaNearNull(target)

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

# PROBABLY_EXPLOITABLE
def isStackOverflow(target):
    if not isAccessViolationSignal(target) or not 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 target.current_instruction().mnemonic and not ( \
           target.current_instruction().mnemonic == "call" and \
           faultingAddress(target) != target.current_instruction().dest.eval() and \
           faultingAddress(target) + target.pointer_size() == target.stack_pointer()):
        return False

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

    return True

def isMalformedInstructionSignal(target):
    siglist = ["SIGILL", "SIGSYS"]
    return isSignalInList(target, siglist)

def isSegFaultOnPcNearNull(target):
    return isSegFaultOnPc(target) and isFaNearNull(target)

def isBranchAvNearNull(target):
    return isBranchAv(target) and isNearNull(faultingAddress(target))

blk_mv_regex = re.compile("^rep.*mov.*$")
def isBlockMove(target):
    if isBenign(target) or not target.current_instruction():
        return False
    m = target.current_instruction().mnemonic
    if re.match(blk_mv_regex, m):
        return True
    return False

def isDestAvNearNull(target):
    return isDestAv(target) and isFaNearNull(target)

# PROBABLY_NOT_EXPLOITABLE

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

def isSourceAvNotNearNull(target):
    return isSourceAv(target) and not isFaNearNull(target)

def isFloatingPointException(target):
    siglist = ["SIGFPE"]
    return isSignalInList(target, siglist)

# UNKNOWN

def isSourceAvNearNull(target):
    return isSourceAv(target) and isFaNearNull(target)

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

def isAccessViolationSignal(target):
    return isSignalInList(target, ["SIGSEGV", "SIGBUS"])

def isUncategorizedSignal(target):
    return not (isAccessViolationSignal(target) or isAbortSignal(target) \
                or isBenignSignal(target) or isFloatingPointException(target) \
                or isMalformedInstructionSignal(target))

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

def isOnSignal(target):
    return bool(target.si_signo())

def isSignalInList(target, siglist):
    '''
    Returns True if target's signo is in siglist, False otherwise
    '''
    if not isOnSignal(target):
        return False
    for s in siglist:
        signo = getattr(signal, s, None) # not all sigs may be defined
        if signo:
            if signo == target.si_signo():
                return True
    return False

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

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

def isFaNearNull(target):
    return isNearNull(faultingAddress(target))

def isBenign(target):
    return isOnSignal(target) and isBenignSignal(target)

def isJumpInstruction(target):
    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 target.current_instruction() and \
        target.current_instruction().mnemonic in jumps

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

def faultingAddress(target):
    if isJumpInstruction(target):
        # 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 target.current_instruction().operands[0].eval()
    return target.si_addr()

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

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

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