| Index: src/processor/exploitability_linux.cc
|
| ===================================================================
|
| --- src/processor/exploitability_linux.cc (revision 1491)
|
| +++ src/processor/exploitability_linux.cc (working copy)
|
| @@ -36,6 +36,16 @@
|
|
|
| #include "processor/exploitability_linux.h"
|
|
|
| +#ifndef _WIN32
|
| +#include <regex.h>
|
| +#include <stdio.h>
|
| +#include <stdlib.h>
|
| +#include <string.h>
|
| +
|
| +#include <sstream>
|
| +#include <iterator>
|
| +#endif // _WIN32
|
| +
|
| #include "google_breakpad/common/minidump_exception_linux.h"
|
| #include "google_breakpad/processor/call_stack.h"
|
| #include "google_breakpad/processor/process_state.h"
|
| @@ -53,6 +63,11 @@
|
| // can determine that the call would overflow the target buffer.
|
| const 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 {
|
| @@ -59,8 +74,16 @@
|
|
|
| ExploitabilityLinux::ExploitabilityLinux(Minidump *dump,
|
| ProcessState *process_state)
|
| - : Exploitability(dump, 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.
|
| @@ -122,7 +145,8 @@
|
| return EXPLOITABILITY_ERR_PROCESSING;
|
| }
|
|
|
| - // Checking for the instruction pointer in a valid instruction region.
|
| + // 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()) {
|
| @@ -129,11 +153,365 @@
|
| 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);
|
| +
|
| + // Put buffer data into stream to output line-by-line.
|
| + std::stringstream objdump_stream;
|
| + objdump_stream.str(string(objdump_output_buffer));
|
| + string line;
|
| +
|
| + // Pipe each output line into the string until the string contains
|
| + // the first instruction from objdump.
|
| + // Loop until the line shows the first instruction or there are no lines left.
|
| + do {
|
| + if (!getline(objdump_stream, line)) {
|
| + BPLOG(INFO) << "Objdump instructions not found";
|
| + return false;
|
| + }
|
| + } while (line.find("0:") == string::npos);
|
| + // This first instruction contains the above substring.
|
| +
|
| + // 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;
|
| +}
|
| +
|
| +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<string> tokens;
|
| + std::istringstream line_stream(line);
|
| + copy(std::istream_iterator<string>(line_stream),
|
| + std::istream_iterator<string>(),
|
| + 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.
|
|
|