Please, send it as a patch, reading just this email I have no clue where to put this, how to install it, ...

Thank you,
Jirka

On 06/11/2013 05:56 PM, Denys Vlasenko wrote:
On 06/11/2013 02:32 PM, Denys Vlasenko wrote:
This is an RFC, please do not commit.

Usage:
Run abrt-action-analyze-vulnerability in a directory which contains
./coredump file.
If crash looks exploitable, the tool creates ./exploitable file.
Example such a file:

"""
Program tried to write to an invalid address
Exploitable rating (1-10 scale):
6
"""

This patch adds abrt-action-analyze-vulnerability invocation to
"EVENT=post-create analyzer=CCpp".


TODO:
* decide what to do if we _dont_ see particularly suspicious stuff -
   do nothing? Or still create a file?
* improve error detection (e.g. what to do if gdb failed to run?)
* suppress stray gdb output
* set $SIGNO_OF_THE_COREDUMP to work around non-working $_siginfo.si_signo
* decide whether to push for $_signo support in gdb (I have a tested gdb patch)
* instruction analyzer is x86 specific now, make it per-arch
   (how to get arch name???)


Updated abrt-gdb-exploitable script (many more instructions added):


#!/usr/bin/python
# This is a GDB plugin.
# Usage:
# gdb --batch -ex "source THIS_FILE" -ex run -ex abrt-exploitable PROG
# or
# gdb --batch -ex "source THIS_FILE" -ex "core COREDUMP" -ex abrt-exploitable

import gdb
import os
import signal

_writing_instr = {
     # insn:N, where N:
     # 0:  this insn never writes to memory
     # -1: this insn always writes to memory
     # -2: writes to memory if any operand is a memory operand
     # 1:  writes to memory if 1st operand is a memory operand
     # 2:  writes to memory if 2nd (or later) operand is a memory operand
     #
     # Two-operand insns
     "add":2,
     "adc":2,
     "sub":2,
     "sbb":2,
     "and":2,
     "xor":2,
     "or":2,
     "xadd":2,
     "cmpxchg":2,
     # One-operand insns. Can use 1 or -2
     "inc":-2,
     "dec":-2,
     "neg":-2,
     "not":-2,
     "pop":-2,
     # "Set byte on condition". One-operand insns.
     "seta":-2,
     "setae":-2,
     "setb":-2,
     "setbe":-2,
     "setc":-2,
     "sete":-2,
     "setg":-2,
     "setge":-2,
     "setl":-2,
     "setle":-2,
     "setna":-2,
     "setnae":-2,
     "setnb":-2,
     "setnbe":-2,
     "setnc":-2,
     "setne":-2,
     "setng":-2,
     "setnge":-2,
     "setnl":-2,
     "setnle":-2,
     "setno":-2,
     "setnp":-2,
     "setns":-2,
     "setnz":-2,
     "seto":-2,
     "setp":-2,
     "setpe":-2,
     "setpo":-2,
     "sets":-2,
     "setz":-2,
     # Shifts.
     # sarl $2,(%rcx)
     # sarl (%rax) - *implicit* operand (shift count) 1.
     # shld 11,%ecx,(%rdi) - *third* operand is r/m.
     # Luckily, any memory operand is a destination, can use -2.
     "shl":-2,
     "shr":-2,
     "sal":-2,
     "sar":-2,
     "rol":-2,
     "ror":-2,
     "rcl":-2,
     "rcr":-2,
     "shld":-2,
     "shrd":-2,
     # Bit tests. Any memory operand is a destination, can use -2.
     "bts":-2,
     "btr":-2,
     "btc":-2,
     # One-operand (register pair is another, implicit operand).
     "cmpxchg8b":-2,
     "cmpxchg16b":-2,

     # Either mem operand indicates write to mem.
     "xchg":-2,

     # String store insns.
     # Look similar to widening signed move "movs[bwl][wlq]",
     # but aliasing doesn't happen since widening move has two siffixes
     "movs":-1,
     "stos":-1,
     # Widening moves never store to mem.
     # May look like we need to list them because otherwise they get caught
     # by "movXXX", but thankfully their 2nd operand is never a memory 
reference,
     # which "movXXX" wildcard checks.
     #"mov[sz][bwl][wlq]":0,

     # These always write to stack:
     "push":-1,
     "pusha":-1,
     "pushf":-1,
     "enter":-1,
     #"call"?

     # One-operand insn.
     # These are system insns, but they do NOT cause exception in userspace.
     "smsw":-2,
     "sgdt":-2,
     "sidt":-2,
     "sldt":-2,
     "str":-2,

     # FPU/SIMD madness follows.

     # FPU store insns. One-operand.
     "fsts":-2,
     "fstl":-2,
     #"fstt" doesn't exist
     "fstps":-2,
     "fstpl":-2,
     "fstpt":-2,
     # Saving state. One-operand insns.
     "fstcw":-2,
     "fnstcw":-2,
     "fstsw":-2,
     "fnstsw":-2,
     "fstenv":-2,
     "fnstenv":-2,
     "fsave":-2,
     "fnsave":-2,
     "fxsave":-2,
     "xsave":-2,
     "xsaveopt":-2,
     "fsave64":-2,
     "fnsave64":-2,
     "fxsave64":-2,
     "xsave64":-2,
     "xsaveopt64":-2,
     "stmxcsr":-2,
     "vstmxcsr":-2,
     # SIMD store insns.
     # Three-operand insns. Any memory operand is a destination.
     "vcvtps2ph":-2,
     "extractps":-2,
     "vextractps":-2,
     #[v]extractpd does not exist
     "vextractf128":-2,
     "vextracti128":-2,
     "pextr":-2,       # covers pextr[bwq]
     "pextrd":-2,
     "vpextr":-2,
     "vpextrd":-2,
     "vmaskmovpd":-2,
     "vmaskmovps":-2,
     "vpmaskmovd":-2,
     "vpmaskmovq":-2,
     # These insns have implicit (%edi) dest operand:
     "maskmovq":-1,    # mmx version
     "maskmovdqu":-1,
     "vmaskmovdqu":-1,

     # check binutils/gas/testsuite/gas/i386/* for more weird insns
     # http://download.intel.com/products/processor/manual/253666.pdf
     # http://download.intel.com/products/processor/manual/253667.pdf
     # 
http://software.intel.com/sites/default/files/m/0/3/c/d/4/18187-d9156103.pdf
     # http://download-software.intel.com/sites/default/files/319433-014.pdf

     #"vmovXXX" - special-cased in the code
     "mov":2
}

