// Copyright (c) 2013 Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * 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. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // exploitability_linux.cc: Linux specific exploitability engine. // // Provides a guess at the exploitability of the crash for the Linux // platform given a minidump and process_state. // // Author: Matthew Riley #include "processor/exploitability_linux.h" #ifndef _WIN32 #include #include #include #include #include #endif // _WIN32 #include #include "google_breakpad/common/minidump_exception_linux.h" #include "google_breakpad/processor/call_stack.h" #include "google_breakpad/processor/process_state.h" #include "google_breakpad/processor/stack_frame.h" #include "processor/logging.h" namespace { // Prefixes for memory mapping names. constexpr char kHeapPrefix[] = "[heap"; constexpr char kStackPrefix[] = "[stack"; // This function in libc is called if the program was compiled with // -fstack-protector and a function's stack canary changes. constexpr char kStackCheckFailureFunction[] = "__stack_chk_fail"; // This function in libc is called if the program was compiled with // -D_FORTIFY_SOURCE=2, a function like strcpy() is called, and the runtime // can determine that the call would overflow the target buffer. constexpr char kBoundsCheckFailureFunction[] = "__chk_fail"; #ifndef _WIN32 const unsigned int MAX_INSTRUCTION_LEN = 15; const unsigned int MAX_OBJDUMP_BUFFER_LEN = 4096; #endif // _WIN32 } // namespace namespace google_breakpad { ExploitabilityLinux::ExploitabilityLinux(Minidump* dump, ProcessState* process_state) : Exploitability(dump, process_state), enable_objdump_(false) { } ExploitabilityLinux::ExploitabilityLinux(Minidump* dump, ProcessState* process_state, bool enable_objdump) : Exploitability(dump, process_state), enable_objdump_(enable_objdump) { } ExploitabilityRating ExploitabilityLinux::CheckPlatformExploitability() { // Check the crashing thread for functions suggesting a buffer overflow or // stack smash. if (process_state_->requesting_thread() != -1) { CallStack* crashing_thread = process_state_->threads()->at(process_state_->requesting_thread()); const vector& crashing_thread_frames = *crashing_thread->frames(); for (size_t i = 0; i < crashing_thread_frames.size(); ++i) { if (crashing_thread_frames[i]->function_name == kStackCheckFailureFunction) { return EXPLOITABILITY_HIGH; } if (crashing_thread_frames[i]->function_name == kBoundsCheckFailureFunction) { return EXPLOITABILITY_HIGH; } } } // Getting exception data. (It should exist for all minidumps.) MinidumpException* exception = dump_->GetException(); if (exception == NULL) { BPLOG(INFO) << "No exception record."; return EXPLOITABILITY_ERR_PROCESSING; } const MDRawExceptionStream* raw_exception_stream = exception->exception(); if (raw_exception_stream == NULL) { BPLOG(INFO) << "No raw exception stream."; return EXPLOITABILITY_ERR_PROCESSING; } // Checking for benign exceptions that caused the crash. if (this->BenignCrashTrigger(raw_exception_stream)) { return EXPLOITABILITY_NONE; } // Check if the instruction pointer is in a valid instruction region // by finding if it maps to an executable part of memory. uint64_t instruction_ptr = 0; uint64_t stack_ptr = 0; const MinidumpContext* context = exception->GetContext(); if (context == NULL) { BPLOG(INFO) << "No exception context."; return EXPLOITABILITY_ERR_PROCESSING; } // Getting the instruction pointer. if (!context->GetInstructionPointer(&instruction_ptr)) { BPLOG(INFO) << "Failed to retrieve instruction pointer."; return EXPLOITABILITY_ERR_PROCESSING; } // Getting the stack pointer. if (!context->GetStackPointer(&stack_ptr)) { BPLOG(INFO) << "Failed to retrieve stack pointer."; return EXPLOITABILITY_ERR_PROCESSING; } // Checking for the instruction pointer in a valid instruction region, // a misplaced stack pointer, and an executable stack or heap. if (!this->InstructionPointerInCode(instruction_ptr) || this->StackPointerOffStack(stack_ptr) || this->ExecutableStackOrHeap()) { return EXPLOITABILITY_HIGH; } // Check for write to read only memory or invalid memory, shelling out // to objdump is enabled. if (enable_objdump_ && this->EndedOnIllegalWrite(instruction_ptr)) { return EXPLOITABILITY_HIGH; } // There was no strong evidence suggesting exploitability, but the minidump // does not appear totally benign either. return EXPLOITABILITY_INTERESTING; } bool ExploitabilityLinux::EndedOnIllegalWrite(uint64_t instruction_ptr) { #ifdef _WIN32 BPLOG(INFO) << "MinGW does not support fork and exec. Terminating method."; #else // Get memory region containing instruction pointer. MinidumpMemoryList* memory_list = dump_->GetMemoryList(); MinidumpMemoryRegion* memory_region = memory_list ? memory_list->GetMemoryRegionForAddress(instruction_ptr) : NULL; if (!memory_region) { BPLOG(INFO) << "No memory region around instruction pointer."; return false; } // Get exception data to find architecture. string architecture = ""; MinidumpException* exception = dump_->GetException(); // This should never evaluate to true, since this should not be reachable // without checking for exception data earlier. if (!exception) { BPLOG(INFO) << "No exception data."; return false; } const MDRawExceptionStream* raw_exception_stream = exception->exception(); const MinidumpContext* context = exception->GetContext(); // This should not evaluate to true, for the same reason mentioned above. if (!raw_exception_stream || !context) { BPLOG(INFO) << "No exception or architecture data."; return false; } // Check architecture and set architecture variable to corresponding flag // in objdump. switch (context->GetContextCPU()) { case MD_CONTEXT_X86: architecture = "i386"; break; case MD_CONTEXT_AMD64: architecture = "i386:x86-64"; break; default: // Unsupported architecture. Note that ARM architectures are not // supported because objdump does not support ARM. return false; break; } // Get memory region around instruction pointer and the number of bytes // before and after the instruction pointer in the memory region. const uint8_t* raw_memory = memory_region->GetMemory(); const uint64_t base = memory_region->GetBase(); if (base > instruction_ptr) { BPLOG(ERROR) << "Memory region base value exceeds instruction pointer."; return false; } const uint64_t offset = instruction_ptr - base; if (memory_region->GetSize() < MAX_INSTRUCTION_LEN + offset) { BPLOG(INFO) << "Not enough bytes left to guarantee complete instruction."; return false; } // Convert bytes into objdump output. char objdump_output_buffer[MAX_OBJDUMP_BUFFER_LEN] = {0}; DisassembleBytes(architecture, raw_memory + offset, MAX_OBJDUMP_BUFFER_LEN, objdump_output_buffer); string line; if (!GetObjdumpInstructionLine(objdump_output_buffer, &line)) { return false; } // Convert objdump instruction line into the operation and operands. string instruction = ""; string dest = ""; string src = ""; TokenizeObjdumpInstruction(line, &instruction, &dest, &src); // Check if the operation is a write to memory. First, the instruction // must one that can write to memory. Second, the write destination // must be a spot in memory rather than a register. Since there are no // symbols from objdump, the destination will be enclosed by brackets. if (dest.size() > 2 && dest.at(0) == '[' && dest.at(dest.size() - 1) == ']' && (!instruction.compare("mov") || !instruction.compare("inc") || !instruction.compare("dec") || !instruction.compare("and") || !instruction.compare("or") || !instruction.compare("xor") || !instruction.compare("not") || !instruction.compare("neg") || !instruction.compare("add") || !instruction.compare("sub") || !instruction.compare("shl") || !instruction.compare("shr"))) { // Strip away enclosing brackets from the destination address. dest = dest.substr(1, dest.size() - 2); uint64_t write_address = 0; CalculateAddress(dest, *context, &write_address); // If the program crashed as a result of a write, the destination of // the write must have been an address that did not permit writing. // However, if the address is under 4k, due to program protections, // the crash does not suggest exploitability for writes with such a // low target address. return write_address > 4096; } #endif // _WIN32 return false; } #ifndef _WIN32 bool ExploitabilityLinux::CalculateAddress(const string& address_expression, const DumpContext& context, uint64_t* write_address) { // The destination should be the format reg+a or reg-a, where reg // is a register and a is a hexadecimal constant. Although more complex // expressions can make valid instructions, objdump's disassembly outputs // it in this simpler format. // TODO(liuandrew): Handle more complex formats, should they arise. if (!write_address) { BPLOG(ERROR) << "Null parameter."; return false; } // Clone parameter into a non-const string. string expression = address_expression; // Parse out the constant that is added to the address (if it exists). size_t delim = expression.find('+'); bool positive_add_constant = true; // Check if constant is subtracted instead of added. if (delim == string::npos) { positive_add_constant = false; delim = expression.find('-'); } uint32_t add_constant = 0; // Save constant and remove it from the expression. if (delim != string::npos) { if (!sscanf(expression.substr(delim + 1).c_str(), "%x", &add_constant)) { BPLOG(ERROR) << "Failed to scan constant."; return false; } expression = expression.substr(0, delim); } // Set the the write address to the corresponding register. // TODO(liuandrew): Add support for partial registers, such as // the rax/eax/ax/ah/al chain. switch (context.GetContextCPU()) { case MD_CONTEXT_X86: if (!expression.compare("eax")) { *write_address = context.GetContextX86()->eax; } else if (!expression.compare("ebx")) { *write_address = context.GetContextX86()->ebx; } else if (!expression.compare("ecx")) { *write_address = context.GetContextX86()->ecx; } else if (!expression.compare("edx")) { *write_address = context.GetContextX86()->edx; } else if (!expression.compare("edi")) { *write_address = context.GetContextX86()->edi; } else if (!expression.compare("esi")) { *write_address = context.GetContextX86()->esi; } else if (!expression.compare("ebp")) { *write_address = context.GetContextX86()->ebp; } else if (!expression.compare("esp")) { *write_address = context.GetContextX86()->esp; } else if (!expression.compare("eip")) { *write_address = context.GetContextX86()->eip; } else { BPLOG(ERROR) << "Unsupported register"; return false; } break; case MD_CONTEXT_AMD64: if (!expression.compare("rax")) { *write_address = context.GetContextAMD64()->rax; } else if (!expression.compare("rbx")) { *write_address = context.GetContextAMD64()->rbx; } else if (!expression.compare("rcx")) { *write_address = context.GetContextAMD64()->rcx; } else if (!expression.compare("rdx")) { *write_address = context.GetContextAMD64()->rdx; } else if (!expression.compare("rdi")) { *write_address = context.GetContextAMD64()->rdi; } else if (!expression.compare("rsi")) { *write_address = context.GetContextAMD64()->rsi; } else if (!expression.compare("rbp")) { *write_address = context.GetContextAMD64()->rbp; } else if (!expression.compare("rsp")) { *write_address = context.GetContextAMD64()->rsp; } else if (!expression.compare("rip")) { *write_address = context.GetContextAMD64()->rip; } else if (!expression.compare("r8")) { *write_address = context.GetContextAMD64()->r8; } else if (!expression.compare("r9")) { *write_address = context.GetContextAMD64()->r9; } else if (!expression.compare("r10")) { *write_address = context.GetContextAMD64()->r10; } else if (!expression.compare("r11")) { *write_address = context.GetContextAMD64()->r11; } else if (!expression.compare("r12")) { *write_address = context.GetContextAMD64()->r12; } else if (!expression.compare("r13")) { *write_address = context.GetContextAMD64()->r13; } else if (!expression.compare("r14")) { *write_address = context.GetContextAMD64()->r14; } else if (!expression.compare("r15")) { *write_address = context.GetContextAMD64()->r15; } else { BPLOG(ERROR) << "Unsupported register"; return false; } break; default: // This should not occur since the same switch condition // should have terminated this method. return false; break; } // Add or subtract constant from write address (if applicable). *write_address = positive_add_constant ? *write_address + add_constant : *write_address - add_constant; return true; } // static bool ExploitabilityLinux::GetObjdumpInstructionLine( const char* objdump_output_buffer, string* instruction_line) { // Put buffer data into stream to output line-by-line. std::stringstream objdump_stream; objdump_stream.str(string(objdump_output_buffer)); // Pipe each output line into the string until the string contains the first // instruction from objdump. All lines before the "<.data>:" section are // skipped. Loop until the line shows the first instruction or there are no // lines left. bool data_section_seen = false; do { if (!getline(objdump_stream, *instruction_line)) { BPLOG(INFO) << "Objdump instructions not found"; return false; } if (instruction_line->find("<.data>:") != string::npos) { data_section_seen = true; } } while (!data_section_seen || instruction_line->find("0:") == string::npos); // This first instruction contains the above substring. return true; } bool ExploitabilityLinux::TokenizeObjdumpInstruction(const string& line, string* operation, string* dest, string* src) { if (!operation || !dest || !src) { BPLOG(ERROR) << "Null parameters passed."; return false; } // Set all pointer values to empty strings. *operation = ""; *dest = ""; *src = ""; // Tokenize the objdump line. vector tokens; std::istringstream line_stream(line); copy(std::istream_iterator(line_stream), std::istream_iterator(), std::back_inserter(tokens)); // Regex for the data in hex form. Each byte is two hex digits. regex_t regex; regcomp(®ex, "^[[:xdigit:]]{2}$", REG_EXTENDED | REG_NOSUB); // Find and set the location of the operator. The operator appears // directly after the chain of bytes that define the instruction. The // operands will be the last token, given that the instruction has operands. // If not, the operator is the last token. The loop skips the first token // because the first token is the instruction number (namely "0:"). string operands = ""; for (size_t i = 1; i < tokens.size(); i++) { // Check if current token no longer is in byte format. if (regexec(®ex, tokens[i].c_str(), 0, NULL, 0)) { // instruction = tokens[i]; *operation = tokens[i]; // If the operator is the last token, there are no operands. if (i != tokens.size() - 1) { operands = tokens[tokens.size() - 1]; } break; } } regfree(®ex); if (operation->empty()) { BPLOG(ERROR) << "Failed to parse out operation from objdump instruction."; return false; } // Split operands into source and destination (if applicable). if (!operands.empty()) { size_t delim = operands.find(','); if (delim == string::npos) { *dest = operands; } else { *dest = operands.substr(0, delim); *src = operands.substr(delim + 1); } } return true; } bool ExploitabilityLinux::DisassembleBytes(const string& architecture, const uint8_t* raw_bytes, const unsigned int buffer_len, char* objdump_output_buffer) { if (!raw_bytes || !objdump_output_buffer) { BPLOG(ERROR) << "Bad input parameters."; return false; } // Write raw bytes around instruction pointer to a temporary file to // pass as an argument to objdump. char raw_bytes_tmpfile[] = "/tmp/breakpad_mem_region-raw_bytes-XXXXXX"; int raw_bytes_fd = mkstemp(raw_bytes_tmpfile); if (raw_bytes_fd < 0) { BPLOG(ERROR) << "Failed to create tempfile."; unlink(raw_bytes_tmpfile); return false; } if (write(raw_bytes_fd, raw_bytes, MAX_INSTRUCTION_LEN) != MAX_INSTRUCTION_LEN) { BPLOG(ERROR) << "Writing of raw bytes failed."; unlink(raw_bytes_tmpfile); return false; } char cmd[1024] = {0}; snprintf(cmd, 1024, "objdump -D -b binary -M intel -m %s %s", architecture.c_str(), raw_bytes_tmpfile); FILE* objdump_fp = popen(cmd, "r"); if (!objdump_fp) { fclose(objdump_fp); unlink(raw_bytes_tmpfile); BPLOG(ERROR) << "Failed to call objdump."; return false; } if (fread(objdump_output_buffer, 1, buffer_len, objdump_fp) <= 0) { fclose(objdump_fp); unlink(raw_bytes_tmpfile); BPLOG(ERROR) << "Failed to read objdump output."; return false; } fclose(objdump_fp); unlink(raw_bytes_tmpfile); return true; } #endif // _WIN32 bool ExploitabilityLinux::StackPointerOffStack(uint64_t stack_ptr) { MinidumpLinuxMapsList* linux_maps_list = dump_->GetLinuxMapsList(); // Inconclusive if there are no mappings available. if (!linux_maps_list) { return false; } const MinidumpLinuxMaps* linux_maps = linux_maps_list->GetLinuxMapsForAddress(stack_ptr); // Checks if the stack pointer maps to a valid mapping and if the mapping // is not the stack. If the mapping has no name, it is inconclusive whether // it is off the stack. return !linux_maps || (linux_maps->GetPathname().compare("") && linux_maps->GetPathname().compare( 0, strlen(kStackPrefix), kStackPrefix)); } bool ExploitabilityLinux::ExecutableStackOrHeap() { MinidumpLinuxMapsList* linux_maps_list = dump_->GetLinuxMapsList(); if (linux_maps_list) { for (size_t i = 0; i < linux_maps_list->get_maps_count(); i++) { const MinidumpLinuxMaps* linux_maps = linux_maps_list->GetLinuxMapsAtIndex(i); // Check for executable stack or heap for each mapping. if (linux_maps && (!linux_maps->GetPathname().compare( 0, strlen(kStackPrefix), kStackPrefix) || !linux_maps->GetPathname().compare( 0, strlen(kHeapPrefix), kHeapPrefix)) && linux_maps->IsExecutable()) { return true; } } } return false; } bool ExploitabilityLinux::InstructionPointerInCode(uint64_t instruction_ptr) { // Get Linux memory mapping from /proc/self/maps. Checking whether the // region the instruction pointer is in has executable permission can tell // whether it is in a valid code region. If there is no mapping for the // instruction pointer, it is indicative that the instruction pointer is // not within a module, which implies that it is outside a valid area. MinidumpLinuxMapsList* linux_maps_list = dump_->GetLinuxMapsList(); const MinidumpLinuxMaps* linux_maps = linux_maps_list ? linux_maps_list->GetLinuxMapsForAddress(instruction_ptr) : NULL; return linux_maps ? linux_maps->IsExecutable() : false; } bool ExploitabilityLinux::BenignCrashTrigger( const MDRawExceptionStream* raw_exception_stream) { // Check the cause of crash. // If the exception of the crash is a benign exception, // it is probably not exploitable. switch (raw_exception_stream->exception_record.exception_code) { case MD_EXCEPTION_CODE_LIN_SIGHUP: case MD_EXCEPTION_CODE_LIN_SIGINT: case MD_EXCEPTION_CODE_LIN_SIGQUIT: case MD_EXCEPTION_CODE_LIN_SIGTRAP: case MD_EXCEPTION_CODE_LIN_SIGABRT: case MD_EXCEPTION_CODE_LIN_SIGFPE: case MD_EXCEPTION_CODE_LIN_SIGKILL: case MD_EXCEPTION_CODE_LIN_SIGUSR1: case MD_EXCEPTION_CODE_LIN_SIGUSR2: case MD_EXCEPTION_CODE_LIN_SIGPIPE: case MD_EXCEPTION_CODE_LIN_SIGALRM: case MD_EXCEPTION_CODE_LIN_SIGTERM: case MD_EXCEPTION_CODE_LIN_SIGCHLD: case MD_EXCEPTION_CODE_LIN_SIGCONT: case MD_EXCEPTION_CODE_LIN_SIGSTOP: case MD_EXCEPTION_CODE_LIN_SIGTSTP: case MD_EXCEPTION_CODE_LIN_SIGTTIN: case MD_EXCEPTION_CODE_LIN_SIGTTOU: case MD_EXCEPTION_CODE_LIN_SIGURG: case MD_EXCEPTION_CODE_LIN_SIGXCPU: case MD_EXCEPTION_CODE_LIN_SIGXFSZ: case MD_EXCEPTION_CODE_LIN_SIGVTALRM: case MD_EXCEPTION_CODE_LIN_SIGPROF: case MD_EXCEPTION_CODE_LIN_SIGWINCH: case MD_EXCEPTION_CODE_LIN_SIGIO: case MD_EXCEPTION_CODE_LIN_SIGPWR: case MD_EXCEPTION_CODE_LIN_SIGSYS: case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED: return true; break; default: return false; break; } } } // namespace google_breakpad