diff options
| author | Peter LaFosse <peter@vector35.com> | 2018-11-08 09:58:36 -0500 |
|---|---|---|
| committer | Peter LaFosse <peter@vector35.com> | 2018-11-13 11:07:27 -0500 |
| commit | d8ff0a6324fe92ccafe296298409c50cc4b526fa (patch) | |
| tree | c5bf8cdf43902d3122a6747d5c42d9197afc0808 | |
| parent | 9df7c7c621c82e6caa7a09c4273e4feba86277f2 (diff) | |
Add expression parsing APIs
Add text-based and constant-based searching
| -rw-r--r-- | binaryninjaapi.h | 6 | ||||
| -rw-r--r-- | binaryninjacore.h | 10 | ||||
| -rw-r--r-- | binaryview.cpp | 25 | ||||
| -rw-r--r-- | python/architecture.py | 2 | ||||
| -rw-r--r-- | python/binaryview.py | 117 | ||||
| -rw-r--r-- | python/scriptingprovider.py | 20 | ||||
| -rw-r--r-- | suite/testcommon.py | 23 |
7 files changed, 182 insertions, 21 deletions
diff --git a/binaryninjaapi.h b/binaryninjaapi.h index 3008c2d6..8288ea33 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -1429,7 +1429,9 @@ namespace BinaryNinja void RegisterPlatformTypes(Platform* platform); - bool FindNextData(uint64_t start, const DataBuffer& data, uint64_t& result, BNFindFlag flags = NoFindFlags); + bool FindNextData(uint64_t start, const DataBuffer& data, uint64_t& addr, BNFindFlag flags = FindCaseSensitive); + bool FindNextText(uint64_t start, const std::string& data, uint64_t& addr, Ref<DisassemblySettings> settings, BNFindFlag flags = FindCaseSensitive); + bool FindNextConstant(uint64_t start, uint64_t constant, uint64_t& addr, Ref<DisassemblySettings> settings); void Reanalyze(); @@ -1484,6 +1486,8 @@ namespace BinaryNinja std::set<NameSpace> GetNameSpaces() const; static NameSpace GetInternalNameSpace(); static NameSpace GetExternalNameSpace(); + + bool ParseExpression(const std::string& expression, uint64_t &offset, uint64_t here, std::string& errorString); }; diff --git a/binaryninjacore.h b/binaryninjacore.h index b7cfcc23..1b20121c 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -1667,7 +1667,7 @@ extern "C" enum BNFindFlag { - NoFindFlags = 0, + FindCaseSensitive = 0, FindCaseInsensitive = 1 }; @@ -2174,6 +2174,10 @@ extern "C" BINARYNINJACOREAPI bool BNFindNextData(BNBinaryView* view, uint64_t start, BNDataBuffer* data, uint64_t* result, BNFindFlag flags); + BINARYNINJACOREAPI bool BNFindNextText(BNBinaryView* view, uint64_t start, const char* data, uint64_t* result, + BNDisassemblySettings* settings, BNFindFlag flags); + BINARYNINJACOREAPI bool BNFindNextConstant(BNBinaryView* view, uint64_t start, uint64_t constant, uint64_t* result, + BNDisassemblySettings* settings); BINARYNINJACOREAPI void BNAddAutoSegment(BNBinaryView* view, uint64_t start, uint64_t length, uint64_t dataOffset, uint64_t dataLength, uint32_t flags); @@ -3810,6 +3814,10 @@ extern "C" BINARYNINJACOREAPI BNDataRendererContainer* BNGetDataRendererContainer(); BINARYNINJACOREAPI void BNRegisterGenericDataRenderer(BNDataRendererContainer* container, BNDataRenderer* renderer); BINARYNINJACOREAPI void BNRegisterTypeSpecificDataRenderer(BNDataRendererContainer* container, BNDataRenderer* renderer); + + BINARYNINJACOREAPI bool BNParseExpression(BNBinaryView* view, const char* expression, uint64_t* offset, uint64_t here, char** errorString); + BINARYNINJACOREAPI void BNFreeParseError(char* errorString); + #ifdef __cplusplus } #endif diff --git a/binaryview.cpp b/binaryview.cpp index 1302b0cb..984edda8 100644 --- a/binaryview.cpp +++ b/binaryview.cpp @@ -1989,6 +1989,17 @@ bool BinaryView::FindNextData(uint64_t start, const DataBuffer& data, uint64_t& return BNFindNextData(m_object, start, data.GetBufferObject(), &result, flags); } +bool BinaryView::FindNextText(uint64_t start, const std::string& data, uint64_t& result, + Ref<DisassemblySettings> settings, BNFindFlag flags) +{ + return BNFindNextText(m_object, start, data.c_str(), &result, settings->GetObject(), flags); +} + +bool BinaryView::FindNextConstant(uint64_t start, uint64_t constant, uint64_t& result, Ref<DisassemblySettings> settings) +{ + return BNFindNextConstant(m_object, start, constant, &result, settings->GetObject()); +} + void BinaryView::Reanalyze() { @@ -2303,6 +2314,20 @@ NameSpace BinaryView::GetExternalNameSpace() } +bool BinaryView::ParseExpression(const string& expression, uint64_t &offset, uint64_t here, string& errorString) +{ + char* err = nullptr; + if (!BNParseExpression(m_object, expression.c_str(), &offset, here, &err)) + { + LogDebug("Failed to parse expression: '%s': Error: '%s'", expression.c_str(), err); + errorString = string(err); + BNFreeParseError(err); + return false; + } + return true; +} + + Relocation::Relocation(BNRelocation* reloc) { m_object = reloc; diff --git a/python/architecture.py b/python/architecture.py index 5f280a98..7bef1b67 100644 --- a/python/architecture.py +++ b/python/architecture.py @@ -2426,6 +2426,8 @@ class CoreArchitecture(Architecture): result = databuffer.DataBuffer() errors = ctypes.c_char_p() if not core.BNAssemble(self.handle, code, addr, result.handle, errors): + error_str = errors.value + core.BNFreeString(ctypes.cast(errors, ctypes.POINTER(ctypes.c_byte))) raise ValueError("Could not assemble: %s" % errors.value) if isinstance(str(result), bytes): return str(result) diff --git a/python/binaryview.py b/python/binaryview.py index afde3a3c..60ccf91f 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -22,11 +22,12 @@ import struct import traceback import ctypes import abc +import numbers # Binary Ninja components from binaryninja import _binaryninjacore as core from binaryninja.enums import (AnalysisState, SymbolType, InstructionTextTokenType, - Endianness, ModificationStatus, StringType, SegmentFlag, SectionSemantics) + Endianness, ModificationStatus, StringType, SegmentFlag, SectionSemantics, FindFlag) import binaryninja from binaryninja import associateddatastore # required for _BinaryViewAssociatedDataStore from binaryninja import log @@ -37,6 +38,7 @@ from binaryninja import basicblock from binaryninja import lineardisassembly from binaryninja import metadata from binaryninja import highlight +from binaryninja import function # 2-3 compatibility from binaryninja import range @@ -3614,21 +3616,20 @@ class BinaryView(object): """ core.BNRegisterPlatformTypes(self.handle, platform.handle) - def find_next_data(self, start, data, flags = 0): + def find_next_data(self, start, data, flags=FindFlag.FindCaseSensitive): """ - ``find_next_data`` searchs for the bytes in data starting at the virtual address ``start`` either, case-sensitive, - or case-insensitive. + ``find_next_data`` searchs for the bytes ``data`` starting at the virtual address ``start`` until the end of the BinaryView. :param int start: virtual address to start searching from. - :param str data: bytes to search for - :param FindFlags flags: case-sensitivity flag, one of the following: + :param str data: data to search for + :param FindFlag flags: (optional) defaults to case-insensitive data search - ==================== ====================== - FindFlags Description - ==================== ====================== - NoFindFlags Case-sensitive find - FindCaseInsensitive Case-insensitive find - ==================== ====================== + ==================== ============================ + FindFlag Description + ==================== ============================ + FindCaseSensitive Case-sensitive search + FindCaseInsensitive Case-insensitive search + ===================== ============================ """ buf = databuffer.DataBuffer(str(data)) result = ctypes.c_ulonglong() @@ -3636,6 +3637,55 @@ class BinaryView(object): return None return result.value + + def find_next_text(self, start, text, settings=None, flags=FindFlag.FindCaseSensitive): + """ + ``find_next_text`` searchs for string ``text`` occuring in the linear view output starting at the virtual + address ``start`` until the end of the BinaryView. + + :param int start: virtual address to start searching from. + :param str text: text to search for + :param FindFlag flags: (optional) defaults to case-insensitive data search + + ==================== ============================ + FindFlag Description + ==================== ============================ + FindCaseSensitive Case-sensitive search + FindCaseInsensitive Case-insensitive search + ===================== ============================ + """ + if not isinstance(text, str): + raise TypeError("text parameter is not str type") + if settings is None: + settings = function.DisassemblySettings() + if not isinstance(settings, function.DisassemblySettings): + raise TypeError("settings parameter is not DisassemblySettings type") + + result = ctypes.c_ulonglong() + if not core.BNFindNextText(self.handle, start, text, result, settings.handle, flags): + return None + return result.value + + def find_next_constant(self, start, constant, settings=None): + """ + ``find_next_constant`` searchs for integer constant ``constant`` occuring in the linear view output starting at the virtual + address ``start`` until the end of the BinaryView. + + :param int start: virtual address to start searching from. + :param int constant: constant to search for + """ + if not isinstance(constant, numbers.Integral): + raise TypeError("constant parameter is not integral type") + if settings is None: + settings = function.DisassemblySettings() + if not isinstance(settings, function.DisassemblySettings): + raise TypeError("settings parameter is not DisassemblySettings type") + + result = ctypes.c_ulonglong() + if not core.BNFindNextConstant(self.handle, start, constant, result, settings.handle): + return None + return result.value + def reanalyze(self): """ ``reanalyze`` causes all functions to be reanalyzed. This function does not wait for the analysis to finish. @@ -3798,6 +3848,49 @@ class BinaryView(object): except AttributeError: raise AttributeError("attribute '%s' is read only" % name) + def parse_expression(self, expression, here=0): + """ + Evaluates an string expression to an integer value. + + The parser uses the following rules: + - symbols are defined by the lexer as `[A-Za-z0-9_:<>][A-Za-z0-9_:$\-<>]+` or anything enclosed in either single or + double quotes + - Numbers are defaulted to hexadecimal thus `_printf + 10` is equivilent to `printf + 0x10` If decimal numbers required use the decimal prefix. + - Since numbers and symbols can be ambiguous its recommended that you prefix your numbers with the following: + - 0x - Hexadecimal + - 0n - Decimal + - 0 - Octal + - In the case of an ambiguous number/symbol (one with no prefix) for instance `12345` we will first attempt + to look up the string as a symbol, if a symbol is found its address is used, otherwise we attempt to convert + it to a hexadecmial number. + - The following operations are valid: +, -, *, /, %, (), &, |, ^, ~ + - In addition to the above operators there are _il-style_ dereference operators + - [<expression>] - read the _current address size_ at <expression> + - [<expression>].b - read the byte at <expression> + - [<expression>].w - read the word (2 bytes) at <expression> + - [<expression>].d - read the dword (4 bytes) at <expression> + - [<expression>].q - read the quadword (8 bytes) at <expression> + - The `$here` keyword can be used in calculations and is defined as the `here` parameter + + :param string expression: Arithmetic expression to be evaluated + :param int here: (optional) Base address for relative expressions, defaults to zero + :rtype: int + """ + offset = ctypes.c_ulonglong() + errors = ctypes.c_char_p() + if not core.BNParseExpression(self.handle, expression, offset, here, errors): + error_str = errors.value + core.BNFreeString(ctypes.cast(errors, ctypes.POINTER(ctypes.c_byte))) + raise ValueError(error_str) + return offset.value + + def eval(self, expression, here=0): + """ + Evaluates an string expression to an integer value. This is a more concise alias for the `parse_expression` API + See `parse_expression` for details on usage. + """ + return self.parse_expression(expression, here) + class BinaryReader(object): """ diff --git a/python/scriptingprovider.py b/python/scriptingprovider.py index 5b5d830e..52584ec7 100644 --- a/python/scriptingprovider.py +++ b/python/scriptingprovider.py @@ -557,12 +557,20 @@ class PythonScriptingInstance(ScriptingInstance): for line in code.split(b'\n'): self.interpreter.push(line.decode('charmap')) - if self.locals["here"] != self.active_addr: - if not self.active_view.file.navigate(self.active_view.file.view, self.locals["here"]): - sys.stderr.write("Address 0x%x is not valid for the current view\n" % self.locals["here"]) - elif self.locals["current_address"] != self.active_addr: - if not self.active_view.file.navigate(self.active_view.file.view, self.locals["current_address"]): - sys.stderr.write("Address 0x%x is not valid for the current view\n" % self.locals["current_address"]) + tryNavigate = True + if isinstance(self.locals["here"], str) or isinstance(self.locals["current_address"], str): + try: + self.locals["here"] = self.active_view.parse_expression(self.locals["here"], self.active_addr) + except ValueError as e: + sys.stderr.write(e) + tryNavigate = False + if tryNavigate: + if self.locals["here"] != self.active_addr: + if not self.active_view.file.navigate(self.active_view.file.view, self.locals["here"]): + sys.stderr.write("Address 0x%x is not valid for the current view\n" % self.locals["here"]) + elif self.locals["current_address"] != self.active_addr: + if not self.active_view.file.navigate(self.active_view.file.view, self.locals["current_address"]): + sys.stderr.write("Address 0x%x is not valid for the current view\n" % self.locals["current_address"]) if self.active_view is not None: self.active_view.update_analysis() except: diff --git a/suite/testcommon.py b/suite/testcommon.py index ae5ed863..fe7b69d5 100644 --- a/suite/testcommon.py +++ b/suite/testcommon.py @@ -377,7 +377,7 @@ class BinaryViewTestBuilder(Builder): class TestBuilder(Builder): - """ The TestBuilder is for tests that need to be checked againsttest_BinaryView + """ The TestBuilder is for tests that need to be checked against a stored oracle data that isn't from a binary. These test are generated on your local machine then run again on the build machine to verify correctness. @@ -791,6 +791,27 @@ class VerifyBuilder(Builder): def get_comments(self, bv): return bv.functions[0].comments + def test_expression_parse(self): + file_name = self.unpackage_file("helloworld") + try: + bv = binja.BinaryViewType.get_view_of_file(file_name) + assert bv.parse_expression("1 + 1") == 2 + assert bv.parse_expression("-1 + 1") == 0 + assert bv.parse_expression("1 - 1") == 0 + assert bv.parse_expression("1 + -1") == 0 + assert bv.parse_expression("[0x8000]") == 0x464c457f + assert bv.parse_expression("[0x8000]b") == 0 + assert bv.parse_expression("[0x8000].b") == 0x7f + assert bv.parse_expression("[0x8000].w") == 0x457f + assert bv.parse_expression("[0x8000].d") == 0x464c457f + assert bv.parse_expression("[0x8000].q") == 0x10101464c457f + assert bv.parse_expression("$here + 1", 12345) == 12345 + 1 + assert bv.parse_expression("_start") == 0x830c + assert bv.parse_expression("_start + 4") == 0x8310 + return True + finally: + self.delete_package("helloworld") + def test_verify_BNDB_round_trip(self): """Binary Ninja Database output doesn't match its input""" # This will test Binja's ability to save and restore databases |
