示例#1
0
class MainDialog(QMainWindow):
    '''Главное диалоговое окно'''

    closeApp = Signal()

    def __init__(self, app, title):
        QMainWindow.__init__(self, parent=None)
        self.app = app
        self.setWindowTitle(title)
        self.setMinimumSize(500, 400)
        dw = QDesktopWidget()

        self.dockWidgets = []

        self.resize(dw.width() * 0.7, dw.height() * 0.3)

        # self.showFullScreen()
        self.gui_init()

    def addToCenter(self, widget, title='new'):
        title = widget.title if widget.title else title
        newDockWidget = DockWidget(self, title=title)
        self.centerMainWindow.addDockWidget(Qt.LeftDockWidgetArea,
                                            newDockWidget)
        newDockWidget.setWidget(widget)
        if len(self.dockWidgets) >= 1:
            self.centerMainWindow.tabifyDockWidget(self.dockWidgets[0],
                                                   newDockWidget)
        self.dockWidgets.append(newDockWidget)

    def addStatusObj(self, obj):
        '''Добовление стркои статуса'''
        self.statusBar().addWidget(obj)

    def addPermanentStatusObj(self, obj):
        '''Добовление стркои статуса'''
        self.statusBar().addPermanentWidget(obj)

    def closeEvent(self, event):
        self.closeApp.emit()

    def addMenuItem(self, obj):
        newItem = self.menuBar().addItem(obj)

    def addMenuItems(self, *objs):
        for obj in objs:
            newItem = self.menuBar().addItem(obj)

    def gui_init(self):

        menubar = menu_bar(self)
        self.setMenuWidget(menubar)

        self.centerMainWindow = QMainWindow()
        self.centerMainWindow.setTabPosition(
            Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea, QTabWidget.North)

        self.setCentralWidget(self.centerMainWindow)
示例#2
0
class MainWindow(QMainWindow):
    """
    The main window of angr management.
    """
    def __init__(self, file_to_open=None, parent=None):
        super(MainWindow, self).__init__(parent)

        icon_location = os.path.join(IMG_LOCATION, 'angr.png')
        self.setWindowIcon(QIcon(icon_location))

        GlobalInfo.main_window = self

        # initialization
        self.caption = "angr Management"
        self.setMinimumSize(QSize(400, 400))
        self.setDockNestingEnabled(True)

        self.workspace = None
        self.central_widget = None
        self._plugin_mgr = None  # type: PluginManager

        self._file_toolbar = None  # type: FileToolbar
        self._states_toolbar = None  # type: StatesToolbar
        self._analysis_toolbar = None  # type: AnalysisToolbar
        self._progressbar = None  # type: QProgressBar
        self._load_binary_dialog = None

        self._status = ""
        self._progress = None

        self._init_menus()
        self._init_toolbars()
        self._init_statusbar()
        self._init_workspace()
        self._init_plugins()

        self.showMaximized()

        # I'm ready to show off!
        self.show()

        self.status = "Ready."

        if file_to_open is not None:
            self.load_file(file_to_open)

    def sizeHint(self, *args, **kwargs):
        return QSize(1200, 800)

    #
    # Properties
    #

    @property
    def caption(self):
        return self.getWindowTitle()

    @caption.setter
    def caption(self, v):
        self.setWindowTitle(v)

    @property
    def status(self):
        return self._status

    @status.setter
    def status(self, v):
        self._status = v

        self.statusBar().showMessage(v)

    @property
    def progress(self):
        return self._progress

    @progress.setter
    def progress(self, v):
        self._progress = v
        self._progressbar.show()
        self._progressbar.setValue(v)

    #
    # Dialogs
    #

    def _open_mainfile_dialog(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "Open a binary",
            ".",
            "All executables (*);;Windows PE files (*.exe);;Core Dumps (*.core);;angr database (*.adb)",
        )
        return file_path

    def _pick_image_dialog(self):
        try:
            prompt = LoadDockerPrompt()
        except LoadDockerPromptError:
            return
        if prompt.exec_() == 0:
            return  # User canceled
        return prompt.textValue()

    def _load_options_dialog(self, partial_ld):
        try:
            self._load_binary_dialog = LoadBinary(partial_ld)
            self._load_binary_dialog.setModal(True)
            self._load_binary_dialog.exec_()

            if self._load_binary_dialog.cfg_args is not None:
                # load the binary
                return (self._load_binary_dialog.load_options,
                        self._load_binary_dialog.cfg_args)
        except LoadBinaryError:
            pass
        return None, None

    def open_newstate_dialog(self):
        new_state_dialog = NewState(self.workspace.instance, parent=self)
        new_state_dialog.exec_()

    def open_doc_link(self):
        QDesktopServices.openUrl(
            QUrl("https://docs.angr.io/", QUrl.TolerantMode))

    def open_about_dialog(self):
        QMessageBox.about(self, "About angr", "Version 8")

    #
    # Widgets
    #

    def _init_statusbar(self):

        self._progressbar = QProgressBar()

        self._progressbar.setMinimum(0)
        self._progressbar.setMaximum(100)
        self._progressbar.hide()

        self.statusBar().addPermanentWidget(self._progressbar)

    def _init_toolbars(self):

        self._file_toolbar = FileToolbar(self)
        self._states_toolbar = StatesToolbar(self)
        self._analysis_toolbar = AnalysisToolbar(self)

        self.addToolBar(Qt.TopToolBarArea, self._file_toolbar.qtoolbar())
        self.addToolBar(Qt.TopToolBarArea, self._states_toolbar.qtoolbar())
        self.addToolBar(Qt.TopToolBarArea, self._analysis_toolbar.qtoolbar())

    #
    # Menus
    #

    def _init_menus(self):
        fileMenu = FileMenu(self)
        analyzeMenu = AnalyzeMenu(self)
        helpMenu = HelpMenu(self)
        self.menuBar().addMenu(fileMenu.qmenu())
        self.menuBar().addMenu(analyzeMenu.qmenu())
        self.menuBar().addMenu(helpMenu.qmenu())

    #
    # Workspace
    #

    def _init_workspace(self):
        self.central_widget = QMainWindow()
        self.setCentralWidget(self.central_widget)

        wk = Workspace(self, Instance())
        self.workspace = wk

        self.central_widget.setTabPosition(Qt.RightDockWidgetArea,
                                           QTabWidget.North)

    #
    # PluginManager
    #

    def _init_plugins(self):
        self._plugin_mgr = PluginManager(self.workspace)
        self._plugin_mgr.initialize_all()

    #
    # Event
    #

    def resizeEvent(self, event):
        """

        :param QResizeEvent event:
        :return:
        """

        self._recalculate_view_sizes(event.oldSize())

    def closeEvent(self, event):
        self._plugin_mgr.stop_all()
        event.accept()

    def event(self, event):

        if event.type() == QEvent.User:
            # our event callback

            try:
                event.result = event.execute()
            except Exception as e:
                event.exception = e
            event.event.set()

            return True

        return super(MainWindow, self).event(event)

    #
    # Actions
    #

    def reload(self):
        self.workspace.reload()

    def open_file_button(self):
        file_path = self._open_mainfile_dialog()
        self.load_file(file_path)

    def open_docker_button(self):
        required = {
            'archr: git clone https://github.com/angr/archr && cd archr && pip install -e .':
            archr,
            'keystone: pip install --no-binary keystone-engine keystone-engine':
            keystone
        }
        is_missing = [key for key, value in required.items() if value is None]
        if len(is_missing) > 0:
            req_msg = 'You need to install the following:\n\n\t' + '\n\t'.join(
                is_missing)
            req_msg += '\n\nInstall them to enable this functionality.'
            req_msg += '\nRelaunch angr-management after install.'
            QMessageBox(self).critical(None, 'Dependency error', req_msg)
            return

        img_name = self._pick_image_dialog()
        if img_name is None:
            return
        self.workspace.instance.set_image(img_name)
        self.load_image(img_name)

    def load_file(self, file_path):
        if os.path.isfile(file_path):
            if file_path.endswith(".adb"):
                self.load_database(file_path)
            else:
                partial_ld = cle.Loader(file_path, perform_relocations=False)
                load_options, cfg_args = self._load_options_dialog(partial_ld)
                partial_ld.close()
                if cfg_args is None:
                    return

                proj = angr.Project(file_path, load_options=load_options)
                self._set_proj(proj, cfg_args)

    def load_image(self, img_name):
        with archr.targets.DockerImageTarget(
                img_name, target_path=None).build().start() as t:
            # this is perhaps the point where we should split out loading of generic targets?
            dsb = archr.arsenal.DataScoutBow(t)
            apb = archr.arsenal.angrProjectBow(t, dsb)
            partial_ld = apb.fire(return_loader=True,
                                  perform_relocations=False)
            load_options, cfg_args = self._load_options_dialog(partial_ld)
            partial_ld.close()
            if cfg_args is None:
                return

            # Create the project, load it, then record the image name on success
            proj = apb.fire(use_sim_procedures=True, load_options=load_options)
            self._set_proj(proj, cfg_args)

    def save_database(self):
        if self.workspace.instance.database_path is None:
            self.save_database_as()
        else:
            self._save_database(self.workspace.instance.database_path)

    def save_database_as(self):

        # Open File window
        file_path, _ = QFileDialog.getSaveFileName(
            self,
            "Save angr database",
            ".",
            "angr databases (*.adb)",
        )

        if not file_path.endswith(".adb"):
            file_path = file_path + ".adb"

        self._save_database(file_path)

    def quit(self):
        self.close()

    def run_variable_recovery(self):
        self.workspace.view_manager.first_view_in_category(
            'disassembly').variable_recovery_flavor = 'accurate'

    def run_induction_variable_analysis(self):
        self.workspace.view_manager.first_view_in_category(
            'disassembly').run_induction_variable_analysis()

    def decompile_current_function(self):
        if self.workspace is not None:
            self.workspace.decompile_current_function()

    def interact(self):
        self.workspace.interact_program(self.workspace.instance.img_name)

    #
    # Other public methods
    #

    def progress_done(self):
        self._progress = None
        self._progressbar.hide()

    #
    # Private methods
    #

    def _set_proj(self, proj, cfg_args=None):
        if cfg_args is None:
            cfg_args = {}
        self.workspace.instance.set_project(proj)
        self.workspace.instance.initialize(cfg_args=cfg_args)

    def _load_database(self, file_path):
        with open(file_path, "rb") as o:
            p, cfg, cfb = pickle.load(o)
        self.workspace.instance.project = p
        self.workspace.instance.cfg = cfg
        self.workspace.instance.cfb = cfb
        self.workspace.reload()
        self.workspace.on_cfg_generated()
        self.workspace.instance.database_path = file_path
        print("DATABASE %s LOADED" % file_path)

    def _save_database(self, file_path):
        with open(file_path, "wb") as o:
            pickle.dump(
                (self.workspace.instance.project, self.workspace.instance.cfg,
                 self.workspace.instance.cfb), o)
        self.workspace.instance.database_path = file_path
        print("DATABASE %s SAVED" % file_path)

    def _recalculate_view_sizes(self, old_size):
        adjustable_dockable_views = [
            dock for dock in self.workspace.view_manager.docks
            if dock.widget().default_docking_position in (
                'left',
                'bottom',
            )
        ]

        if not adjustable_dockable_views:
            return

        for dock in adjustable_dockable_views:
            widget = dock.widget()

            if old_size.width() < 0:
                dock.old_size = widget.sizeHint()
                continue

            if old_size != self.size():
                # calculate the width ratio

                if widget.default_docking_position == 'left':
                    # we want to adjust the width
                    ratio = widget.old_width * 1.0 / old_size.width()
                    new_width = int(self.width() * ratio)
                    widget.width_hint = new_width
                    widget.updateGeometry()
                elif widget.default_docking_position == 'bottom':
                    # we want to adjust the height
                    ratio = widget.old_height * 1.0 / old_size.height()
                    new_height = int(self.height() * ratio)
                    widget.height_hint = new_height
                    widget.updateGeometry()

                dock.old_size = widget.size()

    def _resize_dock_widget(self, dock_widget, new_width, new_height):

        original_size = dock_widget.size()
        original_min = dock_widget.minimumSize()
        original_max = dock_widget.maximumSize()

        dock_widget.resize(new_width, new_height)

        if new_width != original_size.width():
            if original_size.width() > new_width:
                dock_widget.setMaximumWidth(new_width)
            else:
                dock_widget.setMinimumWidth(new_width)

        if new_height != original_size.height():
            if original_size.height() > new_height:
                dock_widget.setMaximumHeight(new_height)
            else:
                dock_widget.setMinimumHeight(new_height)

        dock_widget.original_min = original_min
        dock_widget.original_max = original_max

        QTimer.singleShot(1, dock_widget.restore_original_size)
