├── angrmanagement ├── data │ ├── __init__.py │ ├── highlight_region.py │ ├── jobs │ │ ├── vfg_generation.py │ │ ├── prototype_finding.py │ │ ├── ddg_generation.py │ │ ├── decompile_function.py │ │ ├── simgr_step.py │ │ ├── code_tagging.py │ │ ├── simgr_explore.py │ │ ├── flirt_signature_recognition.py │ │ ├── __init__.py │ │ └── deobfuscation.py │ ├── function_graph.py │ ├── trace.py │ ├── library_docs.py │ └── breakpoint.py ├── ui │ ├── __init__.py │ ├── menus │ │ ├── __init__.py │ │ ├── plugin_menu.py │ │ ├── log_menu.py │ │ ├── help_menu.py │ │ ├── function_context_menu.py │ │ ├── analyze_menu.py │ │ ├── disasm_label_context_menu.py │ │ └── disasm_options_menu.py │ ├── documents │ │ └── __init__.py │ ├── widgets │ │ ├── block_code_objects │ │ │ └── __init__.py │ │ ├── qstate_combobox.py │ │ ├── qgraph_object.py │ │ ├── __init__.py │ │ ├── qicon_label.py │ │ ├── qfont_option.py │ │ ├── qaddress_input.py │ │ ├── state_inspector.py │ │ ├── qipython_widget.py │ │ ├── qfunction_combobox.py │ │ ├── qconstraint_viewer.py │ │ ├── filesystem_table.py │ │ └── qvextemps_viewer.py │ ├── toolbars │ │ ├── __init__.py │ │ ├── toolbar_action.py │ │ ├── file_toolbar.py │ │ ├── function_table_toolbar.py │ │ ├── feature_map_toolbar.py │ │ ├── toolbar_dock.py │ │ └── toolbar.py │ ├── dialogs │ │ ├── __init__.py │ │ ├── progress_dialog.py │ │ ├── fs_mount.py │ │ ├── input_prompt.py │ │ ├── func_doc.py │ │ ├── about.py │ │ └── env_config.py │ ├── views │ │ ├── patches_view.py │ │ ├── trace_map_view.py │ │ ├── jobs_view.py │ │ ├── log_view.py │ │ ├── __init__.py │ │ └── states_view.py │ ├── toolbar_manager.py │ └── icons.py ├── plugins │ ├── value_search │ │ ├── __init__.py │ │ ├── plugin.toml │ │ └── constants.py │ ├── precise_diffing │ │ ├── __init__.py │ │ └── plugin.toml │ ├── varec │ │ ├── __init__.py │ │ └── plugin.toml │ ├── ail2asm │ │ ├── __init__.py │ │ ├── plugin.toml │ │ └── asm_output.py │ ├── coverage │ │ ├── __init__.py │ │ └── plugin.toml │ ├── memory_checker │ │ ├── __init__.py │ │ └── plugin.toml │ ├── trace_viewer │ │ ├── __init__.py │ │ └── plugin.toml │ ├── angr_binsync │ │ ├── __init__.py │ │ └── plugin.toml │ ├── dep_viewer │ │ ├── __init__.py │ │ ├── plugin.toml │ │ └── dep_plugin.py │ ├── source_viewer │ │ ├── __init__.py │ │ └── plugin.toml │ ├── execution_statistics_viewer │ │ ├── __init__.py │ │ └── plugin.toml │ ├── source_importer │ │ ├── plugin.toml │ │ └── __init__.py │ ├── decompiler_poison │ │ └── plugin.toml │ ├── sample_plugin.py │ └── __init__.py ├── resources │ ├── images │ │ ├── angr.ico │ │ ├── angr.png │ │ ├── angr-ds.png │ │ ├── angr_16x16.png │ │ ├── angr_24x24.png │ │ ├── angr_32x32.png │ │ ├── angr_48x48.png │ │ ├── angr_64x64.png │ │ ├── error-icon.png │ │ ├── angr-splash.png │ │ ├── angr_128x128.png │ │ ├── angr_256x256.png │ │ ├── angr_512x512.png │ │ ├── warning-icon.png │ │ ├── angr_1024x1024.png │ │ ├── benchmark-icon.png │ │ ├── toolbar-forward.png │ │ ├── toolbar-previous.png │ │ ├── toolbar-show-alignment.png │ │ └── run-icon.svg │ ├── fonts │ │ ├── DejaVuSansMono.ttf │ │ ├── SourceCodePro-Bold.ttf │ │ ├── SourceCodePro-Black.ttf │ │ ├── SourceCodePro-Italic.ttf │ │ ├── SourceCodePro-Light.ttf │ │ ├── SourceCodePro-Medium.ttf │ │ ├── SourceCodePro-Regular.ttf │ │ ├── SourceCodePro-ExtraBold.ttf │ │ ├── SourceCodePro-SemiBold.ttf │ │ ├── SourceCodePro-BlackItalic.ttf │ │ ├── SourceCodePro-BoldItalic.ttf │ │ ├── SourceCodePro-ExtraLight.ttf │ │ ├── SourceCodePro-LightItalic.ttf │ │ ├── SourceCodePro-MediumItalic.ttf │ │ ├── SourceCodePro-SemiBoldItalic.ttf │ │ ├── SourceCodePro-ExtraBoldItalic.ttf │ │ └── SourceCodePro-ExtraLightItalic.ttf │ └── themes │ │ ├── Light │ │ ├── theme.css │ │ └── images │ │ │ ├── tab-menu.svg │ │ │ ├── minimize-button.svg │ │ │ ├── provenance.txt │ │ │ ├── close.svg │ │ │ ├── drawing-pin.svg │ │ │ └── drawing-pin-light.svg │ │ ├── Dark │ │ ├── images │ │ │ ├── tab-menu.svg │ │ │ ├── minimize-button.svg │ │ │ ├── provenance.txt │ │ │ ├── close.svg │ │ │ └── drawing-pin.svg │ │ └── theme.css │ │ ├── Dracula │ │ ├── images │ │ │ ├── tab-menu.svg │ │ │ ├── minimize-button.svg │ │ │ ├── provenance.txt │ │ │ ├── close.svg │ │ │ └── drawing-pin.svg │ │ └── theme.css │ │ └── Catppuccin Mocha │ │ ├── images │ │ ├── tab-menu.svg │ │ ├── minimize-button.svg │ │ ├── provenance.txt │ │ ├── close.svg │ │ └── drawing-pin.svg │ │ └── theme.css ├── logic │ ├── disassembly │ │ ├── __init__.py │ │ └── jump_history.py │ ├── debugger │ │ └── __init__.py │ ├── commands │ │ ├── __init__.py │ │ ├── command_manager.py │ │ └── command.py │ └── __init__.py ├── __init__.py ├── consts.py ├── utils │ ├── daemon_thread.py │ ├── layout.py │ ├── cache.py │ ├── block_objects.py │ ├── func.py │ ├── edge.py │ ├── env.py │ ├── io.py │ └── cfg.py ├── errors.py ├── daemon │ └── __init__.py └── config │ ├── __init__.py │ └── config_entry.py ├── docs ├── requirements.txt ├── development │ ├── index.rst │ ├── ui.rst │ ├── testing.rst │ ├── pyinstaller.rst │ ├── plugins.rst │ └── overview.rst ├── Makefile ├── index.rst ├── make.bat └── conf.py ├── screenshots ├── disassembly.png └── decompilation.png ├── angr-management.desktop ├── start.py ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── question.yml │ ├── feature-request.yml │ └── bug-report.yml └── workflows │ ├── nightly-ci.yml │ ├── ci.yml │ ├── nightly-build.sh │ ├── release.yml │ ├── windows.yml │ └── nightly-build.yml ├── .gitmodules ├── .git-blame-ignore-revs ├── .gitignore ├── README.md ├── scripts ├── sync-vendor-packages.sh ├── get-version.py └── build-appimage.sh ├── .readthedocs.yaml ├── tests ├── conftest.py ├── test_qaddress_input.py ├── common.py └── test_undefine_code_regions.py ├── LICENSE ├── angr-management.metainfo.xml └── .pre-commit-config.yaml /angrmanagement/data/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angrmanagement/ui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angrmanagement/plugins/value_search/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /angrmanagement/plugins/precise_diffing/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | furo 2 | myst-parser 3 | sphinx 4 | sphinx-autodoc-typehints 5 | -------------------------------------------------------------------------------- /screenshots/disassembly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/screenshots/disassembly.png -------------------------------------------------------------------------------- /screenshots/decompilation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/screenshots/decompilation.png -------------------------------------------------------------------------------- /angrmanagement/resources/images/angr.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/angr.ico -------------------------------------------------------------------------------- /angrmanagement/resources/images/angr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/angr.png -------------------------------------------------------------------------------- /angrmanagement/ui/menus/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .menu import Menu 4 | 5 | __all__ = ("Menu",) 6 | -------------------------------------------------------------------------------- /angrmanagement/plugins/varec/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .varec import VaRec 4 | 5 | __all__ = ["VaRec"] 6 | -------------------------------------------------------------------------------- /angrmanagement/resources/images/angr-ds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/angr-ds.png -------------------------------------------------------------------------------- /angrmanagement/resources/images/angr_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/angr_16x16.png -------------------------------------------------------------------------------- /angrmanagement/resources/images/angr_24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/angr_24x24.png -------------------------------------------------------------------------------- /angrmanagement/resources/images/angr_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/angr_32x32.png -------------------------------------------------------------------------------- /angrmanagement/resources/images/angr_48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/angr_48x48.png -------------------------------------------------------------------------------- /angrmanagement/resources/images/angr_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/angr_64x64.png -------------------------------------------------------------------------------- /angrmanagement/resources/images/error-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/error-icon.png -------------------------------------------------------------------------------- /angrmanagement/resources/images/angr-splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/angr-splash.png -------------------------------------------------------------------------------- /angrmanagement/resources/images/angr_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/angr_128x128.png -------------------------------------------------------------------------------- /angrmanagement/resources/images/angr_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/angr_256x256.png -------------------------------------------------------------------------------- /angrmanagement/resources/images/angr_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/angr_512x512.png -------------------------------------------------------------------------------- /angrmanagement/resources/images/warning-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/warning-icon.png -------------------------------------------------------------------------------- /angrmanagement/plugins/ail2asm/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .ail2arm32 import AIL2ARM32 4 | 5 | __all__ = ["AIL2ARM32"] 6 | -------------------------------------------------------------------------------- /angrmanagement/resources/fonts/DejaVuSansMono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/fonts/DejaVuSansMono.ttf -------------------------------------------------------------------------------- /angrmanagement/resources/images/angr_1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/angr_1024x1024.png -------------------------------------------------------------------------------- /angrmanagement/resources/images/benchmark-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/benchmark-icon.png -------------------------------------------------------------------------------- /angrmanagement/resources/images/toolbar-forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/toolbar-forward.png -------------------------------------------------------------------------------- /angrmanagement/resources/fonts/SourceCodePro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/fonts/SourceCodePro-Bold.ttf -------------------------------------------------------------------------------- /angrmanagement/resources/images/toolbar-previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/toolbar-previous.png -------------------------------------------------------------------------------- /angrmanagement/plugins/coverage/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .coverage import CoveragePlugin 4 | 5 | __all__ = ["CoveragePlugin"] 6 | -------------------------------------------------------------------------------- /angrmanagement/resources/fonts/SourceCodePro-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/fonts/SourceCodePro-Black.ttf -------------------------------------------------------------------------------- /angrmanagement/resources/fonts/SourceCodePro-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/fonts/SourceCodePro-Italic.ttf -------------------------------------------------------------------------------- /angrmanagement/resources/fonts/SourceCodePro-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/fonts/SourceCodePro-Light.ttf -------------------------------------------------------------------------------- /angrmanagement/resources/fonts/SourceCodePro-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/fonts/SourceCodePro-Medium.ttf -------------------------------------------------------------------------------- /angrmanagement/resources/fonts/SourceCodePro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/fonts/SourceCodePro-Regular.ttf -------------------------------------------------------------------------------- /angrmanagement/ui/documents/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .qcodedocument import QCodeDocument 4 | 5 | __all__ = ["QCodeDocument"] 6 | -------------------------------------------------------------------------------- /angrmanagement/resources/fonts/SourceCodePro-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/fonts/SourceCodePro-ExtraBold.ttf -------------------------------------------------------------------------------- /angrmanagement/resources/fonts/SourceCodePro-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/fonts/SourceCodePro-SemiBold.ttf -------------------------------------------------------------------------------- /angrmanagement/resources/images/toolbar-show-alignment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/images/toolbar-show-alignment.png -------------------------------------------------------------------------------- /angrmanagement/plugins/memory_checker/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .memory_checker import MemoryChecker 4 | 5 | __all__ = ["MemoryChecker"] 6 | -------------------------------------------------------------------------------- /angrmanagement/resources/fonts/SourceCodePro-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/fonts/SourceCodePro-BlackItalic.ttf -------------------------------------------------------------------------------- /angrmanagement/resources/fonts/SourceCodePro-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/fonts/SourceCodePro-BoldItalic.ttf -------------------------------------------------------------------------------- /angrmanagement/resources/fonts/SourceCodePro-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/fonts/SourceCodePro-ExtraLight.ttf -------------------------------------------------------------------------------- /angrmanagement/resources/fonts/SourceCodePro-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/fonts/SourceCodePro-LightItalic.ttf -------------------------------------------------------------------------------- /angrmanagement/resources/fonts/SourceCodePro-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/fonts/SourceCodePro-MediumItalic.ttf -------------------------------------------------------------------------------- /angrmanagement/plugins/trace_viewer/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .trace_plugin import TraceViewer 4 | 5 | __all__ = [ 6 | "TraceViewer", 7 | ] 8 | -------------------------------------------------------------------------------- /angrmanagement/resources/fonts/SourceCodePro-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/fonts/SourceCodePro-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /angrmanagement/resources/fonts/SourceCodePro-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/fonts/SourceCodePro-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /angrmanagement/resources/fonts/SourceCodePro-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/angr-management/HEAD/angrmanagement/resources/fonts/SourceCodePro-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /angrmanagement/plugins/angr_binsync/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from binsync.interface_overrides.angr import BinsyncPlugin 4 | 5 | __all__ = ["BinsyncPlugin"] 6 | -------------------------------------------------------------------------------- /angrmanagement/plugins/dep_viewer/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .dep_plugin import DependencyViewer 4 | 5 | __all__ = [ 6 | "DependencyViewer", 7 | ] 8 | -------------------------------------------------------------------------------- /angrmanagement/plugins/source_viewer/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .source_viewer_plugin import SourceViewerPlugin 4 | 5 | __all__ = [ 6 | "SourceViewerPlugin", 7 | ] 8 | -------------------------------------------------------------------------------- /docs/development/index.rst: -------------------------------------------------------------------------------- 1 | Development 2 | =========== 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | 7 | overview 8 | plugins 9 | events 10 | ui 11 | testing 12 | pyinstaller 13 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Light/theme.css: -------------------------------------------------------------------------------- 1 | ads--CAutoHideDockContainer #dockAreaAutoHideButton { 2 | qproperty-icon: url("$theme/images/drawing-pin-light.svg"); 3 | qproperty-iconSize: 15px; 4 | } 5 | -------------------------------------------------------------------------------- /angr-management.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Categories=Utility; 3 | Type=Application 4 | Comment=Open-source graphical binary analysis platform 5 | Icon=angr-management 6 | Exec=angr-management 7 | Name=angr-management 8 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Dark/images/tab-menu.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Dracula/images/tab-menu.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Light/images/tab-menu.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /angrmanagement/plugins/execution_statistics_viewer/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .execution_statistics_viewer import ExecutionStatisticsViewer 4 | 5 | __all__ = [ 6 | "ExecutionStatisticsViewer", 7 | ] 8 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Catppuccin Mocha/images/tab-menu.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /start.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from __future__ import annotations 3 | 4 | import multiprocessing 5 | 6 | from angrmanagement.__main__ import main 7 | 8 | if __name__ == "__main__": 9 | multiprocessing.freeze_support() 10 | main() 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Join our Slack community 4 | url: https://angr.io/invite/ 5 | about: For questions and help with angr-management, you are invited to join the angr Slack community 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "library_docs"] 2 | path = angrmanagement/resources/library_docs 3 | url = https://github.com/angr/library_docs 4 | [submodule "flirt_signatures"] 5 | path = angrmanagement/resources/flirt_signatures 6 | url = https://github.com/angr/flirt_signatures 7 | -------------------------------------------------------------------------------- /.github/workflows/nightly-ci.yml: -------------------------------------------------------------------------------- 1 | name: Nightly CI 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | ci: 10 | uses: angr/ci-settings/.github/workflows/angr-ci.yml@master 11 | with: 12 | nightly: true 13 | secrets: inherit 14 | -------------------------------------------------------------------------------- /angrmanagement/logic/disassembly/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .info_dock import InfoDock, OperandHighlightMode 4 | from .jump_history import JumpHistory 5 | 6 | __all__ = [ 7 | "InfoDock", 8 | "JumpHistory", 9 | "OperandHighlightMode", 10 | ] 11 | -------------------------------------------------------------------------------- /angrmanagement/logic/debugger/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .debugger import Debugger, DebuggerListManager, DebuggerManager, DebuggerWatcher 4 | 5 | __all__ = [ 6 | "Debugger", 7 | "DebuggerListManager", 8 | "DebuggerManager", 9 | "DebuggerWatcher", 10 | ] 11 | -------------------------------------------------------------------------------- /angrmanagement/logic/commands/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .command import BasicCommand, Command, ViewCommand 4 | from .command_manager import CommandManager 5 | 6 | __all__ = [ 7 | "BasicCommand", 8 | "Command", 9 | "CommandManager", 10 | "ViewCommand", 11 | ] 12 | -------------------------------------------------------------------------------- /angrmanagement/data/highlight_region.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | class SynchronizedHighlightRegion: 5 | """ 6 | A region of memory to be highlighted in synchronized views. 7 | """ 8 | 9 | def __init__(self, addr: int, size: int) -> None: 10 | self.addr: int = addr 11 | self.size: int = size 12 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Dark/images/minimize-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Dracula/images/minimize-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Light/images/minimize-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /angrmanagement/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | __version__ = "9.2.191.dev0" 4 | 5 | 6 | try: 7 | # make sure qtpy (which is used in PyQodeNG.core) is using PySide6 8 | import os 9 | 10 | os.environ["QT_API"] = "pyside6" 11 | import qtpy # noqa 12 | except ImportError: 13 | # qtpy is not installed 14 | pass 15 | -------------------------------------------------------------------------------- /angrmanagement/plugins/ail2asm/plugin.toml: -------------------------------------------------------------------------------- 1 | [meta] 2 | plugin_metadata_version = 0 3 | 4 | [plugin] 5 | name = "AIL2ASM" 6 | shortname = "ail2asm" 7 | version = "0.0.0" 8 | description = "" 9 | long_description = "" 10 | platforms = ["windows", "linux", "macos"] 11 | min_angr_version = "9.0.0.0" 12 | author = "Han Dai" 13 | entrypoints = ["ail2arm32.py"] 14 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Catppuccin Mocha/images/minimize-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | ci: 12 | uses: angr/ci-settings/.github/workflows/angr-ci.yml@master 13 | pyinstaller: 14 | uses: ./.github/workflows/pyinstaller-build.yml 15 | windows: 16 | uses: ./.github/workflows/windows.yml 17 | -------------------------------------------------------------------------------- /angrmanagement/plugins/angr_binsync/plugin.toml: -------------------------------------------------------------------------------- 1 | [meta] 2 | plugin_metadata_version = 0 3 | 4 | [plugin] 5 | name = "BinSync" 6 | shortname = "binsync" 7 | version = "0.0.0" 8 | description = "" 9 | long_description = "" 10 | platforms = ["windows", "linux", "macos"] 11 | min_angr_version = "9.0.0.0" 12 | author = "The BinSync Team" 13 | entrypoints = ["__init__.py"] 14 | 15 | -------------------------------------------------------------------------------- /angrmanagement/plugins/coverage/plugin.toml: -------------------------------------------------------------------------------- 1 | [meta] 2 | plugin_metadata_version = 0 3 | 4 | [plugin] 5 | name = "Coverage Viewer" 6 | shortname = "coverage_viewer" 7 | version = "0.0.0" 8 | description = "" 9 | long_description = "" 10 | platforms = ["windows", "linux", "macos"] 11 | min_angr_version = "9.0.0.0" 12 | author = "The angr team" 13 | entrypoints = ["coverage.py"] 14 | -------------------------------------------------------------------------------- /angrmanagement/plugins/varec/plugin.toml: -------------------------------------------------------------------------------- 1 | [meta] 2 | plugin_metadata_version = 0 3 | 4 | [plugin] 5 | name = "Variable Name Recovery - Client" 6 | shortname = "varec" 7 | version = "0.0.0" 8 | description = "" 9 | long_description = "" 10 | platforms = ["windows", "linux", "macos"] 11 | min_angr_version = "9.0.0.0" 12 | author = "The angr team" 13 | entrypoints = ["varec.py"] 14 | -------------------------------------------------------------------------------- /angrmanagement/plugins/dep_viewer/plugin.toml: -------------------------------------------------------------------------------- 1 | [meta] 2 | plugin_metadata_version = 0 3 | 4 | [plugin] 5 | name = "Dependency Viewer" 6 | shortname = "dependency_viewer" 7 | version = "0.0.0" 8 | description = "" 9 | long_description = "" 10 | platforms = ["windows", "linux", "macos"] 11 | min_angr_version = "9.0.0.0" 12 | author = "The angr team" 13 | entrypoints = ["dep_plugin.py"] 14 | -------------------------------------------------------------------------------- /angrmanagement/plugins/memory_checker/plugin.toml: -------------------------------------------------------------------------------- 1 | [meta] 2 | plugin_metadata_version = 0 3 | 4 | [plugin] 5 | name = "Memory Checker" 6 | shortname = "memory_checker" 7 | version = "0.0.0" 8 | description = "" 9 | long_description = "" 10 | platforms = ["windows", "linux", "macos"] 11 | min_angr_version = "9.0.0.0" 12 | author = "The angr team" 13 | entrypoints = ["memory_checker.py"] 14 | -------------------------------------------------------------------------------- /angrmanagement/plugins/source_importer/plugin.toml: -------------------------------------------------------------------------------- 1 | [meta] 2 | plugin_metadata_version = 0 3 | 4 | [plugin] 5 | name = "Source Importer" 6 | shortname = "source_importer" 7 | version = "0.0.0" 8 | description = "" 9 | long_description = "" 10 | platforms = ["windows", "linux", "macos"] 11 | min_angr_version = "9.0.0.0" 12 | author = "The angr team" 13 | entrypoints = ["__init__.py"] 14 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Black + pre-commit 2 | f3d5a6399057ba1780bc83d979e5f77e9f5c7701 # Type comments -> annotations 3 | 2c15ec716391e4728912e1da417aef0c5378340f # Black 4 | 01ecacb6825506ba60dcc436920096d84fbc7a13 # Pyupgrade 5 | 788badfe210911f860e40d6440b8891da18b1e3c # prefer builtins 6 | 6fd6dadcf816b5514976ce33a86adcb17f0ae43b # Whitespace 7 | fb03fe3bea6b8298fe13faba23eea01695a94540 # ruff 8 | -------------------------------------------------------------------------------- /angrmanagement/plugins/source_viewer/plugin.toml: -------------------------------------------------------------------------------- 1 | [meta] 2 | plugin_metadata_version = 0 3 | 4 | [plugin] 5 | name = "Source Viewer" 6 | shortname = "source_viewer" 7 | version = "0.0.0" 8 | description = "" 9 | long_description = "" 10 | platforms = ["windows", "linux", "macos"] 11 | min_angr_version = "9.0.0.0" 12 | author = "The angr team" 13 | entrypoints = ["source_viewer_plugin.py"] 14 | -------------------------------------------------------------------------------- /angrmanagement/plugins/value_search/plugin.toml: -------------------------------------------------------------------------------- 1 | [meta] 2 | plugin_metadata_version = 0 3 | 4 | [plugin] 5 | name = "Value Search Plugin" 6 | shortname = "value_search" 7 | version = "0.0.0" 8 | description = "" 9 | long_description = "" 10 | platforms = ["windows", "linux", "macos"] 11 | min_angr_version = "9.0.0.0" 12 | author = "The angr team" 13 | entrypoints = ["value_search_plugin.py"] 14 | -------------------------------------------------------------------------------- /angrmanagement/plugins/decompiler_poison/plugin.toml: -------------------------------------------------------------------------------- 1 | [meta] 2 | plugin_metadata_version = 0 3 | 4 | [plugin] 5 | name = "Decompiler Poison Plugin" 6 | shortname = "decompiler_poison" 7 | version = "0.0.0" 8 | description = "" 9 | long_description = "" 10 | platforms = ["windows", "linux", "macos"] 11 | min_angr_version = "9.0.0.0" 12 | author = "The angr team" 13 | entrypoints = ["__init__.py"] 14 | -------------------------------------------------------------------------------- /angrmanagement/plugins/precise_diffing/plugin.toml: -------------------------------------------------------------------------------- 1 | [meta] 2 | plugin_metadata_version = 0 3 | 4 | [plugin] 5 | name = "Precise Binary Diff" 6 | shortname = "precise_diff" 7 | version = "0.0.0" 8 | description = "" 9 | long_description = "" 10 | platforms = ["windows", "linux", "macos"] 11 | min_angr_version = "9.0.0.0" 12 | author = "The angr team" 13 | entrypoints = ["precisediff_plugin.py"] 14 | -------------------------------------------------------------------------------- /angrmanagement/plugins/trace_viewer/plugin.toml: -------------------------------------------------------------------------------- 1 | [meta] 2 | plugin_metadata_version = 0 3 | 4 | [plugin] 5 | name = "Trace Viewer" 6 | shortname = "trace_viewer" 7 | version = "0.0.0" 8 | description = "" 9 | long_description = "" 10 | platforms = ["windows", "linux", "macos"] 11 | min_angr_version = "9.0.0.0" 12 | author = "The angr team" 13 | entrypoints = ["trace_plugin.py"] 14 | has_url_actions = true 15 | -------------------------------------------------------------------------------- /angrmanagement/consts.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | 5 | APP_LOCATION = str(os.path.join(os.path.dirname(os.path.realpath(__file__)))) 6 | RES_LOCATION = str(os.path.join(APP_LOCATION, "resources")) 7 | IMG_LOCATION = str(os.path.join(RES_LOCATION, "images")) 8 | FONT_LOCATION = str(os.path.join(RES_LOCATION, "fonts")) 9 | THEME_LOCATION = str(os.path.join(RES_LOCATION, "themes")) 10 | -------------------------------------------------------------------------------- /angrmanagement/plugins/execution_statistics_viewer/plugin.toml: -------------------------------------------------------------------------------- 1 | [meta] 2 | plugin_metadata_version = 0 3 | 4 | [plugin] 5 | name = "Execution Statistics Viewer" 6 | shortname = "exec_stats_viewer" 7 | version = "0.0.0" 8 | description = "" 9 | long_description = "" 10 | platforms = ["windows", "linux", "macos"] 11 | min_angr_version = "9.0.0.0" 12 | author = "The angr team" 13 | entrypoints = ["execution_statistics_viewer.py"] 14 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Light/images/provenance.txt: -------------------------------------------------------------------------------- 1 | close.svg, tab-menu.svg 2 | copyright: 2022–present WorkOS 3 | license: MIT 4 | source: https://icons.radix-ui.com/ 5 | acquired-on: 11/1/2022 6 | 7 | drawing-pin.svg 8 | copyright: 2022–present WorkOS 9 | license: MIT 10 | source: https://icons.radix-ui.com/ 11 | acquired-on: 3/21/2024 12 | 13 | minimize-button.svg 14 | copyright: QtADS 15 | license: LGPL 16 | acquired-on: 3/21/2024 17 | -------------------------------------------------------------------------------- /angrmanagement/ui/widgets/block_code_objects/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .ail_objects import QAilObj 4 | from .base_objects import BlockTreeNode, BlockTreeNodeOptions, QVariableObj 5 | from .disasm_objects import QFunctionHeader 6 | from .vex_objects import QIROpObj 7 | 8 | __all__ = [ 9 | "BlockTreeNode", 10 | "BlockTreeNodeOptions", 11 | "QAilObj", 12 | "QIROpObj", 13 | "QVariableObj", 14 | "QFunctionHeader", 15 | ] 16 | -------------------------------------------------------------------------------- /angrmanagement/ui/toolbars/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .debug_toolbar import DebugToolbar 4 | from .feature_map_toolbar import FeatureMapToolbar 5 | from .file_toolbar import FileToolbar 6 | from .function_table_toolbar import FunctionTableToolbar 7 | from .nav_toolbar import NavToolbar 8 | 9 | __all__ = [ 10 | "DebugToolbar", 11 | "FeatureMapToolbar", 12 | "FileToolbar", 13 | "FunctionTableToolbar", 14 | "NavToolbar", 15 | ] 16 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Dark/images/provenance.txt: -------------------------------------------------------------------------------- 1 | close.svg, tab-menu.svg 2 | copyright: 2022–present WorkOS 3 | license: MIT 4 | source: https://icons.radix-ui.com/ 5 | acquired-on: 11/1/2022 6 | 7 | drawing-pin.svg 8 | copyright: 2022–present WorkOS 9 | license: MIT 10 | source: https://icons.radix-ui.com/ 11 | acquired-on: 3/21/2024 12 | color modified for theme 13 | 14 | minimize-button.svg 15 | copyright: QtADS 16 | license: LGPL 17 | acquired-on: 3/21/2024 18 | -------------------------------------------------------------------------------- /docs/development/ui.rst: -------------------------------------------------------------------------------- 1 | UI 2 | == 3 | 4 | Manipulating UI elements 5 | ^^^^^^^^^^^^^^^^^^^^^^^^ 6 | 7 | The ``workspace`` contains methods to manipulate UI elements. Notably, you can 8 | manipulate all open tabs with `the workspace.view_manager reference 9 | `_. 10 | Additionally, you can pass any sort of object you like to ``workspace.viz()`` 11 | and it will attempt to visualize the object in the current window. 12 | -------------------------------------------------------------------------------- /angrmanagement/utils/daemon_thread.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from threading import Thread 4 | from typing import TYPE_CHECKING, Any 5 | 6 | if TYPE_CHECKING: 7 | from collections.abc import Callable 8 | 9 | 10 | def start_daemon_thread(target: Callable[..., Any], name: str, args: tuple[Any] | None = None) -> Thread: 11 | """ 12 | Start a daemon thread. 13 | """ 14 | t = Thread(target=target, name=name, args=args if args else ()) 15 | t.daemon = True 16 | t.start() 17 | return t 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | projects 2 | 3 | # Python cruft 4 | *.pyc 5 | *.pyo 6 | *.egg-info 7 | dist 8 | build 9 | MANIFEST 10 | start.spec 11 | 12 | # vim temp files 13 | *.swp 14 | 15 | # emacs temp files 16 | *~ 17 | 18 | # enaml cache 19 | __enamlcache__/ 20 | 21 | 22 | .vscode 23 | .idea 24 | 25 | 26 | # Packaging 27 | packaging/pyinstaller/build 28 | packaging/pyinstaller/onefile 29 | packaging/pyinstaller/onedir 30 | packaging/appimage/AppDir 31 | packaging/appimage/appimage-builder-cache 32 | packaging/appimage/AppDir 33 | *.AppImage 34 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Dracula/images/provenance.txt: -------------------------------------------------------------------------------- 1 | close.svg, tab-menu.svg 2 | copyright: 2022–present WorkOS 3 | license: MIT 4 | source: https://icons.radix-ui.com/ 5 | acquired-on: 11/1/2022 6 | colors changed on 7/16/2023 by adamd for this theme 7 | 8 | drawing-pin.svg 9 | copyright: 2022–present WorkOS 10 | license: MIT 11 | source: https://icons.radix-ui.com/ 12 | acquired-on: 3/21/2024 13 | color modified for theme 14 | 15 | minimize-button.svg 16 | copyright: QtADS 17 | license: LGPL 18 | acquired-on: 3/21/2024 19 | -------------------------------------------------------------------------------- /angrmanagement/ui/dialogs/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .analysis_options import AnalysisOptionsDialog 4 | from .assemble_patch import AssemblePatchDialog 5 | from .breakpoint import BreakpointDialog 6 | from .load_binary import LoadBinary 7 | from .load_plugins import LoadPlugins 8 | from .preferences import Preferences 9 | 10 | __all__ = [ 11 | "AnalysisOptionsDialog", 12 | "AssemblePatchDialog", 13 | "BreakpointDialog", 14 | "LoadBinary", 15 | "LoadPlugins", 16 | "Preferences", 17 | ] 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angr-management 2 | 3 | angr-management is a cross-platform, open-source, graphical binary analysis tool powered by the [angr](https://angr.io) binary analysis platform! See [here](https://angr-management.readthedocs.io/en/latest/) for more information. 4 | 5 | Some screenshots: 6 | 7 | [](https://github.com/angr/angr-management/blob/master/screenshots/disassembly.png) 8 | [](https://github.com/angr/angr-management/blob/master/screenshots/decompilation.png) 9 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Catppuccin Mocha/images/provenance.txt: -------------------------------------------------------------------------------- 1 | close.svg, tab-menu.svg 2 | copyright: 2022–present WorkOS 3 | license: MIT 4 | source: https://icons.radix-ui.com/ 5 | acquired-on: 11/1/2022 6 | colors changed on 7/16/2023 by adamd for this theme 7 | 8 | drawing-pin.svg 9 | copyright: 2022–present WorkOS 10 | license: MIT 11 | source: https://icons.radix-ui.com/ 12 | acquired-on: 3/21/2024 13 | color modified for theme 14 | 15 | minimize-button.svg 16 | copyright: QtADS 17 | license: LGPL 18 | acquired-on: 3/21/2024 19 | 20 | -------------------------------------------------------------------------------- /angrmanagement/ui/menus/plugin_menu.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from angrmanagement.ui.icons import icon 6 | 7 | from .menu import Menu, MenuEntry 8 | 9 | if TYPE_CHECKING: 10 | from angrmanagement.ui.main_window import MainWindow 11 | 12 | 13 | class PluginMenu(Menu): 14 | def __init__(self, main_window: MainWindow) -> None: 15 | super().__init__("&Plugins", parent=main_window) 16 | 17 | self.entries.extend( 18 | [MenuEntry("&Manage Plugins...", main_window.open_load_plugins_dialog, icon=icon("plugins"))] 19 | ) 20 | -------------------------------------------------------------------------------- /angrmanagement/utils/layout.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from collections.abc import Iterable 7 | 8 | from PySide6.QtWidgets import QGridLayout, QWidget 9 | 10 | 11 | def add_to_grid(lyt: QGridLayout, cols: int, widgets: Iterable[QWidget]) -> None: 12 | """ 13 | Adds widgets to a grid layout given a desired column count. 14 | """ 15 | r = lyt.rowCount() 16 | c = 0 17 | for item in widgets: 18 | lyt.addWidget(item, r, c) 19 | c += 1 20 | if c >= cols: 21 | c = 0 22 | r += 1 23 | -------------------------------------------------------------------------------- /docs/development/testing.rst: -------------------------------------------------------------------------------- 1 | Testing 2 | ======= 3 | 4 | Coverage 5 | ^^^^^^^^ 6 | 7 | .. image:: https://codecov.io/github/angr/angr-management/graph/badge.svg?token=H4QwMNfjb2 8 | :target: https://codecov.io/github/angr/angr-management 9 | 10 | Writing tests 11 | ^^^^^^^^^^^^^ 12 | 13 | Look at the `existing tests 14 | `_ for examples. 15 | Generally, you can test UI components by creating the component and driving 16 | input to it via QTest. You can create a headless MainWindow instance by passing 17 | ``show=False`` to its constructor - this will also get you access to a workspace 18 | and an instance. 19 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Dark/theme.css: -------------------------------------------------------------------------------- 1 | ads--CDockAreaWidget { 2 | background: palette(dark); 3 | } 4 | 5 | ads--CDockWidgetTab { 6 | background: palette(mid); 7 | border-color: palette(light); 8 | } 9 | 10 | QMenuBar { 11 | background-color: palette(dark); 12 | } 13 | 14 | ToolBarHandle { 15 | background-color: palette(dark); 16 | } 17 | 18 | QMainWindow > QToolBar { 19 | background-color: palette(dark); 20 | border: 0; 21 | } 22 | 23 | QStatusBar { 24 | background-color: palette(dark); 25 | } 26 | 27 | ads--CAutoHideTab { 28 | background: palette(shadow); 29 | } 30 | 31 | ads--CAutoHideSideBar{ 32 | background: palette(shadow); 33 | } 34 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Dracula/theme.css: -------------------------------------------------------------------------------- 1 | ads--CDockAreaWidget { 2 | background: palette(dark); 3 | } 4 | 5 | ads--CDockWidgetTab { 6 | background: palette(mid); 7 | border-color: palette(light); 8 | } 9 | 10 | QMenuBar { 11 | background-color: palette(dark); 12 | } 13 | 14 | ToolBarHandle { 15 | background-color: palette(dark); 16 | } 17 | 18 | QMainWindow > QToolBar { 19 | background-color: palette(dark); 20 | border: 0; 21 | } 22 | 23 | QStatusBar { 24 | background-color: palette(dark); 25 | } 26 | 27 | ads--CAutoHideTab { 28 | background: palette(shadow); 29 | } 30 | 31 | ads--CAutoHideSideBar{ 32 | background: palette(shadow); 33 | } 34 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Catppuccin Mocha/theme.css: -------------------------------------------------------------------------------- 1 | ads--CDockAreaWidget { 2 | background: palette(dark); 3 | } 4 | 5 | ads--CDockWidgetTab { 6 | background: palette(mid); 7 | border-color: palette(light); 8 | } 9 | 10 | QMenuBar { 11 | background-color: palette(dark); 12 | } 13 | 14 | ToolBarHandle { 15 | background-color: palette(dark); 16 | } 17 | 18 | QMainWindow > QToolBar { 19 | background-color: palette(dark); 20 | border: 0; 21 | } 22 | 23 | QStatusBar { 24 | background-color: palette(dark); 25 | } 26 | 27 | ads--CAutoHideTab { 28 | background: palette(shadow); 29 | } 30 | 31 | ads--CAutoHideSideBar{ 32 | background: palette(shadow); 33 | } 34 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Light/images/close.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Dark/images/close.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Dracula/images/close.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Catppuccin Mocha/images/close.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/development/pyinstaller.rst: -------------------------------------------------------------------------------- 1 | Building with PyInstaller 2 | ------------------------- 3 | To build a portable executable using PyInstaller, install angr management into a python envrionment with the :code:`pyinstaller` extra. 4 | Do not install anything in editable mode (pip's :code:`-e`), as PyInstaller currently `fails to bundle `_ modules installed with editable mode. 5 | Then, run :code:`pyinstaller angr-management.spec`. 6 | 7 | If things go wrong, the best bet is to reference the nightly build pipeline and the `PyInstaller docs `_. 8 | The CI environment that produces nightly builds is at :code:`.github/workflows/nightly-build.yml` and :code:`.github/workflows/nightly-build.sh`. 9 | -------------------------------------------------------------------------------- /angrmanagement/logic/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from PySide6.QtCore import QThread 7 | 8 | from angrmanagement.data.library_docs import LibraryDocs 9 | from angrmanagement.plugins.plugin_manager import PluginManager 10 | from angrmanagement.ui.main_window import MainWindow 11 | 12 | 13 | class GlobalInfo: 14 | """ 15 | Global data. 16 | """ 17 | 18 | gui_thread: QThread | None = None 19 | main_window: MainWindow | None = None 20 | daemon_inst = None 21 | daemon_conn = None 22 | client_id: str | None = None 23 | headless_plugin_manager: PluginManager | None = None 24 | library_docs: LibraryDocs = None 25 | autoreload = False 26 | -------------------------------------------------------------------------------- /angrmanagement/ui/toolbars/toolbar_action.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | class ToolbarAction: 5 | def __init__(self, icon, name: str, tooltip, triggered, checkable: bool = False, shortcut=None) -> None: 6 | self.icon = icon 7 | self.name = name 8 | self.tooltip = tooltip 9 | self.triggered = triggered 10 | self.checkable = checkable 11 | self.shortcut = shortcut 12 | 13 | def __hash__(self): 14 | return hash((ToolbarAction, self.name)) 15 | 16 | def __eq__(self, other): 17 | return isinstance(other, ToolbarAction) and self.name == other.name 18 | 19 | 20 | class ToolbarSplitter(ToolbarAction): 21 | def __init__(self) -> None: 22 | super().__init__(None, None, None, None) 23 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | angr-management 2 | =============== 3 | 4 | angr-management is a cross-platform, open-source, graphical binary analysis tool powered by the `angr `_ binary analysis platform! 5 | 6 | .. image:: ../screenshots/disassembly.png 7 | .. image:: ../screenshots/decompilation.png 8 | 9 | Features 10 | -------- 11 | 12 | * `Open source `_ 13 | * Cross platform 14 | * Supports many architectures 15 | * Customizable decompilation 16 | * Extensible through plugins 17 | 18 | Table of Contents 19 | ----------------- 20 | 21 | .. toctree:: 22 | :maxdepth: 2 23 | 24 | quickstart 25 | development/index 26 | 27 | Indices and Tables 28 | ------------------ 29 | 30 | * :ref:`genindex` 31 | * :ref:`modindex` 32 | * :ref:`search` 33 | -------------------------------------------------------------------------------- /scripts/sync-vendor-packages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e #-x 3 | cd "$(dirname "$0")" 4 | cd .. 5 | 6 | sync_vendor_tree() { # (name, src_repo, src_branch, src_subdir, src_license) 7 | echo "[*] Syncing $1" 8 | remote=vendor-$1 src_repo=$2 src_branch=$3 src_dir=$4 dst_dir=angrmanagement/vendor/$1 src_license=$5 9 | if [[ -d $dst_dir ]]; then git rm -rf $dst_dir; fi 10 | git remote add -f -t $src_branch --no-tags $remote $src_repo 2>/dev/null || git fetch $remote --no-tags 11 | git read-tree --prefix=$dst_dir -u $remote/$src_branch:$src_dir 12 | dst_license=$dst_dir/LICENSE 13 | git show $remote/$src_branch:$src_license >$dst_license && git add $dst_license 14 | git commit -m "$dst_dir: Update to $(git rev-parse $remote/$src_branch)" 15 | } 16 | 17 | sync_vendor_tree qtconsole https://github.com/angr/qtconsole angr-management qtconsole LICENSE 18 | -------------------------------------------------------------------------------- /angrmanagement/errors.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | class AngrManagementError(Exception): 5 | """Base class for all errors raised by angr management.""" 6 | 7 | 8 | class InvalidURLError(AngrManagementError): 9 | """InvalidURLError is raised when an invalid URL is provided.""" 10 | 11 | 12 | class UnexpectedStatusCodeError(AngrManagementError): 13 | """UnexpectedStatusCodeError is raised when an unexpected status code is 14 | received from an HTTP request. 15 | """ 16 | 17 | def __init__(self, status_code) -> None: 18 | super().__init__() 19 | self.status_code = status_code 20 | 21 | 22 | class ContainerAlreadyRegisteredError(AngrManagementError): 23 | """ContainerAlreadyRegisteredError is raised when a container is already 24 | registered in the container registry. 25 | """ 26 | -------------------------------------------------------------------------------- /angrmanagement/ui/dialogs/progress_dialog.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtCore import Qt 6 | from PySide6.QtWidgets import QProgressDialog 7 | 8 | if TYPE_CHECKING: 9 | from angrmanagement.ui.main_window import MainWindow 10 | 11 | 12 | class ProgressDialog(QProgressDialog): 13 | """ProgressDialog is a dialog that shows the progress of a job on top of the main window.""" 14 | 15 | main_window: MainWindow 16 | 17 | def __init__(self, main_window: MainWindow): 18 | super().__init__("Waiting...", "Cancel", 0, 100, parent=main_window) 19 | self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint) 20 | self.setModal(True) 21 | self.setMinimumDuration(0) 22 | self.canceled.disconnect() 23 | self.reset() 24 | -------------------------------------------------------------------------------- /docs/development/plugins.rst: -------------------------------------------------------------------------------- 1 | Plugins 2 | ======= 3 | 4 | Writing plugins 5 | ^^^^^^^^^^^^^^^ 6 | 7 | angr management has a very flexible plugin framework. A plugin is a Python file 8 | containing a subclass of ``angrmanagement.plugins.BasePlugin``. Plugin files 9 | will be automatically loaded from the ``plugins`` module of angr management, and 10 | also from ``~/.local/share/angr-management/plugins``. These paths are 11 | configurable through the program configuration, but at the time of writing, this 12 | is not exposed in the UI. 13 | 14 | The best way to see the tools you can use while building a plugin is to read the 15 | `plugin base class source code 16 | `_. 17 | Any method or attribute can be overridden from a base class and will be 18 | automatically called on relevant events. 19 | -------------------------------------------------------------------------------- /angrmanagement/logic/commands/command_manager.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from collections.abc import Sequence 7 | 8 | from .command import Command 9 | 10 | 11 | class CommandManager: 12 | """ 13 | Manages available commands. 14 | """ 15 | 16 | def __init__(self) -> None: 17 | self._commands: dict[str, Command] = {} 18 | 19 | def register_command(self, command: Command) -> None: 20 | assert command.name not in self._commands, "Command by this name already registered" 21 | self._commands[command.name] = command 22 | 23 | def register_commands(self, commands: Sequence[Command]) -> None: 24 | for command in commands: 25 | self.register_command(command) 26 | 27 | def get_commands(self): 28 | return self._commands.values() 29 | -------------------------------------------------------------------------------- /angrmanagement/daemon/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .server import daemon_conn, daemon_exists, run_daemon_process, start_daemon 4 | from .url_handler import handle_url 5 | 6 | 7 | def monkeypatch_rpyc() -> None: 8 | # The AsyncResult in rpyc has a bug that causes a race condition when clients are cascaded. this monkeypatch fixes 9 | # the bug. 10 | from rpyc.core.async_ import AsyncResult, AsyncResultTimeout 11 | 12 | def patched_wait(self): 13 | while not self._is_ready and not self._ttl.expired(): 14 | self._conn.serve(0.01) 15 | if not self._is_ready: 16 | raise AsyncResultTimeout("result expired") 17 | 18 | AsyncResult.wait = patched_wait 19 | 20 | 21 | monkeypatch_rpyc() 22 | 23 | __all__ = [ 24 | "daemon_conn", 25 | "daemon_exists", 26 | "run_daemon_process", 27 | "start_daemon", 28 | "handle_url", 29 | ] 30 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /angrmanagement/ui/toolbars/file_toolbar.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from angrmanagement.ui.icons import icon 6 | 7 | from .toolbar import Toolbar, ToolbarAction 8 | 9 | if TYPE_CHECKING: 10 | from angrmanagement.ui.main_window import MainWindow 11 | 12 | 13 | class FileToolbar(Toolbar): 14 | def __init__(self, main_window: MainWindow) -> None: 15 | super().__init__(main_window, "File") 16 | 17 | self.actions = [ 18 | ToolbarAction( 19 | icon("file-open"), 20 | "Open File", 21 | "Open a new file for analysis", 22 | main_window.open_file_button, 23 | ), 24 | ToolbarAction( 25 | icon("file-save"), 26 | "Save", 27 | "Save angr database", 28 | main_window.save_database, 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /angrmanagement/utils/cache.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from cachetools import LRUCache 6 | 7 | if TYPE_CHECKING: 8 | from collections.abc import Callable 9 | 10 | 11 | class SmartLRUCache(LRUCache): 12 | """ 13 | A smart LRU cache that calls an eviction function when an item is evicted from the cache. 14 | 15 | This is based the SmartLRUCache in claripy. Because we may make claripy optional in the future, I decide to make a 16 | copy of this class in angr management instead. 17 | """ 18 | 19 | def __init__(self, maxsize, getsizeof=None, evict: Callable | None = None): 20 | LRUCache.__init__(self, maxsize, getsizeof=getsizeof) 21 | self._evict: Callable | None = evict 22 | 23 | def popitem(self): 24 | key, val = LRUCache.popitem(self) 25 | if self._evict is not None: 26 | self._evict(key, val) 27 | return key, val 28 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the OS, Python version and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.12" 13 | # You can also specify other tool versions: 14 | # nodejs: "19" 15 | # rust: "1.64" 16 | # golang: "1.19" 17 | 18 | # Build documentation in the "docs/" directory with Sphinx 19 | sphinx: 20 | configuration: docs/conf.py 21 | 22 | # Optionally build your docs in additional formats such as PDF and ePub 23 | # formats: 24 | # - pdf 25 | # - epub 26 | 27 | # Optional but recommended, declare the Python requirements required 28 | # to build your documentation 29 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 30 | python: 31 | install: 32 | - requirements: docs/requirements.txt 33 | -------------------------------------------------------------------------------- /angrmanagement/data/jobs/vfg_generation.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING, Any 4 | 5 | from .job import InstanceJob 6 | 7 | if TYPE_CHECKING: 8 | from angrmanagement.data.instance import Instance 9 | from angrmanagement.logic.jobmanager import JobContext 10 | 11 | 12 | class VFGGenerationJob(InstanceJob): 13 | """A job that runs the VFG analysis for a function at a given address.""" 14 | 15 | def __init__(self, instance: Instance, addr: int) -> None: 16 | super().__init__("VFG generation", instance, on_finish=self._finish) 17 | self._addr = addr 18 | 19 | def run(self, _: JobContext): 20 | return self.instance.project.analyses.VFG(function_start=self._addr) 21 | 22 | def _finish(self, result: Any) -> None: 23 | self.instance.vfgs[self._addr] = result 24 | 25 | def __repr__(self) -> str: 26 | return f"Generating VFG for function at {self._addr:#x}" 27 | -------------------------------------------------------------------------------- /angrmanagement/data/jobs/prototype_finding.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from .job import InstanceJob 6 | 7 | if TYPE_CHECKING: 8 | from angrmanagement.data.instance import Instance 9 | from angrmanagement.logic.jobmanager import JobContext 10 | 11 | 12 | class PrototypeFindingJob(InstanceJob): 13 | def __init__(self, instance: Instance, on_finish=None) -> None: 14 | super().__init__("Function prototype finding", instance, on_finish=on_finish) 15 | 16 | def run(self, ctx: JobContext) -> None: 17 | func_count = len(self.instance.kb.functions) 18 | for i, func in enumerate(self.instance.kb.functions.values()): 19 | if func.is_simprocedure or func.is_plt: 20 | func.find_declaration() 21 | 22 | percentage = i / func_count * 100 23 | ctx.set_progress(percentage) 24 | 25 | def __repr__(self) -> str: 26 | return "PrototypeFindingJob" 27 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import sys 4 | import threading 5 | from functools import partial 6 | 7 | import pytest 8 | 9 | 10 | @pytest.fixture(autouse=True) 11 | def qthread_coverage(monkeypatch): 12 | """ 13 | Patch QThread.run for coverage support. 14 | 15 | See https://github.com/coveragepy/coveragepy/issues/686 for details. 16 | """ 17 | from PySide6.QtCore import QThread # pylint: disable=import-outside-toplevel 18 | 19 | _base_init = QThread.__init__ 20 | 21 | def init_with_trace(self, *args, **kwargs): 22 | _base_init(self, *args, **kwargs) 23 | self._base_run = self.run 24 | self.run = partial(run_with_trace, self) 25 | 26 | def run_with_trace(self): # pragma: no cover 27 | if "coverage" in sys.modules: 28 | sys.settrace(threading._trace_hook) # pyright: ignore[reportAttributeAccessIssue] 29 | self._base_run() 30 | 31 | monkeypatch.setattr(QThread, "__init__", init_with_trace) 32 | -------------------------------------------------------------------------------- /angrmanagement/utils/block_objects.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | class FunctionHeader: 5 | __slots__ = ( 6 | "name", 7 | "demangled_name", 8 | "prototype", 9 | "args", 10 | ) 11 | 12 | def __init__(self, name: str, demangled_name: str | None, prototype=None, args=None) -> None: 13 | self.name = name 14 | self.demangled_name = demangled_name 15 | self.prototype = prototype 16 | self.args = args 17 | 18 | 19 | class Variables: 20 | __slots__ = ["variables"] 21 | 22 | def __init__(self, variables) -> None: 23 | self.variables = variables 24 | 25 | 26 | class PhiVariable(Variables): 27 | __slots__ = ["variable"] 28 | 29 | def __init__(self, variable, variables) -> None: 30 | super().__init__(variables) 31 | self.variable = variable 32 | 33 | 34 | class Label: 35 | __slots__ = ["addr", "text"] 36 | 37 | def __init__(self, addr: int, text: str) -> None: 38 | self.addr = addr 39 | self.text = text 40 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Light/images/drawing-pin.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /angrmanagement/ui/widgets/qstate_combobox.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtWidgets import QComboBox 6 | 7 | if TYPE_CHECKING: 8 | from angrmanagement.data.instance import Instance 9 | 10 | 11 | class QStateComboBox(QComboBox): 12 | def __init__(self, instance: Instance, allow_none: bool = True, parent=None) -> None: 13 | super().__init__(parent) 14 | self.states = instance.states 15 | self.allow_none = allow_none 16 | self._init_items() 17 | 18 | def _init_items(self): 19 | if self.allow_none: 20 | self.addItem("", None) 21 | elif not self.states: 22 | raise ValueError("Created QStateComboBox with allow_none=False and no states available") 23 | for state in self.states: 24 | self.addItem(state.gui_data.name, state) 25 | 26 | @property 27 | def state(self): 28 | idx = self.currentIndex() 29 | if idx == -1: 30 | return None 31 | 32 | return self.itemData(idx) 33 | -------------------------------------------------------------------------------- /.github/workflows/nightly-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | uv sync --python "3.12" 5 | 6 | # Bundle! 7 | uv run pyinstaller angr-management.spec 8 | 9 | mkdir upload 10 | 11 | AM_VERSION=$(python ./scripts/get-version.py) 12 | 13 | # Prepare onedirs 14 | if [[ "$OSTYPE" == "darwin"* ]]; then 15 | mkdir /tmp/angr-management-zip 16 | ZIP_PATH=$(pwd)/upload/angr-management-v$AM_VERSION-macOS-$(uname -m).zip 17 | pushd dist 18 | zip -ry $ZIP_PATH *.app 19 | popd 20 | elif [[ "$OSTYPE" == "linux-gnu" ]]; then 21 | source /etc/os-release 22 | tar -C dist -czf upload/angr-management-v$AM_VERSION-$ID-$VERSION_ID-$(uname -m).tar.gz angr-management 23 | elif [[ "$OSTYPE" == "msys" ]]; then 24 | OUTDIR=$(pwd)/upload 25 | pushd dist 26 | 7z a $OUTDIR/angr-management-v$AM_VERSION-win64-x86_64.zip \* 27 | popd 28 | 29 | # Build Windows installer 30 | makensis \ 31 | -DVERSION=$AM_VERSION \ 32 | -DPRODUCT_VERSION=$(python scripts/get-version.py --format numeric) \ 33 | angr-management.nsi 34 | mv *.exe $OUTDIR 35 | fi 36 | -------------------------------------------------------------------------------- /angrmanagement/data/jobs/ddg_generation.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING, Any 4 | 5 | import networkx 6 | 7 | from .job import InstanceJob 8 | 9 | if TYPE_CHECKING: 10 | from angrmanagement.data.instance import Instance 11 | from angrmanagement.logic.jobmanager import JobContext 12 | 13 | 14 | class DDGGenerationJob(InstanceJob): 15 | """A job that runs the VSA_DDG analysis for a function at a given address.""" 16 | 17 | def __init__(self, instance: Instance, addr: int) -> None: 18 | super().__init__("DDG generation", instance, on_finish=self._finish) 19 | self._addr = addr 20 | 21 | def run(self, _: JobContext): 22 | ddg = self.instance.project.analyses.VSA_DDG(vfg=self.instance.vfgs[self._addr], start_addr=self._addr) 23 | return ddg, networkx.relabel_nodes(ddg.graph, lambda n: n.insn_addr) 24 | 25 | def _finish(self, result: Any) -> None: 26 | self.instance.ddgs[self._addr] = result 27 | 28 | def __repr__(self) -> str: 29 | return f"Generating VSA_DDG for function at {self._addr:#x}" 30 | -------------------------------------------------------------------------------- /angrmanagement/resources/images/run-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 14 | 18 | 24 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /angrmanagement/ui/menus/log_menu.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtGui import QKeySequence 6 | 7 | from .menu import Menu, MenuEntry, MenuSeparator 8 | 9 | if TYPE_CHECKING: 10 | from angrmanagement.ui.widgets.qlog_widget import QLogWidget 11 | 12 | 13 | class LogMenu(Menu): 14 | def __init__(self, log_widget: QLogWidget) -> None: 15 | super().__init__("", parent=log_widget) 16 | 17 | self.entries.extend( 18 | [ 19 | MenuEntry( 20 | "&Copy selected content", 21 | log_widget.copy_selected_messages, 22 | shortcut=QKeySequence("Ctrl+C"), 23 | ), 24 | MenuEntry("Copy selected message", log_widget.copy_selected), 25 | MenuEntry("Copy all content", log_widget.copy_all_messages), 26 | MenuEntry("Copy all messages", log_widget.copy_all), 27 | MenuSeparator(), 28 | MenuEntry("C&lear log", log_widget.clear_log), 29 | ] 30 | ) 31 | -------------------------------------------------------------------------------- /angrmanagement/data/function_graph.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from angrmanagement.utils.graph import to_supergraph 4 | 5 | 6 | def edge_qualifies(data) -> bool: 7 | return data["type"] not in ("call", "return_from_call") 8 | 9 | 10 | class FunctionGraph: 11 | def __init__(self, function, exception_edges: bool = True) -> None: 12 | self.function = function 13 | self.exception_edges = exception_edges 14 | self.edges = None 15 | self._supergraph = None 16 | 17 | def clear_cache(self) -> None: 18 | self._supergraph = None 19 | self.edges = None 20 | 21 | @property 22 | def supergraph(self): 23 | if self._supergraph is not None: 24 | return self._supergraph 25 | 26 | self._supergraph = to_supergraph(self.function.transition_graph_ex(exception_edges=self.exception_edges)) 27 | self.edges = [ 28 | (str(from_.addr), str(to.addr)) 29 | for (from_, to, data) in self._supergraph.edges(data=True) 30 | if edge_qualifies(data) 31 | ] 32 | 33 | return self._supergraph 34 | -------------------------------------------------------------------------------- /angrmanagement/ui/menus/help_menu.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtGui import QAction, QKeySequence 6 | 7 | from angrmanagement.ui.icons import icon 8 | 9 | from .menu import Menu, MenuEntry, MenuSeparator 10 | 11 | if TYPE_CHECKING: 12 | from angrmanagement.ui.main_window import MainWindow 13 | 14 | 15 | class HelpMenu(Menu): 16 | """ 17 | Main 'Help' menu 18 | """ 19 | 20 | def __init__(self, main_window: MainWindow) -> None: 21 | super().__init__("&Help", parent=main_window) 22 | 23 | self.entries.extend( 24 | [ 25 | MenuEntry( 26 | "&Documentation", main_window.open_doc_link, shortcut=QKeySequence("Alt+H"), icon=icon("docs") 27 | ), 28 | MenuSeparator(), 29 | MenuEntry( 30 | "&About angr-management", 31 | main_window.open_about_dialog, 32 | role=QAction.MenuRole.AboutRole, 33 | icon=icon("about"), 34 | ), 35 | ] 36 | ) 37 | -------------------------------------------------------------------------------- /angrmanagement/ui/widgets/qgraph_object.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from PySide6.QtWidgets import QGraphicsItem 4 | 5 | 6 | class QCachedGraphicsItem(QGraphicsItem): 7 | def __init__(self, parent=None) -> None: 8 | super().__init__(parent=parent) 9 | self._cached_bounding_rect = None 10 | 11 | def clear_cache(self) -> None: 12 | self.prepareGeometryChange() 13 | self._cached_bounding_rect = None 14 | 15 | def refresh(self) -> None: 16 | pass 17 | 18 | @property 19 | def width(self): 20 | return self.boundingRect().width() 21 | 22 | @property 23 | def height(self): 24 | return self.boundingRect().height() 25 | 26 | def recalculate_size(self) -> None: 27 | self.prepareGeometryChange() 28 | self._cached_bounding_rect = self._boundingRect() 29 | 30 | def boundingRect(self): 31 | if self._cached_bounding_rect is None: 32 | self._cached_bounding_rect = self._boundingRect() 33 | return self._cached_bounding_rect 34 | 35 | def _boundingRect(self): 36 | raise NotImplementedError 37 | -------------------------------------------------------------------------------- /angrmanagement/ui/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .qaddress_input import QAddressInput 4 | from .qdisasm_base_control import DisassemblyLevel 5 | 6 | # graphs 7 | from .qdisasm_graph import QDisassemblyGraph 8 | from .qdisasm_statusbar import QDisasmStatusBar 9 | 10 | # other widgets 11 | from .qfeature_map import QFeatureMap 12 | from .qicon_label import QIconLabel 13 | from .qinst_annotation import QAvoidAddrAnnotation, QBlockAnnotations, QFindAddrAnnotation, QHookAnnotation 14 | from .qlinear_viewer import QLinearDisassembly, QLinearDisassemblyView 15 | from .qstate_combobox import QStateComboBox 16 | from .qsymexec_graph import QSymExecGraph 17 | from .qtrace_map import QTraceMap 18 | 19 | __all__ = [ 20 | "DisassemblyLevel", 21 | "QAddressInput", 22 | "QAvoidAddrAnnotation", 23 | "QBlockAnnotations", 24 | "QDisasmStatusBar", 25 | "QDisassemblyGraph", 26 | "QFeatureMap", 27 | "QFindAddrAnnotation", 28 | "QHookAnnotation", 29 | "QIconLabel", 30 | "QLinearDisassembly", 31 | "QLinearDisassemblyView", 32 | "QStateComboBox", 33 | "QSymExecGraph", 34 | "QTraceMap", 35 | ] 36 | -------------------------------------------------------------------------------- /scripts/get-version.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import pathlib 5 | import re 6 | 7 | 8 | def main(): 9 | ap = argparse.ArgumentParser() 10 | ap.add_argument("--format", choices=["pep440", "numeric"], default="pep440") 11 | args = ap.parse_args() 12 | 13 | # Get version number from angrmanagement/__init__.py __version__ string 14 | path = pathlib.Path(__file__).parent.parent / "angrmanagement" / "__init__.py" 15 | content = path.read_text(encoding="utf-8") 16 | match = re.search(r'^__version__\s*=\s*"([^"]+)"', content, re.MULTILINE) 17 | if not match: 18 | raise RuntimeError("Version string not found") 19 | version = match.group(1) 20 | 21 | if args.format == "numeric": 22 | # Transform devX prefix into 9000+X 23 | version = list(version.split(".")) 24 | if version[-1].startswith("dev"): 25 | version[-1] = str(9000 + int(version[-1].removeprefix("dev"))) 26 | while len(version) < 4: 27 | version.append("0") 28 | version = ".".join(version) 29 | 30 | print(version) 31 | 32 | 33 | if __name__ == "__main__": 34 | main() 35 | -------------------------------------------------------------------------------- /angrmanagement/ui/dialogs/fs_mount.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtCore import Qt 6 | from PySide6.QtWidgets import QDialog, QVBoxLayout 7 | 8 | from angrmanagement.ui.widgets.filesystem_table import QFileSystemTable 9 | 10 | if TYPE_CHECKING: 11 | from angrmanagement.data.instance import Instance 12 | 13 | 14 | class FilesystemMount(QDialog): 15 | def __init__(self, fs_config=None, instance: Instance | None = None, parent=None) -> None: 16 | super().__init__(parent) 17 | 18 | self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint) 19 | self._instance = instance 20 | self._parent = parent 21 | self.fs_config = fs_config or [] 22 | self._init_widgets() 23 | 24 | def _init_widgets(self) -> None: 25 | layout = QVBoxLayout() 26 | self._table = QFileSystemTable(self.fs_config, self) 27 | layout.addWidget(self._table, 0) 28 | self.setLayout(layout) 29 | 30 | def closeEvent(self, event) -> None: # pylint: disable=unused-argument 31 | self.fs_config = self._table.get_result() 32 | self.close() 33 | -------------------------------------------------------------------------------- /angrmanagement/ui/toolbars/function_table_toolbar.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | 5 | from PySide6.QtGui import QIcon 6 | 7 | from angrmanagement.consts import IMG_LOCATION 8 | 9 | from .toolbar import Toolbar, ToolbarAction 10 | 11 | 12 | class FunctionTableToolbar(Toolbar): 13 | def __init__(self, function_table) -> None: 14 | super().__init__(function_table, "Function table options") 15 | 16 | # TODO: An icon would be great 17 | self._alignment_action = ToolbarAction( 18 | QIcon(os.path.join(IMG_LOCATION, "toolbar-show-alignment.png")), 19 | "Show alignment functions", 20 | "Display alignment function stubs.", 21 | function_table.toggle_show_alignment_functions, 22 | checkable=True, 23 | ) 24 | 25 | self.actions = [ 26 | self._alignment_action, 27 | ] 28 | 29 | def toggle_show_alignment_functions(self) -> None: 30 | self.window.toggle_show_alignment_functions() 31 | 32 | if self._cached_actions and self._alignment_action in self._cached_actions: 33 | self._cached_actions[self._alignment_action].setChecked(self.window.show_alignment_functions) 34 | -------------------------------------------------------------------------------- /angrmanagement/plugins/value_search/constants.py: -------------------------------------------------------------------------------- 1 | # 2 | # POPULAR CONSTANTS 3 | # 4 | from __future__ import annotations 5 | 6 | import math 7 | 8 | # Error computation 9 | MACHINE_EPSILON_DOUBLE_PRECISION = 2.2204460493e-16 10 | MACHINE_EPSILON_SINGLE_PRECISION = 1.1920928955e-07 11 | 12 | # Math 13 | PI = math.pi 14 | E = math.e 15 | 16 | # Physics 17 | SPEED_OF_LIGHT = 299792458 18 | PLANCK_CONSTANT = 6.62607004e-34 19 | GRAVITATIONAL_CONSTANT = 6.67408e-11 20 | ELEMENTARY_CHARGE = 1.6021766208e-19 21 | BOLTZMANN_CONSTANT = 1.38064852e-23 22 | AVOGADRO_CONSTANT = 6.022140857e23 23 | 24 | # Computation 25 | MAX_INT32 = 2147483647 26 | MAX_INT64 = 9223372036854775807 27 | MAX_UINT32 = 4294967295 28 | MAX_UINT64 = 18446744073709551615 29 | MAX_FLOAT32 = 3.40282347e38 30 | MAX_FLOAT64 = 1.7976931348623157e308 31 | 32 | # All consts 33 | CONSTANTS_BY_NAME = { 34 | "Machine Epsilon (Double Precision)": MACHINE_EPSILON_DOUBLE_PRECISION, 35 | "Machine Epsilon (Single Precision)": MACHINE_EPSILON_SINGLE_PRECISION, 36 | "Planck Constant": PLANCK_CONSTANT, 37 | "Gravitational Constant": GRAVITATIONAL_CONSTANT, 38 | "Avogadro Constant": AVOGADRO_CONSTANT, 39 | "Speed of Light": SPEED_OF_LIGHT, 40 | "Pi": PI, 41 | "E": E, 42 | } 43 | -------------------------------------------------------------------------------- /angrmanagement/plugins/sample_plugin.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from angrmanagement.plugins import BasePlugin 6 | from angrmanagement.ui.widgets.qinst_annotation import QInstructionAnnotation, QPassthroughCount 7 | 8 | if TYPE_CHECKING: 9 | from collections.abc import Iterator 10 | 11 | from angr.sim_manager import SimulationManager 12 | 13 | from angrmanagement.ui.widgets.qblock import QBlock 14 | from angrmanagement.ui.workspace import Workspace 15 | 16 | 17 | class SamplePlugin(BasePlugin): 18 | def __init__(self, workspace: Workspace) -> None: 19 | super().__init__(workspace) 20 | 21 | workspace.main_instance.register_container("bookmarks", list, list[int], "Bookmarked addresses") 22 | 23 | MENU_BUTTONS = ("Add Bookmark",) 24 | 25 | def build_context_menu_functions(self, funcs): # pylint: disable=unused-argument 26 | yield ("owo", [("uwu", lambda: None), ("o_O", lambda: None)]) 27 | 28 | def step_callback(self, simgr: SimulationManager) -> None: 29 | print(f"Active States: {simgr}") 30 | 31 | def build_qblock_annotations(self, qblock: QBlock) -> Iterator[QInstructionAnnotation]: 32 | return [QPassthroughCount(qblock.addr, "entry")] 33 | -------------------------------------------------------------------------------- /angrmanagement/ui/menus/function_context_menu.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from angrmanagement.logic import GlobalInfo 6 | 7 | from .menu import Menu, MenuEntry 8 | 9 | if TYPE_CHECKING: 10 | from angrmanagement.ui.workspace import Workspace 11 | 12 | 13 | class FunctionContextMenu(Menu): 14 | def __init__(self, workspace: Workspace, parent) -> None: 15 | super().__init__("Function", parent=parent) 16 | self.workspace = workspace 17 | 18 | self.funcs = [] 19 | 20 | # TODO add Rename, Change Type, xrefs, etc 21 | 22 | def set(self, funcs): 23 | self.funcs = funcs 24 | return self 25 | 26 | def qmenu(self, extra_entries=None): 27 | self.entries = [] 28 | if len(self.funcs): 29 | self.entries.append( 30 | MenuEntry("Show Function Info", lambda: self.workspace.show_function_info(self.funcs[0])) 31 | ) 32 | if extra_entries is None: 33 | extra_entries = () 34 | return super().qmenu( 35 | extra_entries=list(GlobalInfo.main_window.workspace.plugins.build_context_menu_functions(self.funcs)) 36 | + list(extra_entries), 37 | cached=False, 38 | ) 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yml: -------------------------------------------------------------------------------- 1 | name: Ask a question 2 | description: Ask a question about angr-management 3 | labels: [question,needs-triage] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | If you have a question about angr-management, that is not a bug report or a feature request, you can ask it here. For more real-time help with angr and angr-management, from us and the community, join our [Slack](https://angr.io/invite/). 9 | 10 | Before submitting this question, please check the following, which may answer your question: 11 | * Have you checked the [angr FAQ](https://docs.angr.io/introductory-errata/faq)? 12 | * Have you [searched existing issues](https://github.com/angr/angr-management/issues?q=is%3Aissue+label%3Aquestion) to see if this question has been answered before? 13 | * Have you checked that you are running the latest versions of angr-management and its components? angr-management is rapidly-evolving! 14 | 15 | Please note: The angr suite is maintained by a small team. While we cannot guarantee any timeliness for fixes and enhancements, we will do our best. 16 | 17 | - type: textarea 18 | attributes: 19 | label: Question 20 | description: 21 | validations: 22 | required: true 23 | -------------------------------------------------------------------------------- /angrmanagement/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | All functions in this module return a list of mixed BasePlugin class and exception objects to indicate 3 | either the success or failure of a load. 4 | 5 | This file intentionally does not reuse angr.misc.autoimport since it has additional design goals (managing exceptions, 6 | loading out-of-tree files). 7 | """ 8 | 9 | from __future__ import annotations 10 | 11 | import logging 12 | 13 | from .base_plugin import BasePlugin 14 | from .load import ( 15 | load_default_plugins, 16 | load_plugin_description, 17 | load_plugin_descriptions_from_dir, 18 | load_plugins_from_dir, 19 | load_plugins_from_file, 20 | load_plugins_from_module, 21 | load_plugins_from_package, 22 | load_plugins_from_vars, 23 | ) 24 | from .plugin_description import PluginDescription 25 | from .plugin_manager import PluginManager 26 | 27 | log = logging.getLogger(__name__) 28 | 29 | 30 | __all__ = [ 31 | "BasePlugin", 32 | "PluginDescription", 33 | "PluginManager", 34 | "load_plugin_descriptions_from_dir", 35 | "load_plugin_description", 36 | "load_default_plugins", 37 | "load_plugins_from_dir", 38 | "load_plugins_from_package", 39 | "load_plugins_from_file", 40 | "load_plugins_from_module", 41 | "load_plugins_from_vars", 42 | ] 43 | -------------------------------------------------------------------------------- /scripts/build-appimage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | cd "$(dirname "$0")" 4 | cd .. 5 | 6 | SRCDIR="$PWD/dist/angr-management" 7 | if [[ ! -e $SRCDIR ]]; then 8 | echo "Run pyinstaller onedir build first" 9 | exit 1 10 | fi 11 | 12 | # Prepare AppDir 13 | APPDIR="$PWD/appdir" 14 | echo "[*] Preparing AppDir at $APPDIR" 15 | mkdir -p $APPDIR/usr/bin 16 | cp -r $SRCDIR/* $APPDIR/usr/bin/ 17 | for X in 16 24 32 64 128 256; do 18 | INDIR=$APPDIR/usr/bin/_internal/angrmanagement/resources/images 19 | OUTDIR=$APPDIR/usr/share/icons/hicolor/${X}x${X}/apps 20 | install -DT "${INDIR}/angr_${X}x${X}.png" "${OUTDIR}/angr-management.png" 21 | done 22 | install -DT angr-management.desktop $APPDIR/usr/share/applications/angr-management.desktop 23 | install -DT angr-management.metainfo.xml $APPDIR/usr/share/metainfo/io.angr.angr-management.metainfo.xml 24 | 25 | # Build AppImage 26 | echo "[*] Building AppImage" 27 | 28 | LINUXDEPLOY=linuxdeploy-$(uname -m).AppImage 29 | if [[ ! -e $LINUXDEPLOY ]]; then 30 | echo "[>] Fetching linuxdeploy" 31 | wget --no-verbose https://github.com/linuxdeploy/linuxdeploy/releases/latest/download/$LINUXDEPLOY 32 | chmod +x *.AppImage 33 | fi 34 | 35 | LINUXDEPLOY_OUTPUT_VERSION=v$(python scripts/get-version.py) \ 36 | ARCH=$(uname -m) \ 37 | ./$LINUXDEPLOY --appdir=$APPDIR --output appimage 38 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Dark/images/drawing-pin.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Dracula/images/drawing-pin.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Light/images/drawing-pin-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /angrmanagement/resources/themes/Catppuccin Mocha/images/drawing-pin.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /angrmanagement/ui/views/patches_view.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtWidgets import QVBoxLayout 6 | 7 | from angrmanagement.ui.widgets.qpatch_table import QPatchTable 8 | 9 | from .view import InstanceView 10 | 11 | if TYPE_CHECKING: 12 | from angrmanagement.data.instance import Instance 13 | from angrmanagement.ui.workspace import Workspace 14 | 15 | 16 | class PatchesView(InstanceView): 17 | """ 18 | View showing all patches. 19 | """ 20 | 21 | def __init__(self, workspace: Workspace, default_docking_position: str, instance: Instance) -> None: 22 | super().__init__("patches", workspace, default_docking_position, instance) 23 | 24 | self.base_caption = "Patches" 25 | self._patch_table: QPatchTable 26 | 27 | self._init_widgets() 28 | 29 | # Reload upon creation 30 | self.reload() 31 | 32 | def reload(self) -> None: 33 | self._patch_table.reload() 34 | 35 | # 36 | # Private methods 37 | # 38 | 39 | def _init_widgets(self) -> None: 40 | self._patch_table = QPatchTable(self.instance, self) 41 | 42 | layout = QVBoxLayout() 43 | layout.addWidget(self._patch_table) 44 | layout.setSpacing(0) 45 | layout.setContentsMargins(0, 0, 0, 0) 46 | self.setLayout(layout) 47 | -------------------------------------------------------------------------------- /angrmanagement/data/jobs/decompile_function.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from angrmanagement.logic import GlobalInfo 6 | 7 | from .job import InstanceJob 8 | 9 | if TYPE_CHECKING: 10 | from angrmanagement.data.instance import Instance 11 | from angrmanagement.logic.jobmanager import JobContext 12 | 13 | 14 | class DecompileFunctionJob(InstanceJob): 15 | """ 16 | The job for running the decompiler analysis. You can trigger this by pressing f5 in a function. 17 | """ 18 | 19 | def __init__(self, instance: Instance, function, on_finish=None, blocking: bool = False, **kwargs) -> None: 20 | super().__init__("Decompiling", instance, on_finish=on_finish, blocking=blocking) 21 | self.kwargs = kwargs 22 | self.function = function 23 | 24 | def run(self, ctx: JobContext) -> None: 25 | decompiler = self.instance.project.analyses.Decompiler( 26 | self.function, 27 | flavor="pseudocode", 28 | variable_kb=self.instance.pseudocode_variable_kb, 29 | **self.kwargs, 30 | progress_callback=ctx.set_progress, 31 | ) 32 | # cache the result 33 | self.instance.kb.decompilations[(self.function.addr, "pseudocode")] = decompiler.cache 34 | 35 | GlobalInfo.main_window.workspace.plugins.decompile_callback(self.function) 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, The Regents of the University of California 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Release version' 8 | required: false 9 | default: 'master' 10 | type: string 11 | 12 | jobs: 13 | build: 14 | uses: ./.github/workflows/pyinstaller-build.yml 15 | with: 16 | version: ${{ github.event.inputs.version }} 17 | deploy: 18 | runs-on: ubuntu-latest 19 | needs: build 20 | permissions: 21 | contents: write 22 | id-token: write 23 | steps: 24 | - name: Download artifacts 25 | uses: actions/download-artifact@v4 26 | - name: Make release 27 | run: > 28 | gh release create ${{ github.event.inputs.version }} \ 29 | --repo angr/angr-management \ 30 | --title "angr management ${{ github.event.inputs.version }}" \ 31 | --notes "$RELEASE_NOTES" \ 32 | --target $GITHUB_SHA \ 33 | $(find . -type f) 34 | env: 35 | RELEASE_NOTES: > 36 | macOS users: Make sure to select the build that matches your CPU 37 | architecture. Both are currently not signed, so you may need to 38 | permit angr-management to run in your Privacy & Security settings 39 | (more info here: https://support.apple.com/en-us/102445#openanyway). 40 | 41 | GH_TOKEN: ${{ github.token }} 42 | -------------------------------------------------------------------------------- /angrmanagement/data/trace.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | try: 4 | import bintrace 5 | from bintrace.debugger_angr import get_angr_project_load_options_from_trace 6 | except ImportError: 7 | bintrace = None 8 | 9 | 10 | class Trace: 11 | """ 12 | Base class for different trace formats. 13 | """ 14 | 15 | @property 16 | def source(self) -> str: 17 | raise NotImplementedError 18 | 19 | @classmethod 20 | def trace_backend_enabled(cls) -> bool: 21 | return False 22 | 23 | def get_project_load_options(self): # pylint:disable=no-self-use 24 | return None 25 | 26 | 27 | class BintraceTrace(Trace): 28 | """ 29 | Bintrace execution trace. 30 | """ 31 | 32 | def __init__(self, trace: bintrace.Trace) -> None: 33 | assert BintraceTrace.trace_backend_enabled() 34 | self.trace: bintrace.Trace = trace 35 | 36 | @property 37 | def source(self) -> str: 38 | return self.trace.path 39 | 40 | @classmethod 41 | def load_trace(cls, path: str) -> BintraceTrace: 42 | trace = bintrace.Trace() 43 | trace.load_trace(path) 44 | return cls(trace) 45 | 46 | @classmethod 47 | def trace_backend_enabled(cls) -> bool: 48 | return bintrace is not None 49 | 50 | def get_project_load_options(self): 51 | return get_angr_project_load_options_from_trace(self.trace) 52 | -------------------------------------------------------------------------------- /angrmanagement/ui/views/trace_map_view.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtCore import QSize 6 | from PySide6.QtWidgets import QVBoxLayout 7 | 8 | from angrmanagement.ui.views.view import InstanceView 9 | from angrmanagement.ui.widgets import QTraceMap 10 | 11 | if TYPE_CHECKING: 12 | from angrmanagement.data.instance import Instance 13 | from angrmanagement.ui.workspace import Workspace 14 | 15 | 16 | class TraceMapView(InstanceView): 17 | """ 18 | View container for QTraceMap. 19 | """ 20 | 21 | def __init__(self, workspace: Workspace, default_docking_position: str, instance: Instance) -> None: 22 | super().__init__("tracemap", workspace, default_docking_position, instance) 23 | self.base_caption: str = "Trace Map" 24 | self.inner_widget: QTraceMap | None = None 25 | self._init_widgets() 26 | 27 | @staticmethod 28 | def minimumSizeHint(): 29 | return QSize(25, 25) 30 | 31 | @staticmethod 32 | def sizeHint(): 33 | return QSize(25, 25) 34 | 35 | def _init_widgets(self) -> None: 36 | """ 37 | Initialize widgets for this view. 38 | """ 39 | self.inner_widget = QTraceMap(self.instance, parent=self) 40 | lyt = QVBoxLayout() 41 | lyt.setContentsMargins(0, 0, 0, 0) 42 | lyt.addWidget(self.inner_widget) 43 | self.setLayout(lyt) 44 | -------------------------------------------------------------------------------- /angrmanagement/ui/menus/analyze_menu.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtCore import Qt 6 | from PySide6.QtGui import QKeySequence 7 | 8 | from angrmanagement.ui.icons import icon 9 | 10 | from .menu import Menu, MenuEntry, MenuSeparator 11 | 12 | if TYPE_CHECKING: 13 | from angrmanagement.ui.main_window import MainWindow 14 | 15 | 16 | class AnalyzeMenu(Menu): 17 | def __init__(self, main_window: MainWindow) -> None: 18 | super().__init__("&Analyze", parent=main_window) 19 | self.entries.extend( 20 | [ 21 | MenuEntry( 22 | "&Run Analysis...", 23 | main_window.run_analysis, 24 | shortcut=QKeySequence(Qt.Key.Key_F4), 25 | icon=icon("run-analysis"), 26 | ), 27 | MenuSeparator(), 28 | MenuEntry( 29 | "&Decompile", 30 | main_window.decompile_current_function, 31 | shortcut=QKeySequence(Qt.Key.Key_F5), 32 | icon=icon("pseudocode-view"), 33 | ), 34 | MenuEntry( 35 | "View in Proximity &Browser", 36 | main_window.view_proximity_for_current_function, 37 | shortcut=QKeySequence("Ctrl+B"), 38 | ), 39 | ], 40 | ) 41 | -------------------------------------------------------------------------------- /angrmanagement/ui/views/jobs_view.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtCore import QSize 6 | from PySide6.QtWidgets import QVBoxLayout 7 | 8 | from angrmanagement.ui.widgets.qjobs import QJobs 9 | 10 | from .view import InstanceView 11 | 12 | if TYPE_CHECKING: 13 | from angrmanagement.data.instance import Instance 14 | from angrmanagement.ui.workspace import Workspace 15 | 16 | 17 | class JobsView(InstanceView): 18 | """JobsView displays all pending, running, and finished jobs in the project.""" 19 | 20 | qjobs: QJobs 21 | 22 | def __init__(self, workspace: Workspace, default_docking_position: str, instance: Instance) -> None: 23 | super().__init__("jobs", workspace, default_docking_position, instance) 24 | self.base_caption = "Jobs" 25 | 26 | # The QJobs widget is initialized in the view, most functions of jobs table is done through QJobs 27 | self.qjobs = QJobs(workspace) 28 | vlayout = QVBoxLayout() 29 | vlayout.addWidget(self.qjobs) 30 | vlayout.setContentsMargins(0, 0, 0, 0) 31 | self.setLayout(vlayout) 32 | self.reload() 33 | 34 | def closeEvent(self, event) -> None: 35 | self.qjobs.close() 36 | super().closeEvent(event) 37 | 38 | def reload(self) -> None: 39 | pass 40 | 41 | @staticmethod 42 | def minimumSizeHint(): 43 | return QSize(0, 50) 44 | -------------------------------------------------------------------------------- /angrmanagement/ui/views/log_view.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtCore import QSize 6 | from PySide6.QtWidgets import QHBoxLayout 7 | 8 | from angrmanagement.ui.widgets.qlog_widget import QLogWidget 9 | 10 | from .view import InstanceView 11 | 12 | if TYPE_CHECKING: 13 | from angrmanagement.data.instance import Instance 14 | from angrmanagement.ui.workspace import Workspace 15 | 16 | 17 | class LogView(InstanceView): 18 | """ 19 | Log view displays logging output. 20 | """ 21 | 22 | def __init__(self, workspace: Workspace, default_docking_position: str, instance: Instance) -> None: 23 | super().__init__("log", workspace, default_docking_position, instance) 24 | 25 | self.base_caption = "Log" 26 | self._log_widget: QLogWidget = None 27 | 28 | self._init_widgets() 29 | self.reload() 30 | 31 | def closeEvent(self, event) -> None: 32 | self._log_widget.close() 33 | super().closeEvent(event) 34 | 35 | def reload(self) -> None: 36 | pass 37 | 38 | @staticmethod 39 | def minimumSizeHint(): 40 | return QSize(0, 50) 41 | 42 | def _init_widgets(self) -> None: 43 | self._log_widget = QLogWidget(self) 44 | 45 | hlayout = QHBoxLayout() 46 | hlayout.addWidget(self._log_widget) 47 | hlayout.setContentsMargins(0, 0, 0, 0) 48 | 49 | self.setLayout(hlayout) 50 | -------------------------------------------------------------------------------- /angrmanagement/config/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | 5 | from PySide6.QtCore import QStandardPaths 6 | 7 | from .config_manager import ConfigurationManager 8 | 9 | # Global configuration manager instance 10 | # note that we must access QStandardPaths.StandardLocation.AppConfigLocation once the Qt Application has been created 11 | config_dir: str = QStandardPaths.locate( 12 | QStandardPaths.StandardLocation.AppConfigLocation, "", QStandardPaths.LocateOption.LocateDirectory 13 | ) 14 | if config_dir == "": 15 | system_config_dir = QStandardPaths.writableLocation(QStandardPaths.StandardLocation.AppConfigLocation) 16 | if system_config_dir == "": 17 | print("Could not find configuration directory - settings will not be saved") 18 | config_dir = "" 19 | config_dir = os.path.join(system_config_dir, "angr-management") 20 | os.makedirs(config_dir, exist_ok=True) 21 | 22 | config_path: str | None 23 | if config_dir != "": 24 | config_path = os.path.join(config_dir, "config.toml") 25 | try: 26 | Conf = ConfigurationManager.parse_file(config_path) 27 | except FileNotFoundError: 28 | Conf = ConfigurationManager() 29 | else: 30 | config_path = None 31 | print("Could not find configuration directory - settings will not be saved") 32 | Conf = ConfigurationManager() 33 | 34 | 35 | def save_config() -> None: 36 | if config_path is None: 37 | return 38 | Conf.save_file(config_path) 39 | -------------------------------------------------------------------------------- /angr-management.metainfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.angr.angr-management 4 | 5 | angr-management 6 | Open-source graphical binary analysis platform 7 | 8 | CC0-1.0 9 | 10 | 11 | pointing 12 | keyboard 13 | 14 | 15 | 16 | 17 | angr-management is an open-source, cross-platform graphical binary analysis platform. 18 | 19 | 20 | 21 | 22 | https://github.com/angr/angr-management/blob/master/screenshots/decompilation.png?raw=true 23 | https://github.com/angr/angr-management/blob/master/screenshots/disassembly.png?raw=true 24 | 25 | 26 | 27 | Game 28 | 29 | 30 | 31 | 32 | https://angr.io 33 | https://github.com/angr/angr-management/issues 34 | https://docs.angr.io 35 | 36 | angr-management.desktop 37 | 38 | 39 | angr-management 40 | 41 | 42 | angr Developers 43 | 44 | -------------------------------------------------------------------------------- /docs/development/overview.rst: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | .. warning:: 5 | Please note that the documentation and the API for angr management are highly 6 | in-flux. You will need to spend time reading the source code. Grep is your 7 | friend. If you have questions, please ask in the angr Discord server. 8 | 9 | If you build something which uses an API and you want to make sure it doesn't 10 | break, you can contribute a testcase for the API! 11 | 12 | 13 | Main Window, Workspace, and Instance 14 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 15 | 16 | * First, the ``main_window``. This is the ``QMainWindow`` instance for the 17 | application. It contains basic functions that correspond to top-level buttons, 18 | such as loading a binary. 19 | * Next, the ``workspace``. This is a light object which coordinates the UI 20 | elements and manages the tabbed environment. You can use it to access any 21 | analysis-related GUI element, such as the disassembly view. 22 | * Finally, the ``instance``. This is angr management's data model. It contains 23 | mechanisms for synchronizing components on shared data sources, as well as 24 | logic for creating long-running jobs. 25 | 26 | ``workspace`` is also available as an attribute on ``main_window`` and 27 | ``instance`` is available as an attribute on ``workspace``. If you are 28 | programming in a namespace where none of these objects are available, you can 29 | import the ``angrmanagment.logic.GlobalInfo`` object, which contains a reference 30 | to ``main_window``. 31 | -------------------------------------------------------------------------------- /angrmanagement/ui/widgets/qicon_label.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtCore import QEvent, QSize, Signal 6 | from PySide6.QtWidgets import QHBoxLayout, QLabel, QWidget 7 | 8 | if TYPE_CHECKING: 9 | from PySide6.QtGui import QIcon 10 | 11 | 12 | class QIconLabel(QWidget): 13 | """ 14 | Show a label with an icon on the left. 15 | """ 16 | 17 | clicked = Signal() 18 | 19 | def __init__(self, icon: QIcon, text: str = "") -> None: 20 | super().__init__() 21 | lyt = QHBoxLayout() 22 | lyt.setContentsMargins(0, 0, 0, 0) 23 | 24 | self._icon = icon 25 | self._icon_label = QLabel() 26 | self._update_icon() 27 | lyt.addWidget(self._icon_label) 28 | 29 | self._text_label = QLabel(text) 30 | lyt.addWidget(self._text_label) 31 | 32 | self.setLayout(lyt) 33 | self._update_visibility() 34 | 35 | def changeEvent(self, event): 36 | if event.type() == QEvent.Type.PaletteChange: 37 | self._update_icon() 38 | 39 | def mouseReleaseEvent(self, _) -> None: 40 | self.clicked.emit() 41 | 42 | def setText(self, text: str) -> None: 43 | self._text_label.setText(text) 44 | self._update_visibility() 45 | 46 | def _update_visibility(self) -> None: 47 | self._text_label.setVisible(self._text_label.text() != "") 48 | 49 | def _update_icon(self): 50 | self._icon_label.setPixmap(self._icon.pixmap(QSize(16, 16))) 51 | -------------------------------------------------------------------------------- /angrmanagement/utils/func.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from angr.sim_type import SimType, SimTypePointer 6 | 7 | if TYPE_CHECKING: 8 | from angr.knowledge_plugins import Function 9 | 10 | 11 | def type2str(ty: SimType | None) -> str: 12 | """ 13 | Convert a SimType instance to a string that can be displayed. 14 | 15 | :param ty: The SimType instance, or None if it's for void. 16 | :return: A string. 17 | """ 18 | 19 | if ty is None: 20 | return "void" 21 | if isinstance(ty, SimTypePointer): 22 | return f"{type2str(ty.pts_to)}*" 23 | if ty.label: 24 | return ty.label 25 | return ty.c_repr() 26 | 27 | 28 | def function_prototype_str(func: Function) -> str: 29 | if func.prototype is None: 30 | return func.name 31 | 32 | # Type of the return value 33 | s = "" 34 | rt = type2str(func.prototype.returnty) 35 | s += rt + " " 36 | 37 | # function name 38 | s += func.demangled_name 39 | s += "(" 40 | 41 | # arguments 42 | for i, arg_type in enumerate(func.prototype.args): 43 | type_str = type2str(arg_type) 44 | 45 | if func.prototype.arg_names and i < len(func.prototype.arg_names): 46 | arg_name = func.prototype.arg_names[i] 47 | else: 48 | arg_name = f"arg_{i}" 49 | 50 | s += type_str + " " + arg_name 51 | 52 | if i < len(func.prototype.args) - 1: 53 | # splitter 54 | s += "," 55 | 56 | s += ")" 57 | return s 58 | -------------------------------------------------------------------------------- /angrmanagement/ui/widgets/qfont_option.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtWidgets import QFontDialog, QPushButton 6 | 7 | from angrmanagement.config import Conf 8 | 9 | if TYPE_CHECKING: 10 | from PySide6.QtGui import QFont 11 | 12 | 13 | class QFontOption(QPushButton): 14 | """ 15 | A widget used to allow users to change a font stored in Conf 16 | """ 17 | 18 | def __init__(self, config_key: str, parent=None) -> None: 19 | """ 20 | :param key: Key of the font in Conf 21 | :parent: Optional parent of this QWidget 22 | """ 23 | super().__init__(parent) 24 | self._config_key = config_key 25 | self.font: QFont = getattr(Conf, config_key) 26 | self.released.connect(self._prompt) 27 | self._format() 28 | 29 | def update(self) -> None: 30 | """ 31 | Update Conf with the selected font 32 | """ 33 | setattr(Conf, self._config_key, self.font) 34 | 35 | def _format(self) -> None: 36 | """ 37 | Format the widget's UI elements 38 | """ 39 | self.setText(f"{self.font.family()}, {self.font.pointSize()} pt") 40 | self.setFont(self.font) # We do not ._sync.keep_synced this; it should update always 41 | 42 | def _prompt(self) -> None: 43 | """ 44 | Prompt the user to select a font 45 | """ 46 | ok, font = QFontDialog.getFont(self.font, parent=self) 47 | if ok: 48 | self.font = font 49 | self._format() 50 | -------------------------------------------------------------------------------- /angrmanagement/data/jobs/simgr_step.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from .job import InstanceJob 6 | 7 | if TYPE_CHECKING: 8 | from angrmanagement.data.instance import Instance 9 | from angrmanagement.logic.jobmanager import JobContext 10 | 11 | 12 | class SimgrStepJob(InstanceJob): 13 | """A job that runs the step method of the simulation manager.""" 14 | 15 | def __init__(self, instance: Instance, simgr, until_branch: bool = False, step_callback=None) -> None: 16 | super().__init__("Simulation manager stepping", instance, on_finish=self._fire_completion_event) 17 | self._simgr = simgr 18 | self._until_branch = until_branch 19 | self._step_callback = step_callback 20 | 21 | def run(self, _: JobContext): 22 | if self._until_branch: 23 | orig_len = len(self._simgr.active) 24 | if orig_len > 0: 25 | while len(self._simgr.active) == orig_len: 26 | self._simgr.step(step_func=self._step_callback) 27 | self._simgr.prune() 28 | else: 29 | self._simgr.step(step_func=self._step_callback, num_inst=1) 30 | self._simgr.prune() 31 | 32 | return self._simgr 33 | 34 | def _fire_completion_event(self, result) -> None: 35 | self._simgr.am_event(src="job_done", job="step", result=result) 36 | 37 | def __repr__(self) -> str: 38 | if self._until_branch: 39 | return f"Stepping {self._simgr!r} until branch" 40 | else: 41 | return f"Stepping {self._simgr!r}" 42 | -------------------------------------------------------------------------------- /angrmanagement/ui/views/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .breakpoints_view import BreakpointsView 4 | from .call_explorer_view import CallExplorerView 5 | from .code_view import CodeView 6 | from .console_view import ConsoleView 7 | from .data_dep_view import DataDepView 8 | from .dep_view import DependencyView 9 | from .disassembly_view import DisassemblyView 10 | from .functions_view import FunctionsView 11 | from .hex_view import HexView 12 | from .jobs_view import JobsView 13 | from .log_view import LogView 14 | from .patches_view import PatchesView 15 | from .proximity_view import ProximityView 16 | from .registers_view import RegistersView 17 | from .signatures_view import SignaturesView 18 | from .stack_view import StackView 19 | from .states_view import StatesView 20 | from .strings_view import StringsView 21 | from .symexec_view import SymexecView 22 | from .trace_map_view import TraceMapView 23 | from .traces_view import TracesView 24 | from .types_view import TypesView 25 | from .view import BaseView, InstanceView 26 | 27 | __all__ = [ 28 | "BreakpointsView", 29 | "CallExplorerView", 30 | "CodeView", 31 | "ConsoleView", 32 | "DataDepView", 33 | "DependencyView", 34 | "DisassemblyView", 35 | "FunctionsView", 36 | "HexView", 37 | "JobsView", 38 | "LogView", 39 | "PatchesView", 40 | "ProximityView", 41 | "RegistersView", 42 | "StackView", 43 | "StatesView", 44 | "StringsView", 45 | "SymexecView", 46 | "SignaturesView", 47 | "TraceMapView", 48 | "TracesView", 49 | "TypesView", 50 | "BaseView", 51 | "InstanceView", 52 | ] 53 | -------------------------------------------------------------------------------- /angrmanagement/plugins/ail2asm/asm_output.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from PySide6.QtCore import Qt 4 | from PySide6.QtWidgets import QDialog, QDialogButtonBox, QHBoxLayout, QLabel, QPlainTextEdit, QVBoxLayout 5 | 6 | 7 | class AsmOutput(QDialog): 8 | """ 9 | Displays generated assembly code 10 | """ 11 | 12 | def __init__(self, s: str, parent=None) -> None: 13 | super().__init__(parent) 14 | 15 | self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint) 16 | 17 | self._edit: QPlainTextEdit = None 18 | 19 | self.setWindowTitle("Assembly code output") 20 | 21 | self.main_layout = QVBoxLayout() 22 | 23 | self._init_widgets() 24 | 25 | self.setLayout(self.main_layout) 26 | 27 | self._edit.setPlainText(s) 28 | 29 | self.show() 30 | 31 | # 32 | # Private methods 33 | # 34 | 35 | def _init_widgets(self) -> None: 36 | # Assembly label 37 | 38 | asm_label = QLabel(self) 39 | asm_label.setText("Assembly code") 40 | 41 | edit = QPlainTextEdit() 42 | edit.setMinimumWidth(600) 43 | edit.setMinimumHeight(400) 44 | self._edit = edit 45 | 46 | edit_layout = QHBoxLayout() 47 | edit_layout.addWidget(asm_label) 48 | edit_layout.addWidget(edit) 49 | self.main_layout.addLayout(edit_layout) 50 | 51 | # buttons 52 | buttons = QDialogButtonBox(parent=self) 53 | buttons.setStandardButtons(QDialogButtonBox.StandardButton.Ok) 54 | buttons.accepted.connect(self.close) 55 | self.main_layout.addWidget(buttons) 56 | -------------------------------------------------------------------------------- /angrmanagement/ui/views/states_view.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtCore import QSize 6 | from PySide6.QtWidgets import QHBoxLayout 7 | 8 | from angrmanagement.ui.widgets.qstate_table import QStateTable 9 | 10 | from .view import InstanceView 11 | 12 | if TYPE_CHECKING: 13 | from angrmanagement.data.instance import Instance 14 | from angrmanagement.ui.workspace import Workspace 15 | 16 | 17 | class StatesView(InstanceView): 18 | def __init__(self, workspace: Workspace, default_docking_position: str, instance: Instance) -> None: 19 | super().__init__("states", workspace, default_docking_position, instance) 20 | 21 | self.base_caption = "States" 22 | self._state_table: QStateTable 23 | 24 | self._init_widgets() 25 | 26 | def closeEvent(self, event) -> None: 27 | """ 28 | Close children before exiting 29 | """ 30 | self._state_table.close() 31 | 32 | def sizeHint(self): 33 | return QSize(400, 800) 34 | 35 | def _init_widgets(self) -> None: 36 | self._state_table = QStateTable(self.workspace, self.instance, self, selection_callback=self._on_state_selected) 37 | 38 | hlayout = QHBoxLayout() 39 | hlayout.addWidget(self._state_table) 40 | hlayout.setContentsMargins(0, 0, 0, 0) 41 | 42 | self.setLayout(hlayout) 43 | 44 | def _on_state_selected(self, state) -> None: 45 | """ 46 | A new function is on selection right now. Update the disassembly view that is currently at front. 47 | 48 | :param function: 49 | :return: 50 | """ 51 | -------------------------------------------------------------------------------- /angrmanagement/data/jobs/code_tagging.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from angrmanagement.data.analysis_options import AnalysisConfiguration 6 | 7 | from .job import InstanceJob 8 | 9 | if TYPE_CHECKING: 10 | from angrmanagement.data.instance import Instance 11 | from angrmanagement.logic.jobmanager import JobContext 12 | 13 | 14 | class CodeTaggingConfiguration(AnalysisConfiguration): 15 | """ 16 | Configuration for Code Tagging. 17 | """ 18 | 19 | def __init__(self, instance: Instance) -> None: 20 | super().__init__(instance) 21 | self.name = "code_tagging" 22 | self.display_name = "Tag Functions Based on Syntactic Features" 23 | self.description = "Add tags to functions based on syntactic features in assembly code and referenced strings." 24 | self.enabled = False 25 | 26 | 27 | class CodeTaggingJob(InstanceJob): 28 | """ 29 | Job for tagging functions. 30 | """ 31 | 32 | def __init__(self, instance: Instance, on_finish=None) -> None: 33 | super().__init__("Code tagging", instance, on_finish=on_finish) 34 | 35 | def run(self, ctx: JobContext) -> None: 36 | func_count = len(self.instance.kb.functions) 37 | for i, func in enumerate(self.instance.kb.functions.values()): 38 | if func.is_alignment: 39 | continue 40 | ct = self.instance.project.analyses.CodeTagging(func) 41 | func.tags = tuple(ct.tags) 42 | 43 | percentage = i / func_count * 100 44 | ctx.set_progress(percentage) 45 | 46 | def __repr__(self) -> str: 47 | return "CodeTaggingJob" 48 | -------------------------------------------------------------------------------- /angrmanagement/ui/widgets/qaddress_input.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtWidgets import QLineEdit 6 | 7 | if TYPE_CHECKING: 8 | from collections.abc import Callable 9 | 10 | from angrmanagement.data.instance import Instance 11 | 12 | 13 | class QAddressInput(QLineEdit): 14 | def __init__( 15 | self, textchanged_callback: Callable | None, instance: Instance, parent=None, default: str | None = None 16 | ) -> None: 17 | super().__init__(parent) 18 | 19 | self.instance = instance 20 | 21 | if default is not None: 22 | self.setText(str(default)) 23 | 24 | if textchanged_callback is not None: 25 | self.textChanged.connect(textchanged_callback) 26 | 27 | @property 28 | def target(self): 29 | text = self.text() 30 | if self._is_valid_addr_or_label(text): 31 | return self._convert_to_addr(text) 32 | return None 33 | 34 | def _is_valid_addr_or_label(self, input) -> bool: 35 | r = self._convert_to_addr(input) 36 | return r is not None 37 | 38 | def _convert_to_addr(self, input_): 39 | # TODO: take care of labels 40 | # TODO: take care of decimal integers 41 | 42 | # is it a hex? 43 | try: 44 | addr = int(input_, 16) 45 | return addr 46 | except ValueError: 47 | pass 48 | 49 | # is it a function name? 50 | functions = self.instance.project.kb.functions 51 | func = functions.function(name=input_, create=False) 52 | if func is not None: 53 | return func.addr 54 | 55 | return None 56 | -------------------------------------------------------------------------------- /angrmanagement/data/jobs/simgr_explore.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from .job import InstanceJob 6 | 7 | if TYPE_CHECKING: 8 | from angrmanagement.data.instance import Instance 9 | from angrmanagement.logic.jobmanager import JobContext 10 | 11 | 12 | class SimgrExploreJob(InstanceJob): 13 | """A job that runs the explore method of a simulation manager.""" 14 | 15 | def __init__( 16 | self, instance: Instance, simgr, find=None, avoid=None, step_callback=None, until_callback=None, on_finish=None 17 | ) -> None: 18 | super().__init__("Simulation manager exploring", instance, on_finish=on_finish) 19 | self._simgr = simgr 20 | self._find = find 21 | self._avoid = avoid 22 | self._step_callback = step_callback 23 | self._until_callback = until_callback 24 | self._interrupted = False 25 | 26 | def run(self, _: JobContext): 27 | """Run the job. Runs in the worker thread.""" 28 | 29 | def until_callback(*args, **kwargs): 30 | return self._interrupted or callable(self._until_callback) and self._until_callback(*args, **kwargs) 31 | 32 | self._simgr.explore(find=self._find, avoid=self._avoid, step_func=self._step_callback, until=until_callback) 33 | return self._simgr 34 | 35 | def __repr__(self) -> str: 36 | return f"Exploring {self._simgr!r}" 37 | 38 | @classmethod 39 | def create(cls, instance: Instance, simgr, **kwargs): 40 | def callback(result) -> None: 41 | simgr.am_event(src="job_done", job="explore", result=result) 42 | 43 | return cls(instance, simgr, on_finish=callback, **kwargs) 44 | -------------------------------------------------------------------------------- /angrmanagement/data/jobs/flirt_signature_recognition.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | from typing import TYPE_CHECKING 5 | 6 | import angr 7 | 8 | from angrmanagement.data.analysis_options import AnalysisConfiguration 9 | 10 | from .job import InstanceJob 11 | 12 | if TYPE_CHECKING: 13 | from angrmanagement.data.instance import Instance 14 | from angrmanagement.logic.jobmanager import JobContext 15 | 16 | _l = logging.getLogger(name=__name__) 17 | 18 | 19 | class FlirtAnalysisConfiguration(AnalysisConfiguration): 20 | """ 21 | Configuration for Flirt analysis. 22 | """ 23 | 24 | def __init__(self, instance: Instance) -> None: 25 | super().__init__(instance) 26 | self.name = "flirt" 27 | self.display_name = "Function Signature Matching" 28 | doc = angr.analyses.flirt.FlirtAnalysis.__doc__ 29 | self.description = doc.strip() if doc else "" 30 | self.enabled = True 31 | 32 | 33 | class FlirtSignatureRecognitionJob(InstanceJob): 34 | """ 35 | Describes a job for using FLIRT signatures to recognize and match library functions embedded in a binary. 36 | """ 37 | 38 | def __init__(self, instance: Instance, on_finish=None) -> None: 39 | super().__init__("Applying FLIRT signatures", instance, on_finish=on_finish) 40 | 41 | def run(self, _: JobContext) -> None: 42 | if self.instance.project.arch.name.lower() in angr.flirt.FLIRT_SIGNATURES_BY_ARCH: 43 | self.instance.project.analyses.Flirt() 44 | else: 45 | _l.warning("No FLIRT signatures exist for architecture %s.", self.instance.project.arch.name) 46 | 47 | def __repr__(self) -> str: 48 | return "FlirtSignatureRecognitionJob" 49 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: Test on Windows 2 | 3 | on: 4 | workflow_dispatch: 5 | workflow_call: 6 | 7 | jobs: 8 | windows: 9 | name: Test Windows 10 | runs-on: windows-2022 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | path: angr-management 15 | - uses: actions/checkout@v4 16 | with: 17 | repository: angr/binaries 18 | path: binaries 19 | - uses: actions/setup-python@v4 20 | with: 21 | python-version: "3.10" 22 | - run: python -m venv $HOME/venv 23 | name: Create venv 24 | shell: bash 25 | - run: | 26 | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" 27 | call %USERPROFILE%\venv\Scripts\activate 28 | python -m pip install --upgrade pip 29 | pip install -U setuptools wheel setuptools-rust cffi "unicorn==2.0.1.post1" 30 | pip install git+https://github.com/angr/archinfo.git 31 | pip install git+https://github.com/angr/pyvex.git 32 | pip install git+https://github.com/angr/cle.git 33 | pip install git+https://github.com/angr/claripy.git 34 | pip install --no-build-isolation git+https://github.com/angr/angr.git 35 | name: Install dependencies 36 | shell: cmd 37 | - run: | 38 | call %USERPROFILE%\venv\Scripts\activate 39 | pip install .\angr-management 40 | pip install --group .\angr-management\pyproject.toml:testing 41 | name: Install angr-management 42 | shell: cmd 43 | - run: | 44 | call %USERPROFILE%\venv\Scripts\activate 45 | pytest -n auto angr-management 46 | name: Run pytest 47 | shell: cmd 48 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Request a feature 2 | description: Request a new feature for angr-management 3 | labels: [enhancement,needs-triage] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you for taking the time to submit this feature request! 9 | 10 | Before submitting this feature request, please check the following: 11 | * Have you checked that you are running the latest versions of angr-management and its components? angr-management is rapidly-evolving! 12 | * Have you [searched existing issues](https://github.com/angr/angr-management/issues?q=is%3Aissue+label%3Aenhancement+) to see if this feature has been requested before? 13 | 14 | Please note: The angr suite is maintained by a small team. While we cannot guarantee any timeliness for fixes and enhancements, we will do our best. For more real-time help with angr, from us and the community, join our [Slack](https://angr.io/invite/). 15 | 16 | - type: textarea 17 | attributes: 18 | label: Description 19 | description: | 20 | Brief description of the desired feature. If the feature is intended to solve some problem, please clearly describe the problem, including any relevant binaries, etc. 21 | 22 | **Tip:** You can attach files to the issue by first clicking on the textarea to select it, then dragging & dropping the file onto the textarea. 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | attributes: 28 | label: Alternatives 29 | description: Possible alternative solutions or features that you have considered. 30 | 31 | - type: textarea 32 | attributes: 33 | label: Additional context 34 | description: Any other context or screenshots about the feature request. 35 | -------------------------------------------------------------------------------- /angrmanagement/data/jobs/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .calling_convention_recovery import CallingConventionRecoveryConfiguration, CallingConventionRecoveryJob 4 | from .cfg_generation import CFGAnalysisConfiguration, CFGGenerationJob 5 | from .code_tagging import CodeTaggingConfiguration, CodeTaggingJob 6 | from .ddg_generation import DDGGenerationJob 7 | from .decompile_function import DecompileFunctionJob 8 | from .deobfuscation import ( 9 | APIDeobfuscationConfiguration, 10 | APIDeobfuscationJob, 11 | StringDeobfuscationConfiguration, 12 | StringDeobfuscationJob, 13 | ) 14 | from .dependency_analysis import DependencyAnalysisJob 15 | from .flirt_signature_recognition import FlirtAnalysisConfiguration, FlirtSignatureRecognitionJob 16 | from .job import Job 17 | from .prototype_finding import PrototypeFindingJob 18 | from .simgr_explore import SimgrExploreJob 19 | from .simgr_step import SimgrStepJob 20 | from .variable_recovery import VariableRecoveryConfiguration, VariableRecoveryJob 21 | from .vfg_generation import VFGGenerationJob 22 | 23 | __all__ = [ 24 | "APIDeobfuscationConfiguration", 25 | "APIDeobfuscationJob", 26 | "StringDeobfuscationConfiguration", 27 | "StringDeobfuscationJob", 28 | "CallingConventionRecoveryConfiguration", 29 | "CallingConventionRecoveryJob", 30 | "CFGAnalysisConfiguration", 31 | "CFGGenerationJob", 32 | "CodeTaggingConfiguration", 33 | "CodeTaggingJob", 34 | "DDGGenerationJob", 35 | "DecompileFunctionJob", 36 | "DependencyAnalysisJob", 37 | "FlirtAnalysisConfiguration", 38 | "FlirtSignatureRecognitionJob", 39 | "Job", 40 | "PrototypeFindingJob", 41 | "SimgrExploreJob", 42 | "SimgrStepJob", 43 | "VariableRecoveryConfiguration", 44 | "VariableRecoveryJob", 45 | "VFGGenerationJob", 46 | ] 47 | -------------------------------------------------------------------------------- /tests/test_qaddress_input.py: -------------------------------------------------------------------------------- 1 | # pylint:disable=missing-class-docstring 2 | from __future__ import annotations 3 | 4 | import os 5 | import sys 6 | import unittest 7 | 8 | import angr 9 | from angr import load_shellcode 10 | from common import AngrManagementTestCase, test_location 11 | from PySide6.QtTest import QTest 12 | 13 | from angrmanagement.ui.widgets.qaddress_input import QAddressInput 14 | 15 | 16 | class TestQaddressInput(AngrManagementTestCase): 17 | 18 | def test_address_conversion(self): 19 | main = self.main 20 | main.workspace.main_instance.project.am_obj = load_shellcode(b"X", "amd64") 21 | main.workspace.main_instance.project.kb.functions.function(addr=0x1234, name="foo", create=True) 22 | 23 | obj = QAddressInput(None, main.workspace.main_instance) 24 | 25 | obj.setText("") 26 | QTest.keyClicks(obj, "4321") 27 | self.assertEqual(obj.target, 0x4321) 28 | 29 | obj.setText("") 30 | QTest.keyClicks(obj, "foo") 31 | self.assertEqual(obj.target, 0x1234) 32 | 33 | obj.setText("") 34 | QTest.keyClicks(obj, "12x3") 35 | self.assertIsNone(obj.target) 36 | 37 | def test_function_name(self): 38 | proj = angr.Project(os.path.join(test_location, "x86_64", "fauxware"), auto_load_libs=False) 39 | main = self.main 40 | main.workspace.main_instance.project.am_obj = proj 41 | 42 | cfg = proj.analyses.CFG() 43 | obj = QAddressInput(None, main.workspace.main_instance) 44 | 45 | obj.setText("") 46 | QTest.keyClicks(obj, "main") 47 | self.assertEqual(obj.target, cfg.kb.functions["main"].addr) 48 | 49 | obj.setText("") 50 | QTest.keyClicks(obj, "main_1") 51 | self.assertIsNone(obj.target) 52 | 53 | 54 | if __name__ == "__main__": 55 | unittest.main(argv=sys.argv) 56 | -------------------------------------------------------------------------------- /angrmanagement/plugins/source_importer/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from typing import TYPE_CHECKING 5 | 6 | from PySide6.QtWidgets import QFileDialog 7 | 8 | from angrmanagement.plugins import BasePlugin 9 | 10 | if TYPE_CHECKING: 11 | from angrmanagement.ui.workspace import Workspace 12 | 13 | 14 | class SourceImporterPlugin(BasePlugin): 15 | """ 16 | A plugin that adds scraping source code from the local filesystem and displaying it as an alternative to the 17 | pseudocode. 18 | """ 19 | 20 | DISPLAY_NAME = "Source Importer" 21 | 22 | def __init__(self, workspace: Workspace) -> None: 23 | super().__init__(workspace) 24 | 25 | self.source_paths = [] 26 | self._import_from_project() 27 | 28 | def handle_project_initialization(self) -> None: 29 | self._import_from_project() 30 | 31 | def _import_from_project(self) -> None: 32 | self.source_paths = [] 33 | if self.workspace.main_instance.original_binary_path: 34 | self.source_paths.append(os.path.dirname(self.workspace.main_instance.original_binary_path)) 35 | 36 | def decompile_callback(self, func) -> None: 37 | for source_root in self.source_paths: 38 | self.workspace.main_instance.project.analyses.ImportSourceCode( 39 | func, flavor="source", source_root=source_root 40 | ) 41 | 42 | MENU_BUTTONS = ["Import source path"] 43 | 44 | def handle_click_menu(self, idx: int) -> None: 45 | if idx != 0: 46 | return 47 | result = QFileDialog.getExistingDirectory( 48 | self.workspace.main_window, 49 | "Select source root", 50 | ".", 51 | QFileDialog.Option.ShowDirsOnly | QFileDialog.Option.DontResolveSymlinks, 52 | ) 53 | if result is not None: 54 | self.source_paths.append(result) 55 | -------------------------------------------------------------------------------- /angrmanagement/utils/edge.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | class EdgeSort: 5 | DIRECT_JUMP = 0 6 | TRUE_BRANCH = 1 7 | FALSE_BRANCH = 2 8 | BACK_EDGE = 3 9 | EXCEPTION_EDGE = 4 10 | 11 | 12 | class Edge: 13 | def __init__(self, src, dst, sort=EdgeSort.DIRECT_JUMP) -> None: 14 | self.src = src 15 | self.dst = dst 16 | 17 | self.start_index = None 18 | self.max_start_index = None 19 | self.end_index = None 20 | self.max_end_index = None 21 | 22 | self.points = [] 23 | self.moves = [] 24 | self.coordinates = [] 25 | self.sort = sort 26 | 27 | def add_point(self, col, row, index) -> None: 28 | self.points.append((col, row, index)) 29 | 30 | def add_move(self, move) -> None: 31 | self.moves.append(move) 32 | 33 | def add_coordinate(self, x, y) -> None: 34 | if len(self.coordinates) >= 2: 35 | coord_a, coord_b = self.coordinates[-2], self.coordinates[-1] 36 | if coord_b[0] == coord_a[0] == x: 37 | # it moves vertically 38 | # replace coord_b 39 | self.coordinates[-1] = (x, y) 40 | return 41 | elif coord_b[1] == coord_a[1] == y: 42 | # it moves horizontally 43 | # replace coord b 44 | self.coordinates[-1] = (x, y) 45 | return 46 | 47 | self.coordinates.append((x, y)) 48 | 49 | @property 50 | def first_move(self): 51 | if self.moves: 52 | return self.moves[0] 53 | 54 | return 1 # NO_MOVE 55 | 56 | @property 57 | def last_move(self): 58 | if self.moves: 59 | return self.moves[-1] 60 | 61 | return 1 # NO_MOVE 62 | 63 | def __repr__(self) -> str: 64 | return f"" 65 | -------------------------------------------------------------------------------- /angrmanagement/ui/widgets/state_inspector.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtWidgets import QTabWidget 6 | 7 | from .qconstraint_viewer import QConstraintViewer 8 | from .qfiledesc_viewer import QFileDescriptorViewer 9 | from .qmemory_viewer import QMemoryViewer 10 | from .qregister_viewer import QRegisterViewer 11 | from .qvextemps_viewer import QVEXTempsViewer 12 | 13 | if TYPE_CHECKING: 14 | from angrmanagement.ui.workspace import Workspace 15 | 16 | 17 | class StateInspector(QTabWidget): 18 | """ 19 | Dispaly detail information for a selected state. 20 | """ 21 | 22 | def __init__(self, workspace: Workspace, state, parent=None) -> None: 23 | super().__init__(parent=parent) 24 | self.workspace = workspace 25 | self._state = state 26 | 27 | self._register_viewer: QRegisterViewer 28 | self._memory_viewer: QMemoryViewer 29 | self._vextemps_viewer: QVEXTempsViewer 30 | self._constraint_viewer: QConstraintViewer 31 | self._filedesc_viewer: QFileDescriptorViewer 32 | 33 | self._init_widgets() 34 | 35 | def _init_widgets(self) -> None: 36 | self._register_viewer = QRegisterViewer(self._state, self, self.workspace) 37 | self.addTab(self._register_viewer, "Registers") 38 | 39 | self._memory_viewer = QMemoryViewer(self._state, self, self.workspace) 40 | self.addTab(self._memory_viewer, "Memory") 41 | 42 | self._constraint_viewer = QConstraintViewer(self._state, self, self.workspace) 43 | self.addTab(self._constraint_viewer, "Constraints") 44 | 45 | self._filedesc_viewer = QFileDescriptorViewer(self._state, self, self.workspace) 46 | self.addTab(self._filedesc_viewer, "File Descriptors") 47 | 48 | self._vextemps_viewer = QVEXTempsViewer(self._state, self, self.workspace) 49 | self.addTab(self._vextemps_viewer, "Temps") 50 | -------------------------------------------------------------------------------- /angrmanagement/ui/widgets/qipython_widget.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from IPython.lib import guisupport 4 | from qtconsole.inprocess import QtInProcessKernelManager 5 | from qtconsole.rich_jupyter_widget import RichJupyterWidget 6 | 7 | from angrmanagement.ui.css import CSS 8 | 9 | 10 | class QIPythonWidget(RichJupyterWidget): 11 | def __init__(self, *args, banner=None, namespace=None, **kwargs) -> None: 12 | if banner is not None: 13 | self.banner = banner 14 | RichJupyterWidget.__init__(self, *args, **kwargs) 15 | self._on_css_updated() 16 | CSS.global_css.am_subscribe(self._on_css_updated) 17 | 18 | self.kernel_manager = kernel_manager = QtInProcessKernelManager() 19 | kernel_manager.start_kernel() 20 | kernel_manager.kernel.gui = "qt4" 21 | 22 | if namespace is not None: 23 | self.push_namespace(namespace) 24 | 25 | self.kernel_client = kernel_client = self._kernel_manager.client() 26 | kernel_client.start_channels() 27 | 28 | def stop() -> None: 29 | kernel_client.stop_channels() 30 | kernel_manager.shutdown_kernel() 31 | guisupport.get_app_qt4().exit() 32 | 33 | self.exit_requested.connect(stop) 34 | 35 | self.enable_calltips = False 36 | 37 | def __del__(self) -> None: 38 | super().__del__() 39 | CSS.global_css.am_unsubscribe(self._on_css_updated) 40 | 41 | def _on_css_updated(self) -> None: 42 | self.setStyleSheet(CSS.global_css.am_obj) 43 | 44 | def push_namespace(self, namespace) -> None: 45 | self.kernel_manager.kernel.shell.push(namespace) 46 | 47 | def clear_terminal(self) -> None: 48 | self._control.clear() 49 | 50 | def print_text(self, text: str) -> None: 51 | self._append_plain_text(text, True) 52 | 53 | def execute_command(self, command) -> None: 54 | self._execute(command, False) 55 | -------------------------------------------------------------------------------- /angrmanagement/ui/dialogs/input_prompt.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from PySide6.QtWidgets import QDialog, QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit, QVBoxLayout 4 | 5 | 6 | class InputPromptDialog(QDialog): 7 | """ 8 | A generic dialog to prompt for text input. 9 | """ 10 | 11 | def __init__(self, window_title: str, prompt_text: str, initial_input_text: str = "", parent=None) -> None: 12 | super().__init__(parent) 13 | self.prompt_text: str = prompt_text 14 | self.initial_input_text: str = initial_input_text 15 | self.input_edt: QLineEdit = None 16 | self.result = None 17 | 18 | self.setWindowTitle(window_title) 19 | self.main_layout = QVBoxLayout() 20 | self._init_widgets() 21 | self.setLayout(self.main_layout) 22 | 23 | # 24 | # Private methods 25 | # 26 | 27 | def _init_widgets(self) -> None: 28 | prompt_lbl = QLabel(self) 29 | prompt_lbl.setText(self.prompt_text) 30 | 31 | input_edt = QLineEdit(parent=self) 32 | input_edt.setText(self.initial_input_text) 33 | input_edt.selectAll() 34 | self.input_edt = input_edt 35 | 36 | prompt_input_lyt = QHBoxLayout() 37 | prompt_input_lyt.addWidget(prompt_lbl) 38 | prompt_input_lyt.addWidget(input_edt) 39 | self.main_layout.addLayout(prompt_input_lyt) 40 | 41 | buttons = QDialogButtonBox(parent=self) 42 | buttons.setStandardButtons(QDialogButtonBox.StandardButton.Cancel | QDialogButtonBox.StandardButton.Ok) 43 | buttons.accepted.connect(self._on_ok_clicked) 44 | buttons.rejected.connect(self.close) 45 | buttons_lyt = QHBoxLayout() 46 | buttons_lyt.addWidget(buttons) 47 | self.main_layout.addLayout(buttons_lyt) 48 | 49 | # 50 | # Event handlers 51 | # 52 | 53 | def _on_ok_clicked(self) -> None: 54 | self.result = self.input_edt.text() 55 | self.close() 56 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | from __future__ import annotations 6 | 7 | import datetime 8 | 9 | # -- Project information ----------------------------------------------------- 10 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 11 | 12 | project = "angr-management" 13 | project_copyright = f"{datetime.datetime.now().year}, The angr Project contributors" 14 | author = "The angr Project" 15 | 16 | # -- General configuration --------------------------------------------------- 17 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 18 | 19 | extensions = [ 20 | "sphinx.ext.autodoc", 21 | "sphinx.ext.autosummary", 22 | "sphinx.ext.coverage", 23 | "sphinx.ext.napoleon", 24 | "sphinx.ext.todo", 25 | "sphinx.ext.viewcode", 26 | "sphinx_autodoc_typehints", 27 | "myst_parser", 28 | ] 29 | 30 | templates_path = ["_templates"] 31 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 32 | 33 | # -- Options for autodoc ----------------------------------------------------- 34 | # https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#configuration 35 | autoclass_content = "class" 36 | autodoc_default_options = { 37 | "members": True, 38 | "member-order": "bysource", 39 | "inherited-members": True, 40 | "show-inheritance": True, 41 | "undoc-members": True, 42 | } 43 | autodoc_inherit_docstrings = True 44 | autodoc_typehints = "both" 45 | 46 | # -- Options for coverage ---------------------------------------------------- 47 | # https://www.sphinx-doc.org/en/master/usage/extensions/coverage.html 48 | coverage_write_headline = False 49 | 50 | # -- Options for HTML output ------------------------------------------------- 51 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 52 | 53 | html_theme = "furo" 54 | html_static_path = ["_static"] 55 | -------------------------------------------------------------------------------- /angrmanagement/data/library_docs.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | import logging 5 | import os 6 | 7 | from angrmanagement.utils.env import app_root, is_pyinstaller 8 | 9 | _l = logging.getLogger(name=__name__) 10 | 11 | 12 | class LibraryDocs: 13 | """ 14 | Implements the manager of library docs. 15 | """ 16 | 17 | def __init__(self) -> None: 18 | self.func_docs = [] 19 | 20 | def load_func_docs(self, path) -> None: 21 | if not os.path.isabs(path): 22 | path = os.path.join(app_root(), path) if is_pyinstaller() else os.path.join(app_root(), "..", path) 23 | path = os.path.normpath(path) 24 | _l.info("Loading library docs from %s.", path) 25 | docs = [] 26 | if os.path.isdir(path): 27 | for filename in os.listdir(path): 28 | if filename.endswith(".json"): 29 | jpath = os.path.join(path, filename) 30 | with open(jpath) as jfile: 31 | data = json.load(jfile) 32 | docs.append(data) 33 | 34 | self.func_docs = docs 35 | 36 | def get_docstring_for_func_name(self, func_name: str): 37 | for library in self.func_docs: 38 | for func_dict in library: 39 | if "name" not in func_dict: 40 | continue 41 | if "description" not in func_dict: 42 | continue 43 | names = func_dict["name"] 44 | name_list = names.split(",") 45 | for name in name_list: 46 | name = name.strip() 47 | if func_name == name: 48 | doc_string = func_dict["description"] 49 | url = "http://" 50 | ftype = "<>" 51 | if "url" in func_dict: 52 | url = func_dict["url"] 53 | if "type" in func_dict: 54 | ftype = func_dict["type"] 55 | return doc_string, url, ftype 56 | return None 57 | -------------------------------------------------------------------------------- /angrmanagement/logic/disassembly/jump_history.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | class JumpHistory: 5 | """ 6 | A class to store the navigation history of a reversing session. Typically found at DisassemblyView._jump_history 7 | or CodeView.jump_history. Maintains a list of addresses through which the user can navigate forwards and backwards. 8 | """ 9 | 10 | def __init__(self) -> None: 11 | self._history = [] 12 | self._pos = -1 13 | 14 | @property 15 | def history(self): 16 | return self._history 17 | 18 | @property 19 | def pos(self): 20 | return self._pos 21 | 22 | @property 23 | def current(self): 24 | if len(self._history): 25 | return self._history[self._pos] 26 | else: 27 | return None 28 | 29 | def __len__(self) -> int: 30 | return len(self._history) 31 | 32 | def jump_to(self, addr: int) -> None: 33 | if self._pos != len(self._history) - 1: 34 | self.trim() 35 | 36 | if not self._history or self._history[-1] != addr: 37 | self._history.append(addr) 38 | self._pos = len(self._history) - 1 39 | 40 | def record_address(self, addr: int) -> None: 41 | if 0 <= self._pos < len(self._history): 42 | self._history[self._pos] = addr 43 | else: 44 | self.jump_to(addr) 45 | 46 | def trim(self) -> None: 47 | self._history = self._history[: self._pos + 1] 48 | 49 | def backtrack(self): 50 | if self._pos > 0: 51 | self._pos -= 1 52 | 53 | if not 0 <= self._pos < len(self._history): 54 | return None 55 | else: 56 | return self._history[self._pos] 57 | 58 | def forwardstep(self): 59 | if self._pos < len(self._history) - 1: 60 | self._pos += 1 61 | 62 | if self._pos < len(self._history): 63 | return self._history[self._pos] 64 | else: 65 | return None 66 | 67 | def step_position(self, pos: int): 68 | if -1 < pos < len(self._history): 69 | self._pos = pos 70 | return self._history[self._pos] 71 | -------------------------------------------------------------------------------- /angrmanagement/plugins/dep_viewer/dep_plugin.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from angr.sim_type import normalize_cpp_function_name 6 | from PySide6.QtGui import QColor, Qt 7 | from sortedcontainers import SortedDict 8 | 9 | from angrmanagement.plugins.base_plugin import BasePlugin 10 | 11 | from .sinks import VulnerabilityType, sink_manager 12 | 13 | if TYPE_CHECKING: 14 | from angrmanagement.ui.workspace import Workspace 15 | 16 | 17 | class DependencyViewer(BasePlugin): 18 | def __init__(self, workspace: Workspace) -> None: 19 | super().__init__(workspace) 20 | 21 | self.covered_blocks = SortedDict() 22 | 23 | self.sink_color = Qt.GlobalColor.yellow 24 | 25 | def color_insn(self, addr: int, selected, disasm_view) -> QColor | None: 26 | if not selected: 27 | try: 28 | block_addr = next(self.covered_blocks.irange(maximum=addr, reverse=True)) 29 | except StopIteration: 30 | return None 31 | block_size = self.covered_blocks[block_addr] 32 | if block_addr <= addr < block_addr + block_size: 33 | return QColor(0xA5, 0xD0, 0xF3) 34 | return None 35 | 36 | FUNC_COLUMNS = ("Vuln Sink",) 37 | 38 | def extract_func_column(self, func, idx: int): 39 | assert idx == 0 40 | 41 | func_name = func.demangled_name 42 | if "<" in func_name or "{" in func_name: 43 | func_name = normalize_cpp_function_name(func_name) 44 | if "(" in func_name: 45 | # only take function name 46 | func_name = func_name[: func_name.index("(")] 47 | 48 | vulntype_and_sinks = sink_manager.get_function_sinks(func_name) 49 | if not vulntype_and_sinks: 50 | return 0, "" 51 | 52 | vuln_type, sink = vulntype_and_sinks[0] 53 | return 1, VulnerabilityType.to_string(vuln_type) 54 | 55 | def color_func(self, func) -> QColor | None: 56 | # test if we have a match 57 | match = sink_manager.has_function_sink(func.demangled_name) 58 | if match: 59 | return self.sink_color 60 | 61 | return None 62 | -------------------------------------------------------------------------------- /tests/common.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import unittest 5 | from typing import TYPE_CHECKING 6 | 7 | import angr 8 | from PySide6.QtCore import QThread 9 | from PySide6.QtTest import QTest 10 | from PySide6.QtWidgets import QApplication 11 | 12 | from angrmanagement.config import Conf 13 | from angrmanagement.logic import GlobalInfo 14 | from angrmanagement.ui.main_window import MainWindow 15 | 16 | if TYPE_CHECKING: 17 | from angrmanagement.data.instance import Instance 18 | from angrmanagement.ui.workspace import Workspace 19 | 20 | test_location = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "..", "binaries", "tests") 21 | 22 | app = None 23 | 24 | 25 | def create_qapp(): 26 | global app 27 | if app is None: 28 | app = QApplication([]) 29 | Conf.init_font_config() 30 | return app 31 | 32 | 33 | class AngrManagementTestCase(unittest.TestCase): 34 | """A base class for angr management test cases that starts the main window and event loop.""" 35 | 36 | main: MainWindow 37 | 38 | def setUp(self): 39 | self.app = create_qapp() 40 | GlobalInfo.gui_thread = QThread.currentThread() 41 | self.main = MainWindow(show=False) 42 | QTest.qWaitForWindowActive(self.main) 43 | 44 | def tearDown(self) -> None: 45 | self.main.close() 46 | del self.main 47 | 48 | 49 | class ProjectOpenTestCase(AngrManagementTestCase): 50 | """A base class for angr management test cases that opens a project.""" 51 | 52 | def setUp(self): 53 | super().setUp() 54 | self.main.workspace.main_instance.project.am_obj = angr.Project( 55 | os.path.join(test_location, "x86_64", "true"), auto_load_libs=False 56 | ) 57 | self.main.workspace.main_instance.project.am_event() 58 | self.main.workspace.job_manager.join_all_jobs() 59 | 60 | @property 61 | def workspace(self) -> Workspace: 62 | return self.main.workspace 63 | 64 | @property 65 | def instance(self) -> Instance: 66 | return self.workspace.main_instance 67 | 68 | @property 69 | def project(self) -> angr.Project: 70 | return self.instance.project.am_obj 71 | -------------------------------------------------------------------------------- /angrmanagement/ui/menus/disasm_label_context_menu.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from angrmanagement.config import Conf 4 | 5 | from .menu import Menu, MenuEntry, MenuSeparator 6 | 7 | 8 | class DisasmLabelContextMenu(Menu): 9 | def __init__(self, disasm_view) -> None: 10 | super().__init__("", parent=disasm_view) 11 | 12 | self.addr: int = None 13 | 14 | self.entries.extend( 15 | [ 16 | MenuEntry("T&oggle selection", self._toggle_label_selection), 17 | MenuSeparator(), 18 | MenuEntry("&XRefs...", self._popup_xrefs), 19 | MenuSeparator(), 20 | ] 21 | ) 22 | if Conf.has_operation_mango: 23 | self.entries.extend( 24 | [ 25 | MenuEntry("&Depends on...", self._popup_dependson_dialog), 26 | MenuSeparator(), 27 | ] 28 | ) 29 | self.entries.extend( 30 | [ 31 | MenuEntry("E&xecute symbolically...", self._popup_newstate_dialog), 32 | MenuEntry("&Avoid in execution...", self._avoid_in_execution), 33 | MenuEntry("&Find in execution...", self._find_in_execution), 34 | ] 35 | ) 36 | 37 | @property 38 | def _disasm_view(self): 39 | return self.parent 40 | 41 | def _popup_newstate_dialog(self) -> None: 42 | self._disasm_view.popup_newstate_dialog(async_=True) 43 | 44 | def _popup_dependson_dialog(self) -> None: 45 | self._disasm_view.popup_dependson_dialog(addr=self.addr, func=True) 46 | 47 | def _toggle_label_selection(self) -> None: 48 | self._disasm_view.infodock.toggle_label_selection(self.addr) 49 | 50 | def _avoid_in_execution(self) -> None: 51 | self._disasm_view.avoid_addr_in_exec(self.addr) 52 | 53 | def _find_in_execution(self) -> None: 54 | self._disasm_view.find_addr_in_exec(self.addr) 55 | 56 | def _popup_xrefs(self) -> None: 57 | if self._disasm_view is None or self._disasm_view._flow_graph is None: 58 | return 59 | self._disasm_view.popup_xref_dialog(addr=self.addr, variable=None, dst_addr=self.addr, async_=True) 60 | -------------------------------------------------------------------------------- /angrmanagement/ui/dialogs/func_doc.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtGui import Qt 6 | from PySide6.QtWidgets import QDialog, QGridLayout, QLabel, QTextEdit, QVBoxLayout 7 | 8 | from angrmanagement.config import Conf 9 | 10 | if TYPE_CHECKING: 11 | from angrmanagement.data.instance import Instance 12 | 13 | 14 | class FuncDocDialog(QDialog): 15 | """ 16 | Implements the FuncDoc dialog. 17 | """ 18 | 19 | def __init__( 20 | self, instance: Instance, addr: int | None = None, name: str = "", doc_tuple=None, parent=None 21 | ) -> None: 22 | super().__init__(parent) 23 | 24 | # initialization 25 | self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint) 26 | 27 | self.instance = instance 28 | self._addr = addr 29 | self._name = name 30 | self._doc = doc_tuple[0].strip() 31 | self._url = doc_tuple[1].strip() 32 | self._ftype = doc_tuple[2].strip() 33 | self.setWindowTitle("Function Documentation") 34 | self.main_layout = QVBoxLayout() 35 | self._init_widgets() 36 | self.setLayout(self.main_layout) 37 | 38 | def _init_widgets(self) -> None: 39 | layout = QGridLayout() 40 | 41 | # validation_failures = set() 42 | addr = hex(self._addr) 43 | address_label = QLabel(self) 44 | address_label.setText(f"Function at address {addr}: {self._name}") 45 | 46 | layout.addWidget(address_label) 47 | 48 | type_label = QLabel(self) 49 | type_label.setText(f"Type: {self._ftype}") 50 | 51 | layout.addWidget(type_label) 52 | 53 | text_edit = QTextEdit(self) 54 | text_edit.setMinimumWidth(800) 55 | text_edit.setMinimumHeight(450) 56 | text_edit.setFont(Conf.disasm_font) 57 | text_edit.setText(self._doc) 58 | 59 | url_label = QLabel(self) 60 | hyperlink = f'{self._url}' 61 | url_label.setText(hyperlink) 62 | url_label.setOpenExternalLinks(True) 63 | 64 | layout.addWidget(text_edit) 65 | layout.addWidget(url_label) 66 | 67 | self.main_layout.addLayout(layout) 68 | -------------------------------------------------------------------------------- /angrmanagement/ui/widgets/qfunction_combobox.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtWidgets import QComboBox 6 | 7 | if TYPE_CHECKING: 8 | from angr.knowledge_plugins import Function, FunctionManager 9 | 10 | 11 | class QFunctionComboBox(QComboBox): 12 | def __init__(self, show_all_functions: bool = False, selection_callback=None, parent=None) -> None: 13 | super().__init__(parent) 14 | 15 | self._show_all_functions = show_all_functions 16 | self._selection_callback = selection_callback 17 | 18 | self._function_manager: FunctionManager | None = None 19 | 20 | self.currentIndexChanged.connect(self._on_current_index_changed) 21 | 22 | # 23 | # Properties 24 | # 25 | 26 | @property 27 | def functions(self): 28 | return self._function_manager 29 | 30 | @functions.setter 31 | def functions(self, v) -> None: 32 | if v is not self._function_manager: 33 | self._function_manager = v 34 | self.reload() 35 | 36 | # 37 | # Public methods 38 | # 39 | 40 | def reload(self) -> None: 41 | if self._function_manager is None: 42 | return 43 | 44 | self.clear() 45 | 46 | if self._show_all_functions: 47 | self.addItem("All functions", "all") 48 | 49 | for function in self._function_manager.values(): 50 | self.addItem(self._repr_function(function), function) 51 | 52 | def select_function(self, function) -> None: 53 | idx = self.findData(function) 54 | if idx >= 0: 55 | self.setCurrentIndex(idx) 56 | 57 | # 58 | # Event handlers 59 | # 60 | 61 | def _on_current_index_changed(self) -> None: 62 | idx = self.currentIndex() 63 | if idx == -1: 64 | return 65 | 66 | function = self.itemData(idx) 67 | 68 | self._selection_callback(function) 69 | 70 | # 71 | # Private functions 72 | # 73 | 74 | @staticmethod 75 | def _repr_function(func: Function) -> str: 76 | demangled_name = func.demangled_name 77 | if len(demangled_name) > 30: 78 | demangled_name = demangled_name[:30] + "..." 79 | return f"{demangled_name} ({func.addr:#x})" 80 | -------------------------------------------------------------------------------- /angrmanagement/ui/toolbars/feature_map_toolbar.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from angrmanagement.ui.widgets.qfeature_map import QFeatureMap 4 | 5 | from .toolbar import Toolbar 6 | from .toolbar_dock import ToolBarDockWidget 7 | 8 | 9 | class FeatureMapToolbar(Toolbar): 10 | """ 11 | Displays the current feature map. Monitors most recently focused view via instance.activate_view_state to show 12 | cursors on the feature map, and allows selection in the feature map to control location of active view. 13 | """ 14 | 15 | def __init__(self, window) -> None: 16 | super().__init__(window, "Feature Map") 17 | self._toolbar = None 18 | self._feature_map = None 19 | self._is_subscribed = False 20 | 21 | def _subscribe_events(self) -> None: 22 | self.window.workspace.main_instance.active_view_state.am_subscribe(self._on_view_state_updated) 23 | self._feature_map.addr.am_subscribe(self._on_feature_map_addr_selected) 24 | self._is_subscribed = True 25 | 26 | def _unsubscribe_events(self) -> None: 27 | if self._is_subscribed: 28 | self.window.workspace.main_instance.active_view_state.am_unsubscribe(self._on_view_state_updated) 29 | self._feature_map.addr.am_unsubscribe(self._on_feature_map_addr_selected) 30 | 31 | def qtoolbar(self): 32 | if self._toolbar is None: 33 | self._feature_map = QFeatureMap(self.window.workspace.main_instance, parent=self.window) 34 | self._toolbar = ToolBarDockWidget(self._feature_map, "Feature Map", parent=None) 35 | self._subscribe_events() 36 | return self._toolbar 37 | 38 | def shutdown(self) -> None: 39 | self._unsubscribe_events() 40 | 41 | def _on_feature_map_addr_selected(self) -> None: 42 | target_view = self.window.workspace.view_manager.most_recently_focused_view 43 | if hasattr(target_view, "jump_to"): 44 | addr = self._feature_map.addr.am_obj 45 | if addr is not None: 46 | target_view.jump_to(addr) 47 | 48 | def _on_view_state_updated(self) -> None: 49 | vs = self.window.workspace.main_instance.active_view_state 50 | if vs.am_none: 51 | return 52 | self._feature_map.set_cursor_addrs(vs.cursors) 53 | -------------------------------------------------------------------------------- /tests/test_undefine_code_regions.py: -------------------------------------------------------------------------------- 1 | # pylint:disable=missing-class-docstring,wrong-import-order 2 | from __future__ import annotations 3 | 4 | import os 5 | import sys 6 | import unittest 7 | 8 | import angr 9 | from common import AngrManagementTestCase, test_location 10 | 11 | from angrmanagement.ui.views import DisassemblyView 12 | 13 | 14 | class TestUndefineCodeRegions(AngrManagementTestCase): 15 | 16 | def test_undefine_code_region_in_disasm_view(self): 17 | main = self.main 18 | binpath = os.path.join(test_location, "x86_64", "fauxware") 19 | main.workspace.main_instance.project.am_obj = angr.Project(binpath, auto_load_libs=False) 20 | main.workspace.main_instance.project.am_event() 21 | main.workspace.job_manager.join_all_jobs() 22 | 23 | func = main.workspace.main_instance.project.kb.functions["main"] 24 | assert func is not None 25 | 26 | cfg = main.workspace.main_instance.cfg 27 | assert cfg.get_any_node(func.addr) is not None 28 | 29 | # load the disassembly view 30 | disasm_view = main.workspace._get_or_create_view("disassembly", DisassemblyView) 31 | disasm_view.display_disasm_graph() 32 | disasm_view.display_function(func) 33 | 34 | # select the instruction at the beginning of the function 35 | disasm_view.infodock.select_instruction(func.addr) 36 | assert len(disasm_view.infodock.selected_insns) == 1 37 | assert disasm_view.infodock.selected_insns == {func.addr} 38 | # print(len(cfg.graph)) 39 | 40 | # undefine at the beginning of the function 41 | disasm_view.undefine_code() 42 | 43 | main.workspace.job_manager.join_all_jobs() 44 | 45 | # the starting block should no longer be in the graph 46 | assert cfg.get_any_node(func.addr) is None 47 | 48 | main.workspace.job_manager.join_all_jobs() 49 | 50 | # define at the beginning of the function 51 | disasm_view.infodock.select_label(func.addr) 52 | assert disasm_view.infodock.selected_labels == {func.addr} 53 | # print(len(cfg.graph)) 54 | disasm_view.define_code() 55 | 56 | main.workspace.job_manager.join_all_jobs() 57 | # print(len(cfg.graph)) 58 | 59 | assert cfg.get_any_node(func.addr) is not None 60 | 61 | 62 | if __name__ == "__main__": 63 | unittest.main(argv=sys.argv) 64 | -------------------------------------------------------------------------------- /angrmanagement/ui/dialogs/about.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | 5 | from PySide6.QtCore import Qt 6 | from PySide6.QtGui import QFont, QIcon, QPixmap 7 | from PySide6.QtWidgets import QDialog, QHBoxLayout, QLabel, QVBoxLayout 8 | 9 | import angrmanagement 10 | from angrmanagement.consts import IMG_LOCATION 11 | 12 | 13 | class LoadAboutDialog(QDialog): 14 | """ 15 | Dialog that shows application version, credits, etc. 16 | """ 17 | 18 | def __init__(self) -> None: 19 | super().__init__() 20 | self.setWindowFlags(Qt.WindowType.WindowTitleHint | Qt.WindowType.WindowCloseButtonHint) 21 | self.setWindowTitle("About") 22 | # mdiIcon 23 | angr_icon_location = os.path.join(IMG_LOCATION, "angr.png") 24 | self.setWindowIcon(QIcon(angr_icon_location)) 25 | self._init_widgets() 26 | 27 | def _init_widgets(self) -> None: 28 | # icon 29 | icon_label = QLabel(self) 30 | icon_location = os.path.join(IMG_LOCATION, "angr-ds.png") 31 | angr_icon = QPixmap(icon_location) 32 | icon_label.setPixmap(angr_icon) 33 | # textbox 34 | angr_text = QLabel("angr management") 35 | angr_text.setFont(QFont("Consolas", 24, weight=QFont.Weight.Bold)) 36 | version_text_tup = "Version: " + angrmanagement.__version__ 37 | version_text = QLabel(version_text_tup) 38 | version_text.setFont(QFont("Consolas", weight=QFont.Weight.Bold)) 39 | version_text.setAlignment(Qt.AlignmentFlag.AlignCenter) 40 | version_text.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) 41 | credits_text = QLabel('Credits') 42 | credits_text.setFont(QFont("Consolas", weight=QFont.Weight.Bold)) 43 | credits_text.setTextFormat(Qt.TextFormat.RichText) 44 | credits_text.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction) 45 | credits_text.setOpenExternalLinks(True) 46 | 47 | structure = QVBoxLayout() 48 | structure.addWidget(angr_text) 49 | structure.addWidget(version_text) 50 | structure.addWidget(credits_text) 51 | 52 | layout = QHBoxLayout() 53 | layout.addWidget(icon_label) 54 | layout.addLayout(structure) 55 | 56 | self.setLayout(layout) 57 | 58 | # 59 | # Event handlers 60 | # 61 | -------------------------------------------------------------------------------- /angrmanagement/ui/widgets/qconstraint_viewer.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtWidgets import QFrame, QHeaderView, QSizePolicy, QTableWidget, QTableWidgetItem, QVBoxLayout 6 | 7 | if TYPE_CHECKING: 8 | from angrmanagement.ui.workspace import Workspace 9 | 10 | 11 | class QConstraintViewer(QFrame): 12 | """ 13 | `QConstraintViewer` in `StateInspector` 14 | """ 15 | 16 | COLUMNS = ["Constraint", "Cardinality", "Depth", "# Variables"] 17 | 18 | def __init__(self, state, parent, workspace: Workspace) -> None: 19 | super().__init__(parent) 20 | 21 | self._state = state 22 | self.workspace = workspace 23 | 24 | self.table = None 25 | 26 | self._state.am_subscribe(self._watch_state) 27 | 28 | # 29 | # Public methods 30 | # 31 | 32 | def reload(self) -> None: 33 | if self._state.am_none: 34 | return 35 | 36 | self.table.setRowCount(0) 37 | for constraint in self._state.solver.constraints: 38 | count = self.table.rowCount() 39 | self.table.insertRow(count) 40 | self.table.setItem(count, 0, QTableWidgetItem(constraint.shallow_repr())) 41 | self.table.setItem(count, 1, QTableWidgetItem(str(constraint.cardinality))) 42 | self.table.setItem(count, 2, QTableWidgetItem(str(constraint.depth))) 43 | self.table.setItem(count, 3, QTableWidgetItem(str(len(list(constraint.leaf_asts()))))) 44 | 45 | # 46 | # Private methods 47 | # 48 | 49 | def _init_widgets(self) -> None: 50 | if self._state.am_none: 51 | return 52 | 53 | layout = QVBoxLayout() 54 | 55 | table = QTableWidget(self) 56 | table.setColumnCount(len(self.COLUMNS)) 57 | table.setHorizontalHeaderLabels(self.COLUMNS) 58 | table.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) 59 | table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Fixed) 60 | table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) 61 | 62 | self.table = table 63 | layout.addWidget(table) 64 | 65 | self.setLayout(layout) 66 | 67 | def _watch_state(self, **kwargs) -> None: # pylint: disable=unused-argument 68 | if self.table is None: 69 | self._init_widgets() 70 | self.reload() 71 | -------------------------------------------------------------------------------- /angrmanagement/logic/commands/command.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from collections.abc import Callable 7 | 8 | from angrmanagement.ui.views import BaseView 9 | from angrmanagement.ui.workspace import Workspace 10 | 11 | 12 | class Command: 13 | """ 14 | Command to be run. 15 | """ 16 | 17 | _name: str | None = None 18 | _caption: str | None = None 19 | 20 | @property 21 | def name(self) -> str: 22 | """ 23 | Short name for invocation. By default this name will be derived from the class name. 24 | """ 25 | return self._name or self.__class__.__name__ 26 | 27 | @property 28 | def caption(self) -> str: 29 | """ 30 | Message to be displayed to identify the command, e.g. in the command palette. 31 | """ 32 | return self._caption or self.name 33 | 34 | @property 35 | def is_visible(self) -> bool: 36 | """ 37 | Determines whether this command should be displayed or not. 38 | """ 39 | return True 40 | 41 | def run(self) -> None: 42 | """ 43 | Runs the command. 44 | """ 45 | 46 | 47 | class BasicCommand(Command): 48 | """ 49 | Basic command to invoke a callable. 50 | """ 51 | 52 | def __init__(self, name: str, caption: str, action: Callable) -> None: 53 | self._name = name 54 | self._caption = caption 55 | self._action: Callable = action 56 | 57 | def run(self) -> None: 58 | self._action() 59 | 60 | 61 | class ViewCommand(Command): 62 | """ 63 | Commands to invoke a callable on a view. 64 | """ 65 | 66 | def __init__( 67 | self, name: str, caption: str, action: Callable, view_class: type[BaseView], workspace: Workspace 68 | ) -> None: 69 | self._name = name 70 | self._caption = caption 71 | self._action: Callable = action 72 | self._view_class: type[BaseView] = view_class 73 | self._workspace: Workspace = workspace 74 | 75 | @property 76 | def is_visible(self) -> bool: 77 | view = self._workspace.view_manager.most_recently_focused_view 78 | return isinstance(view, self._view_class) 79 | 80 | def run(self) -> None: 81 | view = self._workspace.view_manager.most_recently_focused_view 82 | if isinstance(view, self._view_class): 83 | self._action(view) 84 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Report a bug 2 | description: Report a bug in angr-management 3 | labels: [bug,needs-triage] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you for taking the time to submit this bug report! 9 | 10 | Before submitting this bug report, please check the following, which may resolve your issue: 11 | * Have you checked that you are running the latest versions of angr-management and its components? angr-management is rapidly-evolving! 12 | * Have you [searched existing issues](https://github.com/angr/angr-management/issues?q=is%3Aopen+is%3Aissue+label%3Abug) to see if this bug has been reported before? 13 | * Have you checked the [angr FAQ](https://docs.angr.io/introductory-errata/faq)? 14 | 15 | **Important:** If this bug is a security vulnerability, please submit it privately. See our [security policy](https://github.com/angr/angr/blob/master/SECURITY.md) for more details. 16 | 17 | Please note: The angr suite is maintained by a small team. While we cannot guarantee any timeliness for fixes and enhancements, we will do our best. For more real-time help with angr, from us and the community, join our [Slack](https://angr.io/invite/). 18 | 19 | - type: textarea 20 | attributes: 21 | label: Description 22 | description: | 23 | Brief description of the bug, with any relevant screenshots or log messages. 24 | 25 | **Tip:** You can attach files to the issue by first clicking on the textarea to select it, then dragging & dropping the file onto the textarea. 26 | validations: 27 | required: true 28 | 29 | - type: textarea 30 | attributes: 31 | label: Steps to reproduce the bug 32 | description: | 33 | If possible **attach the binary used**. 34 | 35 | **Tip:** You can attach files to the issue by first clicking on the textarea to select it, then dragging & dropping the file onto the textarea. 36 | - type: textarea 37 | attributes: 38 | label: Environment 39 | description: Many common issues are caused by problems with the local Python environment. Before submitting, double-check that your versions of all modules in the angr suite (angr, cle, pyvex, ...) are up to date and include the output of `python -m angr.misc.bug_report` here. 40 | 41 | - type: textarea 42 | attributes: 43 | label: Additional context 44 | description: Any additional context about the problem. 45 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: ^angrmanagement/vendor 2 | repos: 3 | 4 | # 5 | # Fail fast 6 | # 7 | 8 | - repo: https://github.com/abravalheri/validate-pyproject 9 | rev: v0.24.1 10 | hooks: 11 | - id: validate-pyproject 12 | fail_fast: true 13 | 14 | - repo: https://github.com/pre-commit/pre-commit-hooks 15 | rev: v6.0.0 16 | hooks: 17 | # General 18 | - id: check-merge-conflict 19 | fail_fast: true 20 | - id: check-case-conflict 21 | fail_fast: true 22 | - id: destroyed-symlinks 23 | fail_fast: true 24 | - id: check-symlinks 25 | fail_fast: true 26 | - id: check-added-large-files 27 | fail_fast: true 28 | # Syntax 29 | - id: check-toml 30 | fail_fast: true 31 | - id: check-json 32 | fail_fast: true 33 | - id: check-yaml 34 | fail_fast: true 35 | 36 | - repo: https://github.com/pre-commit/pre-commit-hooks 37 | rev: v6.0.0 38 | hooks: 39 | - id: check-ast 40 | fail_fast: true 41 | 42 | # 43 | # Modifiers 44 | # 45 | 46 | # Disabled because angr-management is not a normalized packaged name 47 | # - repo: https://github.com/asottile/setup-cfg-fmt 48 | # rev: v2.2.0 49 | # hooks: 50 | # - id: setup-cfg-fmt 51 | 52 | - repo: https://github.com/pre-commit/pre-commit-hooks 53 | rev: v6.0.0 54 | hooks: 55 | - id: mixed-line-ending 56 | - id: trailing-whitespace 57 | 58 | - repo: https://github.com/dannysepler/rm_unneeded_f_str 59 | rev: v0.2.0 60 | hooks: 61 | - id: rm-unneeded-f-str 62 | 63 | - repo: https://github.com/astral-sh/ruff-pre-commit 64 | rev: v0.14.10 65 | hooks: 66 | - id: ruff 67 | args: [--fix, --exit-non-zero-on-fix] 68 | 69 | # Last modifier: Coding Standard 70 | - repo: https://github.com/psf/black-pre-commit-mirror 71 | rev: 25.12.0 72 | hooks: 73 | - id: black 74 | 75 | # 76 | # Static Checks 77 | # 78 | 79 | - repo: https://github.com/pre-commit/pygrep-hooks 80 | rev: v1.10.0 81 | hooks: 82 | # Python 83 | - id: python-use-type-annotations 84 | - id: python-no-log-warn 85 | # Documentation 86 | - id: rst-backticks 87 | - id: rst-directive-colons 88 | - id: rst-inline-touching-normal 89 | 90 | - repo: https://github.com/pre-commit/pre-commit-hooks 91 | rev: v6.0.0 92 | hooks: 93 | - id: debug-statements 94 | - id: check-builtin-literals 95 | - id: check-docstring-first 96 | 97 | -------------------------------------------------------------------------------- /angrmanagement/ui/toolbar_manager.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtCore import Qt 6 | from PySide6.QtWidgets import QDockWidget 7 | 8 | from angrmanagement.ui.toolbars import DebugToolbar, FeatureMapToolbar, FileToolbar 9 | 10 | if TYPE_CHECKING: 11 | from collections.abc import Mapping 12 | 13 | from angrmanagement.ui.main_window import MainWindow 14 | from angrmanagement.ui.toolbars.toolbar import Toolbar 15 | 16 | 17 | class ToolbarManager: 18 | """ 19 | Manages toolbars shown on the main window. 20 | """ 21 | 22 | def __init__(self, main_window: MainWindow) -> None: 23 | self._main_window: MainWindow = main_window 24 | self.active: Mapping[type[Toolbar], Toolbar] = {} 25 | self.all_toolbars = [FileToolbar, DebugToolbar, FeatureMapToolbar] 26 | 27 | @staticmethod 28 | def get_name_for_toolbar_class(toolbar_cls: type[Toolbar]) -> str: 29 | return {FileToolbar: "File", DebugToolbar: "Debug", FeatureMapToolbar: "Feature Map"}[toolbar_cls] 30 | 31 | def show_toolbar_by_class(self, cls: type[Toolbar]) -> None: 32 | if cls not in self.active: 33 | tb = cls(self._main_window) 34 | self.active[cls] = tb 35 | qtb = tb.qtoolbar() 36 | if isinstance(qtb, QDockWidget): 37 | self._main_window.addDockWidget(Qt.DockWidgetArea.TopDockWidgetArea, qtb) 38 | else: 39 | self._main_window.addToolBar(Qt.ToolBarArea.TopToolBarArea, qtb) 40 | else: 41 | self.active[cls].qtoolbar().show() 42 | 43 | def hide_toolbar_by_class(self, cls: type[Toolbar]) -> None: 44 | if cls in self.active: 45 | tb = self.active.pop(cls) 46 | qtb = tb.qtoolbar() 47 | if isinstance(qtb, QDockWidget): 48 | self._main_window.removeDockWidget(qtb) 49 | else: 50 | self._main_window.removeToolBar(qtb) 51 | tb.shutdown() 52 | 53 | def set_toolbar_visible_by_class(self, cls: type[Toolbar], visible: bool) -> None: 54 | if visible: 55 | self.show_toolbar_by_class(cls) 56 | else: 57 | self.hide_toolbar_by_class(cls) 58 | 59 | def show_all(self) -> None: 60 | for cls in self.all_toolbars: 61 | self.show_toolbar_by_class(cls) 62 | 63 | def hide_all(self) -> None: 64 | for cls in self.all_toolbars: 65 | self.hide_toolbar_by_class(cls) 66 | -------------------------------------------------------------------------------- /angrmanagement/config/config_entry.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from PySide6.QtCore import QObject, Signal 4 | 5 | from angrmanagement.data.object_container import EventSentinel 6 | 7 | 8 | def _make_config_entry_value(init_value, type_): 9 | class ConfigurationEntryValue(QObject, EventSentinel): 10 | """ 11 | An object container that both calls am_event() and emits 'changed' on an update 12 | This object allows usage of both types of event triggers 13 | """ 14 | 15 | changed = Signal(type_) 16 | 17 | def __init__(self, value) -> None: 18 | QObject.__init__(self) 19 | if not hasattr(self, "am_subscribers"): 20 | EventSentinel.__init__(self) 21 | self._value = value 22 | 23 | def get(self): 24 | return self._value 25 | 26 | def set(self, value) -> None: 27 | """ 28 | Set the value, if it changes, trigger events 29 | """ 30 | if value != self._value: 31 | self._value = value 32 | self.am_event() 33 | self.changed.emit(value) 34 | 35 | return ConfigurationEntryValue(init_value) 36 | 37 | 38 | class ConfigurationEntry: 39 | """ 40 | Describes a configuration entry in angr management. 41 | """ 42 | 43 | __slots__ = ("name", "type_", "_value", "default_value") 44 | 45 | def __init__(self, name: str, type_, value, default_value=None) -> None: 46 | self.name = name 47 | self.type_ = type_ 48 | self._value = _make_config_entry_value(value, self.type_) 49 | self.default_value = value if default_value is None and value is not None else default_value 50 | 51 | def copy(self): 52 | """ 53 | Copies over data, does *not* copy subscribers, signal connections, or slot connections 54 | """ 55 | return ConfigurationEntry(self.name, self.type_, self.value, default_value=self.default_value) 56 | 57 | @property 58 | def value(self): 59 | return self._value.get() 60 | 61 | @value.setter 62 | def value(self, new) -> None: 63 | self._value.set(new) 64 | 65 | # 66 | # Event functions 67 | # 68 | 69 | @property 70 | def changed(self) -> Signal: 71 | return self._value.changed 72 | 73 | def subscribe(self, listener) -> None: 74 | self._value.am_subscribe(listener) 75 | 76 | def unsubscribe(self, listener) -> None: 77 | self._value.am_unsubscribe(listener) 78 | -------------------------------------------------------------------------------- /angrmanagement/data/breakpoint.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from enum import Enum 4 | from typing import TYPE_CHECKING 5 | 6 | from .object_container import ObjectContainer 7 | 8 | if TYPE_CHECKING: 9 | from collections.abc import Sequence 10 | 11 | 12 | class BreakpointType(Enum): 13 | """ 14 | Type of breakpoint. 15 | """ 16 | 17 | Execute = 0 18 | Read = 1 19 | Write = 2 20 | 21 | 22 | class Breakpoint: 23 | """ 24 | A breakpoint / watchpoint. 25 | """ 26 | 27 | __slots__ = ("type", "addr", "_size", "comment") 28 | 29 | def __init__(self, type_: BreakpointType, addr: int, size: int = 1, comment: str = "") -> None: 30 | self.type: BreakpointType = type_ 31 | self.addr: int = addr 32 | self._size = size 33 | self.comment: str = comment 34 | 35 | @property 36 | def size(self): 37 | if self.type == BreakpointType.Execute: 38 | return 1 39 | return self._size 40 | 41 | @size.setter 42 | def size(self, v: int) -> None: 43 | self._size = v 44 | 45 | 46 | class BreakpointManager: 47 | """ 48 | Manager of breakpoints. 49 | """ 50 | 51 | def __init__(self) -> None: 52 | self.breakpoints: ObjectContainer = ObjectContainer([], "List of breakpoints") 53 | 54 | def clear(self) -> None: 55 | self.breakpoints.clear() 56 | self.breakpoints.am_event() 57 | 58 | def add_breakpoint(self, bp: Breakpoint) -> None: 59 | self.breakpoints.append(bp) 60 | self.breakpoints.am_event(added=bp) 61 | 62 | def remove_breakpoint(self, bp: Breakpoint) -> None: 63 | self.breakpoints.remove(bp) 64 | self.breakpoints.am_event(removed=bp) 65 | 66 | def add_exec_breakpoint(self, addr: int) -> None: 67 | self.add_breakpoint(Breakpoint(BreakpointType.Execute, addr)) 68 | 69 | def toggle_exec_breakpoint(self, addr: int) -> None: 70 | # is there a breakpoint at this address? 71 | found_bp = None 72 | for bp in self.breakpoints: 73 | if bp.type == BreakpointType.Execute and bp.addr == addr: 74 | # yes! 75 | found_bp = bp 76 | break 77 | 78 | if found_bp is None: 79 | self.add_exec_breakpoint(addr) 80 | else: 81 | self.remove_breakpoint(found_bp) 82 | 83 | def get_breakpoints_at(self, addr: int) -> Sequence[Breakpoint]: 84 | return [bp for bp in self.breakpoints if bp.addr <= addr < (bp.addr + bp.size)] 85 | -------------------------------------------------------------------------------- /angrmanagement/data/jobs/deobfuscation.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from angr.analyses.deobfuscator import APIObfuscationFinder, StringObfuscationFinder 6 | 7 | from angrmanagement.data.analysis_options import AnalysisConfiguration 8 | 9 | from .job import InstanceJob 10 | 11 | if TYPE_CHECKING: 12 | from angrmanagement.data.instance import Instance 13 | from angrmanagement.logic.jobmanager import JobContext 14 | 15 | 16 | class APIDeobfuscationConfiguration(AnalysisConfiguration): 17 | """ 18 | Configuration for API deobfuscation. 19 | """ 20 | 21 | def __init__(self, instance: Instance) -> None: 22 | super().__init__(instance) 23 | self.name = "api_deobfuscation" 24 | self.display_name = "Deobfuscate API usage" 25 | self.description = "Search for 'obfuscated' API use and attempt to deobfuscate it." 26 | self.enabled = False 27 | 28 | 29 | class StringDeobfuscationConfiguration(AnalysisConfiguration): 30 | """ 31 | Configuration for String deobfuscation. 32 | """ 33 | 34 | def __init__(self, instance: Instance) -> None: 35 | super().__init__(instance) 36 | self.name = "string_deobfuscation" 37 | self.display_name = "Deobfuscate Strings" 38 | self.description = "Search for 'obfuscated' strings and attempt to deobfuscate them." 39 | self.enabled = False 40 | 41 | 42 | class APIDeobfuscationJob(InstanceJob): 43 | """ 44 | Job for deobfuscating API usage. 45 | """ 46 | 47 | def __init__(self, instance: Instance, on_finish=None) -> None: 48 | super().__init__("API Deobfuscation", instance, on_finish=on_finish) 49 | 50 | def run(self, ctx: JobContext) -> None: 51 | self.instance.project.analyses[APIObfuscationFinder].prep(progress_callback=ctx.set_progress)( 52 | variable_kb=self.instance.pseudocode_variable_kb 53 | ) 54 | 55 | def __repr__(self) -> str: 56 | return "APIDeobfuscationJob" 57 | 58 | 59 | class StringDeobfuscationJob(InstanceJob): 60 | """ 61 | Job for deobfuscating strings. 62 | """ 63 | 64 | def __init__(self, instance: Instance, on_finish=None) -> None: 65 | super().__init__("String Deobfuscation", instance, on_finish=on_finish) 66 | 67 | def run(self, ctx: JobContext) -> None: 68 | self.instance.project.analyses[StringObfuscationFinder].prep(progress_callback=ctx.set_progress)() 69 | 70 | def __repr__(self) -> str: 71 | return "StringDeobfuscationJob" 72 | -------------------------------------------------------------------------------- /angrmanagement/utils/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import sys 5 | 6 | 7 | def is_pyinstaller() -> bool: 8 | """ 9 | Detect if we are currently running as a PyInstaller-packaged program. 10 | :return: True if we are running as a PyInstaller-packaged program. False if we are running in Python directly 11 | (e.g., development mode). 12 | """ 13 | return getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS") 14 | 15 | 16 | def app_path(pythonw=None, as_list: bool = False) -> str | list[str]: 17 | """ 18 | Return the path of the application. 19 | 20 | - In standalone mode (a PyInstaller module), we return the absolute path to the executable. 21 | - In development mode, we return the absolute path to the python executable and "-m angr management" 22 | 23 | :return: A string that represents the path to the application that can be used to run angr management. 24 | :rtype: str|list 25 | """ 26 | 27 | if is_pyinstaller(): 28 | # running as a PyInstaller bundle 29 | if as_list: 30 | return [sys.executable] 31 | return sys.executable 32 | else: 33 | # running as a Python package 34 | python_path = os.path.normpath(sys.executable) 35 | if sys.platform.startswith("win"): 36 | # if pythonw is None, we don't do anything 37 | if pythonw is True: 38 | python_path = python_path.replace("python.exe", "pythonw.exe") 39 | elif pythonw is False: 40 | python_path = python_path.replace("pythonw.exe", "python.exe") 41 | if as_list: 42 | return [python_path, "-m", "angrmanagement"] 43 | else: 44 | if " " in python_path: 45 | python_path = f'"{python_path}"' 46 | app_path = python_path + " -m angrmanagement" 47 | return app_path 48 | 49 | 50 | def app_root() -> str: 51 | """ 52 | Return the path of the application. 53 | 54 | - In standalone mode (a PyInstaller module), we return the absolute path of the directory where the executable is. 55 | - In development mode, we return the absolute path to the directory where the angr management package is. 56 | 57 | :return: A string that represents the path to the application that can be used to run angr management. 58 | """ 59 | 60 | if is_pyinstaller(): 61 | # running as a PyInstaller bundle 62 | return os.path.dirname(sys.executable) 63 | else: 64 | # running as a Python package 65 | return os.path.normpath(os.path.join(os.path.abspath(__file__), "..", "..")) 66 | -------------------------------------------------------------------------------- /.github/workflows/nightly-build.yml: -------------------------------------------------------------------------------- 1 | name: Bundle 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | workflow_dispatch: 7 | inputs: 8 | deploy: 9 | description: "Deploy the nightly build" 10 | required: false 11 | default: "false" 12 | 13 | jobs: 14 | build: 15 | uses: ./.github/workflows/pyinstaller-build.yml 16 | with: 17 | version: 'master' 18 | deploy: 19 | name: Deploy release 20 | needs: 21 | - build 22 | runs-on: ubuntu-latest 23 | if: ${{ github.event_name == 'schedule' || github.event.inputs.deploy == 'true'}} 24 | steps: 25 | - name: Download artifacts 26 | uses: actions/download-artifact@v4 27 | - name: Delete old nightly 28 | run: gh release -R angr/angr-management delete nightly --cleanup-tag --yes 29 | env: 30 | GH_TOKEN: ${{ github.token }} 31 | continue-on-error: true 32 | - name: Make nightly release 33 | run: > 34 | gh release create nightly \ 35 | --repo angr/angr-management \ 36 | --title "angr management nightly preview" \ 37 | --notes "$RELEASE_NOTES" \ 38 | --prerelease \ 39 | --target $GITHUB_SHA \ 40 | $(find . -type f) 41 | env: 42 | RELEASE_NOTES: > 43 | This is a nightly release preview. We do our 44 | best to make sure everything works, but please be advised that 45 | features may break or change without notice. 46 | 47 | macOS users: Make sure to select the build that matches your CPU 48 | architecture. Both are currently not signed, so you may need to 49 | permit angr-management to run in your Privacy & Security settings 50 | (more info here: https://support.apple.com/en-us/102445#openanyway). 51 | 52 | GH_TOKEN: ${{ github.token }} 53 | 54 | report: 55 | name: Report status 56 | needs: deploy 57 | runs-on: ubuntu-latest 58 | if: ${{ github.event_name == 'schedule' && failure() }} 59 | steps: 60 | - name: Send result email 61 | env: 62 | MAILGUN_API_TOKEN: ${{ secrets.MAILGUN_API_TOKEN }} 63 | run: | 64 | BUILD_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" 65 | curl -s --user "api:$MAILGUN_API_TOKEN" \ 66 | https://api.mailgun.net/v3/mail.rev.fish/messages \ 67 | -F from="angr management bundle " \ 68 | -F to=angr-dev@asu.edu \ 69 | -F subject="angr management nightly bundle failed" \ 70 | -F text="Link to failed build: $BUILD_URL" 71 | -------------------------------------------------------------------------------- /angrmanagement/ui/icons.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | import qtawesome as qta 6 | from PySide6.QtGui import QColor, QPalette 7 | 8 | if TYPE_CHECKING: 9 | from PySide6.QtGui import QIcon 10 | 11 | 12 | NAME_TO_QTAWESOME_NAME = { 13 | "about": "fa5s.info-circle", 14 | "command-palette": "ph.squares-four-light", 15 | "console-view": "mdi.console-line", 16 | "disassembly-graph": "fa5s.sitemap", 17 | "disassembly-linear": "msc.list-selection", 18 | "disassembly-view": "msc.symbol-constant", 19 | "docs": "mdi6.book-open-page-variant", 20 | "file": "mdi.file", 21 | "file-open": "mdi.folder-open", 22 | "file-save": "mdi.floppy", 23 | "functions-view": "mdi.function", 24 | "hex-view": "mdi.hexadecimal", 25 | "jobs-view": "fa5s.hammer", 26 | "log-view": "mdi.message-bulleted", 27 | "patches-view": "mdi.sticker-outline", 28 | "plugins": "mdi.puzzle-edit", 29 | "preferences": "fa6s.gear", 30 | "pseudocode-view": "msc.json", 31 | "run-analysis": "mdi.arrow-right-drop-circle", 32 | "search": "fa5s.search", 33 | "strings-view": "msc.symbol-string", 34 | "traces-view": "mdi.go-kart-track", 35 | "types-view": "msc.symbol-class", 36 | } 37 | 38 | 39 | # XXX: QtAwesome icons won't update color on palette changes, but we can 40 | # update the QColor objects they are initialized with and that color will be 41 | # used in new paint events. 42 | 43 | ICON_COLOR_ROLES = { 44 | QPalette.ColorGroup.Active: { 45 | QPalette.ColorRole.PlaceholderText: QColor(), 46 | QPalette.ColorRole.Text: QColor(), 47 | }, 48 | QPalette.ColorGroup.Disabled: { 49 | QPalette.ColorRole.Text: QColor(), 50 | }, 51 | } 52 | 53 | 54 | ICON_COLORS = { 55 | "color": ICON_COLOR_ROLES[QPalette.ColorGroup.Active][QPalette.ColorRole.Text], 56 | "color_disabled": ICON_COLOR_ROLES[QPalette.ColorGroup.Disabled][QPalette.ColorRole.Text], 57 | } 58 | 59 | 60 | def icon(key, *args, **kwargs) -> QIcon | None: 61 | qta_name = NAME_TO_QTAWESOME_NAME.get(key) 62 | if "color_role" in kwargs: 63 | kwargs["color"] = ICON_COLOR_ROLES[QPalette.ColorGroup.Active][kwargs["color_role"]] 64 | return qta.icon(qta_name, *args, **kwargs) if qta_name else None 65 | 66 | 67 | def transfer_color(dst: QColor, src: QColor) -> None: 68 | dst.setRgba(src.rgba()) 69 | 70 | 71 | def update_icon_colors(palette: QPalette) -> None: 72 | for group, roles in ICON_COLOR_ROLES.items(): 73 | for role, dst in roles.items(): 74 | src = palette.color(group, role) 75 | transfer_color(dst, src) 76 | 77 | 78 | qta.set_global_defaults(**ICON_COLORS) 79 | -------------------------------------------------------------------------------- /angrmanagement/ui/widgets/filesystem_table.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from PySide6.QtWidgets import QAbstractItemView, QFileDialog, QHeaderView, QMenu, QTableWidget, QTableWidgetItem 4 | 5 | 6 | class QFileSystemTable(QTableWidget): 7 | def __init__(self, items, parent) -> None: 8 | super().__init__(parent) 9 | 10 | header_labels = ["Mount Point", "Host Path"] 11 | 12 | self.setColumnCount(len(header_labels)) 13 | self.setHorizontalHeaderLabels(header_labels) 14 | self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectItems) 15 | header = self.horizontalHeader() 16 | header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) 17 | header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch) 18 | 19 | self.setRowCount(len(items)) 20 | for idx, item in enumerate(items): 21 | for i, it in enumerate(item): 22 | self.setItem(idx, i, QTableWidgetItem(it)) 23 | 24 | def contextMenuEvent(self, event) -> None: 25 | sr = self.currentRow() 26 | 27 | menu = QMenu("", self) 28 | 29 | menu.addAction("Add a Row", self._action_new_row) 30 | menu.addSeparator() 31 | 32 | a = menu.addAction("Delete this Row", self._action_delete) 33 | if sr is None: 34 | a.setDisabled(True) 35 | b = menu.addAction("Select a File", self._action_select_file) 36 | if sr is None: 37 | b.setDisabled(True) 38 | c = menu.addAction("Select a directory", self._action_select_dir) 39 | if sr is None: 40 | c.setDisabled(True) 41 | 42 | menu.exec_(event.globalPos()) 43 | 44 | def _action_new_row(self) -> None: 45 | row = self.rowCount() 46 | self.insertRow(row) 47 | self.setItem(row, 0, QTableWidgetItem("Edit Me")) 48 | self.setItem(row, 1, QTableWidgetItem("")) 49 | 50 | def _action_select_file(self) -> None: 51 | file_path, succ = QFileDialog.getOpenFileName( 52 | self, 53 | "Open a real file", 54 | "", 55 | "All executables (*)", 56 | ) 57 | if succ: 58 | self.setItem(self.currentRow(), 1, QTableWidgetItem(file_path)) 59 | 60 | def _action_select_dir(self) -> None: 61 | dir_path = QFileDialog.getExistingDirectory(self, "Select a directory") 62 | if dir_path: 63 | self.setItem(self.currentRow(), 1, QTableWidgetItem(dir_path)) 64 | 65 | def _action_delete(self) -> None: 66 | self.removeRow(self.currentRow()) 67 | 68 | def get_result(self): 69 | ret = [] 70 | for i in range(self.rowCount()): 71 | ret.append([self.item(i, 0).text(), self.item(i, 1).text()]) 72 | return ret 73 | -------------------------------------------------------------------------------- /angrmanagement/utils/io.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import re 5 | import urllib.parse 6 | 7 | import requests 8 | from PySide6.QtWidgets import QFileDialog 9 | 10 | from angrmanagement.config import Conf 11 | from angrmanagement.errors import InvalidURLError, UnexpectedStatusCodeError 12 | 13 | 14 | def isurl(uri) -> bool: 15 | try: 16 | result = urllib.parse.urlparse(uri) 17 | if result.scheme in ("http", "https"): 18 | return True 19 | except ValueError: 20 | pass 21 | 22 | return False 23 | 24 | 25 | def download_url(url, parent=None, to_file: bool = True, file_path=None, use_proxies: bool = True): 26 | if not isurl(url): 27 | raise TypeError("The given URL %s is not a valid URL.", url) 28 | 29 | r = urllib.parse.urlparse(url) 30 | basename = os.path.basename(r.path) 31 | if use_proxies and (Conf.http_proxy or Conf.https_proxy): 32 | proxies = { 33 | "http": Conf.http_proxy, 34 | "https": Conf.https_proxy, 35 | } 36 | else: 37 | proxies = None 38 | 39 | try: 40 | header = requests.head(url, allow_redirects=True, proxies=proxies) 41 | except requests.exceptions.InvalidURL as ex: 42 | raise InvalidURLError from ex 43 | 44 | if header.status_code != 200: 45 | raise UnexpectedStatusCodeError(header.status_code) 46 | 47 | if "content-disposition" in header.headers: 48 | # update the base name 49 | fnames = re.findall("filename=(.+)", header.headers["content-disposition"]) 50 | if fnames: 51 | basename = fnames[0].strip('"') 52 | 53 | if to_file: 54 | # save the content to a file and then return the path 55 | if file_path is None: 56 | filename, folder = QFileDialog.getSaveFileName(parent, "Download a file to...", basename, "Any file (*);") 57 | if filename and folder: 58 | target_path = os.path.join(folder, filename) 59 | else: 60 | # terminated 61 | return None 62 | else: 63 | target_path = file_path 64 | 65 | # downloading it 66 | req = requests.get(url, allow_redirects=True, proxies=proxies) 67 | if req.status_code != 200: 68 | raise UnexpectedStatusCodeError(req.status_code) 69 | 70 | with open(target_path, "wb") as f: 71 | f.write(req.content) 72 | return target_path 73 | 74 | else: 75 | # download the content and return as a blob 76 | # downloading it 77 | req = requests.get(url, allow_redirects=True, proxies=proxies) 78 | if req.status_code != 200: 79 | raise UnexpectedStatusCodeError(req.status_code) 80 | 81 | return req.content 82 | -------------------------------------------------------------------------------- /angrmanagement/ui/menus/disasm_options_menu.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .menu import Menu, MenuEntry 4 | 5 | 6 | class DisasmOptionsMenu(Menu): 7 | def __init__(self, disasm_view) -> None: 8 | super().__init__("", parent=disasm_view) 9 | 10 | self._show_minimap_action = MenuEntry( 11 | "Show &minimap", self._show_minimap, checkable=True, checked=self.parent.show_minimap 12 | ) 13 | self._smart_highlighting_action = MenuEntry( 14 | "Smart &highlighting", self._smart_highlighting, checkable=True, checked=self.parent.smart_highlighting 15 | ) 16 | self._show_address_action = MenuEntry( 17 | "Show &address", self._show_address, checkable=True, checked=self.parent.show_address 18 | ) 19 | self._show_variable_action = MenuEntry( 20 | "Show &variable", self._show_variable, checkable=True, checked=self.parent.show_variable 21 | ) 22 | self._show_variable_ident_action = MenuEntry( 23 | "Show variable &identifiers", 24 | self._show_variable_identifier, 25 | checkable=True, 26 | checked=self.parent.show_variable_identifier, 27 | ) 28 | self._show_exception_edges_action = MenuEntry( 29 | "Show &exception transition edges", 30 | self._show_exception_edges, 31 | checkable=True, 32 | checked=self.parent.show_exception_edges, 33 | ) 34 | 35 | self.entries.extend( 36 | [ 37 | self._show_minimap_action, 38 | self._smart_highlighting_action, 39 | self._show_address_action, 40 | self._show_variable_action, 41 | self._show_variable_ident_action, 42 | self._show_exception_edges_action, 43 | ] 44 | ) 45 | 46 | def _show_minimap(self) -> None: 47 | checked = self._show_minimap_action.checked 48 | self.parent.toggle_show_minimap(checked) 49 | 50 | def _smart_highlighting(self) -> None: 51 | checked = self._smart_highlighting_action.checked 52 | self.parent.toggle_smart_highlighting(checked) 53 | 54 | def _show_address(self) -> None: 55 | checked = self._show_address_action.checked 56 | self.parent.toggle_show_address(checked) 57 | 58 | def _show_variable(self) -> None: 59 | checked = self._show_variable_action.checked 60 | self.parent.toggle_show_variable(checked) 61 | 62 | def _show_variable_identifier(self) -> None: 63 | checked = self._show_variable_ident_action.checked 64 | self.parent.toggle_show_variable_identifier(checked) 65 | 66 | def _show_exception_edges(self) -> None: 67 | checked = self._show_exception_edges_action.checked 68 | self.parent.toggle_show_exception_edges(checked) 69 | -------------------------------------------------------------------------------- /angrmanagement/ui/toolbars/toolbar_dock.py: -------------------------------------------------------------------------------- 1 | # pylint:disable=no-self-use 2 | from __future__ import annotations 3 | 4 | from PySide6.QtCore import QMargins, QRect, QSize, Qt 5 | from PySide6.QtGui import QPainter 6 | from PySide6.QtWidgets import QDockWidget, QFrame, QHBoxLayout, QStyle, QStyleOptionToolBar, QWidget 7 | 8 | 9 | class ToolBarHandle(QWidget): 10 | """ 11 | Paints a tool bar handle. 12 | """ 13 | 14 | def __init__(self, *vargs, **kwargs) -> None: 15 | super().__init__(*vargs, **kwargs) 16 | self.orientation = False 17 | 18 | def paintEvent(self, _) -> None: 19 | painter = QPainter(self) 20 | painter.setBrush(self.palette().window()) 21 | r = QRect(0, 0, self.width(), self.height()).marginsAdded(QMargins(1, 1, 1, 1)) 22 | painter.drawRect(r) 23 | opt = QStyleOptionToolBar() 24 | style = self.style() 25 | if self.orientation: 26 | opt.state = QStyle.StateFlag.State_Horizontal 27 | opt.features = QStyleOptionToolBar.ToolBarFeature.Movable 28 | opt.toolBarArea = Qt.ToolBarArea.NoToolBarArea 29 | opt.rect = r 30 | style.drawPrimitive(QStyle.PrimitiveElement.PE_IndicatorToolBarHandle, opt, painter, self) 31 | painter.end() 32 | 33 | def sizeHint(self): 34 | return QSize(15, 15) 35 | 36 | 37 | class CustomToolBar(QFrame): 38 | """ 39 | Widget to contain the tool bar handle and main widget. 40 | """ 41 | 42 | def __init__(self, widget) -> None: 43 | super().__init__() 44 | self.setWidget(widget) 45 | 46 | def setWidget(self, widget) -> None: 47 | layout = QHBoxLayout() 48 | layout.setSpacing(0) 49 | layout.setContentsMargins(0, 0, 0, 0) 50 | self.handle = ToolBarHandle(parent=self) 51 | layout.addWidget(self.handle) 52 | layout.addWidget(widget) 53 | self.setLayout(layout) 54 | 55 | def sizeHint(self): 56 | return QSize(25, 25) 57 | 58 | 59 | class ToolBarDockWidget(QDockWidget): 60 | """ 61 | Custom tool bar using QDockWidget for better resize handling than QToolBar for large widgets. 62 | """ 63 | 64 | def __init__(self, widget, title, parent) -> None: 65 | super().__init__(title, parent) 66 | self.toolbar = CustomToolBar(widget) 67 | self.setWidget(self.toolbar) 68 | self.setTitleBarWidget(self.toolbar.handle) 69 | 70 | def resizeEvent(self, event) -> None: 71 | super().resizeEvent(event) 72 | horizontal = self.width() > self.height() 73 | if horizontal: 74 | self.setFeatures(self.features() | QDockWidget.DockWidgetFeature.DockWidgetVerticalTitleBar) 75 | else: 76 | self.setFeatures(self.features() & ~QDockWidget.DockWidgetFeature.DockWidgetVerticalTitleBar) 77 | self.toolbar.handle.orientation = horizontal 78 | -------------------------------------------------------------------------------- /angrmanagement/ui/widgets/qvextemps_viewer.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtCore import QSize, Qt 6 | from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QScrollArea, QSizePolicy, QVBoxLayout 7 | 8 | from .qast_viewer import QASTViewer 9 | 10 | if TYPE_CHECKING: 11 | from angrmanagement.ui.workspace import Workspace 12 | 13 | 14 | class QVEXTempsViewer(QFrame): 15 | def __init__(self, state, parent, workspace: Workspace) -> None: 16 | super().__init__(parent) 17 | self.workspace = workspace 18 | 19 | self.state = state 20 | 21 | # widgets 22 | self._area = None 23 | self._tmps = {} 24 | 25 | self._init_widgets() 26 | 27 | # 28 | # Overridden methods 29 | # 30 | 31 | def sizeHint(self): 32 | return QSize(100, 100) 33 | 34 | # 35 | # Private methods 36 | # 37 | 38 | def _init_widgets(self) -> None: 39 | area = QScrollArea() 40 | area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) 41 | area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) 42 | area.setWidgetResizable(True) 43 | 44 | self._area = area 45 | 46 | base_layout = QVBoxLayout() 47 | base_layout.addWidget(area) 48 | self.setLayout(base_layout) 49 | 50 | def _load_tmps(self) -> None: 51 | state = self.state.am_obj 52 | 53 | layout = QVBoxLayout() 54 | 55 | self._tmps.clear() 56 | tmps = {} if state is None else state.scratch.temps 57 | 58 | # tmps 59 | for tmp_id, tmp_value in tmps.items(): 60 | sublayout = QHBoxLayout() 61 | 62 | lbl_tmp_name = QLabel(self) 63 | lbl_tmp_name.setProperty("class", "reg_viewer_label") 64 | lbl_tmp_name.setText(f"tmp_{tmp_id}") 65 | lbl_tmp_name.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) 66 | sublayout.addWidget(lbl_tmp_name) 67 | 68 | sublayout.addSpacing(10) 69 | 70 | tmp_viewer = QASTViewer(tmp_value, workspace=self.workspace, parent=self) 71 | self._tmps[tmp_id] = tmp_viewer 72 | sublayout.addWidget(tmp_viewer) 73 | 74 | layout.addLayout(sublayout) 75 | 76 | layout.setSpacing(0) 77 | layout.addStretch(0) 78 | layout.setContentsMargins(2, 2, 2, 2) 79 | 80 | # the container 81 | container = QFrame() 82 | container.setAutoFillBackground(True) 83 | palette = container.palette() 84 | palette.setColor(container.backgroundRole(), Qt.GlobalColor.white) 85 | container.setPalette(palette) 86 | container.setLayout(layout) 87 | 88 | self._area.setWidget(container) 89 | 90 | def _watch_state(self, **_) -> None: 91 | self._load_tmps() 92 | -------------------------------------------------------------------------------- /angrmanagement/ui/toolbars/toolbar.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from PySide6.QtCore import QSize 4 | from PySide6.QtGui import QAction 5 | from PySide6.QtWidgets import QToolBar 6 | 7 | from .toolbar_action import ToolbarAction, ToolbarSplitter 8 | 9 | 10 | class Toolbar: 11 | def __init__(self, window, name: str) -> None: 12 | self.window = window 13 | self.name = name 14 | 15 | self.actions = [] 16 | self._cached: QToolBar | None = None 17 | self._cached_actions = {} 18 | 19 | def shutdown(self) -> None: 20 | """ 21 | Prepare for deletion. 22 | """ 23 | 24 | def qtoolbar(self): 25 | if self._cached is not None: 26 | return self._cached 27 | 28 | toolbar = QToolBar(self.name, self.window) 29 | 30 | for action in self.actions: 31 | if action in self._cached_actions: 32 | act = self._cached_actions[action] 33 | else: 34 | act = self._translate_element(toolbar, action) 35 | if act is not None: 36 | self._cached_actions[action] = act 37 | 38 | toolbar.setIconSize(QSize(16, 16)) 39 | 40 | self._cached = toolbar 41 | return toolbar 42 | 43 | @staticmethod 44 | def _translate_element(toolbar, action): 45 | if isinstance(action, ToolbarSplitter): 46 | toolbar.addSeparator() 47 | return None 48 | elif isinstance(action, ToolbarAction): 49 | if action.icon is not None: 50 | act = QAction(action.icon, action.name, toolbar) 51 | else: 52 | act = QAction(action.name, toolbar) 53 | if action.triggered is not None: 54 | act.triggered.connect(action.triggered) 55 | if action.tooltip: 56 | act.setToolTip(action.tooltip) 57 | if action.shortcut: 58 | act.setShortcuts(action.shortcut) 59 | act.setCheckable(action.checkable) 60 | toolbar.addAction(act) 61 | return act 62 | else: 63 | raise TypeError("Bad toolbar action", action) 64 | 65 | def add(self, element) -> None: 66 | if self._cached is not None: 67 | act = self._translate_element(self._cached, element) 68 | if act is not None: 69 | self._cached_actions[element] = act 70 | 71 | self.actions.append(element) 72 | 73 | def remove(self, element): 74 | # REQUIRES object identity 75 | try: 76 | act = self._cached_actions[element] 77 | except KeyError as ex: 78 | raise ValueError(f"Element {element} not found") from ex 79 | 80 | self.actions.remove(element) 81 | if self._cached is not None: 82 | self._cached.removeAction(act) 83 | del self._cached_actions[element] 84 | -------------------------------------------------------------------------------- /angrmanagement/utils/cfg.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | from collections import defaultdict 5 | 6 | from angr.ailment.expression import Const 7 | from angr.ailment.statement import ConditionalJump, Jump 8 | from angr.analyses.decompiler.clinic import Clinic 9 | 10 | from .edge import EdgeSort 11 | 12 | log = logging.getLogger("utils.cfg") 13 | 14 | 15 | def categorize_edges(disassembly, edges) -> None: 16 | """ 17 | Categorize each edge. 18 | 19 | :param disassembly: A Disassembly analysis instance. 20 | :param list edges: A list of edges. 21 | :return: None 22 | """ 23 | 24 | edges_by_node = defaultdict(list) 25 | 26 | for edge in edges: 27 | if edge.sort != EdgeSort.EXCEPTION_EDGE: 28 | edges_by_node[edge.src].append(edge) 29 | 30 | for src_node, items in edges_by_node.items(): 31 | if len(items) == 1: 32 | # is it a back edge? 33 | # TODO: an accurate back edge identification requires us to identify loop heads. although we do identify 34 | # TODO: loop nodes at some point, the information is not available here... 35 | edge = items[0] 36 | if edge.src.addr >= edge.dst.addr: 37 | edge.sort = EdgeSort.BACK_EDGE 38 | 39 | elif len(items) == 2: 40 | # actually, let's determine which branch is the false branch 41 | edge_a, edge_b = items 42 | 43 | if isinstance(disassembly, Clinic): 44 | last_stmt = edge_a.src.statements[-1] if edge_a.src.statements else None 45 | fallthrough = None 46 | if isinstance(last_stmt, ConditionalJump): 47 | fallthrough = last_stmt.false_target.value 48 | elif isinstance(last_stmt, Jump): 49 | # this is unexpected since this block has two successors somehow, but the last statement is a Jump 50 | # anyway we pick the current value as the fallthrough value 51 | if isinstance(last_stmt.target, Const): 52 | fallthrough = last_stmt.target.value 53 | else: 54 | # a block whose last statement is not a jump or a conditional jump but has two successors? 55 | fallthrough = None 56 | else: 57 | fallthrough = src_node.addr + src_node.size 58 | 59 | if edge_a.dst.addr == fallthrough and edge_b.dst.addr != fallthrough: 60 | edge_a.sort = EdgeSort.FALSE_BRANCH 61 | edge_b.sort = EdgeSort.TRUE_BRANCH 62 | elif edge_a.dst.addr != fallthrough and edge_b.dst.addr == fallthrough: 63 | edge_a.sort = EdgeSort.TRUE_BRANCH 64 | edge_b.sort = EdgeSort.FALSE_BRANCH 65 | else: 66 | # huh, there are either two fall-throughs or no fall-throughs 67 | pass 68 | -------------------------------------------------------------------------------- /angrmanagement/ui/dialogs/env_config.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from PySide6.QtCore import Qt 6 | from PySide6.QtWidgets import ( 7 | QAbstractItemView, 8 | QDialog, 9 | QHeaderView, 10 | QMenu, 11 | QTableWidget, 12 | QTableWidgetItem, 13 | QVBoxLayout, 14 | ) 15 | 16 | if TYPE_CHECKING: 17 | from angrmanagement.data.instance import Instance 18 | 19 | 20 | class EnvTable(QTableWidget): 21 | """ 22 | Environment Config Table 23 | """ 24 | 25 | def __init__(self, items, parent) -> None: 26 | super().__init__(parent) 27 | 28 | header_labels = ["Name", "Value"] 29 | 30 | self.setColumnCount(len(header_labels)) 31 | self.setHorizontalHeaderLabels(header_labels) 32 | self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectItems) 33 | header = self.horizontalHeader() 34 | header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) 35 | header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch) 36 | 37 | self.setRowCount(len(items)) 38 | for idx, item in enumerate(items): 39 | for i, it in enumerate(item): 40 | self.setItem(idx, i, QTableWidgetItem(it)) 41 | 42 | def contextMenuEvent(self, event) -> None: 43 | menu = QMenu("", self) 44 | 45 | menu.addAction("Add a Row", self._action_new_row) 46 | menu.addAction("Delete this Row", self._action_delete) 47 | 48 | menu.exec_(event.globalPos()) 49 | 50 | def _action_new_row(self) -> None: 51 | row = self.rowCount() 52 | self.insertRow(row) 53 | self.setItem(row, 0, QTableWidgetItem("change me")) 54 | self.setItem(row, 1, QTableWidgetItem("")) 55 | 56 | def _action_delete(self) -> None: 57 | self.removeRow(self.currentRow()) 58 | 59 | def get_result(self): 60 | ret = [] 61 | for i in range(self.rowCount()): 62 | ret.append([self.item(i, 0).text(), self.item(i, 1).text()]) 63 | return ret 64 | 65 | 66 | class EnvConfig(QDialog): 67 | """ 68 | Environment Config Dialog for new state 69 | """ 70 | 71 | def __init__(self, env_config=None, instance: Instance | None = None, parent=None) -> None: 72 | super().__init__(parent) 73 | 74 | self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint) 75 | self._instance = instance 76 | self._parent = parent 77 | self.env_config = env_config or [] 78 | self._init_widgets() 79 | 80 | def _init_widgets(self) -> None: 81 | layout = QVBoxLayout() 82 | self._table = EnvTable(self.env_config, self) 83 | layout.addWidget(self._table, 0) 84 | self.setLayout(layout) 85 | 86 | def closeEvent(self, event) -> None: # pylint: disable=unused-argument 87 | self.env_config = self._table.get_result() 88 | self.close() 89 | --------------------------------------------------------------------------------
17 | angr-management is an open-source, cross-platform graphical binary analysis platform. 18 |