_jumping_instr = {
     "jmp":-1,  # indirect jumps/calls with garbage data
     "call":-1, # call: also possible that stack is exhausted (infinite 
recursion)
     #"push":-1, ?
     #"pusha":-1,
     #"enter":-1,

     "ret":-1   # stack smashed
}

#Our initial set of testing will use the list Apple included in their
#CrashWrangler announcement:
#
#Exploitable if:
#        Crash on write instruction
#*        Crash executing invalid address
#*        Crash calling an invalid address
#        Crash accessing an uninitialized or freed pointer as indicated by
#            using the MallocScribble environment variable
#*        Illegal instruction exception
#        Abort due to -fstack-protector, _FORTIFY_SOURCE, heap corruption
#            detected
#        Stack trace of crashing thread contains certain functions such as
#            malloc, free, szone_error, objc_MsgSend, etc.

def _get_signal_and_instruction(self):
     self.signo = None
     try:
         sig = gdb.parse_and_eval("$_signo") # ("$_siginfo.si_signo")
         # type(sig) = <type 'gdb.Value'>
         # sig is 8 (for SIGFPE)
         self.signo = int(sig)
     except gdb.error:
         # Python Exception <class 'gdb.error'> Attempt to extract a component 
of a value that is not a structure.:
         # Possible reasons why $_siginfo doesn't exist:
         # program is still running, program exited normally,
         # we work with a coredump from an old kernel.
         #
         # HACK_ALERT: kernels before 3.?.? do not record siginfo in coredumps,
         # so $_siginfo isn't present.
         # Lets see whether we are running from the abrt and it provided us 
with signal number
         #
         try:
             self.signo = int(os.environ["SIGNO_OF_THE_COREDUMP"])
         except KeyError:
             return False

     self.current_instruction = None
     self.mnemonic = None
     self.operands = ""
     try:
         # just "disassemble $pc" won't work if $pc doesn't point
         # inside a known function
         instructions = gdb.execute("disassemble $pc,$pc+32", to_string=True)
         # type(instructions) = <type 'str'>
     except gdb.error:
         # For example, if tracee already exited normally:
         # Python Exception <class 'gdb.error'> No registers.:
         return False

     raw_instructions = instructions
     #print instructions
     instructions = []
     current = None
     for line in raw_instructions.split("\n"):
         # line can be:
         # "Dump of assembler code from 0xAAAA to 0xBBBB:"
         # "[=>] 0x00000000004004dc[ <+0>]:  push   %rbp"
         #   (" <+0>" part is present when we run on a live process,
         #   on coredump it is absent)
         # "End of assembler dump."
         # "" (empty line)
         if line.startswith("=>"):
             line = line[2:]
             current = len(instructions)
         line = line.split(":", 1)
         if len(line) < 2:        # no ":"?
             continue
         line = line[1]           # drop "foo:"
         line = line.strip()      # drop leading/trailing whitespace
         if line:
             instructions.append(line)
     if current == None:
         # not False! we determined that $pc points to a bad address,
         # which is an interesting fact.
         return True

     self.current_instruction = instructions[current]
     # TODO: too simplistic.
     # consider this example:
     # "data32 data32 data32 nopw %cs:0x0(%rax,%rax,1)"
     t = self.current_instruction.split(None,2)
     self.mnemonic = t[0]
     if len(t) > 1:
         self.operands = t[1]
     return True

