aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLiu.andrew.x@gmail.com <Liu.andrew.x@gmail.com>2015-08-21 16:22:19 +0000
committerLiu.andrew.x@gmail.com <Liu.andrew.x@gmail.com>2015-08-21 16:22:19 +0000
commitf073540795b917be5068e119a0888814a767b019 (patch)
tree70e983f55556919d989d75a929954e7737e4faf0 /src
parentDon't use strtok_s for mingw builds (diff)
downloadbreakpad-f073540795b917be5068e119a0888814a767b019.tar.xz
Add check for Linux minidump ending on bad write for exploitability rating.
If a crash occurred as a result to a write to unwritable memory, it is reason to suggest exploitability. The processor checks for a bad write by disassembling the command that caused the crash by piping the raw bytes near the instruction pointer through objdump. This allows the processor to see if the instruction that caused the crash is a write to memory and where the target of the address is located. R=ivanpe@chromium.org Review URL: https://codereview.chromium.org/1273823004 git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@1497 4c0a9323-5329-0410-9bdc-e9ce6186880e
Diffstat (limited to 'src')
-rw-r--r--src/google_breakpad/processor/exploitability.h9
-rw-r--r--src/google_breakpad/processor/minidump_processor.h6
-rw-r--r--src/processor/exploitability.cc11
-rw-r--r--src/processor/exploitability_linux.cc382
-rw-r--r--src/processor/exploitability_linux.h51
-rw-r--r--src/processor/exploitability_unittest.cc120
-rw-r--r--src/processor/minidump_processor.cc13
-rw-r--r--src/processor/testdata/linux_jmp_to_module_not_exe_region.dmpbin0 -> 44936 bytes
-rw-r--r--src/processor/testdata/linux_write_to_nonwritable_module.dmpbin0 -> 44944 bytes
-rw-r--r--src/processor/testdata/linux_write_to_nonwritable_region_math.dmpbin0 -> 40848 bytes
-rw-r--r--src/processor/testdata/linux_write_to_outside_module.dmpbin0 -> 44944 bytes
-rw-r--r--src/processor/testdata/linux_write_to_outside_module_via_math.dmpbin0 -> 44944 bytes
-rw-r--r--src/processor/testdata/linux_write_to_under_4k.dmpbin0 -> 44944 bytes
13 files changed, 584 insertions, 8 deletions
diff --git a/src/google_breakpad/processor/exploitability.h b/src/google_breakpad/processor/exploitability.h
index 67255a3a..014413c9 100644
--- a/src/google_breakpad/processor/exploitability.h
+++ b/src/google_breakpad/processor/exploitability.h
@@ -53,6 +53,15 @@ class Exploitability {
static Exploitability *ExploitabilityForPlatform(Minidump *dump,
ProcessState *process_state);
+ // The boolean parameter signals whether the exploitability engine is
+ // enabled to call out to objdump for disassembly. This is disabled by
+ // default. It is used to check the identity of the instruction that
+ // caused the program to crash. This should not be enabled if there are
+ // portability concerns.
+ static Exploitability *ExploitabilityForPlatform(Minidump *dump,
+ ProcessState *process_state,
+ bool enable_objdump);
+
ExploitabilityRating CheckExploitability();
bool AddressIsAscii(uint64_t);
diff --git a/src/google_breakpad/processor/minidump_processor.h b/src/google_breakpad/processor/minidump_processor.h
index d2c94e2b..387115ef 100644
--- a/src/google_breakpad/processor/minidump_processor.h
+++ b/src/google_breakpad/processor/minidump_processor.h
@@ -125,6 +125,8 @@ class MinidumpProcessor {
// does not exist or cannot be determined.
static string GetAssertion(Minidump* dump);
+ void set_enable_objdump(bool enabled) { enable_objdump_ = enabled; }
+
private:
StackFrameSymbolizer* frame_symbolizer_;
// Indicate whether resolver_helper_ is owned by this instance.
@@ -134,6 +136,10 @@ class MinidumpProcessor {
// guess how likely it is that the crash represents an exploitable
// memory corruption issue.
bool enable_exploitability_;
+
+ // This flag permits the exploitability scanner to shell out to objdump
+ // for purposes of disassembly.
+ bool enable_objdump_;
};
} // namespace google_breakpad
diff --git a/src/processor/exploitability.cc b/src/processor/exploitability.cc
index 384c499c..6ee1e962 100644
--- a/src/processor/exploitability.cc
+++ b/src/processor/exploitability.cc
@@ -58,6 +58,13 @@ ExploitabilityRating Exploitability::CheckExploitability() {
Exploitability *Exploitability::ExploitabilityForPlatform(
Minidump *dump,
ProcessState *process_state) {
+ return ExploitabilityForPlatform(dump, process_state, false);
+}
+
+Exploitability *Exploitability::ExploitabilityForPlatform(
+ Minidump *dump,
+ ProcessState *process_state,
+ bool enable_objdump) {
Exploitability *platform_exploitability = NULL;
MinidumpSystemInfo *minidump_system_info = dump->GetSystemInfo();
if (!minidump_system_info)
@@ -75,7 +82,9 @@ Exploitability *Exploitability::ExploitabilityForPlatform(
break;
}
case MD_OS_LINUX: {
- platform_exploitability = new ExploitabilityLinux(dump, process_state);
+ platform_exploitability = new ExploitabilityLinux(dump,
+ process_state,
+ enable_objdump);
break;
}
case MD_OS_MAC_OS_X:
diff --git a/src/processor/exploitability_linux.cc b/src/processor/exploitability_linux.cc
index 46cad318..a196da79 100644
--- a/src/processor/exploitability_linux.cc
+++ b/src/processor/exploitability_linux.cc
@@ -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,13 +63,26 @@ const char kStackCheckFailureFunction[] = "__stack_chk_fail";
// 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 {
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
@@ -122,18 +145,373 @@ ExploitabilityRating ExploitabilityLinux::CheckPlatformExploitability() {
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()) {
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(&regex, "^[[: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(&regex, 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(&regex);
+
+ 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.
diff --git a/src/processor/exploitability_linux.h b/src/processor/exploitability_linux.h
index 857185b4..93c5082f 100644
--- a/src/processor/exploitability_linux.h
+++ b/src/processor/exploitability_linux.h
@@ -37,7 +37,6 @@
#ifndef GOOGLE_BREAKPAD_PROCESSOR_EXPLOITABILITY_LINUX_H_
#define GOOGLE_BREAKPAD_PROCESSOR_EXPLOITABILITY_LINUX_H_
-#include "common/scoped_ptr.h"
#include "google_breakpad/common/breakpad_types.h"
#include "google_breakpad/processor/exploitability.h"
@@ -48,9 +47,21 @@ class ExploitabilityLinux : public Exploitability {
ExploitabilityLinux(Minidump *dump,
ProcessState *process_state);
+ // Parameters are the minidump to analyze, the object representing process
+ // state, and whether to enable objdump disassembly.
+ // Enabling objdump will allow exploitability analysis to call out to
+ // objdump for diassembly. It is used to check the identity of the
+ // instruction that caused the program to crash. If there are any
+ // portability concerns, this should not be enabled.
+ ExploitabilityLinux(Minidump *dump,
+ ProcessState *process_state,
+ bool enable_objdump);
+
virtual ExploitabilityRating CheckPlatformExploitability();
private:
+ friend class ExploitabilityLinuxTest;
+
// Takes the address of the instruction pointer and returns
// whether the instruction pointer lies in a valid instruction region.
bool InstructionPointerInCode(uint64_t instruction_ptr);
@@ -59,6 +70,40 @@ class ExploitabilityLinux : public Exploitability {
// minidump and reports whether the exception suggests no exploitability.
bool BenignCrashTrigger(const MDRawExceptionStream *raw_exception_stream);
+ // This method checks if the crash occurred during a write to read-only or
+ // invalid memory. It does so by checking if the instruction at the
+ // instruction pointer is a write instruction, and if the target of the
+ // instruction is at a spot in memory that prohibits writes.
+ bool EndedOnIllegalWrite(uint64_t instruction_ptr);
+
+#ifndef _WIN32
+ // Disassembles raw bytes via objdump and pipes the output into the provided
+ // buffer, given the desired architecture, the file from which objdump will
+ // read, and the buffer length. The method returns whether the disassembly
+ // was a success, and the caller owns all pointers.
+ static bool DisassembleBytes(const string &architecture,
+ const uint8_t *raw_bytes,
+ const unsigned int MAX_OBJDUMP_BUFFER_LEN,
+ char *objdump_output_buffer);
+
+ // Tokenizes out the operation and operands from a line of instruction
+ // disassembled by objdump. This method modifies the pointers to match the
+ // tokens of the instruction, and returns if the tokenizing was a success.
+ // The caller owns all pointers.
+ static bool TokenizeObjdumpInstruction(const string &line,
+ string *operation,
+ string *dest,
+ string *src);
+
+ // Calculates the effective address of an expression in the form reg+a or
+ // reg-a, where 'reg' is a register and 'a' is a constant, and writes the
+ // result in the pointer. The method returns whether the calculation was
+ // a success. The caller owns the pointer.
+ static bool CalculateAddress(const string &address_expression,
+ const DumpContext &context,
+ uint64_t *write_address);
+#endif // _WIN32
+
// Checks if the stack pointer points to a memory mapping that is not
// labelled as the stack.
bool StackPointerOffStack(uint64_t stack_ptr);
@@ -66,6 +111,10 @@ class ExploitabilityLinux : public Exploitability {
// Checks if the stack or heap are marked executable according
// to the memory mappings.
bool ExecutableStackOrHeap();
+
+ // Whether this exploitability engine is permitted to shell out to objdump
+ // to disassemble raw bytes.
+ bool enable_objdump_;
};
} // namespace google_breakpad
diff --git a/src/processor/exploitability_unittest.cc b/src/processor/exploitability_unittest.cc
index db7f1cb0..700f9e58 100644
--- a/src/processor/exploitability_unittest.cc
+++ b/src/processor/exploitability_unittest.cc
@@ -37,11 +37,41 @@
#include "google_breakpad/processor/basic_source_line_resolver.h"
#include "google_breakpad/processor/minidump_processor.h"
#include "google_breakpad/processor/process_state.h"
+#ifndef _WIN32
+#include "processor/exploitability_linux.h"
+#endif // _WIN32
#include "processor/simple_symbol_supplier.h"
+#ifndef _WIN32
+namespace google_breakpad {
+
+class ExploitabilityLinuxTest : public ExploitabilityLinux {
+ public:
+ using ExploitabilityLinux::DisassembleBytes;
+ using ExploitabilityLinux::TokenizeObjdumpInstruction;
+ using ExploitabilityLinux::CalculateAddress;
+};
+
+class ExploitabilityLinuxTestMinidumpContext : public MinidumpContext {
+ public:
+ explicit ExploitabilityLinuxTestMinidumpContext(
+ const MDRawContextAMD64& context) : MinidumpContext(NULL) {
+ valid_ = true;
+ SetContextAMD64(new MDRawContextAMD64(context));
+ SetContextFlags(MD_CONTEXT_AMD64);
+ }
+};
+
+} // namespace google_breakpad
+#endif // _WIN32
+
namespace {
using google_breakpad::BasicSourceLineResolver;
+#ifndef _WIN32
+using google_breakpad::ExploitabilityLinuxTest;
+using google_breakpad::ExploitabilityLinuxTestMinidumpContext;
+#endif // _WIN32
using google_breakpad::MinidumpProcessor;
using google_breakpad::ProcessState;
using google_breakpad::SimpleSymbolSupplier;
@@ -59,6 +89,7 @@ ExploitabilityFor(const string& filename) {
SimpleSymbolSupplier supplier(TestDataDir() + "/symbols");
BasicSourceLineResolver resolver;
MinidumpProcessor processor(&supplier, &resolver, true);
+ processor.set_enable_objdump(true);
ProcessState state;
string minidump_file = TestDataDir() + "/" + filename;
@@ -135,6 +166,95 @@ TEST(ExploitabilityTest, TestLinuxEngine) {
ExploitabilityFor("linux_executable_stack.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("linux_executable_heap.dmp"));
+ ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
+ ExploitabilityFor("linux_jmp_to_module_not_exe_region.dmp"));
+#ifndef _WIN32
+ ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
+ ExploitabilityFor("linux_write_to_nonwritable_module.dmp"));
+ ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
+ ExploitabilityFor("linux_write_to_nonwritable_region_math.dmp"));
+ ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
+ ExploitabilityFor("linux_write_to_outside_module.dmp"));
+ ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
+ ExploitabilityFor("linux_write_to_outside_module_via_math.dmp"));
+ ASSERT_EQ(google_breakpad::EXPLOITABILITY_INTERESTING,
+ ExploitabilityFor("linux_write_to_under_4k.dmp"));
+#endif // _WIN32
+}
+
+#ifndef _WIN32
+TEST(ExploitabilityLinuxUtilsTest, DisassembleBytesTest) {
+ ASSERT_FALSE(ExploitabilityLinuxTest::DisassembleBytes("", NULL, 5, NULL));
+ uint8_t bytes[6] = {0xc7, 0x0, 0x5, 0x0, 0x0, 0x0};
+ char buffer[1024] = {0};
+ ASSERT_TRUE(ExploitabilityLinuxTest::DisassembleBytes("i386:x86-64",
+ bytes,
+ 1024,
+ buffer));
+ std::stringstream objdump_stream;
+ objdump_stream.str(string(buffer));
+ string line = "";
+ while ((line.find("0:") == string::npos) && getline(objdump_stream, line)) {
+ }
+ ASSERT_EQ(line, " 0:\tc7 00 05 00 00 00 \tmov DWORD PTR [rax],0x5");
+}
+TEST(ExploitabilityLinuxUtilsTest, TokenizeObjdumpInstructionTest) {
+ ASSERT_FALSE(ExploitabilityLinuxTest::TokenizeObjdumpInstruction("",
+ NULL,
+ NULL,
+ NULL));
+ string line = "0: c7 00 05 00 00 00 mov DWORD PTR [rax],0x5";
+ string operation = "";
+ string dest = "";
+ string src = "";
+ ASSERT_TRUE(ExploitabilityLinuxTest::TokenizeObjdumpInstruction(line,
+ &operation,
+ &dest,
+ &src));
+ ASSERT_EQ(operation, "mov");
+ ASSERT_EQ(dest, "[rax]");
+ ASSERT_EQ(src, "0x5");
+ line = "0: c3 ret";
+ ASSERT_TRUE(ExploitabilityLinuxTest::TokenizeObjdumpInstruction(line,
+ &operation,
+ &dest,
+ &src));
+ ASSERT_EQ(operation, "ret");
+ ASSERT_EQ(dest, "");
+ ASSERT_EQ(src, "");
+ line = "0: 5f pop rdi";
+ ASSERT_TRUE(ExploitabilityLinuxTest::TokenizeObjdumpInstruction(line,
+ &operation,
+ &dest,
+ &src));
+ ASSERT_EQ(operation, "pop");
+ ASSERT_EQ(dest, "rdi");
+ ASSERT_EQ(src, "");
}
+
+TEST(ExploitabilityLinuxUtilsTest, CalculateAddressTest) {
+ MDRawContextAMD64 raw_context;
+ raw_context.rdx = 12345;
+ ExploitabilityLinuxTestMinidumpContext context(raw_context);
+ ASSERT_EQ(context.GetContextAMD64()->rdx, 12345);
+ ASSERT_FALSE(ExploitabilityLinuxTest::CalculateAddress("", context, NULL));
+ uint64_t write_address = 0;
+ ASSERT_TRUE(ExploitabilityLinuxTest::CalculateAddress("rdx-0x4D2",
+ context,
+ &write_address));
+ ASSERT_EQ(write_address, 11111);
+ ASSERT_TRUE(ExploitabilityLinuxTest::CalculateAddress("rdx+0x4D2",
+ context,
+ &write_address));
+ ASSERT_EQ(write_address, 13579);
+ ASSERT_FALSE(ExploitabilityLinuxTest::CalculateAddress("rdx+rax",
+ context,
+ &write_address));
+ ASSERT_FALSE(ExploitabilityLinuxTest::CalculateAddress("0x3482+0x4D2",
+ context,
+ &write_address));
}
+#endif // _WIN32
+
+} // namespace
diff --git a/src/processor/minidump_processor.cc b/src/processor/minidump_processor.cc
index 71dedaba..3a20dfa5 100644
--- a/src/processor/minidump_processor.cc
+++ b/src/processor/minidump_processor.cc
@@ -51,7 +51,8 @@ MinidumpProcessor::MinidumpProcessor(SymbolSupplier *supplier,
SourceLineResolverInterface *resolver)
: frame_symbolizer_(new StackFrameSymbolizer(supplier, resolver)),
own_frame_symbolizer_(true),
- enable_exploitability_(false) {
+ enable_exploitability_(false),
+ enable_objdump_(false) {
}
MinidumpProcessor::MinidumpProcessor(SymbolSupplier *supplier,
@@ -59,14 +60,16 @@ MinidumpProcessor::MinidumpProcessor(SymbolSupplier *supplier,
bool enable_exploitability)
: frame_symbolizer_(new StackFrameSymbolizer(supplier, resolver)),
own_frame_symbolizer_(true),
- enable_exploitability_(enable_exploitability) {
+ enable_exploitability_(enable_exploitability),
+ enable_objdump_(false) {
}
MinidumpProcessor::MinidumpProcessor(StackFrameSymbolizer *frame_symbolizer,
bool enable_exploitability)
: frame_symbolizer_(frame_symbolizer),
own_frame_symbolizer_(false),
- enable_exploitability_(enable_exploitability) {
+ enable_exploitability_(enable_exploitability),
+ enable_objdump_(false) {
assert(frame_symbolizer_);
}
@@ -289,7 +292,9 @@ ProcessResult MinidumpProcessor::Process(
// rating.
if (enable_exploitability_) {
scoped_ptr<Exploitability> exploitability(
- Exploitability::ExploitabilityForPlatform(dump, process_state));
+ Exploitability::ExploitabilityForPlatform(dump,
+ process_state,
+ enable_objdump_));
// The engine will be null if the platform is not supported
if (exploitability != NULL) {
process_state->exploitability_ = exploitability->CheckExploitability();
diff --git a/src/processor/testdata/linux_jmp_to_module_not_exe_region.dmp b/src/processor/testdata/linux_jmp_to_module_not_exe_region.dmp
new file mode 100644
index 00000000..82e266b2
--- /dev/null
+++ b/src/processor/testdata/linux_jmp_to_module_not_exe_region.dmp
Binary files differ
diff --git a/src/processor/testdata/linux_write_to_nonwritable_module.dmp b/src/processor/testdata/linux_write_to_nonwritable_module.dmp
new file mode 100644
index 00000000..46456acf
--- /dev/null
+++ b/src/processor/testdata/linux_write_to_nonwritable_module.dmp
Binary files differ
diff --git a/src/processor/testdata/linux_write_to_nonwritable_region_math.dmp b/src/processor/testdata/linux_write_to_nonwritable_region_math.dmp
new file mode 100644
index 00000000..6cf98610
--- /dev/null
+++ b/src/processor/testdata/linux_write_to_nonwritable_region_math.dmp
Binary files differ
diff --git a/src/processor/testdata/linux_write_to_outside_module.dmp b/src/processor/testdata/linux_write_to_outside_module.dmp
new file mode 100644
index 00000000..2ceeefb6
--- /dev/null
+++ b/src/processor/testdata/linux_write_to_outside_module.dmp
Binary files differ
diff --git a/src/processor/testdata/linux_write_to_outside_module_via_math.dmp b/src/processor/testdata/linux_write_to_outside_module_via_math.dmp
new file mode 100644
index 00000000..4663d3c2
--- /dev/null
+++ b/src/processor/testdata/linux_write_to_outside_module_via_math.dmp
Binary files differ
diff --git a/src/processor/testdata/linux_write_to_under_4k.dmp b/src/processor/testdata/linux_write_to_under_4k.dmp
new file mode 100644
index 00000000..a3ddd621
--- /dev/null
+++ b/src/processor/testdata/linux_write_to_under_4k.dmp
Binary files differ