summaryrefslogtreecommitdiff
path: root/python/examples
diff options
context:
space:
mode:
Diffstat (limited to 'python/examples')
-rw-r--r--python/examples/triage/__init__.py1
-rw-r--r--python/examples/triage/entropy.py91
-rw-r--r--python/examples/triage/headers.py155
-rw-r--r--python/examples/triage/imports.py117
-rw-r--r--python/examples/triage/view.py114
5 files changed, 478 insertions, 0 deletions
diff --git a/python/examples/triage/__init__.py b/python/examples/triage/__init__.py
new file mode 100644
index 00000000..90cdbd77
--- /dev/null
+++ b/python/examples/triage/__init__.py
@@ -0,0 +1 @@
+from . import view
diff --git a/python/examples/triage/entropy.py b/python/examples/triage/entropy.py
new file mode 100644
index 00000000..03249968
--- /dev/null
+++ b/python/examples/triage/entropy.py
@@ -0,0 +1,91 @@
+import math
+import threading
+from PySide2.QtWidgets import QWidget
+from PySide2.QtGui import QImage, QColor, QPainter
+from PySide2.QtCore import Qt, QSize, QTimer
+from binaryninjaui import ViewFrame
+
+
+class EntropyThread(threading.Thread):
+ def __init__(self, data, image, block_size):
+ super(EntropyThread, self).__init__()
+ self.data = data
+ self.image = image
+ self.block_size = block_size
+ self.updated = False
+
+ def run(self):
+ width = self.image.width()
+ for i in range(0, width):
+ block = self.data.read(self.data.start + i * self.block_size, self.block_size)
+ if len(block) == 0:
+ v = 0
+ else:
+ dist = [0] * 0x100
+ for j in range(0, len(block)):
+ value = ord(block[j:j+1])
+ dist[value] += 1
+ s = 0
+ for j in range(0, 256):
+ if dist[j] != 0:
+ s += (float(dist[j]) / len(block)) * math.log(float(dist[j]) / len(block))
+ s = s / math.log(1 / 256.0)
+ v = int(s * 255)
+ if v >= 240:
+ self.image.setPixelColor(i, 0, QColor(v, v, v / 4, 255))
+ else:
+ self.image.setPixelColor(i, 0, QColor(v / 4, v / 4, v, 255))
+ self.updated = True
+
+
+class EntropyWidget(QWidget):
+ def __init__(self, parent, data):
+ super(EntropyWidget, self).__init__(parent)
+ self.data = data
+ self.raw_data = data.file.raw
+
+ self.block_size = 1024
+ self.width = int(len(self.raw_data) / self.block_size)
+ self.image = QImage(self.width, 1, QImage.Format_ARGB32)
+ self.image.fill(QColor(0, 0, 0, 0))
+
+ self.thread = EntropyThread(self.raw_data, self.image, self.block_size)
+ self.started = False
+
+ self.timer = QTimer()
+ self.timer.timeout.connect(self.timerEvent)
+ self.timer.setInterval(250)
+ self.timer.setSingleShot(False)
+ self.timer.start()
+
+ self.setMinimumHeight(32)
+
+ def paintEvent(self, event):
+ p = QPainter(self)
+ p.drawImage(self.rect(), self.image)
+ p.drawRect(self.rect())
+
+ def sizeHint(self):
+ return QSize(640, 32)
+
+ def timerEvent(self):
+ if not self.started:
+ self.thread.start()
+ self.started = True
+ if self.thread.updated:
+ self.thread.updated = False
+ self.update()
+
+ def mousePressEvent(self, event):
+ if event.button() != Qt.LeftButton:
+ return
+ frac = float(event.x()) / self.rect().width()
+ offset = int(frac * self.width * self.block_size)
+ addr = self.data.get_address_for_data_offset(offset)
+ view_frame = ViewFrame.viewFrameForWidget(self)
+ if view_frame is None:
+ return
+ if addr is None:
+ view_frame.navigate("Hex:Raw", offset)
+ else:
+ view_frame.navigate("Linear:" + view_frame.getCurrentDataType(), addr)
diff --git a/python/examples/triage/headers.py b/python/examples/triage/headers.py
new file mode 100644
index 00000000..d800086c
--- /dev/null
+++ b/python/examples/triage/headers.py
@@ -0,0 +1,155 @@
+import time
+from binaryninja.binaryview import StructuredDataView
+from PySide2.QtWidgets import QWidget, QLabel, QGridLayout
+
+
+class PEHeaders(object):
+ def __init__(self, data):
+ dos = StructuredDataView(data, "DOS_Header", data.start)
+ pe_offset = data.start + int(dos.e_lfanew)
+ coff = StructuredDataView(data, "COFF_Header", pe_offset)
+ pe_magic = data.read(pe_offset + len(coff), 2)
+ self.fields = []
+ if pe_magic == b"\x0b\x01":
+ peopt = StructuredDataView(data, "PE32_Optional_Header", pe_offset + len(coff))
+ self.fields.append(("Type", "PE 32-bit"))
+ is64bit = False
+ elif pe_magic == b"\x0b\x02":
+ peopt = StructuredDataView(data, "PE64_Optional_Header", pe_offset + len(coff))
+ self.fields.append(("Type", "PE 64-bit"))
+ is64bit = True
+
+ machine_value = int(coff.machine)
+ machine_enum = data.get_type_by_name("coff_machine")
+ machine_name = str(machine_value)
+ for member in machine_enum.enumeration.members:
+ if member.value == machine_value:
+ machine_name = member.name
+ if machine_name.startswith("IMAGE_FILE_MACHINE_"):
+ machine_name = machine_name[len("IMAGE_FILE_MACHINE_"):]
+ self.fields.append(("Machine", machine_name))
+
+ subsys_value = int(peopt.subsystem)
+ subsys_enum = data.get_type_by_name("pe_subsystem")
+ subsys_name = str(subsys_value)
+ for member in subsys_enum.enumeration.members:
+ if member.value == subsys_value:
+ subsys_name = member.name
+ if subsys_name.startswith("IMAGE_SUBSYSTEM_"):
+ subsys_name = subsys_name[len("IMAGE_SUBSYSTEM_"):]
+ self.fields.append(("Subsystem", subsys_name))
+
+ self.fields.append(("Timestamp", time.strftime("%c", time.localtime(int(coff.timeDateStamp)))))
+
+ base = int(peopt.imageBase)
+ self.fields.append(("Image Base", "0x%x" % base))
+
+ entry_point = base + int(peopt.addressOfEntryPoint)
+ self.fields.append(("Entry Point", "0x%x" % entry_point))
+
+ section_align = int(peopt.sectionAlignment)
+ self.fields.append(("Section Alignment", "0x%x" % section_align))
+
+ file_align = int(peopt.fileAlignment)
+ self.fields.append(("File Alignment", "0x%x" % file_align))
+
+ checksum = int(peopt.checkSum)
+ self.fields.append(("Checksum", "0x%.8x" % checksum))
+
+ code_base = base + int(peopt.baseOfCode)
+ self.fields.append(("Base of Code", "0x%x" % code_base))
+
+ if not is64bit:
+ data_base = base + int(peopt.baseOfData)
+ self.fields.append(("Base of Data", "0x%x" % data_base))
+
+ code_size = int(peopt.sizeOfCode)
+ self.fields.append(("Size of Code", "0x%x" % code_size))
+
+ init_data_size = int(peopt.sizeOfInitializedData)
+ self.fields.append(("Size of Init Data", "0x%x" % init_data_size))
+
+ uninit_data_size = int(peopt.sizeOfUninitializedData)
+ self.fields.append(("Size of Uninit Data", "0x%x" % uninit_data_size))
+
+ header_size = int(peopt.sizeOfHeaders)
+ self.fields.append(("Size of Headers", "0x%x" % header_size))
+
+ image_size = int(peopt.sizeOfImage)
+ self.fields.append(("Size of Image", "0x%x" % image_size))
+
+ stack_commit = int(peopt.sizeOfStackCommit)
+ stack_reserve = int(peopt.sizeOfStackReserve)
+ self.fields.append(("Stack Size", "0x%x / 0x%x" % (stack_commit, stack_reserve)))
+
+ heap_commit = int(peopt.sizeOfHeapCommit)
+ heap_reserve = int(peopt.sizeOfHeapReserve)
+ self.fields.append(("Heap Size", "0x%x / 0x%x" % (heap_commit, heap_reserve)))
+
+ linker_major = int(peopt.majorLinkerVersion)
+ linker_minor = int(peopt.minorLinkerVersion)
+ self.fields.append(("Linker Version", "%d.%.2d" % (linker_major, linker_minor)))
+
+ image_major = int(peopt.majorImageVersion)
+ image_minor = int(peopt.minorImageVersion)
+ self.fields.append(("Image Version", "%d.%.2d" % (image_major, image_minor)))
+
+ os_major = int(peopt.majorOperatingSystemVersion)
+ os_minor = int(peopt.minorOperatingSystemVersion)
+ self.fields.append(("OS Version", "%d.%.2d" % (os_major, os_minor)))
+
+ sub_major = int(peopt.majorSubsystemVersion)
+ sub_minor = int(peopt.minorSubsystemVersion)
+ self.fields.append(("Subsystem Version", "%d.%.2d" % (sub_major, sub_minor)))
+
+ coff_char_value = int(coff.characteristics)
+ coff_char_enum = data.get_type_by_name("coff_characteristics")
+ coff_char_values = []
+ for member in coff_char_enum.enumeration.members:
+ if (coff_char_value & member.value) != 0:
+ if member.name.startswith("IMAGE_FILE_"):
+ coff_char_values.append(member.name[len("IMAGE_FILE_"):])
+ else:
+ coff_char_values.append(member.name)
+ if len(coff_char_values) > 0:
+ self.fields.append(("COFF Characteristics", coff_char_values))
+
+ dll_char_value = int(peopt.dllCharacteristics)
+ dll_char_enum = data.get_type_by_name("pe_dll_characteristics")
+ dll_char_values = []
+ for member in dll_char_enum.enumeration.members:
+ if (dll_char_value & member.value) != 0:
+ if member.name.startswith("IMAGE_DLLCHARACTERISTICS_"):
+ dll_char_values.append(member.name[len("IMAGE_DLLCHARACTERISTICS_"):])
+ else:
+ dll_char_values.append(member.name)
+ if len(dll_char_values) > 0:
+ self.fields.append(("DLL Characteristics", dll_char_values))
+
+
+class HeaderWidget(QWidget):
+ def __init__(self, parent, header):
+ super(HeaderWidget, self).__init__(parent)
+ layout = QGridLayout()
+ layout.setContentsMargins(0, 0, 0, 0)
+ layout.setVerticalSpacing(1)
+ row = 0
+ col = 0
+ for field in header.fields:
+ name = field[0]
+ value = field[1]
+ layout.addWidget(QLabel(name + ": "), row, col)
+ if isinstance(value, list):
+ for i in range(0, len(value)):
+ layout.addWidget(QLabel(value[i]), row, col + 1)
+ row += 1
+ else:
+ layout.addWidget(QLabel(value), row, col + 1)
+ row += 1
+ if (row >= 9) and (col < 6):
+ row = 0
+ col += 3
+ layout.setColumnMinimumWidth(2, 20)
+ layout.setColumnMinimumWidth(5, 20)
+ layout.setColumnStretch(8, 1)
+ self.setLayout(layout)
diff --git a/python/examples/triage/imports.py b/python/examples/triage/imports.py
new file mode 100644
index 00000000..2b2ed253
--- /dev/null
+++ b/python/examples/triage/imports.py
@@ -0,0 +1,117 @@
+from PySide2.QtWidgets import QTreeView
+from PySide2.QtCore import Qt, QAbstractItemModel, QModelIndex
+from binaryninja.enums import SymbolType
+import binaryninjaui
+
+
+class GenericImportsModel(QAbstractItemModel):
+ def __init__(self, data):
+ super(GenericImportsModel, self).__init__()
+ self.entries = []
+ self.has_modules = False
+ self.name_col = 1
+ self.module_col = None
+ self.total_cols = 2
+ for sym in data.get_symbols_of_type(SymbolType.ImportAddressSymbol):
+ self.entries.append(sym)
+ if str(sym.namespace) != "BNINTERNALNAMESPACE":
+ self.has_modules = True
+ if self.has_modules:
+ self.name_col = 2
+ self.module_col = 1
+ self.total_cols = 3
+
+ def columnCount(self, parent):
+ return self.total_cols
+
+ def rowCount(self, parent):
+ return len(self.entries)
+
+ def data(self, index, role):
+ if role != Qt.DisplayRole:
+ return None
+ if index.row() >= len(self.entries):
+ return None
+ if index.column() == 0:
+ return "0x%x" % self.entries[index.row()].address
+ if index.column() == self.name_col:
+ name = self.entries[index.row()].name
+ if name.endswith("@GOT"):
+ name = name[:-len("@GOT")]
+ elif name.endswith("@PLT"):
+ name = name[:-len("@PLT")]
+ elif name.endswith("@IAT"):
+ name = name[:-len("@IAT")]
+ return name
+ if index.column() == self.module_col:
+ return self.getNamespace(self.entries[index.row()])
+ return None
+
+ def headerData(self, section, orientation, role):
+ if orientation == Qt.Vertical:
+ return None
+ if role != Qt.DisplayRole:
+ return None
+ if section == 0:
+ return "Entry"
+ if section == self.name_col:
+ return "Name"
+ if section == self.module_col:
+ return "Module"
+ return None
+
+ def index(self, row, col, parent):
+ if parent.isValid():
+ return QModelIndex()
+ if row >= len(self.entries):
+ return QModelIndex()
+ if col >= self.total_cols:
+ return QModelIndex()
+ return self.createIndex(row, col)
+
+ def parent(self, index):
+ return QModelIndex()
+
+ def getSymbol(self, index):
+ if index.row() >= len(self.entries):
+ return None
+ return self.entries[index.row()]
+
+ def getNamespace(self, sym):
+ name = str(sym.namespace)
+ if name == "BNINTERNALNAMESPACE":
+ return ""
+ return name
+
+ def sort(self, col, order):
+ self.beginResetModel()
+ if col == 0:
+ self.entries.sort(key = lambda sym: sym.address, reverse = order != Qt.AscendingOrder)
+ elif col == self.name_col:
+ self.entries.sort(key = lambda sym: sym.name, reverse = order != Qt.AscendingOrder)
+ elif col == self.module_col:
+ self.entries.sort(key = lambda sym: self.getNamespace(sym), reverse = order != Qt.AscendingOrder)
+ self.endResetModel()
+
+
+class ImportsWidget(QTreeView):
+ def __init__(self, parent, view, data):
+ super(ImportsWidget, self).__init__(parent)
+ self.data = data
+ self.view = view
+
+ self.model = GenericImportsModel(self.data)
+ self.setModel(self.model)
+ self.setRootIsDecorated(False)
+ self.setUniformRowHeights(True)
+ self.setSortingEnabled(True)
+ self.sortByColumn(0, Qt.AscendingOrder)
+
+ self.setFont(binaryninjaui.getMonospaceFont(self))
+
+ self.clicked.connect(self.importSelected)
+
+ def importSelected(self, index):
+ sym = self.model.getSymbol(index)
+ if sym is not None:
+ self.view.setCurrentOffset(sym.address)
diff --git a/python/examples/triage/view.py b/python/examples/triage/view.py
new file mode 100644
index 00000000..e06776ca
--- /dev/null
+++ b/python/examples/triage/view.py
@@ -0,0 +1,114 @@
+import traceback
+import binaryninjaui
+from binaryninja.settings import Settings
+from binaryninja import log
+from binaryninjaui import View, ViewType, UIContext
+from PySide2.QtWidgets import QScrollArea, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QGroupBox
+from . import headers
+from . import entropy
+from . import imports
+
+
+class TriageView(QScrollArea, View):
+ def __init__(self, parent, data):
+ QScrollArea.__init__(self, parent)
+ View.__init__(self)
+ self.setupView(self)
+ self.data = data
+ self.currentOffset = 0
+
+ container = QWidget(self)
+ layout = QVBoxLayout()
+
+ entropyGroup = QGroupBox("Entropy")
+ entropyLayout = QVBoxLayout()
+ entropyLayout.addWidget(entropy.EntropyWidget(entropyGroup, self.data))
+ entropyGroup.setLayout(entropyLayout)
+ layout.addWidget(entropyGroup)
+
+ hdr = None
+ try:
+ if self.data.view_type == "PE":
+ hdr = headers.PEHeaders(self.data)
+ except:
+ log.log_error(traceback.format_exc())
+
+ if hdr is not None:
+ headerGroup = QGroupBox("Headers")
+ headerLayout = QVBoxLayout()
+ headerWidget = headers.HeaderWidget(headerGroup, hdr)
+ headerLayout.addWidget(headerWidget)
+ headerGroup.setLayout(headerLayout)
+ layout.addWidget(headerGroup)
+
+ importGroup = QGroupBox("Imports")
+ importLayout = QVBoxLayout()
+ importLayout.addWidget(imports.ImportsWidget(importGroup, self, self.data))
+ importGroup.setLayout(importLayout)
+ layout.addWidget(importGroup)
+
+ button_layout = QHBoxLayout()
+ button_layout.addStretch(1)
+ self.full_analysis_button = QPushButton("Start Full Analysis")
+ self.full_analysis_button.clicked.connect(self.startFullAnalysis)
+ button_layout.addWidget(self.full_analysis_button)
+ layout.addLayout(button_layout)
+
+ layout.addStretch(1)
+ container.setLayout(layout)
+ self.setWidgetResizable(True)
+ self.setWidget(container)
+
+ if Settings().get_string("analysis.mode", data) == "full":
+ self.full_analysis_button.hide()
+
+ def getData(self):
+ return self.data
+
+ def getCurrentOffset(self):
+ return self.currentOffset
+
+ def setCurrentOffset(self, offset):
+ self.currentOffset = offset
+ UIContext.updateStatus(True)
+
+ def getFont(self):
+ return binaryninjaui.getMonospaceFont(self)
+
+ def navigate(self, addr):
+ return False
+
+ def startFullAnalysis(self):
+ Settings().set_string("analysis.mode", "full", self.data)
+ for f in self.data.functions:
+ if f.analysis_skipped:
+ f.reanalyze()
+ self.data.update_analysis()
+ self.full_analysis_button.hide()
+
+
+class TriageViewType(ViewType):
+ def __init__(self):
+ super(TriageViewType, self).__init__("Triage", "Triage Summary")
+
+ def getPriority(self, data, filename):
+ is_full = Settings().get_string("analysis.mode", data) == "full"
+ always_prefer = Settings().get_bool("ui.always_prefer_triage", data)
+ if data.executable and (always_prefer or not is_full):
+ return 100
+ return 25
+
+ def create(self, data, view_frame):
+ return TriageView(view_frame, data)
+
+
+Settings().register_setting("ui.always_prefer_triage", """
+ {
+ "title" : "Always Prefer Triage Summary View",
+ "type" : "boolean",
+ "default" : false,
+ "description" : "Always prefer opening binaries in Triage Summary view, even when performing full analysis."
+ }
+ """)
+
+ViewType.registerViewType(TriageViewType())