diff options
| author | Peter LaFosse <peter@vector35.com> | 2021-06-07 09:59:54 -0400 |
|---|---|---|
| committer | Peter LaFosse <peter@vector35.com> | 2021-09-05 10:08:09 -0400 |
| commit | 61b4bb24e06aa955484293d35fa926c07887544b (patch) | |
| tree | 29c6b7fecdac6270681260637439926ec07a259e /python/examples/snippets/__init__.py | |
| parent | 75f2463a46cc666e87120f3a30332fa80020b62e (diff) | |
Add type hints to basicblock.py, lowlevelil.py, architecture.py
Diffstat (limited to 'python/examples/snippets/__init__.py')
| -rw-r--r-- | python/examples/snippets/__init__.py | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/python/examples/snippets/__init__.py b/python/examples/snippets/__init__.py new file mode 100644 index 00000000..0f551a06 --- /dev/null +++ b/python/examples/snippets/__init__.py @@ -0,0 +1,444 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import sys +import os +import re +import codecs +from PySide2.QtWidgets import (QLineEdit, QPushButton, QApplication, QTextEdit, QWidget, + QVBoxLayout, QHBoxLayout, QDialog, QFileSystemModel, QTreeView, QLabel, QSplitter, + QInputDialog, QMessageBox, QHeaderView, QMenu, QAction, QKeySequenceEdit, + QPlainTextEdit) +from PySide2.QtCore import (QDir, QObject, Qt, QFileInfo, QItemSelectionModel, QSettings, QUrl) +from PySide2.QtGui import (QFont, QFontMetrics, QDesktopServices, QKeySequence, QIcon) +from binaryninja import user_plugin_path +from binaryninja.plugin import PluginCommand, MainThreadActionHandler +from binaryninja.mainthread import execute_on_main_thread +from binaryninja.log import (log_error, log_debug) +from binaryninjaui import (getMonospaceFont, UIAction, UIActionHandler, Menu, DockHandler, + getThemeColor, ThemeColor) +import numbers +from .QCodeEditor import QCodeEditor, PythonHighlighter + +snippetPath = os.path.realpath(os.path.join(user_plugin_path(), "..", "snippets")) +try: + if not os.path.exists(snippetPath): + os.mkdir(snippetPath) +except IOError: + log_error("Unable to create %s" % snippetPath) + + +def includeWalk(dir, includeExt): + filePaths = [] + for (root, dirs, files) in os.walk(dir): + for f in files: + if os.path.splitext(f)[1] in includeExt: + filePaths.append(os.path.join(root, f)) + return filePaths + + +def loadSnippetFromFile(snippetPath): + try: + snippetText = codecs.open(snippetPath, 'r', "utf-8").readlines() + except: + return ("", "", "") + if (len(snippetText) < 3): + return ("", "", "") + else: + qKeySequence = QKeySequence(snippetText[1].strip()[1:]) + if qKeySequence.isEmpty(): + qKeySequence = None + return (snippetText[0].strip()[1:], + qKeySequence, + ''.join(snippetText[2:]) + ) + + +def actionFromSnippet(snippetName, snippetDescription): + if not snippetDescription: + shortName = os.path.basename(snippetName) + if shortName.endswith('.py'): + shortName = shortName[:-3] + return "Snippets\\" + shortName + else: + return "Snippets\\" + snippetDescription + + +def executeSnippet(code, context): + snippetGlobals = {} + if context.binaryView == None: + dock = DockHandler.getActiveDockHandler() + if not dock: + log_error("Snippet triggered with no context and no dock handler. This should not happen. Please report reproduction steps if possible.") + return + viewFrame = dock.getViewFrame() + if not viewFrame: + log_error("Snippet triggered with no context and no view frame. Snippets require at least one open binary.") + return + viewInterface = viewFrame.getCurrentViewInterface() + context.binaryView = viewInterface.getData() + snippetGlobals['current_view'] = context.binaryView + snippetGlobals['bv'] = context.binaryView + if not context.function: + if not context.lowLevelILFunction: + if not context.mediumLevelILFunction: + snippetGlobals['current_mlil'] = None + snippetGlobals['current_function'] = None + snippetGlobals['current_llil'] = None + else: + snippetGlobals['current_mlil'] = context.mediumLevelILFunction + snippetGlobals['current_function'] = context.mediumLevelILFunction.source_function + snippetGlobals['current_llil'] = context.mediumLevelILFunction.source_function.llil + else: + snippetGlobals['current_llil'] = context.lowLevelILFunction + snippetGlobals['current_function'] = context.lowLevelILFunction.source_function + snippetGlobals['current_mlil'] = context.lowLevelILFunction.source_function.mlil + else: + snippetGlobals['current_function'] = context.function + snippetGlobals['current_mlil'] = context.function.mlil + snippetGlobals['current_llil'] = context.function.llil + snippetGlobals['current_token'] = context.function.llil + + if context.function is not None: + snippetGlobals['current_basic_block'] = context.function.get_basic_block_at(context.address) + else: + snippetGlobals['current_basic_block'] = None + snippetGlobals['current_address'] = context.address + snippetGlobals['here'] = context.address + if context.address is not None and isinstance(context.length, int): + snippetGlobals['current_selection'] = (context.address, context.address+context.length) + else: + snippetGlobals['current_selection'] = None + snippetGlobals['uicontext'] = context + + exec("from binaryninja import *", snippetGlobals) + exec(code, snippetGlobals) + if snippetGlobals['here'] != context.address: + context.binaryView.file.navigate(context.binaryView.file.view, snippetGlobals['here']) + if snippetGlobals['current_address'] != context.address: + context.binaryView.file.navigate(context.binaryView.file.view, snippetGlobals['current_address']) + + +def makeSnippetFunction(code): + return lambda context: executeSnippet(code, context) + +class Snippets(QDialog): + + def __init__(self, context, parent=None): + super(Snippets, self).__init__(parent) + # Create widgets + self.setWindowModality(Qt.ApplicationModal) + self.title = QLabel(self.tr("Snippet Editor")) + self.saveButton = QPushButton(self.tr("&Save")) + self.saveButton.setShortcut(QKeySequence(self.tr("Ctrl+S"))) + self.runButton = QPushButton(self.tr("&Run")) + self.runButton.setShortcut(QKeySequence(self.tr("Ctrl+R"))) + self.closeButton = QPushButton(self.tr("Close")) + self.clearHotkeyButton = QPushButton(self.tr("Clear Hotkey")) + self.setWindowTitle(self.title.text()) + #self.newFolderButton = QPushButton("New Folder") + self.browseButton = QPushButton("Browse Snippets") + self.browseButton.setIcon(QIcon.fromTheme("edit-undo")) + self.deleteSnippetButton = QPushButton("Delete") + self.newSnippetButton = QPushButton("New Snippet") + self.edit = QCodeEditor(HIGHLIGHT_CURRENT_LINE=False, SyntaxHighlighter=PythonHighlighter) + self.edit.setPlaceholderText("python code") + self.resetting = False + self.columns = 3 + self.context = context + + self.keySequenceEdit = QKeySequenceEdit(self) + self.currentHotkey = QKeySequence() + self.currentHotkeyLabel = QLabel("") + self.currentFileLabel = QLabel() + self.currentFile = "" + self.snippetDescription = QLineEdit() + self.snippetDescription.setPlaceholderText("optional description") + + #Set Editbox Size + font = getMonospaceFont(self) + self.edit.setFont(font) + font = QFontMetrics(font) + self.edit.setTabStopWidth(4 * font.width(' ')); #TODO, replace with settings API + + #Files + self.files = QFileSystemModel() + self.files.setRootPath(snippetPath) + self.files.setNameFilters(["*.py"]) + + #Tree + self.tree = QTreeView() + self.tree.setModel(self.files) + self.tree.setSortingEnabled(True) + self.tree.hideColumn(2) + self.tree.sortByColumn(0, Qt.AscendingOrder) + self.tree.setRootIndex(self.files.index(snippetPath)) + for x in range(self.columns): + #self.tree.resizeColumnToContents(x) + self.tree.header().setSectionResizeMode(x, QHeaderView.ResizeToContents) + treeLayout = QVBoxLayout() + treeLayout.addWidget(self.tree) + treeButtons = QHBoxLayout() + #treeButtons.addWidget(self.newFolderButton) + treeButtons.addWidget(self.browseButton) + treeButtons.addWidget(self.newSnippetButton) + treeButtons.addWidget(self.deleteSnippetButton) + treeLayout.addLayout(treeButtons) + treeWidget = QWidget() + treeWidget.setLayout(treeLayout) + + # Create layout and add widgets + buttons = QHBoxLayout() + buttons.addWidget(self.clearHotkeyButton) + buttons.addWidget(self.keySequenceEdit) + buttons.addWidget(self.currentHotkeyLabel) + buttons.addWidget(self.closeButton) + buttons.addWidget(self.runButton) + buttons.addWidget(self.saveButton) + + description = QHBoxLayout() + description.addWidget(QLabel(self.tr("Description: "))) + description.addWidget(self.snippetDescription) + + vlayoutWidget = QWidget() + vlayout = QVBoxLayout() + vlayout.addLayout(description) + vlayout.addWidget(self.edit) + vlayout.addLayout(buttons) + vlayoutWidget.setLayout(vlayout) + + hsplitter = QSplitter() + hsplitter.addWidget(treeWidget) + hsplitter.addWidget(vlayoutWidget) + + hlayout = QHBoxLayout() + hlayout.addWidget(hsplitter) + + self.showNormal() #Fixes bug that maximized windows are "stuck" + #Because you can't trust QT to do the right thing here + if (sys.platform == "darwin"): + self.settings = QSettings("Vector35", "Snippet Editor") + else: + self.settings = QSettings("Vector 35", "Snippet Editor") + if self.settings.contains("ui/snippeteditor/geometry"): + self.restoreGeometry(self.settings.value("ui/snippeteditor/geometry")) + else: + self.edit.setMinimumWidth(80 * font.averageCharWidth()) + self.edit.setMinimumHeight(30 * font.lineSpacing()) + + # Set dialog layout + self.setLayout(hlayout) + + # Add signals + self.saveButton.clicked.connect(self.save) + self.closeButton.clicked.connect(self.close) + self.runButton.clicked.connect(self.run) + self.clearHotkeyButton.clicked.connect(self.clearHotkey) + self.tree.selectionModel().selectionChanged.connect(self.selectFile) + self.newSnippetButton.clicked.connect(self.newFileDialog) + self.deleteSnippetButton.clicked.connect(self.deleteSnippet) + #self.newFolderButton.clicked.connect(self.newFolder) + self.browseButton.clicked.connect(self.browseSnippets) + + if self.settings.contains("ui/snippeteditor/selected"): + selectedName = self.settings.value("ui/snippeteditor/selected") + self.tree.selectionModel().select(self.files.index(selectedName), QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) + if self.tree.selectionModel().hasSelection(): + self.selectFile(self.tree.selectionModel().selection(), None) + self.edit.setFocus() + cursor = self.edit.textCursor() + cursor.setPosition(self.edit.document().characterCount()-1) + self.edit.setTextCursor(cursor) + else: + self.readOnly(True) + else: + self.readOnly(True) + + + @staticmethod + def registerAllSnippets(): + for action in list(filter(lambda x: x.startswith("Snippets\\"), UIAction.getAllRegisteredActions())): + if action == "Snippets\\Snippet Editor...": + continue + UIActionHandler.globalActions().unbindAction(action) + Menu.mainMenu("Tools").removeAction(action) + UIAction.unregisterAction(action) + + for snippet in includeWalk(snippetPath, ".py"): + snippetKeys = None + (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(snippet) + actionText = actionFromSnippet(snippet, snippetDescription) + if snippetCode: + if snippetKeys == None: + UIAction.registerAction(actionText) + else: + UIAction.registerAction(actionText, snippetKeys) + UIActionHandler.globalActions().bindAction(actionText, UIAction(makeSnippetFunction(snippetCode))) + Menu.mainMenu("Tools").addAction(actionText, "Snippets") + + def clearSelection(self): + self.keySequenceEdit.clear() + self.currentHotkey = QKeySequence() + self.currentHotkeyLabel.setText("") + self.currentFileLabel.setText("") + self.snippetDescription.setText("") + self.edit.clear() + self.tree.clearSelection() + self.currentFile = "" + + def reject(self): + self.settings.setValue("ui/snippeteditor/geometry", self.saveGeometry()) + + if self.snippetChanged(): + question = QMessageBox.question(self, self.tr("Discard"), self.tr("You have unsaved changes, quit anyway?")) + if question != QMessageBox.StandardButton.Yes: + return + self.accept() + + def browseSnippets(self): + url = QUrl.fromLocalFile(snippetPath) + QDesktopServices.openUrl(url); + + def newFolder(self): + (folderName, ok) = QInputDialog.getText(self, self.tr("Folder Name"), self.tr("Folder Name: ")) + if ok and folderName: + index = self.tree.selectionModel().currentIndex() + selection = self.files.filePath(index) + if QFileInfo(selection).isDir(): + QDir(selection).mkdir(folderName) + else: + QDir(snippetPath).mkdir(folderName) + + def selectFile(self, new, old): + if (self.resetting): + self.resetting = False + return + if len(new.indexes()) == 0: + self.clearSelection() + self.currentFile = "" + self.readOnly(True) + return + newSelection = self.files.filePath(new.indexes()[0]) + self.settings.setValue("ui/snippeteditor/selected", newSelection) + if QFileInfo(newSelection).isDir(): + self.readOnly(True) + self.clearSelection() + self.currentFile = "" + return + + if old and old.length() > 0: + oldSelection = self.files.filePath(old.indexes()[0]) + if not QFileInfo(oldSelection).isDir() and self.snippetChanged(): + question = QMessageBox.question(self, self.tr("Discard"), self.tr("Snippet changed. Discard changes?")) + if question != QMessageBox.StandardButton.Yes: + self.resetting = True + self.tree.selectionModel().select(old, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) + return False + + self.currentFile = newSelection + self.loadSnippet() + + def loadSnippet(self): + self.currentFileLabel.setText(QFileInfo(self.currentFile).baseName()) + (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile) + self.snippetDescription.setText(snippetDescription) if snippetDescription else self.snippetDescription.setText("") + self.keySequenceEdit.setKeySequence(snippetKeys) if snippetKeys else self.keySequenceEdit.setKeySequence(QKeySequence("")) + self.edit.setPlainText(snippetCode) if snippetCode else self.edit.setPlainText("") + self.readOnly(False) + + def newFileDialog(self): + (snippetName, ok) = QInputDialog.getText(self, self.tr("Snippet Name"), self.tr("Snippet Name: ")) + if ok and snippetName: + if not snippetName.endswith(".py"): + snippetName += ".py" + index = self.tree.selectionModel().currentIndex() + selection = self.files.filePath(index) + if QFileInfo(selection).isDir(): + path = os.path.join(selection, snippetName) + else: + path = os.path.join(snippetPath, snippetName) + self.readOnly(False) + open(path, "w").close() + self.tree.setCurrentIndex(self.files.index(path)) + log_debug("Snippet %s created." % snippetName) + + def readOnly(self, flag): + self.keySequenceEdit.setEnabled(not flag) + self.snippetDescription.setReadOnly(flag) + self.edit.setReadOnly(flag) + if flag: + self.snippetDescription.setDisabled(True) + self.edit.setDisabled(True) + else: + self.snippetDescription.setEnabled(True) + self.edit.setEnabled(True) + + def deleteSnippet(self): + selection = self.tree.selectedIndexes()[::self.columns][0] #treeview returns each selected element in the row + snippetName = self.files.fileName(selection) + question = QMessageBox.question(self, self.tr("Confirm"), self.tr("Confirm deletion: ") + snippetName) + if (question == QMessageBox.StandardButton.Yes): + log_debug("Deleting snippet %s." % snippetName) + self.clearSelection() + self.files.remove(selection) + self.registerAllSnippets() + + def snippetChanged(self): + if (self.currentFile == "" or QFileInfo(self.currentFile).isDir()): + return False + (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile) + if snippetKeys == None and not self.keySequenceEdit.keySequence().isEmpty(): + return True + if snippetKeys != None and snippetKeys != self.keySequenceEdit.keySequence().toString(): + return True + return self.edit.toPlainText() != snippetCode or \ + self.snippetDescription.text() != snippetDescription + + def save(self): + log_debug("Saving snippet %s" % self.currentFile) + outputSnippet = codecs.open(self.currentFile, "w", "utf-8") + outputSnippet.write("#" + self.snippetDescription.text() + "\n") + outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n") + outputSnippet.write(self.edit.toPlainText()) + outputSnippet.close() + self.registerAllSnippets() + + def run(self): + if self.context == None: + log_warn("Cannot run snippets outside of the UI at this time.") + return + if self.snippetChanged(): + question = QMessageBox.question(self, self.tr("Confirm"), self.tr("You have unsaved changes, must save first. Save?")) + if (question == QMessageBox.StandardButton.No): + return + else: + self.save() + actionText = actionFromSnippet(self.currentFile, self.snippetDescription.text()) + UIActionHandler.globalActions().executeAction(actionText, self.context) + + log_debug("Saving snippet %s" % self.currentFile) + outputSnippet = codecs.open(self.currentFile, "w", "utf-8") + outputSnippet.write("#" + self.snippetDescription.text() + "\n") + outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n") + outputSnippet.write(self.edit.toPlainText()) + outputSnippet.close() + self.registerAllSnippets() + + def clearHotkey(self): + self.keySequenceEdit.clear() + + +def launchPlugin(context): + snippets = Snippets(context) + snippets.exec_() + + +if __name__ == '__main__': + app = QApplication(sys.argv) + snippets = Snippets(None) + snippets.show() + sys.exit(app.exec_()) +else: + Snippets.registerAllSnippets() + UIAction.registerAction("Snippets\\Snippet Editor...") + UIActionHandler.globalActions().bindAction("Snippets\\Snippet Editor...", UIAction(launchPlugin)) + Menu.mainMenu("Tools").addAction("Snippets\\Snippet Editor...", "Snippet") |