示例#3
0
class MainWindow(QMainWindow):
    """
    The main window of angr management.
    """
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        icon_location = os.path.join(IMG_LOCATION, 'angr.png')
        self.setWindowIcon(QIcon(icon_location))

        GlobalInfo.main_window = self

        # initialization
        self.setMinimumSize(QSize(400, 400))
        self.setDockNestingEnabled(True)

        self.workspace = None
        self.central_widget = None
        self.central_widget2 = None

        self._file_toolbar = None  # type: FileToolbar
        self._states_toolbar = None  # type: StatesToolbar
        self._analysis_toolbar = None  # type: AnalysisToolbar
        self._progressbar = None  # type: QProgressBar
        self._load_binary_dialog = None

        self._status = ""
        self._progress = None

        self.defaultWindowFlags = None

        # menus
        self._file_menu = None
        self._analyze_menu = None
        self._view_menu = None
        self._help_menu = None
        self._plugin_menu = None
        self._sync_menu = None

        self._init_toolbars()
        self._init_statusbar()
        self._init_workspace()
        self._init_shortcuts()
        self._init_menus()
        self._init_plugins()

        self.showMaximized()

        # event handlers
        self.windowHandle().screenChanged.connect(self.on_screen_changed)

        # I'm ready to show off!
        self.show()

        self.status = "Ready."

    def sizeHint(self, *args, **kwargs):
        return QSize(1200, 800)

    #
    # Properties
    #

    @property
    def caption(self):
        return self.getWindowTitle()

    @caption.setter
    def caption(self, v):
        self.setWindowTitle(v)

    @property
    def status(self):
        return self._status

    @status.setter
    def status(self, v):
        self._status = v

        self.statusBar().showMessage(v)

    @property
    def progress(self):
        return self._progress

    @progress.setter
    def progress(self, v):
        self._progress = v
        self._progressbar.show()
        self._progressbar.setValue(v)

    #
    # Dialogs
    #

    def _open_mainfile_dialog(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "Open a binary",
            "",
            "All executables (*);;Windows PE files (*.exe);;Core Dumps (*.core);;angr database (*.adb)",
        )
        return file_path

    def _pick_image_dialog(self):
        try:
            prompt = LoadDockerPrompt()
        except LoadDockerPromptError:
            return
        if prompt.exec_() == 0:
            return  # User canceled
        return prompt.textValue()

    def open_load_plugins_dialog(self):
        dlg = LoadPlugins(self.workspace.plugins)
        dlg.setModal(True)
        dlg.exec_()

    def open_newstate_dialog(self):
        new_state_dialog = NewState(self.workspace.instance, parent=self)
        new_state_dialog.exec_()

    def open_doc_link(self):
        QDesktopServices.openUrl(
            QUrl("https://docs.angr.io/", QUrl.TolerantMode))

    def open_sync_config_dialog(self):
        if self.workspace.instance.project is None:
            # project does not exist yet
            return

        sync_config = SyncConfig(self.workspace.instance, parent=self)
        sync_config.exec_()

    def open_about_dialog(self):
        dlg = LoadAboutDialog()
        dlg.exec_()

    #
    # Widgets
    #

    def _init_statusbar(self):

        self._progressbar = QProgressBar()

        self._progressbar.setMinimum(0)
        self._progressbar.setMaximum(100)
        self._progressbar.hide()

        self.statusBar().addPermanentWidget(self._progressbar)

    def _init_toolbars(self):

        self._file_toolbar = FileToolbar(self)
        self._states_toolbar = StatesToolbar(self)
        self._analysis_toolbar = AnalysisToolbar(self)

        self.addToolBar(Qt.TopToolBarArea, self._file_toolbar.qtoolbar())
        self.addToolBar(Qt.TopToolBarArea, self._states_toolbar.qtoolbar())
        self.addToolBar(Qt.TopToolBarArea, self._analysis_toolbar.qtoolbar())

    #
    # Menus
    #

    def _init_menus(self):
        self._file_menu = FileMenu(self)
        self._analyze_menu = AnalyzeMenu(self)
        self._view_menu = ViewMenu(self)
        self._help_menu = HelpMenu(self)
        self._plugin_menu = PluginMenu(self)

        self.menuBar().addMenu(self._file_menu.qmenu())
        self.menuBar().addMenu(self._view_menu.qmenu())
        self.menuBar().addMenu(self._analyze_menu.qmenu())
        if has_binsync():
            self._sync_menu = SyncMenu(self)
            self.menuBar().addMenu(self._sync_menu.qmenu())

            def on_load(**kwargs):
                self._sync_menu.action_by_key("config").enable()

            self.workspace.instance._project_container.am_subscribe(on_load)
        self.menuBar().addMenu(self._plugin_menu.qmenu())
        self.menuBar().addMenu(self._help_menu.qmenu())

    #
    # Workspace
    #

    def _init_workspace(self):
        """
        Initialize workspace

        :return:    None
        """

        self.central_widget_main = QSplitter(Qt.Horizontal)
        self.setCentralWidget(self.central_widget_main)
        self.central_widget = QMainWindow()
        self.central_widget2 = QMainWindow()
        self.central_widget_main.addWidget(self.central_widget)
        self.central_widget_main.addWidget(self.central_widget2)
        wk = Workspace(self, Instance())
        self.workspace = wk
        self.workspace.view_manager.tabify_center_views()
        self.central_widget.setTabPosition(Qt.RightDockWidgetArea,
                                           QTabWidget.North)
        self.central_widget2.setTabPosition(Qt.LeftDockWidgetArea,
                                            QTabWidget.North)

        def set_caption(**kwargs):
            if self.workspace.instance.project == None:
                self.caption = ''
            else:
                self.caption = os.path.basename(
                    self.workspace.instance.project.filename)

        self.workspace.instance.project_container.am_subscribe(set_caption)

    #
    # Shortcuts
    #

    def _init_shortcuts(self):
        """
        Initialize shortcuts

        :return:    None
        """

        center_dockable_views = self.workspace.view_manager.get_center_views()
        for i in range(1, len(center_dockable_views) + 1):
            QShortcut(QKeySequence('Ctrl+' + str(i)), self,
                      center_dockable_views[i - 1].raise_)

        # Raise the DisassemblyView after everything has initialized
        center_dockable_views[0].raise_()

    #
    # Plugins
    #

    def _init_plugins(self):
        os.environ['AM_BUILTIN_PLUGINS'] = os.path.dirname(plugins.__file__)
        blacklist = Conf.plugin_blacklist.split(',')
        for search_dir in Conf.plugin_search_path.split(':'):
            search_dir = os.path.expanduser(search_dir)
            search_dir = os.path.expandvars(search_dir)
            for plugin_or_exception in plugins.load_plugins_from_dir(
                    search_dir):
                if isinstance(plugin_or_exception, Exception):
                    self.workspace.log(plugin_or_exception)
                elif not any(dont in repr(plugin_or_exception)
                             for dont in blacklist):
                    self.workspace.plugins.activate_plugin(plugin_or_exception)
                else:
                    self.workspace.plugins.load_plugin(plugin_or_exception)
                    self.workspace.log("Blacklisted plugin %s" %
                                       plugin_or_exception.get_display_name())

    #
    # Event
    #

    def resizeEvent(self, event):
        """

        :param QResizeEvent event:
        :return:
        """

        self._recalculate_view_sizes(event.oldSize())

    def closeEvent(self, event):

        # Ask if the user wants to save things
        if self.workspace.instance is not None and self.workspace.instance.project is not None:
            msgbox = QMessageBox()
            msgbox.setWindowTitle("Save database")
            msgbox.setText(
                "angr management is about to exit. Do you want to save the database?"
            )
            msgbox.setIcon(QMessageBox.Question)
            msgbox.setWindowIcon(self.windowIcon())
            msgbox.setStandardButtons(QMessageBox.Yes | QMessageBox.No
                                      | QMessageBox.Cancel)
            msgbox.setDefaultButton(QMessageBox.Yes)
            r = msgbox.exec_()

            if r == QMessageBox.Cancel:
                event.ignore()
                return
            elif r == QMessageBox.Yes:
                save_r = self.save_database()
                if not save_r:
                    # failed to save the database
                    event.ignore()
                    return

        for plugin in list(self.workspace.plugins.active_plugins):
            self.workspace.plugins.deactivate_plugin(plugin)
        event.accept()

    def event(self, event):

        if event.type() == QEvent.User:
            # our event callback

            try:
                event.result = event.execute()
            except Exception as e:
                event.exception = e
            event.event.set()

            return True

        return super(MainWindow, self).event(event)

    def on_screen_changed(self, screen):
        """
        When changing from one screen to another, ask disassembly views to refresh in case the DPI is changed.
        """
        self.workspace.current_screen.am_obj = screen
        self.workspace.current_screen.am_event()

    #
    # Actions
    #

    def reload(self):
        self.workspace.reload()

    def open_file_button(self):
        file_path = self._open_mainfile_dialog()
        if not file_path:
            return
        self.load_file(file_path)

    def open_docker_button(self):
        required = {
            'archr: git clone https://github.com/angr/archr && cd archr && pip install -e .':
            archr,
            'keystone: pip install --no-binary keystone-engine keystone-engine':
            keystone
        }
        is_missing = [key for key, value in required.items() if value is None]
        if len(is_missing) > 0:
            req_msg = 'You need to install the following:\n\n\t' + '\n\t'.join(
                is_missing)
            req_msg += '\n\nInstall them to enable this functionality.'
            req_msg += '\nRelaunch angr-management after install.'
            QMessageBox(self).critical(None, 'Dependency error', req_msg)
            return

        img_name = self._pick_image_dialog()
        if img_name is None:
            return
        target = archr.targets.DockerImageTarget(img_name, target_path=None)
        self.workspace.instance.add_job(LoadTargetJob(target))
        self.workspace.instance.set_image(img_name)

    def load_file(self, file_path):

        if not isurl(file_path):
            # file
            if os.path.isfile(file_path):
                if file_path.endswith(".adb"):
                    self._load_database(file_path)
                else:
                    self.workspace.instance.add_job(LoadBinaryJob(file_path))
            else:
                QMessageBox.critical(
                    self, "File not found",
                    "angr management cannot open file %s. "
                    "Please make sure that the file exists." % file_path)
        else:
            # url
            r = QMessageBox.question(
                self,
                "Downloading a file",
                "Do you want to download a file from %s and open it in angr management?"
                % file_path,
                defaultButton=QMessageBox.Yes)
            if r == QMessageBox.Yes:
                try:
                    target_path = download_url(file_path,
                                               parent=self,
                                               to_file=True,
                                               file_path=None)
                except InvalidURLError:
                    QMessageBox.critical(
                        self, "Downloading failed",
                        "angr management failed to download the file. The URL is invalid."
                    )
                    return
                except UnexpectedStatusCodeError as ex:
                    QMessageBox.critical(
                        self, "Downloading failed",
                        "angr management failed to retrieve the header of the file. "
                        "The HTTP request returned an unexpected status code %d."
                        % ex.status_code)
                    return

                if target_path:
                    # open the file - now it's a local file
                    self.load_file(target_path)

    def load_database(self):
        # Open File window
        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "Load angr database",
            ".",
            "angr databases (*.adb)",
        )

        if not file_path:
            return

        self._load_database(file_path)

    def save_database(self):
        if self.workspace.instance is None or self.workspace.instance.project is None:
            return True

        if self.workspace.instance.database_path is None:
            return self.save_database_as()
        else:
            return self._save_database(self.workspace.instance.database_path)

    def save_database_as(self):

        if self.workspace.instance is None or self.workspace.instance.project is None:
            return False

        default_database_path = self.workspace.instance.database_path
        if default_database_path is None:
            default_database_path = os.path.normpath(
                self.workspace.instance.project.filename) + ".adb"

        # Open File window
        file_path, _ = QFileDialog.getSaveFileName(
            self,
            "Save angr database",
            default_database_path,
            "angr databases (*.adb)",
        )

        if not file_path:
            return False

        if not file_path.endswith(".adb"):
            file_path = file_path + ".adb"

        return self._save_database(file_path)

    def preferences(self):

        # Open Preferences dialog
        pref = Preferences(self.workspace, parent=self)
        pref.exec_()

    def quit(self):
        self.close()

    def run_variable_recovery(self):
        self.workspace.view_manager.first_view_in_category(
            'disassembly').variable_recovery_flavor = 'accurate'

    def run_induction_variable_analysis(self):
        self.workspace.view_manager.first_view_in_category(
            'disassembly').run_induction_variable_analysis()

    def decompile_current_function(self):
        if self.workspace is not None:
            self.workspace.decompile_current_function()

    def interact(self):
        self.workspace.interact_program(self.workspace.instance.img_name)

    def setup_sync(self):
        self.open_sync_config_dialog()

    #
    # Other public methods
    #

    def progress_done(self):
        self._progress = None
        self._progressbar.hide()

    def bring_to_front(self):
        self.setWindowState((self.windowState() & ~Qt.WindowMinimized)
                            | Qt.WindowActive)
        self.activateWindow()
        self.raise_()

    #
    # Private methods
    #

    def _load_database(self, file_path):

        if AngrDB is None:
            QMessageBox.critical(
                None, 'Error',
                'AngrDB is not enabled. Maybe you do not have SQLAlchemy installed?'
            )
            return

        angrdb = AngrDB()
        try:
            proj = angrdb.load(file_path)
        except angr.errors.AngrIncompatibleDBError as ex:
            QMessageBox.critical(
                None, 'Error',
                "Failed to load the angr database because of compatibility issues.\n"
                "Details: %s" % str(ex))
            return
        except angr.errors.AngrDBError as ex:
            QMessageBox.critical(
                None, 'Error', 'Failed to load the angr database.\n'
                'Details: %s' % str(ex))
            _l.critical("Failed to load the angr database.", exc_info=True)
            return

        cfg = proj.kb.cfgs['CFGFast']
        cfb = proj.analyses.CFB()  # it will load functions from kb

        self.workspace.instance.database_path = file_path

        self.workspace.instance.initialized = True  # skip automated CFG recovery
        self.workspace.instance.project = proj
        self.workspace.instance.cfg = cfg
        self.workspace.instance.cfb = cfb

        # trigger callbacks
        self.workspace.reload()
        self.workspace.on_cfg_generated()

    def _save_database(self, file_path):
        if self.workspace.instance is None or self.workspace.instance.project is None:
            return False

        if AngrDB is None:
            QMessageBox.critical(
                None, 'Error',
                'AngrDB is not enabled. Maybe you do not have SQLAlchemy installed?'
            )
            return False

        angrdb = AngrDB(project=self.workspace.instance.project)
        angrdb.dump(file_path)

        self.workspace.instance.database_path = file_path
        return True

    def _recalculate_view_sizes(self, old_size):
        adjustable_dockable_views = [
            dock for dock in self.workspace.view_manager.docks
            if dock.widget().default_docking_position in (
                'left',
                'bottom',
            )
        ]

        if not adjustable_dockable_views:
            return

        for dock in adjustable_dockable_views:
            widget = dock.widget()

            if old_size.width() < 0:
                dock.old_size = widget.sizeHint()
                continue

            if old_size != self.size():
                # calculate the width ratio

                if widget.default_docking_position == 'left':
                    # we want to adjust the width
                    ratio = widget.old_width * 1.0 / old_size.width()
                    new_width = int(self.width() * ratio)
                    widget.width_hint = new_width
                    widget.updateGeometry()
                elif widget.default_docking_position == 'bottom':
                    # we want to adjust the height
                    ratio = widget.old_height * 1.0 / old_size.height()
                    new_height = int(self.height() * ratio)
                    widget.height_hint = new_height
                    widget.updateGeometry()

                dock.old_size = widget.size()

    def _resize_dock_widget(self, dock_widget, new_width, new_height):

        original_size = dock_widget.size()
        original_min = dock_widget.minimumSize()
        original_max = dock_widget.maximumSize()

        dock_widget.resize(new_width, new_height)

        if new_width != original_size.width():
            if original_size.width() > new_width:
                dock_widget.setMaximumWidth(new_width)
            else:
                dock_widget.setMinimumWidth(new_width)

        if new_height != original_size.height():
            if original_size.height() > new_height:
                dock_widget.setMaximumHeight(new_height)
            else:
                dock_widget.setMinimumHeight(new_height)

        dock_widget.original_min = original_min
        dock_widget.original_max = original_max

        QTimer.singleShot(1, dock_widget.restore_original_size)
