michael@0: // Copyright (c) 2010 Google Inc. michael@0: // All rights reserved. michael@0: // michael@0: // Redistribution and use in source and binary forms, with or without michael@0: // modification, are permitted provided that the following conditions are michael@0: // met: michael@0: // michael@0: // * Redistributions of source code must retain the above copyright michael@0: // notice, this list of conditions and the following disclaimer. michael@0: // * Redistributions in binary form must reproduce the above michael@0: // copyright notice, this list of conditions and the following disclaimer michael@0: // in the documentation and/or other materials provided with the michael@0: // distribution. michael@0: // * Neither the name of Google Inc. nor the names of its michael@0: // contributors may be used to endorse or promote products derived from michael@0: // this software without specific prior written permission. michael@0: // michael@0: // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT michael@0: // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, michael@0: // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT michael@0: // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: michael@0: // exploitability_win.cc: Windows specific exploitability engine. michael@0: // michael@0: // Provides a guess at the exploitability of the crash for the Windows michael@0: // platform given a minidump and process_state. michael@0: // michael@0: // Author: Cris Neckar michael@0: michael@0: #include michael@0: michael@0: #include "processor/exploitability_win.h" michael@0: michael@0: #include "common/scoped_ptr.h" michael@0: #include "google_breakpad/common/minidump_exception_win32.h" michael@0: #include "google_breakpad/processor/minidump.h" michael@0: #include "processor/disassembler_x86.h" michael@0: #include "processor/logging.h" michael@0: michael@0: #include "third_party/libdisasm/libdis.h" michael@0: michael@0: namespace google_breakpad { michael@0: michael@0: // The cutoff that we use to judge if and address is likely an offset michael@0: // from various interesting addresses. michael@0: static const uint64_t kProbableNullOffset = 4096; michael@0: static const uint64_t kProbableStackOffset = 8192; michael@0: michael@0: // The various cutoffs for the different ratings. michael@0: static const size_t kHighCutoff = 100; michael@0: static const size_t kMediumCutoff = 80; michael@0: static const size_t kLowCutoff = 50; michael@0: static const size_t kInterestingCutoff = 25; michael@0: michael@0: // Predefined incremental values for conditional weighting. michael@0: static const size_t kTinyBump = 5; michael@0: static const size_t kSmallBump = 20; michael@0: static const size_t kMediumBump = 50; michael@0: static const size_t kLargeBump = 70; michael@0: static const size_t kHugeBump = 90; michael@0: michael@0: // The maximum number of bytes to disassemble past the program counter. michael@0: static const size_t kDisassembleBytesBeyondPC = 2048; michael@0: michael@0: ExploitabilityWin::ExploitabilityWin(Minidump *dump, michael@0: ProcessState *process_state) michael@0: : Exploitability(dump, process_state) { } michael@0: michael@0: ExploitabilityRating ExploitabilityWin::CheckPlatformExploitability() { michael@0: MinidumpException *exception = dump_->GetException(); michael@0: if (!exception) { michael@0: BPLOG(INFO) << "Minidump does not have exception record."; michael@0: return EXPLOITABILITY_ERR_PROCESSING; michael@0: } michael@0: michael@0: const MDRawExceptionStream *raw_exception = exception->exception(); michael@0: if (!raw_exception) { michael@0: BPLOG(INFO) << "Could not obtain raw exception info."; michael@0: return EXPLOITABILITY_ERR_PROCESSING; michael@0: } michael@0: michael@0: const MinidumpContext *context = exception->GetContext(); michael@0: if (!context) { michael@0: BPLOG(INFO) << "Could not obtain exception context."; michael@0: return EXPLOITABILITY_ERR_PROCESSING; michael@0: } michael@0: michael@0: MinidumpMemoryList *memory_list = dump_->GetMemoryList(); michael@0: bool memory_available = true; michael@0: if (!memory_list) { michael@0: BPLOG(INFO) << "Minidump memory segments not available."; michael@0: memory_available = false; michael@0: } michael@0: uint64_t address = process_state_->crash_address(); michael@0: uint32_t exception_code = raw_exception->exception_record.exception_code; michael@0: michael@0: uint32_t exploitability_weight = 0; michael@0: michael@0: uint64_t stack_ptr = 0; michael@0: uint64_t instruction_ptr = 0; michael@0: uint64_t this_ptr = 0; michael@0: michael@0: switch (context->GetContextCPU()) { michael@0: case MD_CONTEXT_X86: michael@0: stack_ptr = context->GetContextX86()->esp; michael@0: instruction_ptr = context->GetContextX86()->eip; michael@0: this_ptr = context->GetContextX86()->ecx; michael@0: break; michael@0: case MD_CONTEXT_AMD64: michael@0: stack_ptr = context->GetContextAMD64()->rsp; michael@0: instruction_ptr = context->GetContextAMD64()->rip; michael@0: this_ptr = context->GetContextAMD64()->rcx; michael@0: break; michael@0: default: michael@0: BPLOG(INFO) << "Unsupported architecture."; michael@0: return EXPLOITABILITY_ERR_PROCESSING; michael@0: } michael@0: michael@0: // Check if we are executing on the stack. michael@0: if (instruction_ptr <= (stack_ptr + kProbableStackOffset) && michael@0: instruction_ptr >= (stack_ptr - kProbableStackOffset)) michael@0: exploitability_weight += kHugeBump; michael@0: michael@0: switch (exception_code) { michael@0: // This is almost certainly recursion. michael@0: case MD_EXCEPTION_CODE_WIN_STACK_OVERFLOW: michael@0: exploitability_weight += kTinyBump; michael@0: break; michael@0: michael@0: // These exceptions tend to be benign and we can generally ignore them. michael@0: case MD_EXCEPTION_CODE_WIN_INTEGER_DIVIDE_BY_ZERO: michael@0: case MD_EXCEPTION_CODE_WIN_INTEGER_OVERFLOW: michael@0: case MD_EXCEPTION_CODE_WIN_FLOAT_DIVIDE_BY_ZERO: michael@0: case MD_EXCEPTION_CODE_WIN_FLOAT_INEXACT_RESULT: michael@0: case MD_EXCEPTION_CODE_WIN_FLOAT_OVERFLOW: michael@0: case MD_EXCEPTION_CODE_WIN_FLOAT_UNDERFLOW: michael@0: case MD_EXCEPTION_CODE_WIN_IN_PAGE_ERROR: michael@0: exploitability_weight += kTinyBump; michael@0: break; michael@0: michael@0: // These exceptions will typically mean that we have jumped where we michael@0: // shouldn't. michael@0: case MD_EXCEPTION_CODE_WIN_ILLEGAL_INSTRUCTION: michael@0: case MD_EXCEPTION_CODE_WIN_FLOAT_INVALID_OPERATION: michael@0: case MD_EXCEPTION_CODE_WIN_PRIVILEGED_INSTRUCTION: michael@0: exploitability_weight += kLargeBump; michael@0: break; michael@0: michael@0: // These represent bugs in exception handlers. michael@0: case MD_EXCEPTION_CODE_WIN_INVALID_DISPOSITION: michael@0: case MD_EXCEPTION_CODE_WIN_NONCONTINUABLE_EXCEPTION: michael@0: exploitability_weight += kSmallBump; michael@0: break; michael@0: michael@0: case MD_EXCEPTION_CODE_WIN_HEAP_CORRUPTION: michael@0: case MD_EXCEPTION_CODE_WIN_STACK_BUFFER_OVERRUN: michael@0: exploitability_weight += kHugeBump; michael@0: break; michael@0: michael@0: case MD_EXCEPTION_CODE_WIN_GUARD_PAGE_VIOLATION: michael@0: exploitability_weight += kLargeBump; michael@0: break; michael@0: michael@0: case MD_EXCEPTION_CODE_WIN_ACCESS_VIOLATION: michael@0: bool near_null = (address <= kProbableNullOffset); michael@0: bool bad_read = false; michael@0: bool bad_write = false; michael@0: if (raw_exception->exception_record.number_parameters >= 1) { michael@0: MDAccessViolationTypeWin av_type = michael@0: static_cast michael@0: (raw_exception->exception_record.exception_information[0]); michael@0: switch (av_type) { michael@0: case MD_ACCESS_VIOLATION_WIN_READ: michael@0: bad_read = true; michael@0: if (near_null) michael@0: exploitability_weight += kSmallBump; michael@0: else michael@0: exploitability_weight += kMediumBump; michael@0: break; michael@0: case MD_ACCESS_VIOLATION_WIN_WRITE: michael@0: bad_write = true; michael@0: if (near_null) michael@0: exploitability_weight += kSmallBump; michael@0: else michael@0: exploitability_weight += kHugeBump; michael@0: break; michael@0: case MD_ACCESS_VIOLATION_WIN_EXEC: michael@0: if (near_null) michael@0: exploitability_weight += kSmallBump; michael@0: else michael@0: exploitability_weight += kHugeBump; michael@0: break; michael@0: default: michael@0: BPLOG(INFO) << "Unrecognized access violation type."; michael@0: return EXPLOITABILITY_ERR_PROCESSING; michael@0: break; michael@0: } michael@0: MinidumpMemoryRegion *instruction_region = 0; michael@0: if (memory_available) { michael@0: instruction_region = michael@0: memory_list->GetMemoryRegionForAddress(instruction_ptr); michael@0: } michael@0: if (!near_null && instruction_region && michael@0: context->GetContextCPU() == MD_CONTEXT_X86 && michael@0: (bad_read || bad_write)) { michael@0: // Perform checks related to memory around instruction pointer. michael@0: uint32_t memory_offset = michael@0: instruction_ptr - instruction_region->GetBase(); michael@0: uint32_t available_memory = michael@0: instruction_region->GetSize() - memory_offset; michael@0: available_memory = available_memory > kDisassembleBytesBeyondPC ? michael@0: kDisassembleBytesBeyondPC : available_memory; michael@0: if (available_memory) { michael@0: const uint8_t *raw_memory = michael@0: instruction_region->GetMemory() + memory_offset; michael@0: DisassemblerX86 disassembler(raw_memory, michael@0: available_memory, michael@0: instruction_ptr); michael@0: disassembler.NextInstruction(); michael@0: if (bad_read) michael@0: disassembler.setBadRead(); michael@0: else michael@0: disassembler.setBadWrite(); michael@0: if (disassembler.currentInstructionValid()) { michael@0: // Check if the faulting instruction falls into one of michael@0: // several interesting groups. michael@0: switch (disassembler.currentInstructionGroup()) { michael@0: case libdis::insn_controlflow: michael@0: exploitability_weight += kLargeBump; michael@0: break; michael@0: case libdis::insn_string: michael@0: exploitability_weight += kHugeBump; michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: // Loop the disassembler through the code and check if it michael@0: // IDed any interesting conditions in the near future. michael@0: // Multiple flags may be set so treat each equally. michael@0: while (disassembler.NextInstruction() && michael@0: disassembler.currentInstructionValid() && michael@0: !disassembler.endOfBlock()) michael@0: continue; michael@0: if (disassembler.flags() & DISX86_BAD_BRANCH_TARGET) michael@0: exploitability_weight += kLargeBump; michael@0: if (disassembler.flags() & DISX86_BAD_ARGUMENT_PASSED) michael@0: exploitability_weight += kTinyBump; michael@0: if (disassembler.flags() & DISX86_BAD_WRITE) michael@0: exploitability_weight += kMediumBump; michael@0: if (disassembler.flags() & DISX86_BAD_BLOCK_WRITE) michael@0: exploitability_weight += kMediumBump; michael@0: if (disassembler.flags() & DISX86_BAD_READ) michael@0: exploitability_weight += kTinyBump; michael@0: if (disassembler.flags() & DISX86_BAD_BLOCK_READ) michael@0: exploitability_weight += kTinyBump; michael@0: if (disassembler.flags() & DISX86_BAD_COMPARISON) michael@0: exploitability_weight += kTinyBump; michael@0: } michael@0: } michael@0: } michael@0: if (!near_null && AddressIsAscii(address)) michael@0: exploitability_weight += kMediumBump; michael@0: } else { michael@0: BPLOG(INFO) << "Access violation type parameter missing."; michael@0: return EXPLOITABILITY_ERR_PROCESSING; michael@0: } michael@0: } michael@0: michael@0: // Based on the calculated weight we return a simplified classification. michael@0: BPLOG(INFO) << "Calculated exploitability weight: " << exploitability_weight; michael@0: if (exploitability_weight >= kHighCutoff) michael@0: return EXPLOITABILITY_HIGH; michael@0: if (exploitability_weight >= kMediumCutoff) michael@0: return EXPLOITABLITY_MEDIUM; michael@0: if (exploitability_weight >= kLowCutoff) michael@0: return EXPLOITABILITY_LOW; michael@0: if (exploitability_weight >= kInterestingCutoff) michael@0: return EXPLOITABILITY_INTERESTING; michael@0: michael@0: return EXPLOITABILITY_NONE; michael@0: } michael@0: michael@0: } // namespace google_breakpad