def _fetch_insn_from_table(ins, table):
     if not ins:
         return None
     if ins in table.keys():
         return table[ins]
     # Drop common byte/word/long/quad suffix and try again
     if ins[-1] in ("b", "w", "l", "q"):
         ins = ins[:-1]
         if ins in table.keys():
             return table[ins]
     return None

def _instruction_is_writing(self):
     operand = _fetch_insn_from_table(self.mnemonic, _writing_instr)
     if not operand:
         if not self.mnemonic:
             return False
         # There are far too many SSE store instructions,
         # don't want to pollute the table with them.
         # Special-case the check for MOVxxx
         # and its SIMD cousins VMOVxxx:
         if self.mnemonic[:3] != "mov" and self.mnemonic[:4] != "vmov":
             return False
         operand = 2

     if operand == -1:  # no need to check operands, it's a write
         return 1

     #FIXME: this is a memory operand! "add 0x7b,%eax"
     # (as opposed to "add $0x7b,%eax", where 0x7b is immediate)
     #FIXME: st(n) is not a memory operand

     paren = self.operands.find("(")
     if paren < 0:
         return False # no memory operands

     if operand == -2:  # any mem operand indicates write
         return 1

     comma = self.operands.find(",")
     if paren < comma:
         # "%cs:0x0(%rax,%rax,1),foo" - 1st operand is memory
         # "%cs:0x0(%rax),foo" - 1st operand is memory
         memory_operand = 1
     elif comma < 0:
         # "%cs:0x0(%rax)" - 1st operand is memory
         memory_operand = 1
     else:
         # paren is after comma
         # "foo,%cs:0x0(%rax,%rax,1)" - 2nd operand is memory
         # (It also can be a third, fourth etc operand)
         memory_operand = 2

     if operand != memory_operand:
         return False
     return True

def _instruction_is_jump(self):
     if _fetch_insn_from_table(self.mnemonic, _jumping_instr):
         return True
     return False


def _is_exploitable(self):
     self.exploitable_rating = 3
     self.exploitable_desc = ""
     if 0:
         pass
     elif self.signo == signal.SIGFPE:
         self.exploitable_rating = 1
         self.exploitable_desc = "Arithmetic exceptions (such as division by zero) 
are rarely exploitable"
         # TODO? look at instruction, if it is a division, lower rating to 0?
         # Or at least give a better desc ("Division by zero" and "(Other) 
arithmetic exception" is more informative)
     elif self.signo == signal.SIGILL:
         self.exploitable_rating = 5
         self.exploitable_desc = "SIGILL may be an indication that program jumped to 
a random address"
     elif not self.current_instruction:  # TODO: and SIGSEGV?
         self.exploitable_rating = 6
         self.exploitable_desc = "Program jumped to an invalid address"
     elif _instruction_is_writing(self):
         self.exploitable_rating = 6
         self.exploitable_desc = "Program tried to write to an invalid address"
     #elif self.signo = signal.SIGfoo:


class AbrtExploitable(gdb.Command):
     "Analyze a crash to determine exploitability"
     def __init__(self):
         super(AbrtExploitable, self).__init__(
                 "abrt-exploitable",
                 gdb.COMMAND_SUPPORT, # command class
                 gdb.COMPLETE_NONE,   # completion method
                 False  # => it's not a prefix command
         )

     # Called when the command is invoked from GDB
     def invoke(self, arg, from_tty):
         if not _get_signal_and_instruction(self):
             return
         #print "w", _instruction_is_writing(self)
         _is_exploitable(self)
         if self.exploitable_desc and self.exploitable_rating > 3:
             f = sys.stdout
             if arg:
                 f = open(arg, 'w')
             f.write(self.exploitable_desc + "\n")
             f.write("Exploitable rating (1-10 scale):\n" + str(self.exploitable_rating) 
+ "\n")

AbrtExploitable()


Reply via email to