示例#4
0
class MainWindow(QMainWindow):
    """
    The main window of angr management.
    """
    def __init__(self,
                 app: Optional['QApplication'] = None,
                 parent=None,
                 show=True,
                 use_daemon=False):
        super().__init__(parent)

        icon_location = os.path.join(IMG_LOCATION, 'angr.png')
        self.setWindowIcon(QIcon(icon_location))

        GlobalInfo.main_window = self

        # initialization
        self.setMinimumSize(QSize(400, 400))
        self.setDockNestingEnabled(True)

        self.app: Optional['QApplication'] = app
        self.workspace: Workspace = None
        self.central_widget: QMainWindow = None

        self.toolbar_manager: ToolbarManager = ToolbarManager(self)
        self._progressbar = None  # type: QProgressBar
        self._progress_dialog = None  # type: QProgressDialog
        self._load_binary_dialog = None

        self.defaultWindowFlags = None

        # menus
        self._file_menu = None  # FileMenu
        self._analyze_menu = None
        self._view_menu = None
        self._help_menu = None
        self._plugin_menu = None

        self._init_statusbar()
        self._init_workspace()
        self._init_toolbars()
        self._init_menus()
        self._init_plugins()
        self._init_library_docs()
        self._init_url_scheme_handler()

        self.workspace.plugins.on_workspace_initialized(self)

        self._init_shortcuts()
        self._init_flirt_signatures()

        self._run_daemon(use_daemon=use_daemon)

        # I'm ready to show off!
        if show:
            self.showMaximized()
            self.windowHandle().screenChanged.connect(self.on_screen_changed)
            self.show()

        self.status = "Ready."

    def sizeHint(self, *args, **kwargs):  # pylint: disable=unused-argument,no-self-use
        return QSize(1200, 800)

    #
    # Properties
    #

    @property
    def caption(self):
        return self.getWindowTitle()

    @caption.setter
    def caption(self, v):
        self.setWindowTitle(v)

    #
    # Dialogs
    #

    def open_mainfile_dialog(self):
        # pylint: disable=assigning-non-slot
        # https://github.com/PyCQA/pylint/issues/3793
        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "Open a binary",
            Conf.last_used_directory,
            "All executables (*);;"
            "Windows PE files (*.exe);;"
            "Core Dumps (*.core);;"
            "angr database (*.adb)",
        )
        Conf.last_used_directory = os.path.dirname(file_path)
        return file_path

    def _pick_image_dialog(self):
        try:
            prompt = LoadDockerPrompt(parent=self)
        except LoadDockerPromptError:
            return None
        if prompt.exec_() == 0:
            return None  # User canceled
        return prompt.textValue()

    def open_load_plugins_dialog(self):
        dlg = LoadPlugins(self.workspace.plugins)
        dlg.setModal(True)
        dlg.exec_()

    def open_newstate_dialog(self):
        if self.workspace.instance.project.am_none:
            QMessageBox.critical(self, "Cannot create new states",
                                 "Please open a binary to analyze first.")
            return
        new_state_dialog = NewState(self.workspace.instance,
                                    parent=self,
                                    create_simgr=True)
        new_state_dialog.exec_()

    def open_doc_link(self):
        QDesktopServices.openUrl(
            QUrl("https://docs.angr.io/", QUrl.TolerantMode))

    def open_about_dialog(self):
        dlg = LoadAboutDialog()
        dlg.exec_()

    #
    # Widgets
    #

    def _init_statusbar(self):
        self._progressbar = QProgressBar()

        self._progressbar.setMinimum(0)
        self._progressbar.setMaximum(100)
        self._progressbar.hide()

        self.statusBar().addPermanentWidget(self._progressbar)

        self._progress_dialog = QProgressDialog("Waiting...", "Cancel", 0, 100,
                                                self)
        self._progress_dialog.setAutoClose(False)
        self._progress_dialog.setWindowFlags(
            self._progress_dialog.windowFlags()
            & ~Qt.WindowContextHelpButtonHint)
        self._progress_dialog.setModal(True)
        self._progress_dialog.setMinimumDuration(2**31 - 1)

        def on_cancel():
            if self.workspace is None:
                return
            for job in self.workspace.instance.jobs:
                if job.blocking:
                    job.keyboard_interrupt()
                    break

        self._progress_dialog.canceled.connect(on_cancel)
        self._progress_dialog.close()

    def _init_toolbars(self):
        for cls in (FileToolbar, DebugToolbar):
            self.toolbar_manager.show_toolbar_by_class(cls)

    #
    # Menus
    #

    def _init_menus(self):
        self._file_menu = FileMenu(self)
        self._analyze_menu = AnalyzeMenu(self)
        self._view_menu = ViewMenu(self)
        self._help_menu = HelpMenu(self)
        self._plugin_menu = PluginMenu(self)

        for path in Conf.recent_files:
            self._file_menu.add_recent(path)

        # TODO: Eventually fix menu bars to have native support on MacOS
        # if on a Mac, don't use the native menu bar (bug mitigation from QT)
        if sys.platform == 'darwin':
            self.menuBar().setNativeMenuBar(False)

        self.menuBar().addMenu(self._file_menu.qmenu())
        self.menuBar().addMenu(self._view_menu.qmenu())
        self.menuBar().addMenu(self._analyze_menu.qmenu())
        self.menuBar().addMenu(self._plugin_menu.qmenu())
        self.menuBar().addMenu(self._help_menu.qmenu())

    #
    # Workspace
    #

    def _init_workspace(self):
        """
        Initialize workspace

        :return:    None
        """
        self.central_widget = QMainWindow()
        self.setCentralWidget(self.central_widget)
        wk = Workspace(self, Instance())
        self.workspace = wk
        self.workspace.view_manager.tabify_center_views()
        self.central_widget.setTabPosition(Qt.RightDockWidgetArea,
                                           QTabWidget.North)
        self.central_widget.setDockNestingEnabled(True)

        def set_caption(**kwargs):  # pylint: disable=unused-argument
            if self.workspace.instance.project.am_none:
                self.caption = ''
            elif self.workspace.instance.project.filename is None:
                self.caption = "Loaded from stream"
            else:
                self.caption = os.path.basename(
                    self.workspace.instance.project.filename)

        self.workspace.instance.project.am_subscribe(set_caption)

        self.tab = self.central_widget.findChild(QTabBar)
        self.tab.tabBarClicked.connect(self.on_center_tab_clicked)

    #
    # Shortcuts
    #

    def interrupt_current_job(self):
        self.workspace.instance.interrupt_current_job()

    def _init_shortcuts(self):
        """
        Initialize shortcuts

        :return:    None
        """

        center_dockable_views = self.workspace.view_manager.get_center_views()
        for i in range(1, len(center_dockable_views) + 1):
            QShortcut(QKeySequence('Ctrl+' + str(i)), self,
                      center_dockable_views[i - 1].raise_)

        QShortcut(QKeySequence("Ctrl+I"), self, self.interrupt_current_job)

        # Raise the DisassemblyView after everything has initialized
        center_dockable_views[0].raise_()

        # Toggle exec breakpoint
        QShortcut(QKeySequence(Qt.Key_F2), self,
                  self.workspace.toggle_exec_breakpoint)

        # Single step
        QShortcut(QKeySequence(Qt.Key_F7), self, self.workspace.step_forward)

        # Run
        QShortcut(QKeySequence(Qt.Key_F9), self,
                  self.workspace.continue_forward)

    #
    # Plugins
    #

    def _init_plugins(self):
        self.workspace.plugins.discover_and_initialize_plugins()

    #
    # FLIRT Signatures
    #

    def _init_flirt_signatures(self):
        if Conf.flirt_signatures_root:
            # if it's a relative path, it's relative to the angr-management package
            if os.path.isabs(Conf.flirt_signatures_root):
                flirt_signatures_root = Conf.flirt_signatures_root
            else:
                if is_pyinstaller():
                    flirt_signatures_root = os.path.join(
                        app_root(), Conf.flirt_signatures_root)
                else:
                    # when running as a Python package, we should use the git submodule, which is on the same level
                    # with (instead of inside) the angrmanagement module directory.
                    flirt_signatures_root = os.path.join(
                        app_root(), "..", Conf.flirt_signatures_root)
            flirt_signatures_root = os.path.normpath(flirt_signatures_root)
            _l.info("Loading FLIRT signatures from %s.", flirt_signatures_root)
            angr.flirt.load_signatures(flirt_signatures_root)

    #
    # Library docs
    #

    def _init_library_docs(self):
        GlobalInfo.library_docs = LibraryDocs()
        if Conf.library_docs_root:
            GlobalInfo.library_docs.load_func_docs(Conf.library_docs_root)

    #
    # Daemon
    #

    def _run_daemon(self, use_daemon=None):

        if use_daemon is None:
            # Load it from the configuration file
            use_daemon = Conf.use_daemon

        if not use_daemon:
            return

        # connect to daemon (if there is one)
        if not daemon_exists():
            print("[+] Starting a new daemon.")
            run_daemon_process()
            time.sleep(0.2)
        else:
            print("[+] Connecting to an existing angr management daemon.")

        while True:
            try:
                GlobalInfo.daemon_conn = daemon_conn(service=ClientService)
            except ConnectionRefusedError:
                print("[-] Connection failed... try again.")
                time.sleep(0.4)
                continue
            print("[+] Connected to daemon.")
            break

        from rpyc import BgServingThread  # pylint:disable=import-outside-toplevel
        _ = BgServingThread(GlobalInfo.daemon_conn)

    #
    # URL scheme handler setup
    #

    def _init_url_scheme_handler(self):
        # URL scheme
        from ..logic.url_scheme import AngrUrlScheme  # pylint:disable=import-outside-toplevel

        scheme = AngrUrlScheme()
        registered, _ = scheme.is_url_scheme_registered()
        supported = scheme.is_url_scheme_supported()
        checrs_plugin = self.workspace.plugins.get_plugin_instance_by_name(
            "ChessConnector")
        if checrs_plugin is None:
            return

        if not registered and supported:
            btn = QMessageBox.question(
                None,
                "Setting up angr URL scheme",
                "angr URL scheme allows \"deep linking\" from browsers and other applications "
                "by registering the angr:// protocol to the current user. Do you want to "
                "register it? You may unregister at any "
                "time in Preferences.",
                defaultButton=QMessageBox.Yes)
            if btn == QMessageBox.Yes:
                try:
                    AngrUrlScheme().register_url_scheme()
                except (ValueError, FileNotFoundError) as ex:
                    QMessageBox.warning(
                        None, "Error in registering angr URL scheme",
                        "Failed to register the angr URL scheme.\n"
                        "The following exception occurred:\n" + str(ex))

    #
    # Event
    #

    def resizeEvent(self, event: QResizeEvent):
        """

        :param event:
        :return:
        """

        self._recalculate_view_sizes(event.oldSize())

    def closeEvent(self, event):

        # Ask if the user wants to save things
        if self.workspace.instance is not None and not self.workspace.instance.project.am_none:
            msgbox = QMessageBox()
            msgbox.setWindowTitle("Save database")
            msgbox.setText(
                "angr management is about to exit. Do you want to save the database?"
            )
            msgbox.setIcon(QMessageBox.Question)
            msgbox.setWindowIcon(self.windowIcon())
            msgbox.setStandardButtons(QMessageBox.Yes | QMessageBox.No
                                      | QMessageBox.Cancel)
            msgbox.setDefaultButton(QMessageBox.Yes)
            r = msgbox.exec_()

            if r == QMessageBox.Cancel:
                event.ignore()
                return
            elif r == QMessageBox.Yes:
                save_r = self.save_database()
                if not save_r:
                    # failed to save the database
                    event.ignore()
                    return

        for plugin in list(self.workspace.plugins.active_plugins):
            self.workspace.plugins.deactivate_plugin(plugin)
        event.accept()

    def event(self, event):

        if event.type() == QEvent.User:
            # our event callback

            try:
                event.result = event.execute()
            except Exception as ex:  # pylint:disable=broad-except
                event.exception = ex
            event.event.set()

            return True

        return super().event(event)

    def on_screen_changed(self, screen):
        """
        When changing from one screen to another, ask disassembly views to refresh in case the DPI is changed.
        """
        self.workspace.current_screen.am_obj = screen
        self.workspace.current_screen.am_event()

    def on_center_tab_clicked(self, index):
        self.workspace.view_manager.handle_center_tab_click(index)

    #
    # Actions
    #

    def reload(self):
        self.workspace.reload()

    def open_file_button(self):
        file_path = self.open_mainfile_dialog()
        if not file_path:
            return
        self.load_file(file_path)

    def open_trace_file_button(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "Open a trace file",
            Conf.last_used_directory,
            "All files (*);;"
            "Trace files (*.trace);;",
        )
        Conf.last_used_directory = os.path.dirname(file_path)
        if not file_path:
            return
        self.load_trace_file(file_path)

    def open_docker_button(self):
        required = {
            'archr: git clone https://github.com/angr/archr && cd archr && pip install -e .':
            archr,
            'keystone: pip install --no-binary keystone-engine keystone-engine':
            keystone
        }
        is_missing = [key for key, value in required.items() if value is None]
        if len(is_missing) > 0:
            req_msg = 'You need to install the following:\n\n\t' + '\n\t'.join(
                is_missing)
            req_msg += '\n\nInstall them to enable this functionality.'
            req_msg += '\nRelaunch angr-management after install.'
            QMessageBox(self).critical(None, 'Dependency error', req_msg)
            return

        img_name = self._pick_image_dialog()
        if img_name is None:
            return
        target = archr.targets.DockerImageTarget(img_name, target_path=None)
        self.workspace.instance.add_job(LoadTargetJob(target))
        self.workspace.instance.img_name = img_name

    def load_trace_file(self, file_path):
        if isurl(file_path):
            QMessageBox.critical(
                self, "Unsupported Action",
                "Downloading trace files is not yet supported."
                "Please specify a path to a file on disk.")
        else:
            # File
            if os.path.isfile(file_path):
                try:
                    with open(file_path, 'rb') as f:
                        loaded_sim_state = pickle.load(f)
                        analysis_params = {
                            'end_state': loaded_sim_state,
                            'start_addr': None,
                            'end_addr': None,
                            'block_addrs': None,
                        }
                        self.workspace.view_data_dependency_graph(
                            analysis_params)
                    self._recent_file(file_path)
                except pickle.PickleError:
                    QMessageBox.critical(
                        self, "Unable to load trace file",
                        "Trace file must contain a serialized SimState.")
            else:
                QMessageBox.critical(
                    self, "File not found",
                    f"angr management cannot open file {file_path}. "
                    "Please make sure that the file exists.")

    def load_file(self, file_path):

        if not isurl(file_path):
            # file
            if os.path.isfile(file_path):
                if file_path.endswith(".trace"):
                    self.workspace.load_trace_from_path(file_path)
                    return

                self.workspace.instance.binary_path = file_path
                self.workspace.instance.original_binary_path = file_path
                if file_path.endswith(".adb"):
                    self._load_database(file_path)
                else:
                    self._recent_file(file_path)
                    self.workspace.instance.add_job(LoadBinaryJob(file_path))
            else:
                QMessageBox.critical(
                    self, "File not found",
                    f"angr management cannot open file {file_path}. "
                    "Please make sure that the file exists.")
        else:
            # url
            r = QMessageBox.question(
                self,
                "Downloading a file",
                f"Do you want to download a file from {file_path} and open it in angr management?",
                defaultButton=QMessageBox.Yes)
            if r == QMessageBox.Yes:
                try:
                    target_path = download_url(file_path,
                                               parent=self,
                                               to_file=True,
                                               file_path=None)
                except InvalidURLError:
                    QMessageBox.critical(
                        self, "Downloading failed",
                        "angr management failed to download the file. The URL is invalid."
                    )
                    return
                except UnexpectedStatusCodeError as ex:
                    QMessageBox.critical(
                        self, "Downloading failed",
                        "angr management failed to retrieve the header of the file. "
                        f"The HTTP request returned an unexpected status code {ex.status_code}."
                    )
                    return

                if target_path:
                    # open the file - now it's a local file
                    self.workspace.instance.binary_path = target_path
                    self.workspace.instance.original_binary_path = file_path
                    self.load_file(target_path)

    def load_database(self):
        # Open File window
        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "Load angr database",
            ".",
            "angr databases (*.adb)",
        )

        if not file_path:
            return

        self._load_database(file_path)

    def save_database(self):
        if self.workspace.instance is None or self.workspace.instance.project.am_none:
            return True

        if self.workspace.instance.database_path is None:
            return self.save_database_as()
        else:
            return self._save_database(self.workspace.instance.database_path)

    def save_database_as(self):

        if self.workspace.instance is None or self.workspace.instance.project.am_none:
            return False

        default_database_path = self.workspace.instance.database_path
        if default_database_path is None:
            default_database_path = os.path.normpath(
                self.workspace.instance.project.filename) + ".adb"

        # Open File window
        file_path, _ = QFileDialog.getSaveFileName(
            self,
            "Save angr database",
            default_database_path,
            "angr databases (*.adb)",
        )

        if not file_path:
            return False

        if not file_path.endswith(".adb"):
            file_path = file_path + ".adb"

        return self._save_database(file_path)

    def load_trace(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "Load trace", ".",
                                                   "bintrace (*.trace)")
        if not file_path:
            return
        self.workspace.load_trace_from_path(file_path)

    def preferences(self):

        # Open Preferences dialog
        pref = Preferences(self.workspace, parent=self)
        pref.exec_()

    def quit(self):
        self.close()

    def run_variable_recovery(self):
        self.workspace._get_or_create_disassembly_view(
        ).variable_recovery_flavor = 'accurate'

    def run_induction_variable_analysis(self):
        self.workspace._get_or_create_disassembly_view(
        ).run_induction_variable_analysis()

    def run_dependency_analysis(self,
                                func_addr: Optional[int] = None,
                                func_arg_idx: Optional[int] = None):
        if self.workspace is None or self.workspace.instance is None:
            return
        dep_analysis_job = DependencyAnalysisJob(func_addr=func_addr,
                                                 func_arg_idx=func_arg_idx)
        self.workspace.instance.add_job(dep_analysis_job)

    def decompile_current_function(self):
        if self.workspace is not None:
            self.workspace.decompile_current_function()

    def view_proximity_for_current_function(self):
        if self.workspace is not None:
            self.workspace.view_proximity_for_current_function()

    def interact(self):
        self.workspace.interact_program(self.workspace.instance.img_name)

    #
    # Other public methods
    #

    def progress(self, status, progress):
        self.statusBar().showMessage(f'Working... {status}')
        self._progress_dialog.setLabelText(status)
        self._progressbar.show()
        self._progressbar.setValue(progress)
        self._progress_dialog.setValue(progress)

    def progress_done(self):
        self._progressbar.hide()
        self.statusBar().showMessage("Ready.")
        self._progress_dialog.hide()

    def bring_to_front(self):
        self.setWindowState((self.windowState() & ~Qt.WindowMinimized)
                            | Qt.WindowActive)
        self.activateWindow()
        self.raise_()

    #
    # Private methods
    #

    def _recent_file(self, file_path):
        file_path = os.path.abspath(file_path)
        self._file_menu.add_recent(file_path)
        Conf.recent_file(file_path)
        save_config()

    def _load_database(self, file_path):

        if AngrDB is None:
            QMessageBox.critical(
                None, 'Error',
                'AngrDB is not enabled. Maybe you do not have SQLAlchemy installed?'
            )
            return

        angrdb = AngrDB()
        other_kbs = {}
        extra_info = {}
        try:
            proj = angrdb.load(file_path,
                               kb_names=["global", "pseudocode_variable_kb"],
                               other_kbs=other_kbs,
                               extra_info=extra_info)
        except angr.errors.AngrIncompatibleDBError as ex:
            QMessageBox.critical(
                None, 'Error',
                "Failed to load the angr database because of compatibility issues.\n"
                f"Details: {ex}")
            return
        except angr.errors.AngrDBError as ex:
            QMessageBox.critical(
                None, 'Error', 'Failed to load the angr database.\n'
                f'Details: {ex}')
            _l.critical("Failed to load the angr database.", exc_info=True)
            return

        self._recent_file(file_path)

        cfg = proj.kb.cfgs['CFGFast']
        cfb = proj.analyses.CFB()  # it will load functions from kb

        self.workspace.instance.database_path = file_path

        self.workspace.instance._reset_containers()
        self.workspace.instance.project = proj
        self.workspace.instance.cfg = cfg
        self.workspace.instance.cfb = cfb
        if "pseudocode_variable_kb" in other_kbs:
            self.workspace.instance.pseudocode_variable_kb = other_kbs[
                "pseudocode_variable_kb"]
        else:
            self.workspace.instance.initialize_pseudocode_variable_kb()
        self.workspace.instance.project.am_event(initialized=True)

        # trigger callbacks
        self.workspace.reload()
        self.workspace.on_cfg_generated()
        self.workspace.plugins.angrdb_load_entries(extra_info)

    def _save_database(self, file_path):
        if self.workspace.instance is None or self.workspace.instance.project.am_none:
            return False

        if AngrDB is None:
            QMessageBox.critical(
                None, 'Error',
                'AngrDB is not enabled. Maybe you do not have SQLAlchemy installed?'
            )
            return False

        self.workspace.plugins.handle_project_save(file_path)

        angrdb = AngrDB(project=self.workspace.instance.project)
        extra_info = self.workspace.plugins.angrdb_store_entries()
        angrdb.dump(
            file_path,
            kbs=[
                self.workspace.instance.kb,
                self.workspace.instance.pseudocode_variable_kb,
            ],
            extra_info=extra_info,
        )

        self.workspace.instance.database_path = file_path
        return True

    def _recalculate_view_sizes(self, old_size):
        adjustable_dockable_views = [
            dock for dock in self.workspace.view_manager.docks
            if dock.widget().default_docking_position in (
                'left',
                'bottom',
            )
        ]

        if not adjustable_dockable_views:
            return

        for dock in adjustable_dockable_views:
            widget = dock.widget()

            if old_size.width() < 0:
                dock.old_size = widget.sizeHint()
                continue

            if old_size != self.size():
                # calculate the width ratio

                if widget.default_docking_position == 'left':
                    # we want to adjust the width
                    ratio = widget.old_width * 1.0 / old_size.width()
                    new_width = int(self.width() * ratio)
                    widget.width_hint = new_width
                    widget.updateGeometry()
                elif widget.default_docking_position == 'bottom':
                    # we want to adjust the height
                    ratio = widget.old_height * 1.0 / old_size.height()
                    new_height = int(self.height() * ratio)
                    widget.height_hint = new_height
                    widget.updateGeometry()

                dock.old_size = widget.size()

    def _resize_dock_widget(self, dock_widget, new_width, new_height):

        original_size = dock_widget.size()
        original_min = dock_widget.minimumSize()
        original_max = dock_widget.maximumSize()

        dock_widget.resize(new_width, new_height)

        if new_width != original_size.width():
            if original_size.width() > new_width:
                dock_widget.setMaximumWidth(new_width)
            else:
                dock_widget.setMinimumWidth(new_width)

        if new_height != original_size.height():
            if original_size.height() > new_height:
                dock_widget.setMaximumHeight(new_height)
            else:
                dock_widget.setMinimumHeight(new_height)

        dock_widget.original_min = original_min
        dock_widget.original_max = original_max

        QTimer.singleShot(1, dock_widget.restore_original_size)
