# Copyright (c) 2015-2019 Vector 35 Inc
#
# 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
import traceback
# Binary Ninja components
from binaryninja import _binaryninjacore as core
from binaryninja.enums import FormInputFieldType, MessageBoxIcon, MessageBoxButtonSet, MessageBoxButtonResult, ReportType
from binaryninja import binaryview
from binaryninja import log
from binaryninja import flowgraph
# 2-3 compatibility
from binaryninja import range
[docs]class LabelField(object):
"""
``LabelField`` adds a text label to the display.
"""
[docs] def __init__(self, text):
self.text = text
def _fill_core_struct(self, value):
value.type = FormInputFieldType.LabelFormField
value.prompt = self.text
def _fill_core_result(self, value):
pass
def _get_result(self, value):
pass
[docs]class SeparatorField(object):
"""
``SeparatorField`` adds vertical separation to the display.
"""
def _fill_core_struct(self, value):
value.type = FormInputFieldType.SeparatorFormField
def _fill_core_result(self, value):
pass
def _get_result(self, value):
pass
[docs]class TextLineField(object):
"""
``TextLineField`` Adds prompt for text string input. Result is stored in self.result as a string on completion.
"""
[docs] def __init__(self, prompt):
self.prompt = prompt
self.result = None
def _fill_core_struct(self, value):
value.type = FormInputFieldType.TextLineFormField
value.prompt = self.prompt
def _fill_core_result(self, value):
value.stringResult = core.BNAllocString(str(self.result))
def _get_result(self, value):
self.result = value.stringResult
[docs]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.
"""
[docs] def __init__(self, prompt):
self.prompt = prompt
self.result = None
def _fill_core_struct(self, value):
value.type = FormInputFieldType.MultilineTextFormField
value.prompt = self.prompt
def _fill_core_result(self, value):
value.stringResult = core.BNAllocString(str(self.result))
def _get_result(self, value):
self.result = value.stringResult
[docs]class IntegerField(object):
"""
``IntegerField`` add prompt for integer. Result is stored in self.result as an int.
"""
[docs] def __init__(self, prompt):
self.prompt = prompt
self.result = None
def _fill_core_struct(self, value):
value.type = FormInputFieldType.IntegerFormField
value.prompt = self.prompt
def _fill_core_result(self, value):
value.intResult = self.result
def _get_result(self, value):
self.result = value.intResult
[docs]class AddressField(object):
"""
``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. The result is stored as in int in self.result.
Note: This API currently functions differently on the command-line, as the view and current_address are
disregarded. Additionally where as in the UI the result defaults to hexadecimal on the command-line 0x must be
specified.
"""
[docs] def __init__(self, prompt, view=None, current_address=0):
self.prompt = prompt
self.view = view
self.current_address = current_address
self.result = None
def _fill_core_struct(self, value):
value.type = FormInputFieldType.AddressFormField
value.prompt = self.prompt
value.view = None
if self.view is not None:
value.view = self.view.handle
value.currentAddress = self.current_address
def _fill_core_result(self, value):
value.addressResult = self.result
def _get_result(self, value):
self.result = value.addressResult
[docs]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 choices array.
:attr str prompt: prompt to be presented to the user
:attr list(str) choices: list of choices to choose from
"""
[docs] def __init__(self, prompt, choices):
self.prompt = prompt
self.choices = choices
self.result = None
def _fill_core_struct(self, value):
value.type = FormInputFieldType.ChoiceFormField
value.prompt = self.prompt
choice_buf = (ctypes.c_char_p * len(self.choices))()
for i in range(0, len(self.choices)):
choice_buf[i] = self.choices[i].encode('charmap')
value.choices = choice_buf
value.count = len(self.choices)
def _fill_core_result(self, value):
value.indexResult = self.result
def _get_result(self, value):
self.result = value.indexResult
[docs]class OpenFileNameField(object):
"""
``OpenFileNameField`` prompts the user to specify a file name to open. Result is stored in self.result as a string.
"""
[docs] def __init__(self, prompt, ext=""):
self.prompt = prompt
self.ext = ext
self.result = None
def _fill_core_struct(self, value):
value.type = FormInputFieldType.OpenFileNameFormField
value.prompt = self.prompt
value.ext = self.ext
def _fill_core_result(self, value):
value.stringResult = core.BNAllocString(str(self.result))
def _get_result(self, value):
self.result = value.stringResult
[docs]class SaveFileNameField(object):
"""
``SaveFileNameField`` prompts the user to specify a file name to save. Result is stored in self.result as a string.
"""
[docs] def __init__(self, prompt, ext="", default_name=""):
self.prompt = prompt
self.ext = ext
self.default_name = default_name
self.result = None
def _fill_core_struct(self, value):
value.type = FormInputFieldType.SaveFileNameFormField
value.prompt = self.prompt
value.ext = self.ext
value.defaultName = self.default_name
def _fill_core_result(self, value):
value.stringResult = core.BNAllocString(str(self.result))
def _get_result(self, value):
self.result = value.stringResult
[docs]class DirectoryNameField(object):
"""
``DirectoryNameField`` prompts the user to specify a directory name to open. Result is stored in self.result as
a string.
"""
[docs] 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 = FormInputFieldType.DirectoryNameFormField
value.prompt = self.prompt
value.defaultName = self.default_name
def _fill_core_result(self, value):
value.stringResult = core.BNAllocString(str(self.result))
def _get_result(self, value):
self.result = value.stringResult
[docs]class InteractionHandler(object):
_interaction_handler = None
[docs] def __init__(self):
self._cb = core.BNInteractionHandlerCallbacks()
self._cb.context = 0
self._cb.showPlainTextReport = self._cb.showPlainTextReport.__class__(self._show_plain_text_report)
self._cb.showMarkdownReport = self._cb.showMarkdownReport.__class__(self._show_markdown_report)
self._cb.showHTMLReport = self._cb.showHTMLReport.__class__(self._show_html_report)
self._cb.showGraphReport = self._cb.showGraphReport.__class__(self._show_graph_report)
self._cb.showReportCollection = self._cb.showReportCollection.__class__(self._show_report_collection)
self._cb.getTextLineInput = self._cb.getTextLineInput.__class__(self._get_text_line_input)
self._cb.getIntegerInput = self._cb.getIntegerInput.__class__(self._get_int_input)
self._cb.getAddressInput = self._cb.getAddressInput.__class__(self._get_address_input)
self._cb.getChoiceInput = self._cb.getChoiceInput.__class__(self._get_choice_input)
self._cb.getOpenFileNameInput = self._cb.getOpenFileNameInput.__class__(self._get_open_filename_input)
self._cb.getSaveFileNameInput = self._cb.getSaveFileNameInput.__class__(self._get_save_filename_input)
self._cb.getDirectoryNameInput = self._cb.getDirectoryNameInput.__class__(self._get_directory_name_input)
self._cb.getFormInput = self._cb.getFormInput.__class__(self._get_form_input)
self._cb.showMessageBox = self._cb.showMessageBox.__class__(self._show_message_box)
[docs] def register(self):
self.__class__._interaction_handler = self
core.BNRegisterInteractionHandler(self._cb)
def _show_plain_text_report(self, ctxt, view, title, contents):
try:
if view:
view = binaryview.BinaryView(handle = core.BNNewViewReference(view))
else:
view = None
self.show_plain_text_report(view, title, contents)
except:
log.log_error(traceback.format_exc())
def _show_markdown_report(self, ctxt, view, title, contents, plaintext):
try:
if view:
view = binaryview.BinaryView(handle = core.BNNewViewReference(view))
else:
view = None
self.show_markdown_report(view, title, contents, plaintext)
except:
log.log_error(traceback.format_exc())
def _show_html_report(self, ctxt, view, title, contents, plaintext):
try:
if view:
view = binaryview.BinaryView(handle = core.BNNewViewReference(view))
else:
view = None
self.show_html_report(view, title, contents, plaintext)
except:
log.log_error(traceback.format_exc())
def _show_graph_report(self, ctxt, view, title, graph):
try:
if view:
view = binaryview.BinaryView(handle = core.BNNewViewReference(view))
else:
view = None
self.show_graph_report(view, title, flowgraph.CoreFlowGraph(core.BNNewFlowGraphReference(graph)))
except:
log.log_error(traceback.format_exc())
def _show_report_collection(self, ctxt, title, reports):
try:
self.show_report_collection(title, ReportCollection(core.BNNewReportCollectionReference(reports)))
except:
log.log_error(traceback.format_exc())
def _get_text_line_input(self, ctxt, result, prompt, title):
try:
value = self.get_text_line_input(prompt, title)
if value is None:
return False
result[0] = core.BNAllocString(str(value))
return True
except:
log.log_error(traceback.format_exc())
def _get_int_input(self, ctxt, result, prompt, title):
try:
value = self.get_int_input(prompt, title)
if value is None:
return False
result[0] = value
return True
except:
log.log_error(traceback.format_exc())
def _get_address_input(self, ctxt, result, prompt, title, view, current_address):
try:
if view:
view = binaryview.BinaryView(handle = core.BNNewViewReference(view))
else:
view = None
value = self.get_address_input(prompt, title, view, current_address)
if value is None:
return False
result[0] = value
return True
except:
log.log_error(traceback.format_exc())
def _get_choice_input(self, ctxt, result, prompt, title, choice_buf, count):
try:
choices = []
for i in range(0, count):
choices.append(choice_buf[i])
value = self.get_choice_input(prompt, title, choices)
if value is None:
return False
result[0] = value
return True
except:
log.log_error(traceback.format_exc())
def _get_open_filename_input(self, ctxt, result, prompt, ext):
try:
value = self.get_open_filename_input(prompt, ext)
if value is None:
return False
result[0] = core.BNAllocString(str(value))
return True
except:
log.log_error(traceback.format_exc())
def _get_save_filename_input(self, ctxt, result, prompt, ext, default_name):
try:
value = self.get_save_filename_input(prompt, ext, default_name)
if value is None:
return False
result[0] = core.BNAllocString(str(value))
return True
except:
log.log_error(traceback.format_exc())
def _get_directory_name_input(self, ctxt, result, prompt, default_name):
try:
value = self.get_directory_name_input(prompt, default_name)
if value is None:
return False
result[0] = core.BNAllocString(str(value))
return True
except:
log.log_error(traceback.format_exc())
def _get_form_input(self, ctxt, fields, count, title):
try:
field_objs = []
for i in range(0, count):
if fields[i].type == FormInputFieldType.LabelFormField:
field_objs.append(LabelField(fields[i].prompt))
elif fields[i].type == FormInputFieldType.SeparatorFormField:
field_objs.append(SeparatorField())
elif fields[i].type == FormInputFieldType.TextLineFormField:
field_objs.append(TextLineField(fields[i].prompt))
elif fields[i].type == FormInputFieldType.MultilineTextFormField:
field_objs.append(MultilineTextField(fields[i].prompt))
elif fields[i].type == FormInputFieldType.IntegerFormField:
field_objs.append(IntegerField(fields[i].prompt))
elif fields[i].type == FormInputFieldType.AddressFormField:
view = None
if fields[i].view:
view = binaryview.BinaryView(handle = core.BNNewViewReference(fields[i].view))
field_objs.append(AddressField(fields[i].prompt, view, fields[i].currentAddress))
elif fields[i].type == FormInputFieldType.ChoiceFormField:
choices = []
for j in range(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 == FormInputFieldType.DirectoryNameFormField:
field_objs.append(DirectoryNameField(fields[i].prompt, fields[i].defaultName))
else:
field_objs.append(LabelField(fields[i].prompt))
if not self.get_form_input(field_objs, title):
return False
for i in range(0, count):
field_objs[i]._fill_core_result(fields[i])
return True
except:
log.log_error(traceback.format_exc())
def _show_message_box(self, ctxt, title, text, buttons, icon):
try:
return self.show_message_box(title, text, buttons, icon)
except:
log.log_error(traceback.format_exc())
[docs] def show_plain_text_report(self, view, title, contents):
pass
[docs] def show_markdown_report(self, view, title, contents, plaintext):
self.show_html_report(view, title, markdown_to_html(contents), plaintext)
[docs] def show_html_report(self, view, title, contents, plaintext):
if len(plaintext) != 0:
self.show_plain_text_report(view, title, plaintext)
[docs] def show_graph_report(self, view, title, graph):
pass
[docs] def show_report_collection(self, title, reports):
pass
[docs] def get_text_line_input(self, prompt, title):
return None
[docs] def show_message_box(self, title, text, buttons, icon):
return MessageBoxButtonResult.CancelButton
[docs]class PlainTextReport(object):
[docs] def __init__(self, title, contents, view = None):
self.view = view
self.title = title
self.contents = contents
def __repr__(self):
return "<plaintext report: %s>" % self.title
def __str__(self):
return self.contents
[docs]class MarkdownReport(object):
[docs] def __init__(self, title, contents, plaintext = "", view = None):
self.view = view
self.title = title
self.contents = contents
self.plaintext = plaintext
def __repr__(self):
return "<markdown report: %s>" % self.title
def __str__(self):
return self.contents
[docs]class HTMLReport(object):
[docs] def __init__(self, title, contents, plaintext = "", view = None):
self.view = view
self.title = title
self.contents = contents
self.plaintext = plaintext
def __repr__(self):
return "<html report: %s>" % self.title
def __str__(self):
return self.contents
[docs]class FlowGraphReport(object):
[docs] def __init__(self, title, graph, view = None):
self.view = view
self.title = title
self.graph = graph
def __repr__(self):
return "<graph report: %s>" % self.title
[docs]class ReportCollection(object):
[docs] def __init__(self, handle = None):
if handle is None:
self.handle = core.BNCreateReportCollection()
else:
self.handle = handle
def __len__(self):
return core.BNGetReportCollectionCount(self.handle)
def _report_from_index(self, i):
report_type = core.BNGetReportType(self.handle, i)
title = core.BNGetReportTitle(self.handle, i)
view = core.BNGetReportView(self.handle, i)
if view:
view = binaryview.BinaryView(handle = view)
else:
view = None
if report_type == ReportType.PlainTextReportType:
contents = core.BNGetReportContents(self.handle, i)
return PlainTextReport(title, contents, view)
elif report_type == ReportType.MarkdownReportType:
contents = core.BNGetReportContents(self.handle, i)
plaintext = core.BNGetReportPlainText(self.handle, i)
return MarkdownReport(title, contents, plaintext, view)
elif report_type == ReportType.HTMLReportType:
contents = core.BNGetReportContents(self.handle, i)
plaintext = core.BNGetReportPlainText(self.handle, i)
return HTMLReport(title, contents, plaintext, view)
elif report_type == ReportType.FlowGraphReportType:
graph = flowgraph.CoreFlowGraph(core.BNGetReportFlowGraph(self.handle, i))
return FlowGraphReport(title, graph, view)
raise TypeError("invalid report type %s" % repr(report_type))
def __getitem__(self, i):
if isinstance(i, slice) or isinstance(i, tuple):
raise IndexError("expected integer report index")
if (i < 0) or (i >= len(self)):
raise IndexError("index out of range")
return self._report_from_index(i)
def __iter__(self):
count = len(self)
for i in range(0, count):
yield self._report_from_index(i)
def __repr__(self):
return "<reports: %s>" % repr(list(self))
[docs] def append(self, report):
if report.view is None:
view = None
else:
view = report.view.handle
if isinstance(report, PlainTextReport):
core.BNAddPlainTextReportToCollection(self.handle, view, report.title, report.contents)
elif isinstance(report, MarkdownReport):
core.BNAddMarkdownReportToCollection(self.handle, view, report.title, report.contents, report.plaintext)
elif isinstance(report, HTMLReport):
core.BNAddHTMLReportToCollection(self.handle, view, report.title, report.contents, report.plaintext)
elif isinstance(report, FlowGraphReport):
core.BNAddGraphReportToCollection(self.handle, view, report.title, report.graph.handle)
else:
raise TypeError("expected report object")
[docs]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)
[docs]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 pop-up is used. On the command-line
a simple text prompt is used.
:param str title: title to display in the UI pop-up.
:param str contents: plaintext contents to display
:rtype: None
:Example:
>>> show_plain_text_report("title", "contents")
contents
"""
core.BNShowPlainTextReport(None, title, contents)
[docs]def show_markdown_report(title, contents, plaintext=""):
"""
``show_markdown_report`` displays the markdown contents in UI applications and plaintext in command-line
applications. This API doesn't support hyperlinking into the BinaryView, use the BinaryView.show_markdown_report
if hyperlinking is needed.
Note: This API function differently on the command-line vs the UI. In the UI a pop-up is used. On the command-line
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)
[docs]def show_html_report(title, contents, plaintext=""):
"""
``show_html_report`` displays the HTML contents in UI applications and plaintext in command-line
applications. This API doesn't support hyperlinking into the BinaryView, use the BinaryView.show_html_report
if hyperlinking is needed.
Note: This API function differently on the command-line vs the UI. In the UI a pop-up is used. On the command-line
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)
[docs]def show_graph_report(title, graph):
"""
``show_graph_report`` displays a flow graph in UI applications.
Note: This API function will have no effect outside the UI.
:param FlowGraph graph: Flow graph to display
:rtype: None
"""
func = graph.function
if func is None:
core.BNShowGraphReport(None, title, graph.handle)
else:
core.BNShowGraphReport(func.view.handle, title, graph.handle)
[docs]def show_report_collection(title, reports):
"""
``show_report_collection`` displays multiple reports in UI applications.
Note: This API function will have no effect outside the UI.
:param ReportCollection reports: Reports to display
:rtype: None
"""
core.BNShowReportCollection(title, reports.handle)
[docs]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 pop-up is used. On the command-line
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
result = value.value
core.BNFreeString(ctypes.cast(value, ctypes.POINTER(ctypes.c_byte)))
return result
[docs]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
provided range.
:param str title: Text title for the message box.
:param str text: Text for the main body of the message box.
:param MessageBoxButtonSet buttons: One of :py:class:`MessageBoxButtonSet`
:param MessageBoxIcon icon: One of :py:class:`MessageBoxIcon`
:return: Which button was selected
:rtype: MessageBoxButtonResult
"""
return core.BNShowMessageBox(title, text, buttons, icon)