diff options
Diffstat (limited to 'python')
| -rw-r--r-- | python/__init__.py | 1 | ||||
| -rw-r--r-- | python/architecture.py | 8 | ||||
| -rw-r--r-- | python/basicblock.py | 14 | ||||
| -rw-r--r-- | python/binaryview.py | 126 | ||||
| -rw-r--r-- | python/callingconvention.py | 6 | ||||
| -rw-r--r-- | python/examples/angr_plugin.py | 4 | ||||
| -rw-r--r-- | python/function.py | 13 | ||||
| -rw-r--r-- | python/generator.cpp | 11 | ||||
| -rw-r--r-- | python/interaction.py | 257 | ||||
| -rw-r--r-- | python/lowlevelil.py | 5 | ||||
| -rw-r--r-- | python/mediumlevelil.py | 11 | ||||
| -rw-r--r-- | python/metadata.py | 266 | ||||
| -rw-r--r-- | python/platform.py | 12 | ||||
| -rw-r--r-- | python/types.py | 4 |
14 files changed, 676 insertions, 62 deletions
diff --git a/python/__init__.py b/python/__init__.py index ec25839b..b066e863 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -48,6 +48,7 @@ from .highlight import * from .scriptingprovider import * from .pluginmanager import * from .setting import * +from .metadata import * def shutdown(): diff --git a/python/architecture.py b/python/architecture.py index b9c6fcc9..fa235985 100644 --- a/python/architecture.py +++ b/python/architecture.py @@ -361,7 +361,7 @@ class Architecture(object): cc = core.BNGetArchitectureCallingConventions(self.handle, count) result = {} for i in xrange(0, count.value): - obj = callingconvention.CallingConvention(None, core.BNNewCallingConventionReference(cc[i])) + obj = callingconvention.CallingConvention(handle=core.BNNewCallingConventionReference(cc[i])) result[obj.name] = obj core.BNFreeCallingConventionList(cc, count) return result @@ -898,7 +898,7 @@ class Architecture(object): :param str data: bytes to be interpreted as low-level IL instructions :param int addr: virtual address of start of ``data`` :param LowLevelILFunction il: LowLevelILFunction object to append LowLevelILExpr objects to - :rtype: None + :rtype: length of bytes read on success, None on failure """ raise NotImplementedError @@ -1295,7 +1295,7 @@ class Architecture(object): for i in xrange(len(operands)): if isinstance(operands[i], str): operand_list[i].constant = False - operand_list[i].reg = self.regs[operands[i]] + operand_list[i].reg = self.regs[operands[i]].index elif isinstance(operands[i], lowlevelil.ILRegister): operand_list[i].constant = False operand_list[i].reg = operands[i].index @@ -1319,7 +1319,7 @@ class Architecture(object): for i in xrange(len(operands)): if isinstance(operands[i], str): operand_list[i].constant = False - operand_list[i].reg = self.regs[operands[i]] + operand_list[i].reg = self.regs[operands[i]].index elif isinstance(operands[i], lowlevelil.ILRegister): operand_list[i].constant = False operand_list[i].reg = operands[i].index diff --git a/python/basicblock.py b/python/basicblock.py index c92336c5..4a5caa14 100644 --- a/python/basicblock.py +++ b/python/basicblock.py @@ -48,6 +48,8 @@ class BasicBlock(object): def __init__(self, view, handle): self.view = view self.handle = core.handle_of_type(handle, core.BNBasicBlock) + self._arch = None + self._func = None def __del__(self): core.BNFreeBasicBlock(self.handle) @@ -65,18 +67,26 @@ class BasicBlock(object): @property def function(self): """Basic block function (read-only)""" + if self._func is not None: + return self._func func = core.BNGetBasicBlockFunction(self.handle) if func is None: return None - return function.Function(self.view, func) + self._func = function.Function(self.view, func) + return self._func @property def arch(self): """Basic block architecture (read-only)""" + # The arch for a BasicBlock isn't going to change so just cache + # it the first time we need it + if self._arch is not None: + return self._arch arch = core.BNGetBasicBlockArchitecture(self.handle) if arch is None: return None - return architecture.Architecture(arch) + self._arch = architecture.Architecture(arch) + return self._arch @property def start(self): diff --git a/python/binaryview.py b/python/binaryview.py index 15e5d5da..31fb8d73 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -26,7 +26,8 @@ import threading # Binary Ninja components import _binaryninjacore as core -from enums import AnalysisState, SymbolType, InstructionTextTokenType, Endianness, ModificationStatus, StringType, SegmentFlag +from enums import (AnalysisState, SymbolType, InstructionTextTokenType, + Endianness, ModificationStatus, StringType, SegmentFlag) import function import startup import architecture @@ -39,6 +40,7 @@ import databuffer import basicblock import types import lineardisassembly +import metadata class BinaryDataNotification(object): @@ -115,6 +117,10 @@ class AnalysisCompletionEvent(object): pass def cancel(self): + """ + .. warning: This method should only be used when the system is being + shut down and no further analysis should be done afterward. + """ self.callback = self._empty_callback core.BNCancelAnalysisCompletionEvent(self.handle) @@ -1685,11 +1691,25 @@ class BinaryView(object): return core.BNSaveToFilename(self.handle, str(dest)) def register_notification(self, notify): + """ + `register_notification` provides a mechanism for receiving callbacks for various analysis events. A full + list of callbacks can be seen in :py:Class:`BinaryDataNotification`. + + :param BinaryDataNotification notify: notify is a subclassed instance of :py:Class:`BinaryDataNotification`. + :rtype: None + """ cb = BinaryDataNotificationCallbacks(self, notify) cb._register() self.notifications[notify] = cb def unregister_notification(self, notify): + """ + `unregister_notification` unregisters the :py:Class:`BinaryDataNotification` object passed to + `register_notification` + + :param BinaryDataNotification notify: notify is a subclassed instance of :py:Class:`BinaryDataNotification`. + :rtype: None + """ if notify in self.notifications: self.notifications[notify]._unregister() del self.notifications[notify] @@ -1801,28 +1821,7 @@ class BinaryView(object): :rtype: None """ - class WaitEvent(object): - def __init__(self): - self.cond = threading.Condition() - self.done = False - - def complete(self): - self.cond.acquire() - self.done = True - self.cond.notify() - self.cond.release() - - def wait(self): - self.cond.acquire() - while not self.done: - self.cond.wait() - self.cond.release() - - wait = WaitEvent() - # TODO: figure out if we actually need this 'event' variable, likely we do - event = AnalysisCompletionEvent(self, lambda: wait.complete()) - core.BNUpdateAnalysis(self.handle) - wait.wait() + core.BNUpdateAnalysisAndWait(self.handle) def abort_analysis(self): """ @@ -1918,11 +1917,27 @@ class BinaryView(object): return None return DataVariable(var.address, types.Type(var.type, confidence = var.typeConfidence), var.autoDiscovered) + def get_functions_containing(self, addr): + """ + ``get_functions_containing`` returns a list of functions which contain the given address or None on failure. + + :param int addr: virtual address to query. + :rtype: list of Function objects or None + """ + basic_blocks = self.get_basic_blocks_at(addr) + if len(basic_blocks) == 0: + return None + + result = [] + for block in basic_blocks: + result.append(block.function) + return result + def get_function_at(self, addr, plat=None): """ - ``get_function_at`` gets a binaryninja.Function object for the function at the virtual address ``addr``: + ``get_function_at`` gets a Function object for the function that starts at virtual address ``addr``: - :param int addr: virtual address of the desired function + :param int addr: starting virtual address of the desired function :param Platform plat: plat of the desired function :return: returns a Function object or None for the function at the virtual address provided :rtype: Function @@ -3250,6 +3265,67 @@ class BinaryView(object): core.BNFreeStringList(outgoing_names, len(name_list)) return result + def query_metadata(self, key): + """ + `query_metadata` retrieves a metadata associated with the given key stored in the current BinaryView. + + :param string key: key to query + :rtype: metadata associated with the key + :Example: + + >>> bv.store_metadata("integer", 1337) + >>> bv.query_metadata("integer") + 1337L + >>> bv.store_metadata("list", [1,2,3]) + >>> bv.query_metadata("list") + [1L, 2L, 3L] + >>> bv.store_metadata("string", "my_data") + >>> bv.query_metadata("string") + 'my_data' + """ + md_handle = core.BNBinaryViewQueryMetadata(self.handle, key) + if md_handle is None: + raise KeyError(key) + return metadata.Metadata(handle=md_handle).value + + def store_metadata(self, key, md): + """ + `store_metadata` stores an object for the given key in the current BinaryView. Objects stored using + `store_metadata` can be retrieved when the database is reopend. Objects stored are not arbitrary python + objects! The values stored must be able to be held in a Metadata object. See :py:class:`Metadata` + for more information. Python objects could obviously be serialized using pickle but this intentionally + a task left to the user since there is the potential security issues. + + :param string key: key value to associate the Metadata object with + :param Varies md: object to store. + :rtype: None + :Example: + + >>> bv.store_metadata("integer", 1337) + >>> bv.query_metadata("integer") + 1337L + >>> bv.store_metadata("list", [1,2,3]) + >>> bv.query_metadata("list") + [1L, 2L, 3L] + >>> bv.store_metadata("string", "my_data") + >>> bv.query_metadata("string") + 'my_data' + """ + core.BNBinaryViewStoreMetadata(self.handle, key, metadata.Metadata(md).handle) + + def remove_metadata(self, key): + """ + `remove_metadata` removes the metadata associated with key from the current BinaryView. + + :param string key: key associated with metadata to remove from the BinaryView + :rtype: None + :Example: + + >>> bv.store_metadata("integer", 1337) + >>> bv.remove_metadata("integer") + """ + core.BNBinaryViewRemoveMetadata(self.handle, key) + def __setattr__(self, name, value): try: object.__setattr__(self, name, value) diff --git a/python/callingconvention.py b/python/callingconvention.py index db473c53..21c8c95a 100644 --- a/python/callingconvention.py +++ b/python/callingconvention.py @@ -41,8 +41,10 @@ class CallingConvention(object): _registered_calling_conventions = [] - def __init__(self, arch, handle = None, confidence = types.max_confidence): + def __init__(self, arch=None, name=None, handle=None, confidence=types.max_confidence): if handle is None: + if arch is None or name is None: + raise ValueError("Must specify either handle or architecture and name") self.arch = arch self._pending_reg_lists = {} self._cb = core.BNCustomCallingConvention() @@ -56,7 +58,7 @@ class CallingConvention(object): self._cb.getIntegerReturnValueRegister = self._cb.getIntegerReturnValueRegister.__class__(self._get_int_return_reg) self._cb.getHighIntegerReturnValueRegister = self._cb.getHighIntegerReturnValueRegister.__class__(self._get_high_int_return_reg) self._cb.getFloatReturnValueRegister = self._cb.getFloatReturnValueRegister.__class__(self._get_float_return_reg) - self.handle = core.BNCreateCallingConvention(arch.handle, self.__class__.name, self._cb) + self.handle = core.BNCreateCallingConvention(arch.handle, name, self._cb) self.__class__._registered_calling_conventions.append(self) else: self.handle = handle diff --git a/python/examples/angr_plugin.py b/python/examples/angr_plugin.py index c84373be..26f8040c 100644 --- a/python/examples/angr_plugin.py +++ b/python/examples/angr_plugin.py @@ -42,7 +42,7 @@ from binaryninja.binaryview import BinaryView from binaryninja.plugin import BackgroundTaskThread, PluginCommand from binaryninja.interaction import show_plain_text_report, show_message_box from binaryninja.highlight import HighlightColor -from binaryninja.enums import HighlightStandardColor, MessageBoxButtonSet +from binaryninja.enums import HighlightStandardColor, MessageBoxButtonSet, MessageBoxIcon # Disable warning logs as they show up as errors in the UI logging.disable(logging.WARNING) @@ -137,7 +137,7 @@ def solve(bv): if len(bv.session_data.angr_find) == 0: show_message_box("Angr Solve", "You have not specified a goal instruction.\n\n" + "Please right click on the goal instruction and select \"Find Path to This Instruction\" to " + - "continue.", MessageBoxButtonSet.OKButtonSet, MessageBoxButtonSet.ErrorIcon) + "continue.", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon) return # Start a solver thread for the path associated with the view diff --git a/python/function.py b/python/function.py index c3310b6c..f0b6faee 100644 --- a/python/function.py +++ b/python/function.py @@ -206,6 +206,12 @@ class Variable(object): def __str__(self): return self.name + def __eq__(self, other): + return self.identifier == other.identifier + + def __hash__(self): + return hash(self.identifier) + class ConstantReference(object): def __init__(self, val, size, ptr, intermediate): @@ -261,6 +267,9 @@ class Function(object): return True return ctypes.addressof(self.handle.contents) != ctypes.addressof(value.handle.contents) + def __hash__(self): + return hash((self.start, self.arch.name, self.platform.name)) + @classmethod def _unregister(cls, func): handle = ctypes.cast(func, ctypes.c_void_p) @@ -471,7 +480,11 @@ class Function(object): def get_comment_at(self, addr): return core.BNGetCommentForAddress(self.handle, addr) + def set_comment_at(self, addr, comment): + core.BNSetCommentForAddress(self.handle, addr, comment) + def set_comment(self, addr, comment): + """Deprecated""" core.BNSetCommentForAddress(self.handle, addr, comment) def get_low_level_il_at(self, addr, arch=None): diff --git a/python/generator.cpp b/python/generator.cpp index 6f19db66..554f82cd 100644 --- a/python/generator.cpp +++ b/python/generator.cpp @@ -165,8 +165,15 @@ int main(int argc, char* argv[]) // Parse API header to get type and function information map<QualifiedName, Ref<Type>> types, vars, funcs; string errors; - bool ok = Architecture::GetByName("generator")->ParseTypesFromSourceFile(argv[1], types, vars, funcs, errors); - fprintf(stderr, "%s", errors.c_str()); + auto arch = Architecture::GetByName("generator"); + if (!arch) + { + printf("ERROR: License file validation failed (most likely)\n"); + return 1; + } + + bool ok = arch->ParseTypesFromSourceFile(argv[1], types, vars, funcs, errors); + fprintf(stderr, "Errors: %s", errors.c_str()); if (!ok) return 1; diff --git a/python/interaction.py b/python/interaction.py index 60607692..979549f5 100644 --- a/python/interaction.py +++ b/python/interaction.py @@ -29,6 +29,9 @@ import log class LabelField(object): + """ + ``LabelField`` adds a text label to the display. + """ def __init__(self, text): self.text = text @@ -44,6 +47,9 @@ class LabelField(object): class SeparatorField(object): + """ + ``SeparatorField`` adds vertical separation to the display. + """ def _fill_core_struct(self, value): value.type = FormInputFieldType.SeparatorFormField @@ -55,6 +61,9 @@ class SeparatorField(object): class TextLineField(object): + """ + ``TextLineField`` Adds prompt for text string input. Result is stored in self.result as a string on completion. + """ def __init__(self, prompt): self.prompt = prompt self.result = None @@ -71,6 +80,10 @@ class TextLineField(object): class MultilineTextField(object): + """ + ``MultilineTextField`` add multi-line text string input field. Result is stored in self.result + as a string. This option is not supported on the command line. + """ def __init__(self, prompt): self.prompt = prompt self.result = None @@ -87,6 +100,9 @@ class MultilineTextField(object): class IntegerField(object): + """ + ``IntegerField`` add prompt for integer. Result is stored in self.result as an int. + """ def __init__(self, prompt): self.prompt = prompt self.result = None @@ -103,7 +119,15 @@ class IntegerField(object): class AddressField(object): - def __init__(self, prompt, view = None, current_address = 0): + """ + ``AddressField`` prompts the user for an address. By passing the optional view and current_address parameters + offsets can be used instead of just an address. Th reslut is stored as in int in self.result. + + Note: This API currenlty functions differently on the command line, as the view and current_address are + disregarded. Additionally where as in the ui the result defaults to hexidecimal on the command line 0x must be + specified. + """ + def __init__(self, prompt, view=None, current_address=0): self.prompt = prompt self.view = view self.current_address = current_address @@ -125,6 +149,10 @@ class AddressField(object): class ChoiceField(object): + """ + ``ChoiceField`` prompts the user to choose from the list of strings provided in ``choices``. Result is stored + in self.result as an index in to the coices array. + """ def __init__(self, prompt, choices): self.prompt = prompt self.choices = choices @@ -147,7 +175,10 @@ class ChoiceField(object): class OpenFileNameField(object): - def __init__(self, prompt, ext = ""): + """ + ``OpenFileNameField`` prompts the user to specify a file name to open. Result is stored in self.result as a string. + """ + def __init__(self, prompt, ext=""): self.prompt = prompt self.ext = ext self.result = None @@ -165,7 +196,10 @@ class OpenFileNameField(object): class SaveFileNameField(object): - def __init__(self, prompt, ext = "", default_name = ""): + """ + ``SaveFileNameField`` prompts the user to specify a file name to save. Result is stored in self.result as a string. + """ + def __init__(self, prompt, ext="", default_name=""): self.prompt = prompt self.ext = ext self.default_name = default_name @@ -185,13 +219,17 @@ class SaveFileNameField(object): class DirectoryNameField(object): - def __init__(self, prompt, default_name = ""): + """ + ``DirectoryNameField`` prompts the user to specify a directory name to open. Result is stored in self.result as + a string. + """ + def __init__(self, prompt, default_name=""): self.prompt = prompt self.default_name = default_name self.result = None def _fill_core_struct(self, value): - value.type = DirectoryNameField + value.type = FormInputFieldType.DirectoryNameFormField value.prompt = self.prompt value.defaultName = self.default_name @@ -353,14 +391,14 @@ class InteractionHandler(object): field_objs.append(AddressField(fields[i].prompt, view, fields[i].currentAddress)) elif fields[i].type == FormInputFieldType.ChoiceFormField: choices = [] - for i in xrange(0, fields[i].count): - choices.append(fields[i].choices[i]) + for j in xrange(0, fields[i].count): + choices.append(fields[i].choices[j]) field_objs.append(ChoiceField(fields[i].prompt, choices)) elif fields[i].type == FormInputFieldType.OpenFileNameFormField: field_objs.append(OpenFileNameField(fields[i].prompt, fields[i].ext)) elif fields[i].type == FormInputFieldType.SaveFileNameFormField: field_objs.append(SaveFileNameField(fields[i].prompt, fields[i].ext, fields[i].defaultName)) - elif fields[i].type == DirectoryNameField: + elif fields[i].type == FormInputFieldType.DirectoryNameFormField: field_objs.append(DirectoryNameField(fields[i].prompt, fields[i].defaultName)) else: field_objs.append(LabelField(fields[i].prompt)) @@ -424,22 +462,86 @@ class InteractionHandler(object): def markdown_to_html(contents): + """ + ``markdown_to_html`` converts the provided markdown to HTML. + + :param string contents: Markdown contents to convert to HTML. + :rtype: string + :Example: + >>> markdown_to_html("##Yay") + '<h2>Yay</h2>' + """ return core.BNMarkdownToHTML(contents) def show_plain_text_report(title, contents): + """ + ``show_plain_text_report`` displays contents to the user in the UI or on the command line. + + Note: This API function differently on the command line vs. the UI. In the UI a popup is used. On the commandline + a simple text prompt is used. + + :param str title: title to display in the UI popup. + :param str contents: plain text contents to display + :rtype: None + :Example: + >>> show_plain_text_report("title", "contents") + contents + """ core.BNShowPlainTextReport(None, title, contents) -def show_markdown_report(title, contents, plaintext = ""): +def show_markdown_report(title, contents, plaintext=""): + """ + ``show_markdown_report`` displays the markdown contents in UI applications and plaintext in command line + applications. + + Note: This API function differently on the command line vs. the UI. In the UI a popup is used. On the commandline + a simple text prompt is used. + + :param str contents: markdown contents to display + :param str plaintext: Plain text version to display (used on the command line) + :rtype: None + :Example: + >>> show_markdown_report("title", "##Contents", "Plain text contents") + Plain text contents + """ core.BNShowMarkdownReport(None, title, contents, plaintext) -def show_html_report(title, contents, plaintext = ""): +def show_html_report(title, contents, plaintext=""): + """ + ``show_html_report`` displays the html contents in UI applications and plaintext in command line + applications. + + Note: This API function differently on the command line vs. the UI. In the UI a popup is used. On the commandline + a simple text prompt is used. + + :param str contents: HTML contents to display + :param str plaintext: Plain text version to display (used on the command line) + :rtype: None + :Example" + >>> show_html_report("title", "<h1>Contents</h1>", "Plain text contents") + Plain text contents + """ core.BNShowHTMLReport(None, title, contents, plaintext) def get_text_line_input(prompt, title): + """ + ``get_text_line_input`` prompts the user to input a string with the given prompt and title. + + Note: This API function differently on the command line vs. the UI. In the UI a popup is used. On the commandline + a simple text prompt is used. + + :param str prompt: String to prompt with. + :param str title: Title of the window when executed in the UI. + :rtype: string containing the input without trailing newline character. + :Example: + >>> get_text_line_input("PROMPT>", "getinfo") + PROMPT> Input! + 'Input!' + """ value = ctypes.c_char_p() if not core.BNGetTextLineInput(value, prompt, title): return None @@ -449,6 +551,20 @@ def get_text_line_input(prompt, title): def get_int_input(prompt, title): + """ + ``get_int_input`` prompts the user to input a integer with the given prompt and title. + + Note: This API function differently on the command line vs. the UI. In the UI a popup is used. On the commandline + a simple text prompt is used. + + :param str prompt: String to prompt with. + :param str title: Title of the window when executed in the UI. + :rtype: integer value input by the user. + :Example: + >>> get_int_input("PROMPT>", "getinfo") + PROMPT> 10 + 10 + """ value = ctypes.c_longlong() if not core.BNGetIntegerInput(value, prompt, title): return None @@ -456,6 +572,20 @@ def get_int_input(prompt, title): def get_address_input(prompt, title): + """ + ``get_address_input`` prompts the user for an address with the given prompt and title. + + Note: This API function differently on the command line vs. the UI. In the UI a popup is used. On the commandline + a simple text prompt is used. + + :param str prompt: String to prompt with. + :param str title: Title of the window when executed in the UI. + :rtype: integer value input by the user. + :Example: + >>> get_address_input("PROMPT>", "getinfo") + PROMPT> 10 + 10L + """ value = ctypes.c_ulonglong() if not core.BNGetAddressInput(value, prompt, title, None, 0): return None @@ -463,6 +593,25 @@ def get_address_input(prompt, title): def get_choice_input(prompt, title, choices): + """ + ``get_choice_input`` prompts the user to select the one of the provided choices. + + Note: This API function differently on the command line vs. the UI. In the UI a popup is used. On the commandline + a simple text prompt is used. The ui uses a combo box. + + :param str prompt: String to prompt with. + :param str title: Title of the window when executed in the UI. + :param list choices: A list of strings for the user to choose from. + :rtype: integer array index of the selected option + :Example: + >>> get_choice_input("PROMPT>", "choices", ["Yes", "No", "Maybe"]) + choices + 1) Yes + 2) No + 3) Maybe + PROMPT> 1 + 0L + """ choice_buf = (ctypes.c_char_p * len(choices))() for i in xrange(0, len(choices)): choice_buf[i] = str(choices[i]) @@ -472,7 +621,20 @@ def get_choice_input(prompt, title, choices): return value.value -def get_open_filename_input(prompt, ext = ""): +def get_open_filename_input(prompt, ext=""): + """ + ``get_open_filename_input`` prompts the user for a file name to open. + + Note: This API function differently on the command line vs. the UI. In the UI a popup is used. On the commandline + a simple text prompt is used. The ui uses the native window popup for file selection. + + :param str prompt: Prompt to display. + :param str ext: Optional, file extension + :Example: + >>> get_open_filename_input("filename:", "exe") + filename: foo.exe + 'foo.exe' + """ value = ctypes.c_char_p() if not core.BNGetOpenFileNameInput(value, prompt, ext): return None @@ -481,7 +643,22 @@ def get_open_filename_input(prompt, ext = ""): return result -def get_save_filename_input(prompt, ext = "", default_name = ""): +def get_save_filename_input(prompt, ext="", default_name=""): + """ + ``get_save_filename_input`` prompts the user for a file name to save as, optionally providing a file extension and + default_name. + + Note: This API function differently on the command line vs. the UI. In the UI a popup is used. On the commandline + a simple text prompt is used. The ui uses the native window popup for file selection. + + :param str prompt: Prompt to display. + :param str ext: Optional, file extension + :param str default_name: Optional, default file name. + :Example: + >>> get_save_filename_input("filename:", "exe", "foo.exe") + filename: foo.exe + 'foo.exe' + """ value = ctypes.c_char_p() if not core.BNGetSaveFileNameInput(value, prompt, ext, default_name): return None @@ -490,7 +667,22 @@ def get_save_filename_input(prompt, ext = "", default_name = ""): return result -def get_directory_name_input(prompt, default_name = ""): +def get_directory_name_input(prompt, default_name=""): + """ + ``get_directory_name_input`` prompts the user for a directory name to save as, optionally providing and + default_name. + + Note: This API function differently on the command line vs. the UI. In the UI a popup is used. On the commandline + a simple text prompt is used. The ui uses the native window popup for file selection. + + :param str prompt: Prompt to display. + :param str default_name: Optional, default directory name. + :rtype: str + :Example: + >>> get_directory_name_input("prompt") + prompt dirname + 'dirname' + """ value = ctypes.c_char_p() if not core.BNGetDirectoryNameInput(value, prompt, default_name): return None @@ -500,6 +692,43 @@ def get_directory_name_input(prompt, default_name = ""): def get_form_input(fields, title): + """ + ``get_from_input`` Prompts the user for a set of inputs specified in ``fields`` with given title. + The fields parameter is a list which can contain the following types: + - str - an alias for LabelField + - None - an alias for SeparatorField + - LabelField - Text output + - SeparatorField - Vertical spacing + - TextLineField - Prompt for a string value + - MultilineTextField - Prompt for multi-line string value + - IntegerField - Prompt for an integer + - AddressField - Prompt for an address + - ChoiceField - Prompt for a choice from provided options + - OpenFileNameField - Prompt for file to open + - SaveFileNameField - Prompt for file to save to + - DirectoryNameField - Prompt for directory name + This API is flexible and works both in the UI via a popup dialog and on the command line. + :params list fields: A list containing of the above specified classes, strings or None + :params str title: The title of the popup dialog. + :Example: + + >>> int_f = IntegerField("Specify Integer") + >>> tex_f = TextLineField("Specify name") + >>> choice_f = ChoiceField("Options", ["Yes", "No", "Maybe"]) + >>> get_form_input(["Get Data", None, int_f, tex_f, choice_f], "The options") + Get Data + + Specify Integer 1337 + Specify name Peter + The options + 1) Yes + 2) No + 3) Maybe + Options 1 + >>> True + >>> print tex_f.result, int_f.result, choice_f.result + Peter 1337 0 + """ value = (core.BNFormInputField * len(fields))() for i in xrange(0, len(fields)): if isinstance(fields[i], str): @@ -517,7 +746,7 @@ def get_form_input(fields, title): return True -def show_message_box(title, text, buttons = MessageBoxButtonSet.OKButtonSet, icon = MessageBoxIcon.InformationIcon): +def show_message_box(title, text, buttons=MessageBoxButtonSet.OKButtonSet, icon=MessageBoxIcon.InformationIcon): """ ``show_message_box`` Displays a configurable message box in the UI, or prompts on the console as appropriate retrieves a list of all Symbol objects of the provided symbol type in the optionally diff --git a/python/lowlevelil.py b/python/lowlevelil.py index 08ca04e6..9f532e6f 100644 --- a/python/lowlevelil.py +++ b/python/lowlevelil.py @@ -731,17 +731,18 @@ class LowLevelILFunction(object): """ return self.expr(LowLevelILOperation.LLIL_LOAD, addr.index, size=size) - def store(self, size, addr, value): + def store(self, size, addr, value, flags=None): """ ``store`` Writes ``size`` bytes to expression ``addr`` read from expression ``value`` :param int size: number of bytes to write :param LowLevelILExpr addr: the expression to write to :param LowLevelILExpr value: the expression to be written + :param str flags: which flags are set by this operation :return: The expression ``[addr].size = value`` :rtype: LowLevelILExpr """ - return self.expr(LowLevelILOperation.LLIL_STORE, addr.index, value.index, size=size) + return self.expr(LowLevelILOperation.LLIL_STORE, addr.index, value.index, size=size, flags=flags) def push(self, size, value): """ diff --git a/python/mediumlevelil.py b/python/mediumlevelil.py index 3377ab6a..eefbe787 100644 --- a/python/mediumlevelil.py +++ b/python/mediumlevelil.py @@ -37,6 +37,15 @@ class SSAVariable(object): def __repr__(self): return "<ssa %s version %d>" % (repr(self.var), self.version) + def __eq__(self, other): + return ( + (self.var.identifier, self.version) == + (other.var.identifier, other.version) + ) + + def __hash__(self): + return hash((self.var.identifier, self.version)) + class MediumLevelILLabel(object): def __init__(self, handle = None): @@ -142,7 +151,7 @@ class MediumLevelILInstruction(object): MediumLevelILOperation.MLIL_UNIMPL_MEM: [("src", "expr")], MediumLevelILOperation.MLIL_SET_VAR_SSA: [("dest", "var_ssa"), ("src", "expr")], MediumLevelILOperation.MLIL_SET_VAR_SSA_FIELD: [("prev", "var_ssa_dest_and_src"), ("offset", "int"), ("src", "expr")], - MediumLevelILOperation.MLIL_SET_VAR_SPLIT_SSA: [("high", "expr"), ("low", "expr"), ("src", "expr")], + MediumLevelILOperation.MLIL_SET_VAR_SPLIT_SSA: [("high", "ssa_var"), ("low", "ssa_var"), ("src", "expr")], MediumLevelILOperation.MLIL_SET_VAR_ALIASED: [("prev", "var_ssa_dest_and_src"), ("src", "expr")], MediumLevelILOperation.MLIL_SET_VAR_ALIASED_FIELD: [("prev", "var_ssa_dest_and_src"), ("offset", "int"), ("src", "expr")], MediumLevelILOperation.MLIL_VAR_SSA: [("src", "var_ssa")], diff --git a/python/metadata.py b/python/metadata.py new file mode 100644 index 00000000..554bbcf4 --- /dev/null +++ b/python/metadata.py @@ -0,0 +1,266 @@ +# Copyright (c) 2015-2017 Vector 35 LLC +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + + +import ctypes + +# Binary Ninja components +import _binaryninjacore as core +from enums import MetadataType + + +class Metadata(object): + def __init__(self, value=None, signed=None, raw=None, handle=None): + if handle is not None: + self.handle = handle + elif isinstance(value, int): + if signed: + self.handle = core.BNCreateMetadataSignedIntegerData(value) + else: + self.handle = core.BNCreateMetadataUnsignedIntegerData(value) + elif isinstance(value, bool): + self.handle = core.BNCreateMetadataBooleanData(value) + elif isinstance(value, str): + if raw: + buffer = (ctypes.c_ubyte * len(value)).from_buffer_copy(value) + self.handle = core.BNCreateMetadataRawData(buffer, len(value)) + else: + self.handle = core.BNCreateMetadataStringData(value) + elif isinstance(value, float): + self.handle = core.BNCreateMetadataDoubleData(value) + elif isinstance(value, list): + self.handle = core.BNCreateMetadataOfType(MetadataType.ArrayDataType) + for elm in value: + md = Metadata(elm, signed, raw) + core.BNMetadataArrayAppend(self.handle, md.handle) + elif isinstance(value, dict): + self.handle = core.BNCreateMetadataOfType(MetadataType.KeyValueDataType) + for elm in value: + md = Metadata(value[elm], signed, raw) + core.BNMetadataSetValueForKey(self.handle, str(elm), md.handle) + else: + raise ValueError("List doesn't not contain type of: int, bool, str, float, list, dict") + + @property + def value(self): + if self.is_integer: + return int(self) + elif self.is_string or self.is_raw: + return str(self) + elif self.is_float: + return float(self) + elif self.is_boolean: + return bool(self) + elif self.is_array: + return list(self) + elif self.is_dict: + return self.get_dict() + raise TypeError() + + def get_dict(self): + if not self.is_dict: + raise TypeError() + result = {} + for key in self: + result[key] = self[key] + return result + + @property + def type(self): + return MetadataType(core.BNMetadataGetType(self.handle)) + + @property + def is_integer(self): + return self.is_signed_integer or self.is_unsigned_integer + + @property + def is_signed_integer(self): + return core.BNMetadataIsSignedInteger(self.handle) + + @property + def is_unsigned_integer(self): + return core.BNMetadataIsUnsignedInteger(self.handle) + + @property + def is_float(self): + return core.BNMetadataIsDouble(self.handle) + + @property + def is_boolean(self): + return core.BNMetadataIsBoolean(self.handle) + + @property + def is_string(self): + return core.BNMetadataIsString(self.handle) + + @property + def is_raw(self): + return core.BNMetadataIsRaw(self.handle) + + @property + def is_array(self): + return core.BNMetadataIsArray(self.handle) + + @property + def is_dict(self): + return core.BNMetadataIsKeyValueStore(self.handle) + + def remove(self, key_or_index): + if isinstance(key_or_index, str) and self.is_dict: + core.BNMetadataRemoveKey(self.handle, key_or_index) + elif isinstance(key_or_index, int) and self.is_array: + core.BNMetadataRemoveIndex(self.handle, key_or_index) + else: + raise TypeError("remove only valid for dict and array objects") + + def __len__(self): + if self.is_array or self.is_dict or self.is_string or self.is_raw: + return core.BNMetadataSize(self.handle) + raise Exception("Metadata object doesn't support len()") + + def __iter__(self): + if self.is_array: + for i in xrange(core.BNMetadataSize(self.handle)): + yield Metadata(handle=core.BNMetadataGetForIndex(self.handle, i)).value + elif self.is_dict: + result = core.BNMetadataGetValueStore(self.handle) + try: + for i in xrange(result.contents.size): + yield result.contents.keys[i] + finally: + core.BNFreeMetadataValueStore(result) + else: + raise Exception("Metadata object doesn't support iteration") + + def __getitem__(self, value): + if self.is_array: + if not isinstance(value, int): + raise ValueError("Metadata object only supports integers for indexing") + if value >= len(self): + raise IndexError("Index value out of range") + return Metadata(handle=core.BNMetadataGetForIndex(self.handle, value)).value + if self.is_dict: + if not isinstance(value, str): + raise ValueError("Metadata object only supports strings for indexing") + handle = core.BNMetadataGetForKey(self.handle, value) + if handle is None: + raise KeyError(value) + return Metadata(handle=handle).value + + raise NotImplementedError("Metadata object doesn't support indexing") + + def __str__(self): + if self.is_string: + return core.BNMetadataGetString(self.handle) + if self.is_raw: + length = ctypes.c_ulonglong() + length.value = 0 + native_list = core.BNMetadataGetRaw(self.handle, ctypes.byref(length)) + out_list = [] + for i in xrange(length.value): + out_list.append(native_list[i]) + core.BNFreeMetadataRaw(native_list) + return ''.join(chr(a) for a in out_list) + + raise ValueError("Metadata object not a string or raw type") + + def __int__(self): + if self.is_signed_integer: + return core.BNMetadataGetSignedInteger(self.handle) + if self.is_unsigned_integer: + return core.BNMetadataGetUnsignedInteger(self.handle) + + raise ValueError("Metadata object not of integer type") + + def __float__(self): + if not self.is_float: + raise ValueError("Metadata object is not float type") + return core.BNMetadataGetDouble(self.handle) + + def __nonzero__(self): + if not self.is_boolean: + raise ValueError("Metadata object is not boolean type") + return core.BNMetadataGetBoolean(self.handle) + + def __eq__(self, other): + if isinstance(other, int) and self.is_integer: + return int(self) == other + elif isinstance(other, str) and (self.is_string or self.is_raw): + return str(self) == other + elif isinstance(other, float) and self.is_float: + return float(self) == other + elif isinstance(other, bool) and self.is_boolean: + return bool(self) == other + elif self.is_array and ((isinstance(other, Metadata) and other.is_array) or isinstance(other, list)): + if len(self) != len(other): + return False + for a, b in zip(self, other): + if a != b: + return False + return True + elif self.is_dict and ((isinstance(other, Metadata) and other.is_dict) or isinstance(other, dict)): + if len(self) != len(other): + return False + for a, b in zip(self, other): + if a != b or self[a] != other[b]: + return False + return True + elif isinstance(other, Metadata) and self.is_integer and other.is_integer: + return int(self) == int(other) + elif isinstance(other, Metadata) and (self.is_string or self.is_raw) and (other.is_string or other.is_raw): + return str(self) == str(other) + elif isinstance(other, Metadata) and self.is_float and other.is_float: + return float(self) == float(other) + elif isinstance(other, Metadata) and self.is_boolean and other.is_boolean: + return bool(self) == bool(other) + raise NotImplementedError() + + def __ne__(self, other): + if isinstance(other, int) and self.is_integer: + return int(self) != other + elif isinstance(other, str) and (self.is_string or self.is_raw): + return str(self) != other + elif isinstance(other, float) and self.is_float: + return float(self) != other + elif isinstance(other, bool): + return bool(self) != other + elif self.is_array and ((isinstance(other, Metadata) and other.is_array) or isinstance(other, list)): + if len(self) != len(other): + return True + areEqual = True + for a, b in zip(self, other): + if a != b: + areEqual = False + return not areEqual + elif self.is_dict and ((isinstance(other, Metadata) and other.is_dict) or isinstance(other, dict)): + if len(self) != len(other): + return True + for a, b in zip(self, other): + if a != b or self[a] != other[b]: + return True + return False + elif isinstance(other, Metadata) and self.is_integer and other.is_integer: + return int(self) != int(other) + elif isinstance(other, Metadata) and (self.is_string or self.is_raw) and (other.is_string or other.is_raw): + return str(self) != str(other) + elif isinstance(other, Metadata) and self.is_float and other.is_float: + return float(self) != float(other) + elif isinstance(other, Metadata) and self.is_boolean and other.is_boolean: + return bool(self) != bool(other) diff --git a/python/platform.py b/python/platform.py index 9ba7625f..1c2fdcd3 100644 --- a/python/platform.py +++ b/python/platform.py @@ -132,7 +132,7 @@ class Platform(object): result = core.BNGetPlatformDefaultCallingConvention(self.handle) if result is None: return None - return callingconvention.CallingConvention(None, result) + return callingconvention.CallingConvention(handle=result) @default_calling_convention.setter def default_calling_convention(self, value): @@ -150,7 +150,7 @@ class Platform(object): result = core.BNGetPlatformCdeclCallingConvention(self.handle) if result is None: return None - return callingconvention.CallingConvention(None, result) + return callingconvention.CallingConvention(handle=result) @cdecl_calling_convention.setter def cdecl_calling_convention(self, value): @@ -168,7 +168,7 @@ class Platform(object): result = core.BNGetPlatformStdcallCallingConvention(self.handle) if result is None: return None - return callingconvention.CallingConvention(None, result) + return callingconvention.CallingConvention(handle=result) @stdcall_calling_convention.setter def stdcall_calling_convention(self, value): @@ -186,7 +186,7 @@ class Platform(object): result = core.BNGetPlatformFastcallCallingConvention(self.handle) if result is None: return None - return callingconvention.CallingConvention(None, result) + return callingconvention.CallingConvention(handle=result) @fastcall_calling_convention.setter def fastcall_calling_convention(self, value): @@ -204,7 +204,7 @@ class Platform(object): result = core.BNGetPlatformSystemCallConvention(self.handle) if result is None: return None - return callingconvention.CallingConvention(None, result) + return callingconvention.CallingConvention(handle=result) @system_call_convention.setter def system_call_convention(self, value): @@ -222,7 +222,7 @@ class Platform(object): cc = core.BNGetPlatformCallingConventions(self.handle, count) result = [] for i in xrange(0, count.value): - result.append(callingconvention.CallingConvention(None, core.BNNewCallingConventionReference(cc[i]))) + result.append(callingconvention.CallingConvention(handle=core.BNNewCallingConventionReference(cc[i]))) core.BNFreeCallingConventionList(cc, count.value) return result diff --git a/python/types.py b/python/types.py index ab3fb337..47d99bee 100644 --- a/python/types.py +++ b/python/types.py @@ -279,7 +279,7 @@ class Type(object): result = core.BNGetTypeCallingConvention(self.handle) if not result.convention: return None - return callingconvention.CallingConvention(None, result, confidence = result.confidence) + return callingconvention.CallingConvention(None, handle = result, confidence = result.confidence) @property def parameters(self): @@ -854,7 +854,7 @@ class TypeParserResult(object): self.functions = functions def __repr__(self): - return "{types: %s, variables: %s, functions: %s}" % (self.types, self.variables, self.functions) + return "<types: %s, variables: %s, functions: %s>" % (self.types, self.variables, self.functions) def preprocess_source(source, filename=None, include_dirs=[]): |