class MainWindow(QMainWindow):
    """
    The main window of angr management.
    """
    def __init__(self, file_to_open=None, parent=None):
        super(MainWindow, self).__init__(parent)

        icon_location = os.path.join(IMG_LOCATION, 'angr.png')
        self.setWindowIcon(QIcon(icon_location))

        GlobalInfo.main_window = self

        # initialization
        self.caption = "angr Management"
        self.setMinimumSize(QSize(400, 400))
        self.setDockNestingEnabled(True)

        self.workspace = None
        self.central_widget = None
        self._plugin_mgr = None  # type: PluginManager

        self._file_toolbar = None  # type: FileToolbar
        self._states_toolbar = None  # type: StatesToolbar
        self._analysis_toolbar = None  # type: AnalysisToolbar
        self._progressbar = None  # type: QProgressBar
        self._load_binary_dialog = None

        self._status = ""
        self._progress = None

        self.defaultWindowFlags = None

        # menus
        self._file_menu = None
        self._analyze_menu = None
        self._view_menu = None
        self._help_menu = None
        self._plugin_menu = None
        self._sync_menu = None

        self._init_toolbars()
        self._init_statusbar()
        self._init_workspace()
        self._init_shortcuts()
        self._init_menus()
        self._init_plugins()

        self.showMaximized()

        # I'm ready to show off!
        self.show()

        self.status = "Ready."

        if file_to_open is not None:
            self.load_file(file_to_open)

    def sizeHint(self, *args, **kwargs):
        return QSize(1200, 800)

    #
    # Properties
    #

    @property
    def caption(self):
        return self.getWindowTitle()

    @caption.setter
    def caption(self, v):
        self.setWindowTitle(v)

    @property
    def status(self):
        return self._status

    @status.setter
    def status(self, v):
        self._status = v

        self.statusBar().showMessage(v)

    @property
    def progress(self):
        return self._progress

    @progress.setter
    def progress(self, v):
        self._progress = v
        self._progressbar.show()
        self._progressbar.setValue(v)

    #
    # Dialogs
    #

    def _open_mainfile_dialog(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "Open a binary", ".",
                                                   "All executables (*);;Windows PE files (*.exe);;Core Dumps (*.core);;angr database (*.adb)",
                                                   )
        return file_path

    def _pick_image_dialog(self):
        try:
            prompt = LoadDockerPrompt()
        except LoadDockerPromptError:
            return
        if prompt.exec_() == 0:
            return # User canceled
        return prompt.textValue()

    def open_load_plugins_dialog(self):
        try:
            dlg = LoadPlugins(self._plugin_mgr)
            dlg.setModal(True)
            dlg.exec_()

        except LoadPluginsError:
            pass

    def open_newstate_dialog(self):
        new_state_dialog = NewState(self.workspace.instance, parent=self)
        new_state_dialog.exec_()

    def open_doc_link(self):
        QDesktopServices.openUrl(QUrl("https://docs.angr.io/", QUrl.TolerantMode))

    def open_about_dialog(self):
        QMessageBox.about(self, "About angr", "Version 8")

    def open_sync_config_dialog(self):
        if self.workspace.instance.project is None:
            # project does not exist yet
            return

        sync_config = SyncConfig(self.workspace.instance, parent=self)
        sync_config.exec_()

    #
    # Widgets
    #

    def _init_statusbar(self):

        self._progressbar = QProgressBar()

        self._progressbar.setMinimum(0)
        self._progressbar.setMaximum(100)
        self._progressbar.hide()

        self.statusBar().addPermanentWidget(self._progressbar)

    def _init_toolbars(self):

        self._file_toolbar = FileToolbar(self)
        self._states_toolbar = StatesToolbar(self)
        self._analysis_toolbar = AnalysisToolbar(self)

        self.addToolBar(Qt.TopToolBarArea, self._file_toolbar.qtoolbar())
        self.addToolBar(Qt.TopToolBarArea, self._states_toolbar.qtoolbar())
        self.addToolBar(Qt.TopToolBarArea, self._analysis_toolbar.qtoolbar())

    #
    # Menus
    #

    def _init_menus(self):
        self._file_menu = FileMenu(self)
        self._analyze_menu = AnalyzeMenu(self)
        self._view_menu = ViewMenu(self)
        self._help_menu = HelpMenu(self)
        self._plugin_menu = PluginMenu(self)

        self.menuBar().addMenu(self._file_menu.qmenu())
        self.menuBar().addMenu(self._view_menu.qmenu())
        self.menuBar().addMenu(self._analyze_menu.qmenu())
        if has_binsync():
            self._sync_menu = SyncMenu(self)
            self.menuBar().addMenu(self._sync_menu.qmenu())
            def on_load(**kwargs):
                self._sync_menu.action_by_key("config").enable()
            self.workspace.instance._project_container.am_subscribe(on_load)
        self.menuBar().addMenu(self._plugin_menu.qmenu())
        self.menuBar().addMenu(self._help_menu.qmenu())

    #
    # Workspace
    #

    def _init_workspace(self):
        """
        Initialize workspace

        :return:    None
        """

        self.central_widget_main = QSplitter(Qt.Horizontal)
        self.setCentralWidget(self.central_widget_main)
        self.central_widget = QMainWindow()
        self.central_widget2 = QMainWindow()
        self.central_widget_main.addWidget(self.central_widget)
        self.central_widget_main.addWidget(self.central_widget2)
        wk = Workspace(self, Instance())
        self.workspace = wk
        self.workspace.view_manager.tabify_center_views()
        self.central_widget.setTabPosition(Qt.RightDockWidgetArea, QTabWidget.North)

    #
    # Shortcuts
    #

    def _init_shortcuts(self):
        """
        Initialize shortcuts

        :return:    None
        """

        center_dockable_views = self.workspace.view_manager.get_center_views()
        for i in range(1, len(center_dockable_views)+1):
            QShortcut(QKeySequence('Ctrl+'+str(i)), self, center_dockable_views[i-1].raise_)

        # Raise the DisassemblyView after everything has initialized
        center_dockable_views[0].raise_()

    #
    # PluginManager
    #

    def _init_plugins(self):
        self._plugin_mgr = PluginManager(self.workspace, autoload=True)


    #
    # Event
    #

    def resizeEvent(self, event):
        """

        :param QResizeEvent event:
        :return:
        """

        self._recalculate_view_sizes(event.oldSize())

    def closeEvent(self, event):
        self._plugin_mgr.stop_all_plugin_threads()
        event.accept()

    def event(self, event):

        if event.type() == QEvent.User:
            # our event callback

            try:
                event.result = event.execute()
            except Exception as e:
                event.exception = e
            event.event.set()

            return True

        return super(MainWindow, self).event(event)

    #
    # Actions
    #

    def reload(self):
        self.workspace.reload()

    def open_file_button(self):
        file_path = self._open_mainfile_dialog()
        self.load_file(file_path)

    def open_docker_button(self):
        required = {
            'archr: git clone https://github.com/angr/archr && cd archr && pip install -e .':archr,
            'keystone: pip install --no-binary keystone-engine keystone-engine':keystone
            }
        is_missing = [ key for key, value in required.items() if value is None ]
        if len(is_missing) > 0:
            req_msg = 'You need to install the following:\n\n\t' + '\n\t'.join(is_missing)
            req_msg += '\n\nInstall them to enable this functionality.'
            req_msg += '\nRelaunch angr-management after install.'
            QMessageBox(self).critical(None, 'Dependency error', req_msg)
            return

        img_name = self._pick_image_dialog()
        if img_name is None:
            return
        target = archr.targets.DockerImageTarget(img_name, target_path=None)
        self.workspace.instance.add_job(LoadTargetJob(target))
        self.workspace.instance.set_image(img_name)

    def load_file(self, file_path):
        if os.path.isfile(file_path):
            if file_path.endswith(".adb"):
                self.load_database(file_path)
            else:
                self.workspace.instance.add_job(LoadBinaryJob(file_path))

    def save_database(self):
        if self.workspace.instance.database_path is None:
            self.save_database_as()
        else:
            self._save_database(self.workspace.instance.database_path)

    def save_database_as(self):

        # Open File window
        file_path, _ = QFileDialog.getSaveFileName(
            self, "Save angr database", ".",
            "angr databases (*.adb)",
        )

        if not file_path.endswith(".adb"):
            file_path = file_path + ".adb"

        self._save_database(file_path)

    def quit(self):
        self.close()

    def run_variable_recovery(self):
        self.workspace.view_manager.first_view_in_category('disassembly').variable_recovery_flavor = 'accurate'

    def run_induction_variable_analysis(self):
        self.workspace.view_manager.first_view_in_category('disassembly').run_induction_variable_analysis()

    def decompile_current_function(self):
        if self.workspace is not None:
            self.workspace.decompile_current_function()

    def interact(self):
        self.workspace.interact_program(self.workspace.instance.img_name)

    def setup_sync(self):
        self.open_sync_config_dialog()

    #
    # Other public methods
    #

    def progress_done(self):
        self._progress = None
        self._progressbar.hide()

    #
    # Private methods
    #

    def _load_database(self, file_path):
        with open(file_path, "rb") as o:
            p,cfg,cfb = pickle.load(o)
        self.workspace.instance.project = p
        self.workspace.instance.cfg = cfg
        self.workspace.instance.cfb = cfb
        self.workspace.reload()
        self.workspace.on_cfg_generated()
        self.workspace.instance.database_path = file_path
        print("DATABASE %s LOADED" % file_path)

    def _save_database(self, file_path):
        with open(file_path, "wb") as o:
            pickle.dump((self.workspace.instance.project, self.workspace.instance.cfg, self.workspace.instance.cfb), o)
        self.workspace.instance.database_path = file_path
        print("DATABASE %s SAVED" % file_path)

    def _recalculate_view_sizes(self, old_size):
        adjustable_dockable_views = [dock for dock in self.workspace.view_manager.docks
                                     if dock.widget().default_docking_position in ('left', 'bottom', )]

        if not adjustable_dockable_views:
            return

        for dock in adjustable_dockable_views:
            widget = dock.widget()

            if old_size.width() < 0:
                dock.old_size = widget.sizeHint()
                continue

            if old_size != self.size():
                # calculate the width ratio

                if widget.default_docking_position == 'left':
                    # we want to adjust the width
                    ratio = widget.old_width * 1.0 / old_size.width()
                    new_width = int(self.width() * ratio)
                    widget.width_hint = new_width
                    widget.updateGeometry()
                elif widget.default_docking_position == 'bottom':
                    # we want to adjust the height
                    ratio = widget.old_height * 1.0 / old_size.height()
                    new_height = int(self.height() * ratio)
                    widget.height_hint = new_height
                    widget.updateGeometry()

                dock.old_size = widget.size()

    def _resize_dock_widget(self, dock_widget, new_width, new_height):

        original_size = dock_widget.size()
        original_min = dock_widget.minimumSize()
        original_max = dock_widget.maximumSize()

        dock_widget.resize(new_width, new_height)

        if new_width != original_size.width():
            if original_size.width() > new_width:
                dock_widget.setMaximumWidth(new_width)
            else:
                dock_widget.setMinimumWidth(new_width)

        if new_height != original_size.height():
            if original_size.height() > new_height:
                dock_widget.setMaximumHeight(new_height)
            else:
                dock_widget.setMinimumHeight(new_height)

        dock_widget.original_min = original_min
        dock_widget.original_max = original_max

        QTimer.singleShot(1, dock_widget.restore_original_size)
