diff options
-rw-r--r-- | src/google_breakpad/processor/exploitability.h | 9 | ||||
-rw-r--r-- | src/google_breakpad/processor/minidump_processor.h | 6 | ||||
-rw-r--r-- | src/processor/exploitability.cc | 11 | ||||
-rw-r--r-- | src/processor/exploitability_linux.cc | 382 | ||||
-rw-r--r-- | src/processor/exploitability_linux.h | 51 | ||||
-rw-r--r-- | src/processor/exploitability_unittest.cc | 120 | ||||
-rw-r--r-- | src/processor/minidump_processor.cc | 13 | ||||
-rw-r--r-- | src/processor/testdata/linux_jmp_to_module_not_exe_region.dmp | bin | 0 -> 44936 bytes | |||
-rw-r--r-- | src/processor/testdata/linux_write_to_nonwritable_module.dmp | bin | 0 -> 44944 bytes | |||
-rw-r--r-- | src/processor/testdata/linux_write_to_nonwritable_region_math.dmp | bin | 0 -> 40848 bytes | |||
-rw-r--r-- | src/processor/testdata/linux_write_to_outside_module.dmp | bin | 0 -> 44944 bytes | |||
-rw-r--r-- | src/processor/testdata/linux_write_to_outside_module_via_math.dmp | bin | 0 -> 44944 bytes | |||
-rw-r--r-- | src/processor/testdata/linux_write_to_under_4k.dmp | bin | 0 -> 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(®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. 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 Binary files differnew file mode 100644 index 00000000..82e266b2 --- /dev/null +++ b/src/processor/testdata/linux_jmp_to_module_not_exe_region.dmp diff --git a/src/processor/testdata/linux_write_to_nonwritable_module.dmp b/src/processor/testdata/linux_write_to_nonwritable_module.dmp Binary files differnew file mode 100644 index 00000000..46456acf --- /dev/null +++ b/src/processor/testdata/linux_write_to_nonwritable_module.dmp diff --git a/src/processor/testdata/linux_write_to_nonwritable_region_math.dmp b/src/processor/testdata/linux_write_to_nonwritable_region_math.dmp Binary files differnew file mode 100644 index 00000000..6cf98610 --- /dev/null +++ b/src/processor/testdata/linux_write_to_nonwritable_region_math.dmp diff --git a/src/processor/testdata/linux_write_to_outside_module.dmp b/src/processor/testdata/linux_write_to_outside_module.dmp Binary files differnew file mode 100644 index 00000000..2ceeefb6 --- /dev/null +++ b/src/processor/testdata/linux_write_to_outside_module.dmp 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 Binary files differnew file mode 100644 index 00000000..4663d3c2 --- /dev/null +++ b/src/processor/testdata/linux_write_to_outside_module_via_math.dmp diff --git a/src/processor/testdata/linux_write_to_under_4k.dmp b/src/processor/testdata/linux_write_to_under_4k.dmp Binary files differnew file mode 100644 index 00000000..a3ddd621 --- /dev/null +++ b/src/processor/testdata/linux_write_to_under_4k.dmp |