class DebugConsoleWidget(QWidget, DockContextHandler): def __init__(self, parent, name, data): if not type(data) == binaryninja.binaryview.BinaryView: raise Exception('expected widget data to be a BinaryView') self.bv = data QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) layout = QVBoxLayout() self.consoleText = QTextEdit(self) self.consoleText.setReadOnly(True) layout.addWidget(self.consoleText, 1) inputLayout = QHBoxLayout() inputLayout.setContentsMargins(4, 4, 4, 4) promptLayout = QVBoxLayout() promptLayout.setContentsMargins(0, 5, 0, 5) inputLayout.addLayout(promptLayout) self.consoleEntry = QLineEdit(self) inputLayout.addWidget(self.consoleEntry, 1) label = QLabel("lldb>>> ", self) label.setFont(getMonospaceFont(self)) promptLayout.addWidget(label) promptLayout.addStretch(1) self.consoleEntry.returnPressed.connect(lambda: self.consoleText.append("TODO")) layout.addLayout(inputLayout) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.setLayout(layout) def sizeHint(self): return QSize(300, 100) #-------------------------------------------------------------------------- # callbacks to us api/ui/dockhandler.h #-------------------------------------------------------------------------- def notifyOffsetChanged(self, offset): pass def notifyViewChanged(self, view_frame): pass def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler) def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True
class HelloDockWidget(QWidget, DockContextHandler): def __init__(self, parent, name, data): global instance_id QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) offset_layout = QHBoxLayout() offset_layout.addWidget(QLabel("Offset: ")) self.offset = QLabel(hex(0)) offset_layout.addWidget(self.offset) offset_layout.setAlignment(QtCore.Qt.AlignCenter) datatype_layout = QHBoxLayout() datatype_layout.addWidget(QLabel("Data Type: ")) self.datatype = QLabel("") datatype_layout.addWidget(self.datatype) datatype_layout.setAlignment(QtCore.Qt.AlignCenter) layout = QVBoxLayout() title = QLabel(name, self) title.setAlignment(QtCore.Qt.AlignCenter) instance = QLabel("Instance: " + str(instance_id), self) instance.setAlignment(QtCore.Qt.AlignCenter) layout.addStretch() layout.addWidget(title) layout.addWidget(instance) layout.addLayout(datatype_layout) layout.addLayout(offset_layout) layout.addStretch() self.setLayout(layout) instance_id += 1 self.data = data def notifyOffsetChanged(self, offset): self.offset.setText(hex(offset)) def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True def notifyViewChanged(self, view_frame): if view_frame is None: self.datatype.setText("None") self.data = None else: self.datatype.setText(view_frame.getCurrentView()) view = view_frame.getCurrentViewInterface() self.data = view.getData() def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler) @staticmethod def create_widget(name, parent, data=None): return HelloDockWidget(parent, name, data)
class DebugBreakpointsWidget(QWidget, DockContextHandler): def __init__(self, parent, name, data): if not type(data) == binaryninja.binaryview.BinaryView: raise Exception('expected widget data to be a BinaryView') self.bv = data QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) self.table = QTableView(self) self.model = DebugBreakpointsListModel(self.table, data) self.table.setModel(self.model) self.item_delegate = DebugBreakpointsItemDelegate(self) self.table.setItemDelegate(self.item_delegate) # self.table.setSortingEnabled(True) self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.table.setSelectionMode(QAbstractItemView.ExtendedSelection) self.table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) self.table.verticalHeader().setVisible(False) self.table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) self.table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) self.table.resizeColumnsToContents() self.table.resizeRowsToContents() for i in range(len(self.model.columns)): self.table.setColumnWidth(i, self.item_delegate.sizeHint(self.table.viewOptions(), self.model.index(-1, i, QModelIndex())).width()) self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.table) self.setLayout(layout) def notifyOffsetChanged(self, offset): pass def notifyBreakpointsChanged(self, new_rows): self.model.update_rows(new_rows) def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler) def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True
class DockableWidget(QtWidgets.QWidget, DockContextHandler): """ A dockable Qt widget for Binary Ninja. """ def __init__(self, parent, name): QtWidgets.QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) self._active_view = None self._visible_for_view = collections.defaultdict(lambda: False) @property def visible(self): return self._visible_for_view[self._active_view] @visible.setter def visible(self, is_visible): self._visible_for_view[self._active_view] = is_visible def shouldBeVisible(self, view_frame): if not view_frame: return False if USING_PYSIDE6: import shiboken6 as shiboken else: import shiboken2 as shiboken vf_ptr = shiboken.getCppPointer(view_frame)[0] return self._visible_for_view[vf_ptr] def notifyVisibilityChanged(self, is_visible): self.visible = is_visible def notifyViewChanged(self, view_frame): if not view_frame: self._active_view = None return if USING_PYSIDE6: import shiboken6 as shiboken else: import shiboken2 as shiboken self._active_view = shiboken.getCppPointer(view_frame)[0] if self.visible: dock_handler = DockHandler.getActiveDockHandler() dock_handler.setVisible(self.m_name, True)
class HelloGlobalAreaWidget(GlobalAreaWidget): def __init__(self, name): global instance_id GlobalAreaWidget.__init__(self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) offset_layout = QHBoxLayout() offset_layout.addWidget(QLabel("Offset: ")) self.offset = QLabel(hex(0)) offset_layout.addWidget(self.offset) offset_layout.setAlignment(QtCore.Qt.AlignCenter) datatype_layout = QHBoxLayout() datatype_layout.addWidget(QLabel("Data Type: ")) self.datatype = QLabel("") datatype_layout.addWidget(self.datatype) datatype_layout.setAlignment(QtCore.Qt.AlignCenter) layout = QVBoxLayout() title = QLabel(name, self) title.setAlignment(QtCore.Qt.AlignCenter) instance = QLabel("Instance: " + str(instance_id), self) instance.setAlignment(QtCore.Qt.AlignCenter) layout.addStretch() layout.addWidget(title) layout.addWidget(instance) layout.addLayout(datatype_layout) layout.addLayout(offset_layout) layout.addStretch() self.setLayout(layout) instance_id += 1 self.data = None def notifyOffsetChanged(self, offset): self.offset.setText(hex(offset)) def notifyViewChanged(self, view_frame): if view_frame is None: self.datatype.setText("None") self.data = None else: self.datatype.setText(view_frame.getCurrentView()) view = view_frame.getCurrentViewInterface() self.data = view.getData() def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler)
class DebugMemoryWidget(QWidget, DockContextHandler): def __init__(self, parent, name, data): if not type(data) == binaryninja.binaryview.BinaryView: raise Exception('expected widget data to be a BinaryView') self.bv = data memory_view = binjaplug.get_state(data).memory_view QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.editor = LinearView(memory_view, ViewFrame.viewFrameForWidget(self)) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.editor) self.setLayout(layout) def notifyOffsetChanged(self, offset): pass def notifyMemoryChanged(self): adapter = binjaplug.get_state(self.bv).adapter # Refresh the editor if adapter is None: self.editor.navigate(0) return self.editor.navigate(adapter.reg_read('rsp')) def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True
class HyaraDockWidget(QtWidgets.QWidget, DockContextHandler): def __init__(self, parent, name, data): QtWidgets.QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) self.HyaraBinaryNinja = HyaraBinaryNinja() self.setLayout(self.HyaraBinaryNinja.layout) def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True def notifyViewChanged(self, view_frame): pass @staticmethod def create_widget(name, parent, data=None): return HyaraDockWidget(parent, name, data)
class JumpOverview(QWidget, DockContextHandler): def __init__(self, parent, name): QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) font = getMonospaceFont(self) fm = QFontMetricsF(font) table_layout = QVBoxLayout() self.table = QTableView() self.table.setFont(font) self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.verticalHeader().hide() self.table.verticalHeader().setSectionResizeMode(QHeaderView.Fixed) self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) # sorting self.table.setSortingEnabled(True) self.table.horizontalHeader().setSortIndicator(0, QtCore.Qt.AscendingOrder) data = [] self.model = TableModel(data) self.table.setModel(self.model) table_layout.addWidget(self.table) layout = QVBoxLayout() layout.addLayout(table_layout) self.setLayout(layout) # init double click action self.table.doubleClicked.connect(self._ui_entry_double_click) # init right click menu self.ctx_menu = QMenu() self._action_patch = QAction("Invert Branch", None) self.ctx_menu.addAction(self._action_patch) self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self._ui_table_ctx_menu_handler) self.bv = None self.filename = None self.do_sync = True def _ui_entry_double_click(self, index): self.navigate_to_addr(index) def _ui_table_ctx_menu_handler(self, pos): action = self.ctx_menu.exec_(self.table.viewport().mapToGlobal(pos)) if not action: return rows = self.table.selectionModel().selectedRows() indices = [i.row() for i in rows] if len(indices) > 0 and action == self._action_patch: for i in indices: addr = int(self.model._data[i][0], 16) if self.patch(addr): if not addr in self.model._patched: self.model._patched.append(addr) else: self.model._patched.remove(addr) self.model.data(rows[i], role=QtCore.Qt.BackgroundRole) else: interaction.show_message_box( "Patching Error", "This architecture does not seem to support auto-patching yet.", icon=MessageBoxIcon.ErrorIcon, ) def navigate_to_addr(self, index): addr = int(self.model._data[index.row()][0], 16) dh = DockHandler.getActiveDockHandler() vf = dh.getViewFrame() vi = vf.getCurrentViewInterface() vi.navigate(addr) def patch(self, addr): if self.bv is None: return False try: return self.bv.invert_branch(addr) except: return False def notifyViewChanged(self, view_frame): if view_frame is None: pass else: self.bv = view_frame.actionContext().binaryView self.filename = self.bv.file.original_filename @staticmethod def create_widget(name, parent, data=None): return JumpOverview(parent, name)
class TriageFilePicker(QWidget): def __init__(self, context): super(TriageFilePicker, self).__init__() self.context = context self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) self.contextMenu = Menu() self.contextMenuManager = ContextMenuManager(self) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self.model = QFileSystemModel() self.model.setRootPath("") self.model.setFilter(QDir.AllEntries | QDir.Hidden | QDir.System) self.tree = QTreeView(self) self.tree.setModel(self.model) self.tree.setSelectionMode(QAbstractItemView.ExtendedSelection) self.tree.setColumnWidth(0, 500) layout.addWidget(self.tree, 1) self.setLayout(layout) self.tree.doubleClicked.connect(self.onDoubleClick) recentFile = QSettings().value("triage/recentFile", os.path.expanduser("~")) while len(recentFile) > 0: f = self.model.index(recentFile) if f.isValid(): self.tree.scrollTo(f) self.tree.setExpanded(f, True) break parentDir = os.path.dirname(recentFile) if parentDir == recentFile: break recentFile = parentDir self.actionHandler.bindAction("Open Selected Files", UIAction( lambda context: self.openSelectedFiles(), lambda context: self.areFilesSelected())) self.contextMenu.addAction("Open Selected Files", "Open") def contextMenuEvent(self, event): self.contextMenuManager.show(self.contextMenu, self.actionHandler) def onDoubleClick(self, index): self.openSelectedFiles() def openSelectedFiles(self): failedToOpen = [] files = set() for index in self.tree.selectionModel().selectedIndexes(): if self.model.fileInfo(index).isFile(): files.add(self.model.fileInfo(index).absoluteFilePath()) for filename in files: QSettings().setValue("triage/recentFile", filename) f = FileContext.openFilename(filename) if not f: failedToOpen.append(filename) continue f.createBinaryViews() for data in f.getAllDataViews(): Settings().set_string("analysis.mode", Settings().get_string("triage.analysisMode"), data) Settings().set_bool("triage.preferSummaryView", True, data) if data.view_type != "Raw": linearSweepMode = Settings().get_string("triage.linearSweep") if linearSweepMode == "none": Settings().set_bool("analysis.linearSweep.autorun", False, data) elif linearSweepMode == "partial": Settings().set_bool("analysis.linearSweep.autorun", True, data) Settings().set_bool("analysis.linearSweep.controlFlowGraph", False, data) elif linearSweepMode == "full": Settings().set_bool("analysis.linearSweep.autorun", True, data) Settings().set_bool("analysis.linearSweep.controlFlowGraph", True, data) self.context.openFileContext(f) if len(failedToOpen) > 0: QMessageBox.critical(self, "Error", "Unable to open:\n" + "\n".join(failedToOpen)) def areFilesSelected(self): return self.tree.selectionModel().hasSelection()
class DebugModulesWidget(QWidget, DockContextHandler): def __init__(self, parent, name, data): if not type(data) == binaryninja.binaryview.BinaryView: raise Exception('expected widget data to be a BinaryView') self.bv = data QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) self.table = QTableView(self) self.model = DebugModulesListModel(self.table, data) self.table.setModel(self.model) self.item_delegate = DebugModulesItemDelegate(self) self.table.setItemDelegate(self.item_delegate) # self.table.setSortingEnabled(True) self.table.setSelectionBehavior( QAbstractItemView.SelectionBehavior.SelectRows) self.table.setSelectionMode(QAbstractItemView.ExtendedSelection) self.table.verticalHeader().setSectionResizeMode( QHeaderView.ResizeToContents) self.table.verticalHeader().setVisible(False) self.table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) self.table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) self.table.resizeColumnsToContents() self.table.resizeRowsToContents() for i in range(len(self.model.columns)): self.table.setColumnWidth( i, self.item_delegate.sizeHint( self.table.viewOptions(), self.model.index(-1, i, QModelIndex())).width()) update_layout = QHBoxLayout() update_layout.setContentsMargins(0, 0, 0, 0) update_label = QLabel("Data is Stale") update_button = QPushButton("Refresh") update_button.clicked.connect(lambda: self.refresh()) update_layout.addWidget(update_label) update_layout.addStretch(1) update_layout.addWidget(update_button) self.update_box = QWidget() self.update_box.setLayout(update_layout) self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(0) self.layout.addWidget(self.table) self.setLayout(self.layout) def notifyOffsetChanged(self, offset): pass def refresh(self): debug_state = binjaplug.get_state(self.bv) debug_state.ui.update_modules() def notifyModulesChanged(self, new_modules): self.model.update_rows(new_modules) self.table.resizeColumnsToContents() self.layout.removeWidget(self.update_box) self.update_box.setVisible(False) def mark_dirty(self): self.layout.addWidget(self.update_box) self.update_box.setVisible(True) def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler) def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True
class DebugConsoleWidget(QWidget, DockContextHandler): def __init__(self, parent, name, data): if not type(data) == binaryninja.binaryview.BinaryView: raise Exception('expected widget data to be a BinaryView') self.bv = data QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) layout = QVBoxLayout() self.consoleText = QTextEdit(self) self.consoleText.setReadOnly(True) self.consoleText.setFont(getMonospaceFont(self)) layout.addWidget(self.consoleText, 1) inputLayout = QHBoxLayout() inputLayout.setContentsMargins(4, 4, 4, 4) promptLayout = QVBoxLayout() promptLayout.setContentsMargins(0, 5, 0, 5) inputLayout.addLayout(promptLayout) self.consoleEntry = QLineEdit(self) inputLayout.addWidget(self.consoleEntry, 1) label = QLabel("dbg>>> ", self) label.setFont(getMonospaceFont(self)) promptLayout.addWidget(label) promptLayout.addStretch(1) self.consoleEntry.returnPressed.connect(lambda: self.sendLine()) layout.addLayout(inputLayout) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.setLayout(layout) def sizeHint(self): return QSize(300, 100) def sendLine(self): line = self.consoleEntry.text() self.consoleEntry.setText("") debug_state = binjaplug.get_state(self.bv) try: debug_state.send_console_input(line) except Exception as e: self.notifyStdout("Error sending input: {} {}\n".format( type(e).__name__, ' '.join(e.args))) def notifyStdout(self, line): self.consoleText.insertPlainText(line) # Scroll down cursor = self.consoleText.textCursor() cursor.clearSelection() cursor.movePosition(QTextCursor.End) self.consoleText.setTextCursor(cursor) #-------------------------------------------------------------------------- # callbacks to us api/ui/dockhandler.h #-------------------------------------------------------------------------- def notifyOffsetChanged(self, offset): pass def notifyViewChanged(self, view_frame): pass def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler) def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True
class BBViewerWidget(QWidget, DockContextHandler): PER_PAGE_COUNT = 50 def __init__(self, name, parent, bv, db): QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.hitcounts = [] self.orig_hitcounts = [] self.db = db self.bv = bv self.descending = True self.highlight = False self.current_page = 0 self.hitcounts = self.db.get_hitcounts() self.orig_hitcounts = [e for e in self.hitcounts] self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) vlayout = QVBoxLayout() # Top label self.hitcount_counter = QLabel("Basic block count: {}".format( self.db.hitcount_count)) self.hitcount_counter.setAlignment(Qt.AlignLeft | Qt.AlignTop) # BB Hitcount table self.hit_table = QTableWidget(0, 3) self.hit_table.setHorizontalHeaderLabels( ["Address", "Hitcount", "Function"]) self.hit_table.verticalHeader().hide() self.hit_table.horizontalHeader().setStretchLastSection(True) self.hit_table.itemDoubleClicked.connect(self._cb_table) self._render_page() # Option buttons optionslayout = QHBoxLayout() optionsbox = QGroupBox("Options") ascending_checkbox = QCheckBox("Sort ascending") highlight_checkbox = QCheckBox("Highlight basic blocks") ascending_checkbox.stateChanged.connect(self._cb_ascending) highlight_checkbox.stateChanged.connect(self._cb_highlight) optionslayout.addWidget(ascending_checkbox) optionslayout.addWidget(highlight_checkbox) optionsbox.setLayout(optionslayout) # Diffing buttons diffinglayout = QHBoxLayout() diffingoptions = QGroupBox("Diffing") diffing_reset_button = QPushButton("Reset") diffing_diff_button = QPushButton("Difference") diffing_inter_button = QPushButton("Intersection") diffing_reset_button.clicked.connect(self._cb_diff_reset) diffing_diff_button.clicked.connect(self._cb_diff_diff) diffing_inter_button.clicked.connect(self._cb_diff_inter) diffinglayout.addWidget(diffing_diff_button) diffinglayout.addWidget(diffing_inter_button) diffinglayout.addWidget(diffing_reset_button) diffingoptions.setLayout(diffinglayout) # Bottom buttons for page change prevnextlayout = QHBoxLayout() self.back_button = QPushButton("<") self.next_button = QPushButton(">") self.page_count_label = QLabel("") self.page_count_label.setAlignment(Qt.AlignCenter) self._render_nav_line() self.back_button.clicked.connect(self._cb_prev_page) self.next_button.clicked.connect(self._cb_next_page) prevnextlayout.addWidget(self.back_button) prevnextlayout.addWidget(self.page_count_label) prevnextlayout.addWidget(self.next_button) vlayout.addWidget(self.hitcount_counter) vlayout.addWidget(self.hit_table) vlayout.addWidget(optionsbox) vlayout.addWidget(diffingoptions) vlayout.addLayout(prevnextlayout) self.setLayout(vlayout) def _cb_table(self, elem): addr = self.hitcounts[(BBViewerWidget.PER_PAGE_COUNT * self.current_page) + elem.row()][0] self.bv.navigate(self.bv.view, addr) def _cb_ascending(self, elem): self.descending = not self.descending self.hitcounts = sorted(self.hitcounts, key=lambda a: a[1], reverse=self.descending) self.current_page = 0 self._render_nav_line() self._render_page() def _cb_prev_page(self, elem): # We do not need to check as _render_nav_line disables the button at the limit self._render_nav_line() self.current_page -= 1 self._render_nav_line() self._render_page() def _cb_next_page(self, elem): # We do not need to check as _render_nav_line disables the button at the limit self._render_nav_line() self.current_page += 1 self._render_nav_line() self._render_page() def _cb_highlight(self, elem): self.highlight = not self.highlight self._bb_highlight(self.highlight) def _cb_diff_reset(self, item): if self.highlight: self._bb_highlight(False) self.hitcounts = [e for e in self.orig_hitcounts] if self.highlight: self._bb_highlight(True) self.current_page = 0 self.hitcount_counter.setText("Basic block count: {}".format( len(self.hitcounts))) self._render_nav_line() self._render_page() def _cb_diff_diff(self, item): new_db = _load_db(self.bv) if not new_db: return source_bbs = set([e[0] for e in self.hitcounts]) new_bbs = set([e[0] for e in new_db.get_hitcounts()]) result_bbs = source_bbs - new_bbs if self.highlight: self._bb_highlight(False) previous_count = len(self.hitcounts) self.hitcounts = list( filter(lambda e: e[0] in result_bbs, self.hitcounts)) self.hitcount_counter.setText( "Basic block count: {} (previously {})".format( len(self.hitcounts), previous_count)) if self.highlight: self._bb_highlight(True) self.current_page = 0 self._render_nav_line() self._render_page() def _cb_diff_inter(self, item): new_db = _load_db(self.bv) if not new_db: return source_bbs = set([e[0] for e in self.hitcounts]) new_bbs = set([e[0] for e in new_db.get_hitcounts()]) result_bbs = source_bbs & new_bbs if self.highlight: self._bb_highlight(False) previous_count = len(self.hitcounts) self.hitcounts = list( filter(lambda e: e[0] in result_bbs, self.hitcounts)) self.hitcount_counter.setText( "Basic block count: {} (previously {})".format( len(self.hitcounts), previous_count)) if self.highlight: self._bb_highlight(True) self.current_page = 0 self._render_nav_line() self._render_page() def _render_nav_line(self): max_pages = math.ceil( len(self.hitcounts) / BBViewerWidget.PER_PAGE_COUNT) self.page_count_label.setText("Page: {} / {}".format( self.current_page + 1, max_pages)) if self.current_page == 0: self.back_button.setEnabled(False) else: self.back_button.setEnabled(True) if self.current_page == max_pages - 1 or max_pages == 0: self.next_button.setEnabled(False) else: self.next_button.setEnabled(True) def _render_page(self): start = BBViewerWidget.PER_PAGE_COUNT * self.current_page hitcounts = self.hitcounts[start:start + BBViewerWidget.PER_PAGE_COUNT] self.hit_table.setRowCount(len(hitcounts)) for i, e in enumerate(hitcounts): address_item = QTableWidgetItem("0x{:x}".format(e[0])) hitcount_item = QTableWidgetItem("{}".format(e[1])) name_item = QTableWidgetItem(_name_from_address(self.bv, e[0])) address_item.setFlags(Qt.ItemIsEnabled) hitcount_item.setFlags(Qt.ItemIsEditable) name_item.setFlags(Qt.ItemIsEditable) self.hit_table.setItem(i, 0, address_item) self.hit_table.setItem(i, 1, hitcount_item) self.hit_table.setItem(i, 2, name_item) def _bb_highlight(self, highlight): def delete_hitcount_str(input_str): if "(hitcount: " not in input_str: return input_str chk = input_str[input_str.find("(hitcount: "):] if ")\n" not in input_str: return input_str chk = chk[:input_str.find(")\n") + 2] return input_str.replace(chk, "") # (0, 255, 106) colorHighlight = HighlightColor(red=0, green=255, blue=106) for bbaddr, bbhitcount in self.hitcounts: bbs = self.bv.get_basic_blocks_at(bbaddr) if not bbs: print("Could not find basic block at address: 0x{:x}".format( bbaddr)) continue bb = bbs[0] fn = bb.function if not fn: print( "Could not find function containing block at address: 0x{:x}" .format(bbaddr)) continue cur_comment = delete_hitcount_str(fn.get_comment_at(bbaddr)) if highlight: bb.set_user_highlight(colorHighlight) fn.set_comment_at( bbaddr, "(hitcount: {})\n".format(bbhitcount) + cur_comment) else: bb.set_user_highlight(HighlightStandardColor.NoHighlightColor) fn.set_comment_at(bbaddr, cur_comment)
class SyncDockWidget(QWidget, DockContextHandler): def __init__(self, parent, name, data): QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) status_layout = QHBoxLayout() status_layout.addWidget(QLabel('Status: ')) self.status = QLabel('idle') status_layout.addWidget(self.status) status_layout.setAlignment(QtCore.Qt.AlignCenter) client_dbg_layout = QHBoxLayout() client_dbg_layout.addWidget(QLabel('Client debugger: ')) self.client_dbg = QLabel('n/a') client_dbg_layout.addWidget(self.client_dbg) client_dbg_layout.setAlignment(QtCore.Qt.AlignCenter) client_pgm_layout = QHBoxLayout() client_pgm_layout.addWidget(QLabel('Client program: ')) self.client_pgm = QLabel('n/a') client_pgm_layout.addWidget(self.client_pgm) client_pgm_layout.setAlignment(QtCore.Qt.AlignCenter) layout = QVBoxLayout() layout.addStretch() layout.addLayout(status_layout) layout.addLayout(client_dbg_layout) layout.addLayout(client_pgm_layout) layout.addStretch() self.setLayout(layout) def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler) def set_status(self, status): if status == SyncStatus.RUNNING: self.status.setStyleSheet('color: green') elif status == SyncStatus.ENABLED: self.status.setStyleSheet('color: blue') else: self.status.setStyleSheet('') self.status.setText(status) def set_connected(self, dialect): self.set_status(SyncStatus.RUNNING) self.client_dbg.setText(dialect) def set_program(self, pgm): self.client_pgm.setText(pgm) def reset_client(self): self.set_status(SyncStatus.ENABLED) self.client_pgm.setText('n/a') self.client_dbg.setText('n/a') def reset_status(self): self.set_status(SyncStatus.IDLE) self.client_pgm.setText('n/a') self.client_dbg.setText('n/a') @staticmethod def create_widget(name, parent, data=None): return SyncDockWidget(parent, name, data)
class MemoryView(QWidget, DockContextHandler): def __init__(self, parent, name, data): QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) self.data = data self.parent = parent self.current_state = None self.arch = None self.address_start = None self.size = 512 self.changes = set() self.tab_name = None self.symb_idx = 0 self._layout = QVBoxLayout() self.button = QPushButton("Monitor Memory") self.button.clicked.connect(self.on_monitor_button_click) self.hexWidget = HexViewWidget( menu_handler=self.on_customContextMenuRequested) self.hexWidget.data_edited.connect(self._handle_data_edited) self.hexWidget.setEnabled(False) self._layout.addWidget(self.button) self._layout.addWidget(self.hexWidget) self._layout.setContentsMargins(0, 0, 0, 0) self.setMaximumWidth(self.hexWidget.optimal_width + 25) self.setLayout(self._layout) def _monitor_changes(self, address, size): if symbolic(address): self.changes.clear() self.changes.add( (self.address_start, self.address_start + self.size)) return address = address.value if (self.address_start + self.size >= address and self.address_start <= address + size): to_monitor_min = max(self.address_start, address) - self.address_start to_monitor_max = min(self.address_start + self.size, address + size) - self.address_start self.changes.add((to_monitor_min, to_monitor_max)) def on_monitor_button_click(self): if self.current_state is None: return address = get_int_input("Memory address", "Set Memory Monitor") if address is None: return self.hexWidget.setEnabled(True) self.address_start = address self.size = 512 self.current_state.mem.register_store_hook(self._monitor_changes) self.update_mem(self.current_state) def init(self, arch, state): self.arch = arch self.tab_name = _normalize_tab_name(self.parent.getTabName()) self.update_mem(state) def update_mem(self, state): self.current_state = state if self.address_start is None: return self.changes.clear() data = {} for i in range(self.size): if not state.mem.is_mapped(self.address_start + i): val = "__" else: b = state.mem.load(self.address_start + i, 1) val = ".." if isinstance(b, BVV): val = "{:02x}".format(b.value) data[i] = val self.hexWidget.full_data_changed.emit(self.address_start, data, self.size) def update_mem_delta(self, state): self.current_state = state if self.address_start is None: return load_cache = {} for begin, end in self.changes: for i in range(begin, end): if not state.mem.is_mapped(self.address_start + i): val = "__" else: if i in load_cache: b = load_cache[i] else: b = state.mem.load(self.address_start + i, 1) load_cache[i] = b val = ".." if isinstance(b, BVV): val = "{:02x}".format(b.value) self.hexWidget.single_data_changed.emit(i, val) # self.hexWidget.view.viewport().update() self.changes.clear() def reset(self): self.current_state = None self.address_start = None self.size = None self.hexWidget.full_data_changed.emit(0, {}, 0) def _handle_data_edited(self, offset, value): if not self.current_state or not self.address_start: return self.current_state.mem.store(self.address_start + offset, BVV(value, 8)) # self.hexWidget.single_data_changed.emit(address - self.address_start, hex(value)[2:]) def _show_reg_expression(self, address, expr): show_message_box("Expression at %s" % hex(address), str(expr.z3obj)) def _evaluate_with_solver(self, address, expr): val = "" if not self.current_state.solver.symbolic(expr): new_expr = self.current_state.solver.evaluate(expr) self.current_state.mem.store(address, new_expr) self.changes.add( (address - self.address_start, address - self.address_start + new_expr.size // 8)) self.update_mem_delta(self.current_state) show_message_box("Expression at %s" % hex(address), "The value was indeed concrete! State modified") else: val = self.current_state.solver.evaluate(expr).value show_message_box("Value at %s (with solver):" % hex(address), hex(val)) def _concretize(self, address, expr): new_expr = self.current_state.solver.evaluate(expr) res = get_choice_input( "Concretize *%s to %s?" % (hex(address), hex(new_expr.value)), "Concretize", ["Yes", "No"]) if res == 0: self.current_state.mem.store(address, new_expr) self.current_state.solver.add_constraints(expr == new_expr) self.changes.add( (address - self.address_start, address - self.address_start + new_expr.size // 8)) self.update_mem_delta(self.current_state) def _make_symbolic(self, address, size): expr = BVS("symbol_injected_through_ui_mem_%d" % self.symb_idx, size * 8) self.current_state.mem.store(address, expr) self.changes.add((address - self.address_start, size)) self.symb_idx += 1 self.update_mem_delta(self.current_state) def _copy_big_endian(self, expr): mime = QMimeData() mime.setText(hex(expr.value)) QApplication.clipboard().setMimeData(mime) def _copy_little_endian(self, expr): mime = QMimeData() expr_bytes = split_bv_in_list(expr, 8) res = 0 i = 0 for el in reversed(expr_bytes): res += el.value << i * 8 i += 1 mime.setText(hex(res)) QApplication.clipboard().setMimeData(mime) def _copy_string(self, expr): mime = QMimeData() expr_bytes = split_bv_in_list(expr, 8) res = "" for el in reversed(expr_bytes): res += chr(el.value) if el.value >= 32 and el.value <= 126 else "." mime.setText(res) QApplication.clipboard().setMimeData(mime) def _copy_binary(self, expr): mime = QMimeData() expr_bytes = split_bv_in_list(expr, 8) res = "\"" for el in reversed(expr_bytes): res += "\\x{:02x}".format(el.value) res += "\"" mime.setText(res) QApplication.clipboard().setMimeData(mime) @staticmethod def _condom(f, *pars): def g(): f(*pars) return g def on_customContextMenuRequested(self, qpoint): if self.current_state is None: return menu = QMenu(self) index = self.hexWidget.view.indexAt(qpoint) sel_start = self.hexWidget._hsm.start sel_end = self.hexWidget._hsm.end if sel_start is None: return expr = self.current_state.mem.load(sel_start + self.address_start, sel_end - sel_start + 1) if symbolic(expr): a = menu.addAction("Show reg expression") a.triggered.connect( MemoryView._condom(self._show_reg_expression, sel_start + self.address_start, expr)) a = menu.addAction("Evaluate with solver") a.triggered.connect( MemoryView._condom(self._evaluate_with_solver, sel_start + self.address_start, expr)) a = menu.addAction("Concretize") a.triggered.connect( MemoryView._condom(self._concretize, sel_start + self.address_start, expr)) else: a = menu.addAction("Make symbolic") a.triggered.connect( MemoryView._condom(self._make_symbolic, sel_start + self.address_start, sel_end - sel_start + 1)) copy_menu = menu.addMenu("Copy...") a = copy_menu.addAction("Copy Little Endian") a.triggered.connect( MemoryView._condom(self._copy_little_endian, expr)) a = copy_menu.addAction("Copy Big Endian") a.triggered.connect(MemoryView._condom(self._copy_big_endian, expr)) a = copy_menu.addAction("Copy String") a.triggered.connect(MemoryView._condom(self._copy_string, expr)) a = copy_menu.addAction("Copy Binary") a.triggered.connect(MemoryView._condom(self._copy_binary, expr)) return menu def notifyOffsetChanged(self, offset): pass def shouldBeVisible(self, view_frame): if view_frame is None: return False elif self.tab_name is None: return False elif _normalize_tab_name(view_frame.getTabName()) != self.tab_name: return False return True def notifyViewChanged(self, view_frame): if view_frame is None: pass else: pass # implement this def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler)
class DebugThreadsWidget(QWidget, DockContextHandler): def __init__(self, parent, name, data): if not type(data) == BinaryView: raise Exception('expected widget data to be a BinaryView') self.bv = data QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) self.table = QTableView(self) self.model = DebugThreadsListModel(self.table) self.table.setModel(self.model) self.table.clicked.connect(self.threadRowClicked) self.item_delegate = DebugThreadsItemDelegate(self) self.table.setItemDelegate(self.item_delegate) # self.table.setSortingEnabled(True) self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.table.setSelectionMode(QAbstractItemView.ExtendedSelection) self.table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) self.table.verticalHeader().setVisible(False) self.table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) self.table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) self.table.resizeColumnsToContents() self.table.resizeRowsToContents() for i in range(len(self.model.columns)): self.table.setColumnWidth(i, self.item_delegate.sizeHint(self.table.viewOptions(), self.model.index(-1, i, QModelIndex())).width()) self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.table) self.setLayout(layout) def notifyOffsetChanged(self, offset): pass # called from QTableView's clicked signal # index: QModelIndex def threadRowClicked(self, index): index = self.model.createIndex(index.row(), 0) tid_str = self.model.data(index, Qt.DisplayRole) #print('clicked to change to thread %s' % tid_str) stateObj = binjaplug.get_state(self.bv) if stateObj.connected and not stateObj.running: tid = int(tid_str, 16) stateObj.threads.current = tid stateObj.ui.context_display() stateObj.ui.on_step() else: print('cannot set thread in current state') # called from plugin's context_display() function def notifyThreadsChanged(self, new_threads): idx_selected = self.model.update_rows(new_threads) if idx_selected: self.table.setCurrentIndex(idx_selected) def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler) def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True
class MemoryView(QWidget, DockContextHandler): def __init__(self, parent, name, data, bnwidgets): QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) self.data = data self.parent = parent self.bnwidgets = bnwidgets self.current_state = None self.arch = None self.address_start = None self.size = 512 self.changes = set() self.tab_name = None self.monitor_history = list() self.symb_idx = 0 self._layout = QGridLayout() self.button = QPushButton("Monitor Memory") self.button.setStyleSheet("margin-left: 10px;") self.button.clicked.connect( self._condom_async(self, self.on_monitor_button_click)) self.back_button = QPushButton("Back") self.back_button.setStyleSheet("margin-right: 10px;") self.back_button.clicked.connect( self._condom_async(self, self.on_back_click)) self.hexWidget = HexViewWidget( menu_handler=self.on_customContextMenuRequested) self.hexWidget.data_edited.connect(self._handle_data_edited) self.hexWidget.setEnabled(False) self._layout.addWidget(self.button, 0, 0, 1, 4) self._layout.addWidget(self.back_button, 0, 4, 1, 1) self._layout.addWidget(self.hexWidget, 1, 0, 1, 5) self._layout.setContentsMargins(0, 0, 0, 0) self.setMaximumWidth(self.hexWidget.optimal_width + 25) self.setLayout(self._layout) def _monitor_changes(self, address, size): if symbolic(address): self.changes.clear() self.changes.add( (self.address_start, self.address_start + self.size)) return address = address.value if (self.address_start + self.size >= address and self.address_start <= address + size): to_monitor_min = max(self.address_start, address) - self.address_start to_monitor_max = min(self.address_start + self.size, address + size) - self.address_start self.changes.add((to_monitor_min, to_monitor_max)) def on_monitor_button_click(self): if self.current_state is None: return address = get_int_input("Memory address", "Set Memory Monitor") if address is None: return address = address & 0xffffffffffffffff # signed to unsigned self.monitor_history.append(address) self.hexWidget.setEnabled(True) self.address_start = address self.size = 512 self.current_state.mem.register_store_hook(self._monitor_changes) self.update_mem(self.current_state) def on_back_click(self): if self.current_state is None: return if len(self.monitor_history) < 2: return self.monitor_history.pop() address = self.monitor_history[-1] self.hexWidget.setEnabled(True) self.address_start = address self.size = 512 self.current_state.mem.register_store_hook(self._monitor_changes) self.update_mem(self.current_state) def init(self, arch, state): self.arch = arch self.tab_name = _normalize_tab_name(self.parent.getTabName()) self.update_mem(state) def update_mem(self, state): self.current_state = state if self.address_start is None: return self.changes.clear() data = {} for i in range(self.size): if not state.mem.is_mapped(self.address_start + i): val = "__" else: b = state.mem.load(self.address_start + i, 1) val = ".." if isinstance(b, BVV): val = "{:02x}".format(b.value) data[i] = val self.hexWidget.full_data_changed.emit(self.address_start, data, self.size) def update_mem_delta(self, state): self.current_state = state if self.address_start is None: return load_cache = {} for begin, end in self.changes: for i in range(begin, end): if not state.mem.is_mapped(self.address_start + i): val = "__" else: if i in load_cache: b = load_cache[i] else: b = state.mem.load(self.address_start + i, 1) load_cache[i] = b val = ".." if isinstance(b, BVV): val = "{:02x}".format(b.value) self.hexWidget.single_data_changed.emit(i, val) # self.hexWidget.view.viewport().update() self.changes.clear() def reset(self): self.tab_name = None self.current_state = None self.address_start = None self.size = None self.hexWidget.full_data_changed.emit(0, {}, 0) def _handle_data_edited(self, offset, value): if not self.current_state or not self.address_start: return self.current_state.mem.store(self.address_start + offset, BVV(value, 8)) # self.hexWidget.single_data_changed.emit(address - self.address_start, hex(value)[2:]) def _show_expression(self, address, expr): show_message_box("Expression at %s" % hex(address), str(expr.z3obj.sexpr())) def _evaluate_with_solver(self, address, expr): val = "" if not self.current_state.solver.symbolic(expr): new_expr = self.current_state.solver.evaluate(expr) self.current_state.mem.store(address, new_expr) self.changes.add( (address - self.address_start, address - self.address_start + new_expr.size // 8)) self.update_mem_delta(self.current_state) show_message_box("Expression at %s" % hex(address), "The value was indeed concrete! State modified") else: val = self.current_state.solver.evaluate(expr).value show_message_box("Value at %s (with solver):" % hex(address), hex(val)) def _concretize(self, address, expr): new_expr = self.current_state.solver.evaluate(expr) self.current_state.mem.store(address, new_expr) self.current_state.solver.add_constraints(expr == new_expr) self.changes.add((address - self.address_start, address - self.address_start + new_expr.size // 8)) self.update_mem_delta(self.current_state) def _concretize_ascii_str(self, address, expr): extra_constraints = [] for i in range(expr.size // 8): b = expr.Extract(i * 8 + 7, i * 8) extra_constraints.extend([b <= 0x7e, b >= 0x20]) if not self.current_state.solver.satisfiable(extra_constraints): show_message_box( "Info", "The selected memory is not an ascii str (unsat)") return new_expr = self.current_state.solver.evaluate(expr, extra_constraints) self.current_state.mem.store(address, new_expr) self.current_state.solver.add_constraints(expr == new_expr) self.changes.add((address - self.address_start, address - self.address_start + new_expr.size // 8)) self.update_mem_delta(self.current_state) def _make_symbolic(self, address, size): buff = BVS("b_ui_mem_%d" % self.symb_idx, size * 8) self.current_state.mem.store(address, buff) self.current_state.symbolic_buffers.append((buff, address, "")) self.changes.add((address - self.address_start, size)) self.symb_idx += 1 self.update_mem_delta(self.current_state) self.bnwidgets.BW.onNewBufferSignal.emit(self.current_state) def _copy_big_endian(self, expr): mime = QMimeData() mime.setText(hex(expr.value)) QApplication.clipboard().setMimeData(mime) def _copy_little_endian(self, expr): mime = QMimeData() expr_bytes = split_bv_in_list(expr, 8) res = 0 i = 0 for el in reversed(expr_bytes): res += el.value << i * 8 i += 1 mime.setText(hex(res)) QApplication.clipboard().setMimeData(mime) def _copy_string(self, expr): mime = QMimeData() expr_bytes = split_bv_in_list(expr, 8) res = "" for el in reversed(expr_bytes): res += chr(el.value) if el.value >= 32 and el.value <= 126 else "." mime.setText(res) QApplication.clipboard().setMimeData(mime) def _copy_expression(self, expr): mime = QMimeData() mime.setText(str(expr.z3obj.sexpr())) QApplication.clipboard().setMimeData(mime) def _copy_binary(self, expr): mime = QMimeData() expr_bytes = split_bv_in_list(expr, 8) res = "\"" for el in reversed(expr_bytes): res += "\\x{:02x}".format(el.value) res += "\"" mime.setText(res) QApplication.clipboard().setMimeData(mime) @staticmethod def _condom(f, *pars): def g(): f(*pars) return g @staticmethod def _condom_async(mw, f, *pars): def g(): bt = MemoryViewBT("MemoryView background task...", mw, f, pars) bt.start() return g def on_customContextMenuRequested(self, qpoint): if self.current_state is None: return menu = QMenu(self) sel_start = self.hexWidget._hsm.start sel_end = self.hexWidget._hsm.end if sel_start is None: return if not self.current_state.mem.is_mapped(self.address_start + sel_start): return if not self.current_state.mem.is_mapped(self.address_start + sel_end): return expr = self.current_state.mem.load(sel_start + self.address_start, sel_end - sel_start + 1) if symbolic(expr): a = menu.addAction("Show expression") a.triggered.connect( MemoryView._condom(self._show_expression, sel_start + self.address_start, expr)) a = menu.addAction("Evaluate with solver") a.triggered.connect( MemoryView._condom_async(self, self._evaluate_with_solver, sel_start + self.address_start, expr)) a = menu.addAction("Concretize") a.triggered.connect( MemoryView._condom_async(self, self._concretize, sel_start + self.address_start, expr)) a = menu.addAction("Concretize (ascii str)") a.triggered.connect( MemoryView._condom_async(self, self._concretize_ascii_str, sel_start + self.address_start, expr)) a = menu.addAction("Copy expression") a.triggered.connect(MemoryView._condom(self._copy_expression, expr)) else: a = menu.addAction("Make symbolic") a.triggered.connect( MemoryView._condom(self._make_symbolic, sel_start + self.address_start, sel_end - sel_start + 1)) copy_menu = menu.addMenu("Copy...") a = copy_menu.addAction("Copy Little Endian") a.triggered.connect( MemoryView._condom(self._copy_little_endian, expr)) a = copy_menu.addAction("Copy Big Endian") a.triggered.connect(MemoryView._condom(self._copy_big_endian, expr)) a = copy_menu.addAction("Copy String") a.triggered.connect(MemoryView._condom(self._copy_string, expr)) a = copy_menu.addAction("Copy Binary") a.triggered.connect(MemoryView._condom(self._copy_binary, expr)) return menu def notifyOffsetChanged(self, offset): pass def shouldBeVisible(self, view_frame): if view_frame is None: return False elif self.tab_name is None: return False elif _normalize_tab_name(view_frame.getTabName()) != self.tab_name: return False return True def notifyViewChanged(self, view_frame): if view_frame is None: pass else: pass # implement this def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler)
class BufferView(QWidget, DockContextHandler): onNewBufferSignal = QtCore.Signal(object) def __init__(self, parent, name, data): QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.onNewBufferSignal.connect(self.update) self.parent = parent self.current_state = None self.data = data self.tab_name = None self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) self._layout = QVBoxLayout() # Set up register table self._table = QTableWidget() self._table.setColumnCount(4) self._table.setHorizontalHeaderLabels( ['Address', 'Name', 'Size', 'Constraints']) self._table.horizontalHeader().setStretchLastSection(True) self._table.verticalHeader().setVisible(False) self._table.setContextMenuPolicy(Qt.CustomContextMenu) self._table.customContextMenuRequested.connect( self.on_customContextMenuRequested) self._table.doubleClicked.connect(self.on_doubleClick) self.button = QPushButton("New Buffer") self.button.clicked.connect(self.on_newBufferClick) self._layout.addWidget(self.button) self._layout.addWidget(self._table) self.setLayout(self._layout) def on_newBufferClick(self): if self.current_state is None: return blacklisted_names = [ b[0].name for b in self.current_state.symbolic_buffers] new_buff_dialog = CreateBufferDialog( blacklisted_names=blacklisted_names) new_buff_dialog.exec_() if new_buff_dialog.res_name is None: return buff = BVS(new_buff_dialog.res_name, new_buff_dialog.res_width * 8) if new_buff_dialog.res_terminator: buff_to_store = buff.Concat(BVV(0, 8)) else: buff_to_store = buff address = self.current_state.mem.allocate(new_buff_dialog.res_width) if new_buff_dialog.res_constraints == ALPHANUMERIC_STRING: constraint_alphanumeric_string(buff, self.current_state) elif new_buff_dialog.res_constraints == ASCII_STRING: constraint_ascii_string(buff, self.current_state) constraint_str = "" if new_buff_dialog.res_constraints != NO_CONSTRAINTS: constraint_str = CreateBufferDialog.constraint_list[new_buff_dialog.res_constraints] self.current_state.mem.store(address, buff_to_store) self.current_state.symbolic_buffers.append( (buff, address, constraint_str) ) self.update_state(self.current_state) def reset(self): self.tab_name = None self.current_state = None self._table.setRowCount(0) def init(self, state): self.current_state = state self.tab_name = _normalize_tab_name(self.parent.getTabName()) self.update_state(state) def update_state(self, state): self.current_state = state self._table.setRowCount(0) self._table.setRowCount(len(self.current_state.symbolic_buffers)) i = 0 for buff, address, constraints in self.current_state.symbolic_buffers: self._table.setItem(i, 0, _makewidget(self, hex(address))) self._table.setItem(i, 1, _makewidget(self, buff.name)) self._table.setItem(i, 2, _makewidget(self, buff.size // 8)) self._table.setItem(i, 3, _makewidget(self, constraints)) i += 1 @staticmethod def _condom(f, *pars): def g(): f(*pars) return g @staticmethod def _condom_async(bw, f, *pars): def g(): bt = BufferViewBT("BufferView background task...", bw, f, pars) bt.start() return g # right click menu def on_customContextMenuRequested(self, pos): item = self._table.itemAt(pos) if item is None: return row_idx = item.row() menu = QMenu() copy_address = menu.addAction("Copy address") copy_address.triggered.connect(BufferView._condom( self._menuAction_copy_address, row_idx)) eval_upto = menu.addAction("Evaluate upto") eval_upto.triggered.connect(BufferView._condom_async( self, self._menuAction_evaluate_upto_buffer, row_idx)) eval_as_bytes = menu.addAction("Evaluate as bytes") eval_as_bytes.triggered.connect(BufferView._condom_async( self, self._menuAction_evaluate_buffer, row_idx)) copy_eval = menu.addAction("Copy evaluated bytes") copy_eval.triggered.connect(BufferView._condom_async( self, self._menuAction_copy_evaluated_buffer, row_idx)) add_constraint = menu.addAction("Add constraint") add_constraint.triggered.connect(BufferView._condom( self._menuAction_add_constraint, row_idx)) menu.exec_(self._table.viewport().mapToGlobal(pos)) def _menuAction_copy_address(self, buffer_id): mime = QMimeData() mime.setText(hex(self.current_state.symbolic_buffers[buffer_id][1])) QApplication.clipboard().setMimeData(mime) def _menuAction_evaluate_buffer(self, buffer_id): buff = self.current_state.symbolic_buffers[buffer_id][0] res = self.current_state.solver.evaluate(buff).as_bytes() res = repr(res)[2:-1] show_message_box("%s evaluate" % buff.name, res) def _menuAction_evaluate_upto_buffer(self, buffer_id): buff = self.current_state.symbolic_buffers[buffer_id][0] n_eval = get_int_input("How many values (upto) ?", "Number of distinct values") r = "" for i, v in enumerate(self.current_state.solver.evaluate_upto(buff, n_eval)): r += "solution %d: %s\n" % (i, hex(v.value)) show_message_box("%s evaluate" % buff.name, r) def _menuAction_copy_evaluated_buffer(self, buffer_id): mime = QMimeData() buff = self.current_state.symbolic_buffers[buffer_id][0] res = self.current_state.solver.evaluate(buff).as_bytes() res = '"' + repr(res)[2:-1] + '"' mime.setText(res) QApplication.clipboard().setMimeData(mime) def _menuAction_add_constraint(self, buffer_id): buff = self.current_state.symbolic_buffers[buffer_id][0] constraints = self.current_state.symbolic_buffers[buffer_id][2] if constraints != "": show_message_box("Error", "The buffer already has a constraint.") return choices = [CreateBufferDialog.constraint_list[i] for i in CreateBufferDialog.constraint_list.keys() if i != NO_CONSTRAINTS] res = get_choice_input( "Constraint buffer", "choices:", choices ) if choices[res] == "Alphanumeric string": constraint_alphanumeric_string(buff, self.current_state) elif choices[res] == "ASCII string": constraint_ascii_string(buff, self.current_state) else: return t = self.current_state.symbolic_buffers[buffer_id] t = t[0], t[1], choices[res] self.current_state.symbolic_buffers[buffer_id] = t self.update_state(self.current_state) # double click event def on_doubleClick(self, item): # row_idx = item.row() pass def shouldBeVisible(self, view_frame): if view_frame is None: return False elif self.tab_name is None: return False elif _normalize_tab_name(view_frame.getTabName()) != self.tab_name: return False return True def notifyViewChanged(self, view_frame): if view_frame is None: pass else: pass def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler)
class SourceryPane(QWidget, DockContextHandler): def __init__(self, parent, name): global panes panes.append(self) QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) # Top: Headers with line info header_layout = QFormLayout() self.function_info = QLabel("") self.line_info = QLabel("") header_layout.addRow(self.tr("Function:"), self.function_info) header_layout.addRow(self.tr("Line:"), self.line_info) # Middle: main source display pane textbox_layout = QVBoxLayout() self.textbox = QPlainTextEdit() self.textbox.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap) self.textbox.setReadOnly(True) font = getMonospaceFont(self) self.textbox.setFont(font) font = QFontMetrics(font) self.textbox.setMinimumWidth(40 * font.averageCharWidth()) self.textbox.setMinimumHeight(30 * font.lineSpacing()) textbox_layout.addWidget(self.textbox) # Bottom: buttons for stop/start, and substitution paths footer_layout = QVBoxLayout() sync_button_layout = QHBoxLayout() self.sync_button = QPushButton("Turn Source Sync Off") sync_button_layout.addWidget(self.sync_button) path_layout = QFormLayout() self.original_path = QLineEdit() self.substitute_path = QLineEdit() self.substitute_path_button = QPushButton("Do Path Substitution") path_layout.addRow(self.tr("Original Path:"), self.original_path) path_layout.addRow(self.substitute_path_button, self.substitute_path) footer_layout.addLayout(sync_button_layout) footer_layout.addLayout(path_layout) # Putting all the child layouts together layout = QVBoxLayout() layout.addLayout(header_layout) layout.addLayout(textbox_layout) layout.addLayout(footer_layout) self.setLayout(layout) # Set up button signals self.substitute_path_button.clicked.connect(self.do_path_substitution) self.sync_button.clicked.connect(self.toggle_sync) # Actual storage variables self.bv = None self.filename = None self.do_sync = True self.path_substitutions = {} self.failed_substitutions = [] def do_path_substitution(self): original_path = self.original_path.text() new_path = self.substitute_path.text() if isinstance(original_path, bytes): original_path = original_path.decode() new_path = new_path() if original_path == "": log_warn("Path substitution error: Original path can't be blank") elif new_path == "": if original_path in self.path_substitutions: old_sub = self.path_substitutions.pop(original_path) log_info("Removed path substitution: %s -> %s" % (original_path, old_sub)) else: log_warn( "Path substitution error: New substitute path can't be blank" ) else: self.path_substitutions[original_path] = new_path log_info("Added path substitution: %s -> %s" % (original_path, new_path)) self.failed_substitutions = [ ] # clear failures when new path added def toggle_sync(self): if self.do_sync is True: self.do_sync = False self.sync_button.setText("Turn Source Sync On") else: # self.do_sync is False: self.do_sync = True self.sync_button.setText("Turn Source Sync Off") def set_text(self, text): self.textbox.setPlainText(text) def set_line(self, text): self.line_info.setText(text) def set_function(self, text): self.function_info.setText(text) def check_path_substitution(self, path): """Checks for files using path substitutions, going from longest to shortest original path""" sorted_original_paths = sorted(self.path_substitutions.keys(), key=lambda k: len(k), reverse=True) candidate_matches = [] for candidate_path in sorted_original_paths: if candidate_path in path: substitute_pattern = self.path_substitutions[candidate_path] substitute_path = path.replace(candidate_path, substitute_pattern) substitute_path = os.path.expanduser(substitute_path) candidate_matches.append(substitute_path) if os.path.exists(substitute_path): return substitute_path # Only log_warn once per file, and only if the user has tried to add translations if path not in self.failed_substitutions: if len(self.path_substitutions) > 0: log_warn("Failed to find substitution for %s" % path) log_info("Current substitution paths:") for orig_path, sub_path in self.path_substitutions.items(): log_info(" %s => %s" % (orig_path, sub_path)) log_info("Matching patterns' failed substitute paths:") for candidate in candidate_matches: log_info(" %s" % candidate) self.failed_substitutions.append(path) return "" def update_source(self, current_location): source_line = addr2line(self.filename, current_location) line_number_int = -1 text = "" function_name = "" if source_line.startswith("?"): line_text = "No source mapping for address 0x%x" % current_location elif source_line.startswith("ERROR:"): line_text = "%s" % source_line else: filepath, line_number_str, function_name = source_line.split(":") # handle lines like: "16 (discriminator 1)" line_number_int = int(line_number_str.split(' ')[0]) line_text = "%s:%s" % (filepath, line_number_str) # Check for the file, then for subsitutions if not os.path.exists(filepath): new_path = self.check_path_substitution(filepath) if new_path == "": self.textbox.setLineWrapMode( QPlainTextEdit.LineWrapMode.WidgetWidth) text = '[!] Source file "%s" not found\n' % filepath text += '[*] Associated line info: "%s"' % source_line else: filepath = new_path # If we still don't have a good path, the text is set to the correct error if os.path.exists(filepath): self.textbox.setLineWrapMode( QPlainTextEdit.LineWrapMode.NoWrap) with open(filepath, "r") as f: text = f.read() self.set_text(text) self.set_line(line_text) self.set_function(function_name) if line_number_int != -1: self.set_cursor(line_number_int) else: self.reset_cursor() def reset_cursor(self): doc = self.textbox.document() cursor = QTextCursor(doc) cursor.movePosition(QTextCursor.Start) self.textbox.setTextCursor(cursor) def set_cursor(self, line_number): doc = self.textbox.document() cursor = QTextCursor(doc) cursor.movePosition(QTextCursor.Start) for _ in range(line_number - 1): cursor.movePosition(QTextCursor.Down) cursor.movePosition(QTextCursor.EndOfLine, QTextCursor.KeepAnchor) self.textbox.setTextCursor(cursor) self.textbox.centerCursor() def notifyOffsetChanged(self, offset): if self.filename: if self.do_sync: self.update_source(offset) def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True def notifyViewChanged(self, view_frame): if view_frame is None: pass else: self.bv = view_frame.actionContext().binaryView self.filename = self.bv.file.original_filename def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler) @staticmethod def create_widget(name, parent, data=None): return SourceryPane(parent, name)
class ExportsTreeView(QTreeView, FilterTarget): def __init__(self, parent, view, data): QTreeView.__init__(self, parent) FilterTarget.__init__(self) self.data = data self.parent = parent self.view = view # Allow view-specific shortcuts when exports are focused self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) self.actionHandler.setActionContext(lambda: self.view.actionContext()) self.model = GenericExportsModel(self.data) self.setModel(self.model) self.setRootIsDecorated(False) self.setUniformRowHeights(True) self.setSortingEnabled(True) self.sortByColumn(0, Qt.AscendingOrder) if self.model.ordinal_col is not None: self.setColumnWidth(self.model.ordinal_col, 55) self.setFont(binaryninjaui.getMonospaceFont(self)) self.selectionModel().currentChanged.connect(self.exportSelected) self.doubleClicked.connect(self.exportDoubleClicked) def exportSelected(self, cur, prev): sym = self.model.getSymbol(cur) if sym is not None: self.view.setCurrentOffset(sym.address) def exportDoubleClicked(self, cur): sym = self.model.getSymbol(cur) if sym is not None: viewFrame = ViewFrame.viewFrameForWidget(self) if len(self.data.get_functions_at(sym.address)) > 0: viewFrame.navigate("Graph:" + viewFrame.getCurrentDataType(), sym.address) else: viewFrame.navigate("Linear:" + viewFrame.getCurrentDataType(), sym.address) def setFilter(self, filterText): self.model.setFilter(filterText) def scrollToFirstItem(self): self.scrollToTop() def scrollToCurrentItem(self): self.scrollTo(self.currentIndex()) def selectFirstItem(self): self.setCurrentIndex(self.model.index(0, 0, QModelIndex())) def activateFirstItem(self): self.exportDoubleClicked(self.model.index(0, 0, QModelIndex())) def closeFilter(self): self.setFocus(Qt.OtherFocusReason) def keyPressEvent(self, event): if (len(event.text()) == 1) and (ord(event.text()[0:1]) > 32) and (ord( event.text()[0:1]) < 127): self.parent.filter.showFilter(event.text()) event.accept() elif (event.key() == Qt.Key_Return) or (event.key() == Qt.Key_Enter): sel = self.selectedIndexes() if len(sel) != 0: self.exportDoubleClicked(sel[0]) super(ExportsTreeView, self).keyPressEvent(event)
class GhinjaDockWidget(QWidget, DockContextHandler): def __init__(self, parent, name, data): # Read the configuration settings = Settings() settings.register_group("ghinja", "Ghinja") settings.register_setting("ghinja.ghidra_install_path", """ { "title" : "Ghidra Installation Path", "type" : "string", "default" : "", "description" : "Path to analyzeHeadless file in Ghidra installation dir." } """) if not os.path.exists(settings.get_string("ghinja.ghidra_install_path")): show_message_box("Path to Ghidra headless was not found!", "To allow the Ghinja plugin to work, you will be prompted to specify the path to the \"analyzeHeadless(.bat)\" file.", buttons=0, icon=2) settings.set_string("ghinja.ghidra_install_path",get_open_filename_input("Provide Path to Ghidra \"analyzeHeadless(.bat)\" file (Usually: <GHIDRA_INSTALL>/support/analyzeHeadless)").decode("utf-8")) self.rename_settings = Settings() self.rename_settings.register_group("ghinja_rename","Rename") self.rename_settings.register_setting("ghinja_rename.ghinja_rename_struct", """ { "title" : "Ghidra Rename Struct", "type" : "string", "default" : "{}", "description" : "Settings to hold renames for variables." } """) global instance_id self.binja_renames = {} # {"function_name":[{"original":"new"})]} self.current_function = None self.current_offset = None self.decomp = None self.current_view = None self.function_output = None self.decompile_result_path = None self.decompile_offset_path = None self.decompiler_done = False self.function_args = [] QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) layout = QVBoxLayout() self.editor = QTextEdit(self) self.editor.setReadOnly(True) self.editor.installEventFilter(self) self.editor.setStyleSheet("QTextEdit { font-family: Consolas }") self.editor.setPlainText(" Click anywhere in the dock to start decompiler") self.editor.selectionChanged.connect(self.onSelect) highlighter = Highlighter(self.editor.document(),"",self.function_args) layout.addWidget(self.editor) layout.setAlignment(QtCore.Qt.AlignLeft) self.setLayout(layout) instance_id += 1 self.data = data def onSelect(self): cursor = self.editor.textCursor() if cursor.selectedText(): ch = Highlighter(self.editor.document(),"\\b" + cursor.selectedText() + "\\b",self.function_args) else: ch = Highlighter(self.editor.document(),"",self.function_args) def notifyOffsetChanged(self, offset): if self.decompiler_done: #if not self.current_function or not (self.current_function.lowest_address < offset and self.current_function.highest_address > offset): scrollbar_value = self.editor.verticalScrollBar().value() self.current_offset = offset try: self.editor.setPlainText(self.find_function(offset)) ch = Highlighter(self.editor.document(),"",self.function_args) self.editor.verticalScrollBar().setValue(scrollbar_value) except: pass def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True def eventFilter(self, obj, event): if event.type() == QtCore.QEvent.FocusIn and self.editor.hasFocus() and not self.decompiler_done: self.decomp = Decompiler(self.filename,self.current_path) self.editor.setPlainText(" Decompiler running ... ") self.decomp.start() self.decompiler_done = True if event.type() == QtCore.QEvent.KeyPress and obj is self.editor: cursor = self.editor.textCursor() if event.key() == QtCore.Qt.Key_F and self.editor.hasFocus(): # Find TODO search_string = get_text_line_input("Find: ","Find") if search_string: ch = Highlighter(self.editor.document(),search_string.decode("UTF-8"),self.function_args) if event.key() == QtCore.Qt.Key_N and self.editor.hasFocus(): if self.current_view.file.has_database == False: show_message_box("Project not saved", "To enable renaming, make sure that the current project is saved to a BNDB file.", buttons=0, icon=2) return False # Handle rename action selected = cursor.selectedText() if selected != "": #self.binja_renames = json.loads(self.rename_settings.get_string("ghinja_rename.ghinja_rename_struct",self.current_view)) # Get selected text new_name = get_text_line_input(f"Rename {selected}: ","Rename") if new_name and not re.match(b"^\\w+$", new_name): show_message_box("Name not valid", "Please use only 'word' characters (A-Z, a-z, 0-9 and _)", buttons=0, icon=2) return False if re.search(f"\\b{new_name.decode('UTF-8')}\\b",self.function_output): show_message_box("Name not unique", "Please use only unique name to avoid conflicts.", buttons=0, icon=2) return False found = False for key in self.binja_renames[hex(self.current_function.start)]: if key["original"] == selected: # Was already renamed so just reassign to avoid accumulating tons of data key["new"] = new_name.decode("UTF-8") found = True if key["new"] == selected: key["new"] = new_name.decode("UTF-8") found = True if not found: self.binja_renames[hex(self.current_function.start)].append({"original":selected,"new":new_name.decode("UTF-8")}) self.rename_settings.set_string("ghinja_rename.ghinja_rename_struct",json.dumps(self.binja_renames),self.current_view,SettingsScope.SettingsResourceScope) self.notifyOffsetChanged(self.current_offset) return False def notifyViewChanged(self, view_frame): if view_frame is None: pass else: self.current_view = view_frame.actionContext().binaryView self.binja_renames = json.loads(self.rename_settings.get_string("ghinja_rename.ghinja_rename_struct",self.current_view)) md5 = hashlib.md5() try: with open(view_frame.actionContext().binaryView.file.original_filename,'rb') as binary: file_content = binary.read() md5.update(file_content) self.current_path = Path(Path(user_plugin_path()) / ".." / f"ghinja_projects/{str(Path(view_frame.actionContext().binaryView.file.original_filename).name) + '_' + md5.hexdigest()}") self.filename = view_frame.actionContext().binaryView.file.original_filename self.current_path.mkdir(parents=True, exist_ok=True) except: # File does not exist tmp_path = Path(user_plugin_path()) / ".." / f"ghinja_projects/{str(Path(view_frame.actionContext().binaryView.file.original_filename).name)}" self.current_view.save(str(tmp_path)) while not os.path.exists(str(tmp_path)): pass with open(str(tmp_path),'rb') as binary: file_content = binary.read() md5.update(file_content) self.current_path = Path(Path(user_plugin_path()) / ".." / f"ghinja_projects/{str(Path(view_frame.actionContext().binaryView.file.original_filename).name) + '_' + md5.hexdigest()}") self.current_path.mkdir(parents=True, exist_ok=True) shutil.move(tmp_path, self.current_path / tmp_path.name) self.filename = str(self.current_path / tmp_path.name) # Create relevant_folder #current_path = Path(Path.home() / f".ghinja_projects/{str(Path(view_frame.actionContext().binaryView.file.original_filename).name) + '_' + md5.hexdigest()}") self.decompile_result_path = Path(self.current_path / "decomp_") self.decompile_offset_path = Path(self.current_path / "decomp_offset") if os.path.exists(str(self.decompile_offset_path)): # Already decompiled we dont have to do anything self.decompiler_done = True def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler) @staticmethod def create_widget(name, parent, data = None): return GhinjaDockWidget(parent, name, data) def myroundup(self,n, step): return ((n - 1) // step + 1) * step def find_function(self, offset): function_output = "DECOMPILER OUTPUT FOR THIS FUNCTION WAS NOT FOUND" try: self.current_function = self.current_view.get_functions_containing(offset)[0] try: self.binja_renames[hex(self.current_function.start)] except: self.binja_renames[hex(self.current_function.start)] = [] except: return "DECOMPILER OUTPUT FOR THIS FUNCTION WAS NOT FOUND" # Get different offset functions os.listdir() offset = 0 with open(str(self.decompile_offset_path),"r") as offset_file: ghidra_offset = int(offset_file.read()) offset_diff = ghidra_offset - self.current_view.functions[0].start if offset_diff > 0x100 and not os.path.exists(str(self.decompile_result_path) + str(offset_diff)): offset_diff = self.myroundup(offset_diff,0x100) #log_info(f"GHIDRA OFFSET: {hex(ghidra_offset)} DIFF: {hex(offset_diff)} ROUND: {hex(self.myroundup(offset_diff,0x100))}") if offset_diff == 0: offset = self.current_function.start else: offset = self.current_function.start + offset_diff if os.path.exists(str(self.decompile_result_path) + str(offset)): with open(str(self.decompile_result_path) + str(offset),"r") as function_file: function_output = function_file.read() # Replace function name function_output = re.sub("\\b\\w*\\(", self.current_function.name + "(", function_output, 1) # Rename functions for callee in self.current_function.callees: if offset_diff == 0: look_for = f'FUN_{callee.start:08x}' else: look_for = f'FUN_{callee.start + offset_diff:08x}' function_output = function_output.replace(look_for,callee.name) #for ghinja_rename in self.binja_renames[hex(self.current_function.start)]: js = json.loads(self.rename_settings.get_string("ghinja_rename.ghinja_rename_struct",self.current_view)) if js: try: for ghinja_rename in js[hex(self.current_function.start)]: function_output = re.sub('\\b'+ghinja_rename["original"]+'\\b',ghinja_rename["new"],function_output) except: pass # Searching in frist 300 chars is enough self.function_args = [] for arg in re.findall("\\w+ [*&]?(\\w+),|\\w+ [*&]?(\\w+)\\)", function_output[:300]): if arg[0]: self.function_args.append(arg[0]) elif arg[1]: self.function_args.append(arg[1]) # Rename locals - quite buggy actually for local in self.current_function.stack_layout: if local.storage < 0: look_for = f"local_{hex(local.storage)[3:]}" function_output = re.sub(look_for,local.name,function_output) self.function_output = function_output return function_output
class RegisterView(QWidget, DockContextHandler): dirty_color = QBrush(QColor(255, 153, 51)) symbolic_color = QBrush(QColor(245, 66, 72)) no_color = QBrush(QColor(255, 255, 255)) def __init__(self, parent, name, data): QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) self.parent = parent self.arch = None self.current_state = None self.symb_idx = 0 self.reg_to_index = dict() self.index_to_reg = dict() self.reg_cache = dict() self.data = data self.tab_name = None self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) self._layout = QVBoxLayout() # Set up register table self._table = QTableWidget() self._table.setColumnCount(2) self._table.setHorizontalHeaderLabels(['Register', 'Value']) self._table.horizontalHeader().setStretchLastSection(True) self._table.verticalHeader().setVisible(False) self._table.setContextMenuPolicy(Qt.CustomContextMenu) self._table.customContextMenuRequested.connect( self.on_customContextMenuRequested) self._table.doubleClicked.connect(self.on_doubleClick) self._layout.addWidget(self._table) self.setLayout(self._layout) def reset(self): self.tab_name = None self.arch = None self.reg_to_index = dict() self.symb_idx = 0 self._table.setRowCount(0) def init(self, arch, state): self.arch = arch self.tab_name = _normalize_tab_name(self.parent.getTabName()) regs = self.arch.reg_names() self._table.setRowCount(len(regs)) for i, reg in enumerate(regs): self.reg_to_index[reg] = i self.index_to_reg[i] = reg self._table.setItem(i, 0, _makewidget(self, reg)) self._table.setItem(i, 1, _makewidget(self, "")) self.set_reg_values(state) def set_reg_value(self, reg, value, color=None): assert self.arch is not None idx = self.reg_to_index[reg] if symbolic(value): if isinstance(value, BVS): val_str = value.name else: val_str = "< symbolic expression >" if color is None: color = RegisterView.symbolic_color else: val_str = "0x{obj:0{width}x}".format(obj=value.value, width=(value.size + 3) // 4) self.reg_cache[reg] = val_str table_item = self._table.item(idx, 1) table_item.setText(val_str) if color is not None: table_item.setForeground(color) else: table_item.setForeground(self.no_color) def set_reg_values(self, state): self.current_state = state for reg in self.reg_to_index: val = getattr(state.regs, reg) self.set_reg_value(reg, val) # right click menu def on_customContextMenuRequested(self, pos): item = self._table.itemAt(pos) if item is None: return row_idx = item.row() if self.index_to_reg[row_idx] == self.arch.getip_reg(): return expr = getattr(self.current_state.regs, self.index_to_reg[row_idx]) menu = QMenu() show_reg_expr = menu.addAction( "Show reg expression") if not isinstance(expr, BVV) else None make_reg_symb = menu.addAction("Make reg symbolic") if isinstance( expr, BVV) else None set_reg_value = menu.addAction("Set reg value") eval_with_sol = menu.addAction( "Evaluate with solver") if not isinstance(expr, BVV) else None eval_upto_with_sol = menu.addAction( "Evaluate upto with solver") if not isinstance(expr, BVV) else None concretize = menu.addAction("Concretize") if not isinstance( expr, BVV) else None copy = menu.addAction("Copy to clipboard") if not isinstance( expr, BVS) else None bind_to_buffer = menu.addAction("Bind to symbolic buffer") action = menu.exec_(self._table.viewport().mapToGlobal(pos)) if action is None: return if action == bind_to_buffer: buffer_names = [ b[0].name for b in self.current_state.symbolic_buffers ] if len(buffer_names) == 0: return buff_id = get_choice_input("Select a buffer", "choices", buffer_names) address = self.current_state.symbolic_buffers[buff_id][1] buff_p = BVV(address, self.current_state.arch.bits()) setattr(self.current_state.regs, self.index_to_reg[row_idx], buff_p) self.set_reg_value(self.index_to_reg[row_idx], buff_p, RegisterView.dirty_color) if action == show_reg_expr: show_message_box("Reg Expression", str(expr.z3obj.sexpr())) if action == make_reg_symb: new_expr = BVS('symb_injected_through_ui_%d' % self.symb_idx, expr.size) setattr(self.current_state.regs, self.index_to_reg[row_idx], new_expr) self.set_reg_value(self.index_to_reg[row_idx], new_expr, RegisterView.dirty_color) self.symb_idx += 1 if action == set_reg_value: self.on_doubleClick(item) if action == eval_with_sol: expr = getattr(self.current_state.regs, self.index_to_reg[row_idx]) if not self.current_state.solver.symbolic(expr): new_expr = self.current_state.solver.evaluate(expr) setattr(self.current_state.regs, self.index_to_reg[row_idx], new_expr) self.set_reg_value(self.index_to_reg[row_idx], new_expr, RegisterView.dirty_color) show_message_box( "Reg Value (with solver)", "The value was indeed concrete! State modified") else: show_message_box( "Reg Value (with solver)", hex(self.current_state.solver.evaluate(expr).value)) if action == eval_upto_with_sol: expr = getattr(self.current_state.regs, self.index_to_reg[row_idx]) if not self.current_state.solver.symbolic(expr): new_expr = self.current_state.solver.evaluate(expr) setattr(self.current_state.regs, self.index_to_reg[row_idx], new_expr) self.set_reg_value(self.index_to_reg[row_idx], new_expr, RegisterView.dirty_color) show_message_box( "Reg Value (with solver)", "The value was indeed concrete! State modified") else: n_eval = get_int_input("How many values (upto) ?", "Number of distinct values") r = "" for i, v in enumerate( self.current_state.solver.evaluate_upto(expr, n_eval)): r += "solution %d: %s\n" % (i, hex(v.value)) show_message_box("Reg Value (with solver)", r) if action == concretize: expr = getattr(self.current_state.regs, self.index_to_reg[row_idx]) new_expr = self.current_state.solver.evaluate(expr) res = get_choice_input( "Concretize %s to %s?" % (self.index_to_reg[row_idx], hex(new_expr.value)), "Concretize", ["Yes", "No"]) if res == 0: setattr(self.current_state.regs, self.index_to_reg[row_idx], new_expr) self.current_state.solver.add_constraints(expr == new_expr) self.set_reg_value(self.index_to_reg[row_idx], new_expr, RegisterView.dirty_color) if action == copy: mime = QMimeData() if isinstance(expr, BVV): mime.setText(hex(expr.value)) else: mime.setText(str(expr.z3obj.sexpr())) QApplication.clipboard().setMimeData(mime) # double click event def on_doubleClick(self, item): row_idx = item.row() if self.index_to_reg[row_idx] == self.arch.getip_reg(): return old_expr = getattr(self.current_state.regs, self.index_to_reg[row_idx]) new_val = get_int_input("value for %s" % self.index_to_reg[row_idx], "Set Reg") if new_val is None: return new_expr = BVV(new_val, old_expr.size) setattr(self.current_state.regs, self.index_to_reg[row_idx], new_expr) self.set_reg_value(self.index_to_reg[row_idx], new_expr, RegisterView.dirty_color) def notifyOffsetChanged(self, offset): pass def shouldBeVisible(self, view_frame): if view_frame is None: return False elif self.tab_name is None: return False elif _normalize_tab_name(view_frame.getTabName()) != self.tab_name: return False return True def notifyViewChanged(self, view_frame): if view_frame is None: pass else: pass def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler)
class HelloPaneWidget(QWidget, UIContextNotification): def __init__(self, data): global instance_id QWidget.__init__(self) self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) offset_layout = QHBoxLayout() offset_layout.addWidget(QLabel("Offset: ")) self.offset = QLabel(hex(0)) offset_layout.addWidget(self.offset) offset_layout.setAlignment(QtCore.Qt.AlignCenter) datatype_layout = QHBoxLayout() datatype_layout.addWidget(QLabel("Data Type: ")) self.datatype = QLabel("") datatype_layout.addWidget(self.datatype) datatype_layout.setAlignment(QtCore.Qt.AlignCenter) layout = QVBoxLayout() title = QLabel("Hello Pane", self) title.setAlignment(QtCore.Qt.AlignCenter) instance = QLabel("Instance: " + str(instance_id), self) instance.setAlignment(QtCore.Qt.AlignCenter) layout.addStretch() layout.addWidget(title) layout.addWidget(instance) layout.addLayout(datatype_layout) layout.addLayout(offset_layout) layout.addStretch() self.setLayout(layout) instance_id += 1 self.data = data # Populate initial state self.updateState() # Set up view and address change notifications self.notifications = HelloNotifications(self) def updateState(self): # Get the currently active view frame for this group of panes. There can be # multiple view frames in a single window, or the pane could be popped out # into its own window. UIContext.currentViewFrameForWidget will determine # the best view frame to use for context. frame = UIContext.currentViewFrameForWidget(self) # Update UI according to the active frame if frame: self.datatype.setText(frame.getCurrentView()) view = frame.getCurrentViewInterface() self.data = view.getData() self.offset.setText(hex(view.getCurrentOffset())) else: self.datatype.setText("None") self.data = None def contextMenuEvent(self, event): self.m_contextMenuManager.show(self.m_menu, self.actionHandler) @staticmethod def createPane(context): if context.context and context.binaryView: widget = HelloPaneWidget(context.binaryView) pane = WidgetPane(widget, "Hello") context.context.openPane(pane) @staticmethod def canCreatePane(context): return context.context and context.binaryView