示例#6
0
class MainWindow(QMainWindow):
    """
    The main window of angr management.
    """
    def __init__(self, file_to_open=None, parent=None):
        super(MainWindow, self).__init__(parent)

        icon_location = os.path.join(IMG_LOCATION, 'angr.png')
        self.setWindowIcon(QIcon(icon_location))

        GlobalInfo.main_window = self

        # initialization
        self.caption = "angr Management"
        self.setMinimumSize(QSize(400, 400))
        self.setDockNestingEnabled(True)

        self.workspace = None
        self.central_widget = None

        self._file_toolbar = None  # type: FileToolbar
        self._states_toolbar = None  # type: StatesToolbar
        self._analysis_toolbar = None  # type: AnalysisToolbar
        self._progressbar = None  # type: QProgressBar
        self._load_binary_dialog = None

        self._status = ""
        self._progress = None

        self._init_menus()
        self._init_toolbars()
        self._init_statusbar()
        self._init_workspace()

        self.showMaximized()

        # I'm ready to show off!
        self.show()

        self.status = "Ready."

        if file_to_open is not None:
            # load a binary
            self._open_loadbinary_dialog(file_to_open)

    #
    # Properties
    #

    @property
    def caption(self):
        return self.getWindowTitle()

    @caption.setter
    def caption(self, v):
        self.setWindowTitle(v)

    @property
    def status(self):
        return self._status

    @status.setter
    def status(self, v):
        self._status = v

        self.statusBar().showMessage(v)

    @property
    def progress(self):
        return self._progress

    @progress.setter
    def progress(self, v):
        self._progress = v
        self._progressbar.show()
        self._progressbar.setValue(v)

    #
    # Dialogs
    #

    def _open_loadbinary_dialog(self, file_to_open):
        try:
            self._load_binary_dialog = LoadBinary(file_to_open)
            self._load_binary_dialog.setModal(True)
            self._load_binary_dialog.exec_()

            if self._load_binary_dialog.cfg_args is not None:
                # load the binary
                self._load_binary(
                    file_to_open,
                    load_options=self._load_binary_dialog.load_options,
                    cfg_args=self._load_binary_dialog.cfg_args)
        except LoadBinaryError:
            pass

    def open_newstate_dialog(self):
        new_state_dialog = NewState(self.workspace, parent=self)
        new_state_dialog.exec_()

    #
    # Widgets
    #

    def _init_statusbar(self):

        self._progressbar = QProgressBar()

        self._progressbar.setMinimum(0)
        self._progressbar.setMaximum(100)
        self._progressbar.hide()

        self.statusBar().addPermanentWidget(self._progressbar)

    def _init_toolbars(self):

        self._file_toolbar = FileToolbar(self)
        self._states_toolbar = StatesToolbar(self)
        self._analysis_toolbar = AnalysisToolbar(self)

        self.addToolBar(Qt.TopToolBarArea, self._file_toolbar.qtoolbar())
        self.addToolBar(Qt.TopToolBarArea, self._states_toolbar.qtoolbar())
        self.addToolBar(Qt.TopToolBarArea, self._analysis_toolbar.qtoolbar())

    #
    # Menus
    #

    def _init_menus(self):
        fileMenu = FileMenu(self)
        self.menuBar().addMenu(fileMenu.qmenu())

    #
    # Workspace
    #

    def _init_workspace(self):
        self.central_widget = QMainWindow()
        self.setCentralWidget(self.central_widget)

        wk = Workspace(self, Instance())
        self.workspace = wk

        right_dockable_views = [
            dock for dock in self.workspace.dockable_views
            if dock.widget().default_docking_position == 'right'
        ]

        for d0, d1 in zip(right_dockable_views, right_dockable_views[1:]):
            self.central_widget.tabifyDockWidget(d0, d1)
        right_dockable_views[0].raise_()

        self.central_widget.setTabPosition(Qt.RightDockWidgetArea,
                                           QTabWidget.North)

    #
    # Event
    #

    def resizeEvent(self, event):
        """

        :param QResizeEvent event:
        :return:
        """

        pass
        # self._recalculate_view_sizes(event.oldSize())

    def event(self, event):

        if event.type() == QEvent.User:
            # our event callback

            try:
                event.result = event.execute()
            except Exception as e:
                event.exception = e
            event.event.set()

            return True

        return super(MainWindow, self).event(event)

    #
    # Actions
    #

    def reload(self):
        self.workspace.reload()

    def load_binary(self):

        # Open File window
        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "Open a binary",
            ".",
            "All executables (*);;Windows PE files (*.exe);;Core Dumps (*.core)",
        )

        if os.path.isfile(file_path):
            self._open_loadbinary_dialog(file_path)

    def quit(self):
        self.close()

    def run_variable_recovery(self):
        self.workspace.views_by_category['disassembly'][
            0].variable_recovery_flavor = 'accurate'

    def run_induction_variable_analysis(self):
        self.workspace.views_by_category['disassembly'][
            0].run_induction_variable_analysis()

    #
    # Other public methods
    #

    def progress_done(self):
        self._progress = None
        self._progressbar.hide()

    #
    # Private methods
    #

    def _load_binary(self, file_path, load_options=None, cfg_args=None):
        if load_options is None:
            load_options = {}

        if cfg_args is None:
            cfg_args = {}

        proj = angr.Project(file_path, load_options=load_options)
        self.workspace.instance.set_project(proj)
        self.workspace.instance.initialize(cfg_args=cfg_args)

    def _recalculate_view_sizes(self, old_size):
        adjustable_dockable_views = [
            dock for dock in self.workspace.dockable_views
            if dock.widget().default_docking_position in ('left', 'bottom',
                                                          'right')
        ]

        if not adjustable_dockable_views:
            return

        for dock in adjustable_dockable_views:
            widget = dock.widget()

            if old_size.width() < 0:
                dock.old_size = widget.sizeHint()
                continue

            if old_size != self.size():
                # calculate the width ratio

                if widget.default_docking_position == 'left':
                    # we want to adjust the width
                    ratio = dock.old_size.width() * 1.0 / old_size.width()
                    new_width = int(self.width() * ratio)
                    self._resize_dock_widget(dock, new_width, widget.height())
                elif widget.default_docking_position == 'bottom':
                    # we want to adjust the height
                    ratio = dock.old_size.height() * 1.0 / old_size.height()
                    new_height = int(self.height() * ratio)
                    self._resize_dock_widget(dock, widget.width(), new_height)

                dock.old_size = widget.size()

    def _resize_dock_widget(self, dock_widget, new_width, new_height):

        original_size = dock_widget.size()
        original_min = dock_widget.minimumSize()
        original_max = dock_widget.maximumSize()

        dock_widget.resize(new_width, new_height)

        if new_width != original_size.width():
            if original_size.width() > new_width:
                dock_widget.setMaximumWidth(new_width)
            else:
                dock_widget.setMinimumWidth(new_width)

        if new_height != original_size.height():
            if original_size.height() > new_height:
                dock_widget.setMaximumHeight(new_height)
            else:
                dock_widget.setMinimumHeight(new_height)

        dock_widget.original_min = original_min
        dock_widget.original_max = original_max

        QTimer.singleShot(1, dock_widget.restore_original_size)