├── .python-version ├── src └── binaryninja_mcp │ ├── __init__.py │ ├── consts.py │ ├── utils.py │ ├── log.py │ ├── plugin.py │ ├── resources.py │ ├── cli.py │ ├── tools.py │ └── server.py ├── requirements.txt ├── docs └── demo-1.jpg ├── tests ├── binary │ ├── beleaf.elf │ └── beleaf.elf.bndb ├── smoke_test.py ├── conftest.py ├── test_tools.py ├── test_mcp_tools.py └── __snapshots__ │ ├── test_mcp_tools.ambr │ └── test_tools.ambr ├── __init__.py ├── .pre-commit-config.yaml ├── llm-install.md ├── plugin.json ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── pyproject.toml ├── CHANGELOG.md ├── .gitignore ├── README.zh.md ├── README.md └── LICENSE /.python-version: -------------------------------------------------------------------------------- 1 | 3.10 2 | -------------------------------------------------------------------------------- /src/binaryninja_mcp/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | binaryninja-mcp==0.3.1 2 | -------------------------------------------------------------------------------- /docs/demo-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MCPPhalanx/binaryninja-mcp/HEAD/docs/demo-1.jpg -------------------------------------------------------------------------------- /tests/binary/beleaf.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MCPPhalanx/binaryninja-mcp/HEAD/tests/binary/beleaf.elf -------------------------------------------------------------------------------- /src/binaryninja_mcp/consts.py: -------------------------------------------------------------------------------- 1 | DEFAULT_PORT = 7000 2 | TEST_BINARY_PATH_ELF = 'tests/binary/beleaf.elf.bndb' 3 | -------------------------------------------------------------------------------- /tests/binary/beleaf.elf.bndb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MCPPhalanx/binaryninja-mcp/HEAD/tests/binary/beleaf.elf.bndb -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | import binaryninja as bn 2 | 3 | from binaryninja_mcp.plugin import plugin_init 4 | 5 | # check if binaryninja plugin system is initialized 6 | if bn.core_ui_enabled(): 7 | plugin_init() 8 | else: 9 | import warnings 10 | 11 | warnings.warn('BinaryNinja Plugin is accidentally loaded outside of BinaryNinja') 12 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | # Ruff version. 4 | rev: v0.11.4 5 | hooks: 6 | # Run the linter. 7 | - id: ruff 8 | args: [ --fix ] 9 | # Run the formatter. 10 | - id: ruff-format 11 | 12 | - repo: https://github.com/pre-commit/mirrors-prettier 13 | rev: v3.1.0 # Use the appropriate version 14 | hooks: 15 | - id: prettier 16 | name: Format markdown files 17 | args: [--prose-wrap, always, --write] 18 | files: \.(md)$ 19 | -------------------------------------------------------------------------------- /llm-install.md: -------------------------------------------------------------------------------- 1 | # MCP Server Installation Guide 2 | 3 | This simplified installation guide is designed to be machine-readed for Clients 4 | like Cline to automate the MCP server setup. 5 | 6 | Do not clone the git repository, 7 | 8 | # Installation Steps 9 | 10 | 1. Prompt user to open binary ninja and install `Another MCP Server` from plugin 11 | manager, then open a binaryview after plugin installed, add a confirmation 12 | step. 13 | 2. Add the following server configuration to mcp settings JSON 14 | ```json 15 | { 16 | "mcpServers": { 17 | "binaryninja": { 18 | "url": "http://127.0.0.1:7000/sse", 19 | "disabled": false, 20 | "autoApprove": [] 21 | } 22 | } 23 | } 24 | ``` 25 | 3. Done! get a list of tools and call `list_filename` tool to check if the 26 | server works correctly. 27 | -------------------------------------------------------------------------------- /tests/smoke_test.py: -------------------------------------------------------------------------------- 1 | def test_cli_load(): 2 | from binaryninja_mcp.cli import cli 3 | 4 | try: 5 | cli(['--help']) 6 | except SystemExit as e: 7 | assert e.code == 0, 'Exit code != 0' 8 | print('test_cli_load pass') 9 | 10 | 11 | def test_binja_plugin_version(): 12 | import json 13 | 14 | with open('plugin.json') as f: 15 | plugin = json.load(f) 16 | with open('requirements.txt') as f: 17 | requirements = f.readlines() 18 | 19 | pip_package, pip_version = requirements[0].strip().split('==', maxsplit=1) 20 | json_version = plugin['version'] 21 | assert pip_package == 'binaryninja-mcp' 22 | assert pip_version == json_version 23 | print(f'test_binja_plugin_version pass, pip_version == json_version == {json_version}') 24 | 25 | 26 | if __name__ == '__main__': 27 | test_cli_load() 28 | test_binja_plugin_version() 29 | print('smoke test done!') 30 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginmetadataversion": 2, 3 | "name": "Another MCP Server", 4 | "type": [ 5 | "ui", 6 | "helper" 7 | ], 8 | "api": [ 9 | "python3" 10 | ], 11 | "description": "The MCP server plugin, enables LLM integration by Model Context Protocol (MCP) through SSE or STDIO transport.", 12 | "longdescription": "", 13 | "license": { 14 | "name": "Apache-2.0", 15 | "text": "Copyright 2025 Known Rabbit.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License." 16 | }, 17 | "platforms": [ 18 | "Darwin", 19 | "Linux", 20 | "Windows" 21 | ], 22 | "installinstructions": { 23 | "Darwin": "no special instructions, package manager is recommended", 24 | "Linux": "no special instructions, package manager is recommended", 25 | "Windows": "no special instructions, package manager is recommended" 26 | }, 27 | "version": "0.3.1", 28 | "author": "Known Rabbit", 29 | "minimumbinaryninjaversion": 3164 30 | } 31 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | from pathlib import Path 4 | 5 | import binaryninja as bn 6 | import pytest 7 | 8 | from binaryninja_mcp.utils import disable_binaryninja_user_plugins 9 | 10 | 11 | def setup_binaryninja_user_config(): 12 | """ 13 | Create an isolated BN user config with hardcoded settings. 14 | """ 15 | # copy license file to new user config directory. 16 | userconfigdir_current = Path(bn.user_directory()) 17 | userconfigdir_new = Path(__file__).parent / 'bnuserconfig' 18 | 19 | license_file = userconfigdir_current / 'license.dat' 20 | license_file_new = userconfigdir_new / 'license.dat' 21 | if not userconfigdir_new.exists(): 22 | userconfigdir_new.mkdir() 23 | if not license_file_new.exists(): 24 | shutil.copy(license_file, license_file_new) 25 | os.environ['BN_USER_DIRECTORY'] = str(userconfigdir_new) 26 | bn_settings = bn.Settings() 27 | 28 | # opinionated, set tab width to 3 29 | bn_settings.set_integer('rendering.hlil.tabWidth', 3) 30 | 31 | 32 | @pytest.fixture 33 | def bv(): 34 | """Fixture that loads the BNDB for beleaf.elf binary""" 35 | bv = bn.load('tests/binary/beleaf.elf.bndb') 36 | yield bv 37 | 38 | 39 | @pytest.fixture 40 | def bvs(bv): 41 | """Fixture that loads the BNDB and ELF file for beleaf.elf binary""" 42 | bv2 = bn.load('tests/binary/beleaf.elf') 43 | yield [bv, bv2] 44 | 45 | 46 | disable_binaryninja_user_plugins() 47 | setup_binaryninja_user_config() 48 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # Basic CI setup: Lint with ruff, run tests with pytest 2 | name: Test 3 | 4 | on: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | lint: 12 | name: Lint 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Install uv 17 | uses: astral-sh/setup-uv@v5 18 | with: 19 | enable-cache: true 20 | cache-dependency-glob: "uv.lock" 21 | - name: Ruff lint 22 | run: uv run ruff check . 23 | - name: Ruff format 24 | run: uv run ruff format --diff . 25 | # This isn't a general Python lint, this style is just used in this repository 26 | - name: Prettier format 27 | run: npx prettier@3.1.0 --prose-wrap always --check "**/*.md" 28 | 29 | test: 30 | name: Run tests 31 | strategy: 32 | matrix: 33 | os: [ubuntu-latest] 34 | runs-on: ${{ matrix.os }} 35 | steps: 36 | - uses: actions/checkout@v4 37 | - name: Install uv and setup python environment 38 | uses: astral-sh/setup-uv@v5 39 | with: 40 | enable-cache: true 41 | cache-dependency-glob: "uv.lock" 42 | - name: Setup Binary Ninja 43 | uses: MCPPhalanx/action-setup-binaryninja@main 44 | with: 45 | version: '4.2.6455' 46 | password: ${{ secrets.BN_PASSWORD_426455 }} 47 | - name: Run MCP Server Test Suite 48 | run: | 49 | uv sync --dev 50 | uv run binaryninja-mcp install-api 51 | uv run pytest 52 | -------------------------------------------------------------------------------- /src/binaryninja_mcp/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | from pathlib import Path, PurePath 4 | 5 | try: 6 | import binaryninja as bn 7 | except ImportError: 8 | import warnings 9 | 10 | warnings.warn('Install BinaryNinja API First') 11 | 12 | 13 | def bv_name(bv: 'bn.BinaryView') -> str: 14 | return PurePath(bv.file.filename).name if bv.file else 'unnamed' 15 | 16 | 17 | def disable_binaryninja_user_plugins(): 18 | if (bn_already_init := getattr(bn, '_plugin_init')) is not None: 19 | assert bn_already_init is False, ( 20 | 'disable_binaryninja_user_plugins should be called before Binary Ninja initialization' 21 | ) 22 | os.environ['BN_DISABLE_USER_PLUGINS'] = 'y' 23 | 24 | 25 | def find_binaryninja_path(extra_path: str = None) -> Path | None: 26 | # If user provided path, check it first 27 | if extra_path: 28 | binja_paths = [Path(extra_path)] 29 | else: 30 | # Platform-specific default paths 31 | system = platform.system() 32 | if system == 'Windows': 33 | binja_paths = [ 34 | Path('C:/Program Files/Vector35/BinaryNinja'), 35 | Path.home() / 'AppData/Local/Programs/Vector35/BinaryNinja', 36 | Path.home() / 'AppData/Local/Vector35/BinaryNinja', 37 | ] 38 | elif system == 'Darwin': 39 | binja_paths = [ 40 | Path('/Applications/Binary Ninja.app/Contents/Resources'), 41 | Path.home() / 'Applications/Binary Ninja.app/Contents/Resources', 42 | ] 43 | else: # Linux/other 44 | binja_paths = [Path('/opt/binaryninja'), Path.home() / 'binaryninja'] 45 | 46 | # Look for install script in scripts directory 47 | for path in binja_paths: 48 | script_path = path / 'scripts/install_api.py' 49 | if script_path.exists(): 50 | return path 51 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "binaryninja-mcp" 3 | description = "The MCP server plugin for Binary Ninja, enables LLM integration by Model Context Protocol (MCP) through SSE or STDIO transport." 4 | readme = "README.md" 5 | authors = [ 6 | { name = "ttimasdf", email = "opensource@rabit.pw" } 7 | ] 8 | 9 | keywords = ["mcp", "automation", "binaryninja", "plugin", "llm", "gpt", "server", "client"] 10 | license = { text = "Apache 2.0" } 11 | classifiers = [ 12 | "Development Status :: 4 - Beta", 13 | "Intended Audience :: Developers", 14 | "Intended Audience :: End Users/Desktop", 15 | "Intended Audience :: Information Technology", 16 | "License :: OSI Approved :: Apache Software License", 17 | "Programming Language :: Python :: 3", 18 | "Programming Language :: Python :: 3.10", 19 | "Programming Language :: Python :: 3.11", 20 | "Programming Language :: Python :: 3.12", 21 | "Programming Language :: Python :: 3.13", 22 | "Operating System :: Microsoft :: Windows", 23 | "Operating System :: POSIX :: Linux", 24 | ] 25 | 26 | requires-python = ">=3.10" 27 | dependencies = [ 28 | "anyio>=4.9.0", 29 | "hypercorn>=0.17.3", 30 | "mcp>=1.6.0", 31 | "trio>=0.29.0", 32 | ] 33 | 34 | dynamic = ["version"] 35 | 36 | [dependency-groups] 37 | dev = [ 38 | "pre-commit>=4.2.0", 39 | "pytest>=8.3.5", 40 | "ruff>=0.11.3", 41 | "syrupy>=4.9.1", 42 | ] 43 | 44 | [project.scripts] 45 | binaryninja-mcp = "binaryninja_mcp.cli:cli" 46 | 47 | [build-system] 48 | requires = ["hatchling"] 49 | build-backend = "hatchling.build" 50 | 51 | [tool.hatch.version] 52 | source = "regex" 53 | path = "plugin.json" 54 | pattern = '"version" *: *"(?P[^"]+)"' 55 | 56 | [tool.ruff] 57 | line-length = 100 58 | 59 | [tool.ruff.lint] 60 | extend-select = ["I"] 61 | 62 | [tool.ruff.format] 63 | quote-style = "single" 64 | indent-style = "tab" 65 | docstring-code-format = true 66 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | # Publish on any tag starting with a `v`, e.g. v1.2.3 7 | - v* 8 | 9 | jobs: 10 | release-build: 11 | name: Build Release Package 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Install uv 16 | uses: astral-sh/setup-uv@v5 17 | with: 18 | enable-cache: true 19 | cache-dependency-glob: "uv.lock" 20 | - run: uv build 21 | # Check that basic features work and we didn't miss to include crucial files 22 | - name: Smoke test (wheel) 23 | run: uv run --isolated --no-project --with dist/*.whl tests/smoke_test.py 24 | - name: Smoke test (source distribution) 25 | run: uv run --isolated --no-project --with dist/*.tar.gz tests/smoke_test.py 26 | - uses: actions/upload-artifact@v4 27 | with: 28 | name: uv-dists 29 | path: dist/binaryninja_mcp-* 30 | 31 | upload_github: 32 | name: Upload to Github Release 33 | needs: [release-build] 34 | runs-on: ubuntu-latest 35 | permissions: 36 | contents: write 37 | environment: 38 | name: release 39 | steps: 40 | - uses: actions/download-artifact@v4 41 | with: 42 | name: uv-dists 43 | path: dist 44 | - uses: softprops/action-gh-release@v2 45 | with: 46 | files: dist/binaryninja_mcp-* 47 | generate_release_notes: true 48 | 49 | upload_pypi: 50 | name: Upload to PyPI 51 | needs: [release-build] 52 | runs-on: ubuntu-latest 53 | environment: 54 | name: release 55 | permissions: 56 | id-token: write 57 | steps: 58 | - uses: actions/download-artifact@v4 59 | with: 60 | name: uv-dists 61 | path: dist 62 | - name: Install uv 63 | uses: astral-sh/setup-uv@v5 64 | - run: uv publish --trusted-publishing always 65 | -------------------------------------------------------------------------------- /src/binaryninja_mcp/log.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | import warnings 4 | 5 | try: 6 | from binaryninja.log import Logger as BNLogger 7 | except ImportError: 8 | warnings.warn('Install BinaryNinja API First') 9 | 10 | BINJA_LOG_TAG = 'MCPServer' 11 | 12 | 13 | class BinjaLogHandler(logging.Handler): 14 | """Logging handler that routes messages to BinaryNinja's logging system""" 15 | 16 | def __init__(self, level=logging.NOTSET): 17 | super().__init__(level) 18 | self.setFormatter(logging.Formatter('[%(name)s] %(message)s')) 19 | try: 20 | self.logger = BNLogger(0, BINJA_LOG_TAG) 21 | except NameError: 22 | raise ImportError('Install BinaryNinja API First') 23 | 24 | def emit(self, record): 25 | try: 26 | msg = self.format(record) 27 | if record.levelno >= logging.FATAL: 28 | self.logger.log_alert(msg) 29 | elif record.levelno >= logging.ERROR: 30 | self.logger.log_error(msg) 31 | elif record.levelno >= logging.WARNING: 32 | self.logger.log_warn(msg) 33 | elif record.levelno >= logging.INFO: 34 | self.logger.log_info(msg) 35 | elif record.levelno >= logging.DEBUG: 36 | self.logger.log_debug(msg) 37 | except Exception: 38 | self.handleError(record) 39 | 40 | 41 | def setup_logging( 42 | log_level=logging.INFO, third_party_log_level=logging.WARNING, setup_for_plugin=False 43 | ): 44 | """Configure Python logging to use BinaryNinja's logging system 45 | 46 | Args: 47 | dev_mode (bool): If True, set log level to DEBUG 48 | """ 49 | log_handlers = [] 50 | if setup_for_plugin: 51 | # Configure handlers 52 | try: 53 | log_handlers.append(BinjaLogHandler()) 54 | except ImportError: 55 | warnings.warn('Skipped BinaryNinja Logger since BN API not installed') 56 | else: 57 | log_handlers.append(logging.StreamHandler(sys.stderr)) 58 | 59 | logging.basicConfig(level=third_party_log_level, handlers=log_handlers) 60 | 61 | current_package = logging.getLogger('binaryninja_mcp') 62 | current_package.setLevel(log_level) 63 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.3.1 4 | 5 | ### Fixes 6 | 7 | - Fix dependencies not installed by BinaryNinja package manager. 8 | 9 | ### Development Changes 10 | 11 | - Github Actions will now run full test suite instead of a simple smoke test. 12 | 13 | ## v0.3.0 14 | 15 | ### Major Changes 16 | 17 | - Now the **Start/Stop Server** button in UI actually worked!! 18 | - Added client option `--retry-interval` to improve connection reliability 19 | - `binaryninja-mcp install-api` now works in uv, previously you need a 20 | `--install-on-pyenv` flag, since uv is _the_ project management tool for the 21 | MCP community, this flag is now set by default, and could be disabled by 22 | `--install-on-usersite` 23 | 24 | ### Fixes 25 | 26 | - Fixed compatibility issues with ExceptionGroup imports 27 | - Fixed resource tools functionality 28 | - Fixed ClientSession interference with STDIO clients 29 | 30 | ### Improvements 31 | 32 | - Implemented SSEServerThread for correct server lifecycle management 33 | - Refactored server implementation with FastMCP 34 | - Improved error handling using FastMCP error handling system 35 | - Updated project metadata 36 | - Enhanced documentation in README 37 | 38 | ### Development Changes 39 | 40 | - Replaced Starlette with Hypercorn for improved server performance 41 | - Added test cases for MCP Server with snapshot testing 42 | - Removed pytest dependency from release workflow and smoke tests 43 | - Added workflow permissions for GitHub release creation 44 | - Added isort to ruff configuration 45 | - Improved code organization with better import sorting 46 | - Added test snapshots for MCP tools 47 | 48 | ## v0.2.2 49 | 50 | ### Development Changes 51 | 52 | - Added GitHub release action 53 | - Moved Binary Ninja configuration to conftest 54 | - Added pre-commit hooks for code quality 55 | 56 | ## v0.2.1 57 | 58 | ### Fixes 59 | 60 | - Fixed disassembly tool functionality 61 | - Resolved log module import issues when BinaryNinja is not installed 62 | - Improved error handling and logging mechanisms 63 | - Fixed debug logs in BinaryNinja integration 64 | 65 | ### Improvements 66 | 67 | - Ensured CLI can run without BinaryNinja being installed 68 | - Updated development instructions 69 | - Added warning for BinaryNinja API not being installed 70 | - Optimized logging processes 71 | 72 | ### Development Changes 73 | 74 | - Added Continuous Integration (CI) workflow 75 | - Added smoke test suite 76 | - Reformatted entire codebase for improved accessibility 77 | - Integrated ruff for code formatting and linting 78 | - Enhanced test infrastructure (creating binaryview fixture separately for each 79 | test case) 80 | - Updated test snapshots 81 | - Added dependencies for release workflow 82 | 83 | ## v0.2.0 84 | 85 | Initial Release. The following tools are available. 86 | 87 | - `rename_symbol`: Rename a function or a data variable 88 | - `pseudo_c`: Get pseudo C code of a specified function 89 | - `pseudo_rust`: Get pseudo Rust code of a specified function 90 | - `high_level_il`: Get high level IL of a specified function 91 | - `medium_level_il`: Get medium level IL of a specified function 92 | - `disassembly`: Get disassembly of function or specified range 93 | - `update_analysis_and_wait`: Update analysis for the binary and wait for 94 | completion 95 | -------------------------------------------------------------------------------- /tests/test_tools.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from binaryninja_mcp.tools import MCPTools 4 | 5 | ADDR_MAIN = '0x000008a1' 6 | 7 | 8 | @pytest.fixture 9 | def tools(bv): 10 | """Fixture that provides an MCPTools instance""" 11 | return MCPTools(bv) 12 | 13 | 14 | def test_rename_symbol_function(tools, snapshot): 15 | """Test renaming a function symbol""" 16 | result = tools.rename_symbol(ADDR_MAIN, 'new_function_name') 17 | assert isinstance(result, str) 18 | assert result == snapshot 19 | 20 | 21 | def test_rename_symbol_invalid_address(tools): 22 | """Test renaming with invalid address""" 23 | with pytest.raises(ValueError) as excinfo: 24 | tools.rename_symbol('invalid_address', 'new_name') 25 | assert 'No function or data variable found' in str(excinfo.value) 26 | 27 | 28 | def test_pseudo_c(tools, snapshot): 29 | """Test getting pseudo C code for a function""" 30 | result = tools.pseudo_c(ADDR_MAIN) 31 | assert isinstance(result, str) 32 | assert result == snapshot 33 | 34 | 35 | def test_pseudo_c_invalid_address(tools): 36 | """Test getting pseudo C with invalid address""" 37 | with pytest.raises(ValueError) as excinfo: 38 | tools.pseudo_c('invalid_address') 39 | assert 'No function found' in str(excinfo.value) 40 | 41 | 42 | def test_pseudo_rust(tools, snapshot): 43 | """Test getting pseudo Rust code for a function""" 44 | result = tools.pseudo_rust(ADDR_MAIN) 45 | assert isinstance(result, str) 46 | assert result == snapshot 47 | 48 | 49 | def test_high_level_il(tools, snapshot): 50 | """Test getting HLIL for a function""" 51 | result = tools.high_level_il(ADDR_MAIN) 52 | assert isinstance(result, str) 53 | assert result == snapshot 54 | 55 | 56 | def test_medium_level_il(tools, snapshot): 57 | """Test getting MLIL for a function""" 58 | result = tools.medium_level_il(ADDR_MAIN) 59 | assert isinstance(result, str) 60 | assert result == snapshot 61 | 62 | 63 | def test_disassembly_function(tools, snapshot): 64 | """Test getting function disassembly""" 65 | result = tools.disassembly(ADDR_MAIN) 66 | assert isinstance(result, str) 67 | assert result == snapshot 68 | 69 | 70 | def test_disassembly_range(tools, snapshot): 71 | """Test getting disassembly for a range""" 72 | result = tools.disassembly(ADDR_MAIN, length=16) 73 | assert isinstance(result, str) 74 | assert result == snapshot 75 | 76 | 77 | def test_update_analysis_and_wait(tools, snapshot): 78 | """Test updating analysis""" 79 | result = tools.update_analysis_and_wait() 80 | assert isinstance(result, str) 81 | assert result == snapshot 82 | 83 | 84 | def test_get_triage_summary(tools, snapshot): 85 | """Test getting triage summary""" 86 | result = tools.get_triage_summary() 87 | assert isinstance(result, dict) 88 | assert result == snapshot 89 | 90 | 91 | def test_get_imports(tools, snapshot): 92 | """Test getting imports""" 93 | result = tools.get_imports() 94 | assert isinstance(result, dict) 95 | assert result == snapshot 96 | 97 | 98 | def test_get_exports(tools, snapshot): 99 | """Test getting exports""" 100 | result = tools.get_exports() 101 | assert isinstance(result, list) 102 | assert result == snapshot 103 | 104 | 105 | def test_get_segments(tools, snapshot): 106 | """Test getting segments""" 107 | result = tools.get_segments() 108 | assert isinstance(result, list) 109 | assert result == snapshot 110 | 111 | 112 | def test_get_sections(tools, snapshot): 113 | """Test getting sections""" 114 | result = tools.get_sections() 115 | assert isinstance(result, list) 116 | assert result == snapshot 117 | 118 | 119 | def test_get_strings(tools, snapshot): 120 | """Test getting strings""" 121 | result = tools.get_strings() 122 | assert isinstance(result, list) 123 | assert result == snapshot 124 | 125 | 126 | def test_get_functions(tools, snapshot): 127 | """Test getting functions""" 128 | result = tools.get_functions() 129 | assert isinstance(result, list) 130 | assert result == snapshot 131 | 132 | 133 | def test_get_data_variables(tools, snapshot): 134 | """Test getting data variables""" 135 | result = tools.get_data_variables() 136 | assert isinstance(result, list) 137 | assert result == snapshot 138 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /api-docs/ 2 | .vscode/ 3 | /tests/binary/beleaf.elf.*.bndb 4 | /tests/bnuserconfig/ 5 | 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | cover/ 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | local_settings.py 66 | db.sqlite3 67 | db.sqlite3-journal 68 | 69 | # Flask stuff: 70 | instance/ 71 | .webassets-cache 72 | 73 | # Scrapy stuff: 74 | .scrapy 75 | 76 | # Sphinx documentation 77 | docs/_build/ 78 | 79 | # PyBuilder 80 | .pybuilder/ 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # pyenv 91 | # For a library or package, you might want to ignore these files since the code is 92 | # intended to run in multiple environments; otherwise, check them in: 93 | # .python-version 94 | 95 | # pipenv 96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 99 | # install all needed dependencies. 100 | #Pipfile.lock 101 | 102 | # UV 103 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 104 | # This is especially recommended for binary packages to ensure reproducibility, and is more 105 | # commonly ignored for libraries. 106 | #uv.lock 107 | 108 | # poetry 109 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 110 | # This is especially recommended for binary packages to ensure reproducibility, and is more 111 | # commonly ignored for libraries. 112 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 113 | #poetry.lock 114 | 115 | # pdm 116 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 117 | #pdm.lock 118 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 119 | # in version control. 120 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 121 | .pdm.toml 122 | .pdm-python 123 | .pdm-build/ 124 | 125 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 126 | __pypackages__/ 127 | 128 | # Celery stuff 129 | celerybeat-schedule 130 | celerybeat.pid 131 | 132 | # SageMath parsed files 133 | *.sage.py 134 | 135 | # Environments 136 | .env 137 | .venv 138 | env/ 139 | venv/ 140 | ENV/ 141 | env.bak/ 142 | venv.bak/ 143 | 144 | # Spyder project settings 145 | .spyderproject 146 | .spyproject 147 | 148 | # Rope project settings 149 | .ropeproject 150 | 151 | # mkdocs documentation 152 | /site 153 | 154 | # mypy 155 | .mypy_cache/ 156 | .dmypy.json 157 | dmypy.json 158 | 159 | # Pyre type checker 160 | .pyre/ 161 | 162 | # pytype static type analyzer 163 | .pytype/ 164 | 165 | # Cython debug symbols 166 | cython_debug/ 167 | 168 | # PyCharm 169 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 170 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 171 | # and can be added to the global gitignore or merged into this file. For a more nuclear 172 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 173 | #.idea/ 174 | 175 | # Ruff stuff: 176 | .ruff_cache/ 177 | 178 | # PyPI configuration file 179 | .pypirc 180 | -------------------------------------------------------------------------------- /src/binaryninja_mcp/plugin.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Dict, Optional 3 | 4 | from binaryninja.binaryview import BinaryView, BinaryViewType 5 | from binaryninja.plugin import PluginCommand 6 | from binaryninja.settings import Settings 7 | 8 | from binaryninja_mcp.consts import DEFAULT_PORT 9 | from binaryninja_mcp.log import setup_logging 10 | from binaryninja_mcp.server import SSEServerThread, create_mcp_server 11 | from binaryninja_mcp.utils import bv_name 12 | 13 | logger = logging.getLogger(__name__) 14 | SETTINGS_NAMESPACE = 'mcpserver' 15 | 16 | 17 | class MCPServerPlugin: 18 | def __init__(self): 19 | self.bvs: Dict[str, BinaryView] = {} 20 | self.server_thread: Optional[SSEServerThread] = None 21 | self.settings = Settings() 22 | self.register_settings() 23 | self.load_settings() 24 | 25 | def register_settings(self): 26 | """Register settings with default values""" 27 | self.settings.register_group(SETTINGS_NAMESPACE, 'MCP Server') 28 | self.settings.register_setting( 29 | f'{SETTINGS_NAMESPACE}.listen_host', 30 | '{"title":"Listen Host","description":"Server bind address","type":"string","default":"localhost"}', 31 | ) 32 | self.settings.register_setting( 33 | f'{SETTINGS_NAMESPACE}.listen_port', 34 | f'{{"title":"Listen Port","description":"Server port number","type":"number","minValue":1,"maxValue":65535,"default":{DEFAULT_PORT}}}', 35 | ) 36 | self.settings.register_setting( 37 | f'{SETTINGS_NAMESPACE}.auto_start', 38 | '{"title":"Auto Start","description":"Start MCP server when the first file is loaded","type":"boolean","default":true}', 39 | ) 40 | 41 | def load_settings(self): 42 | """Load settings from persistent storage""" 43 | self.listen_host = self.settings.get_string(f'{SETTINGS_NAMESPACE}.listen_host') 44 | self.listen_port = self.settings.get_integer(f'{SETTINGS_NAMESPACE}.listen_port') 45 | self.auto_start = self.settings.get_bool(f'{SETTINGS_NAMESPACE}.auto_start') 46 | 47 | def save_settings(self): 48 | """Persist current settings""" 49 | self.settings.set_string(f'{SETTINGS_NAMESPACE}.listen_host', self.listen_host) 50 | self.settings.set_integer(f'{SETTINGS_NAMESPACE}.listen_port', self.listen_port) 51 | self.settings.set_bool(f'{SETTINGS_NAMESPACE}.listen_port', self.auto_start) 52 | 53 | def on_binaryview_initial_analysis_completion(self, bv: BinaryView): 54 | name = bv_name(bv) 55 | self.bvs[name] = bv 56 | logger.debug('Detected new BinaryView. bv=%s bv.file=%s name=%s', bv, bv.file, name) 57 | if self.auto_start and not self.server_running(): 58 | # Auto-start server on plugin init 59 | self.start_server() 60 | 61 | def server_running(self) -> bool: 62 | status = bool(self.server_thread and self.server_thread.is_alive()) 63 | logger.debug('server_running: %s', status) 64 | return status 65 | 66 | def start_server(self): 67 | """Start the MCP server""" 68 | self.load_settings() 69 | if not self.server_running(): 70 | mcp = create_mcp_server(list(self.bvs.values())) 71 | self.server_thread = SSEServerThread(mcp.sse_app(), self.listen_host, self.listen_port) 72 | self.server_thread.start() 73 | logger.info('MCP Server started on %s:%d', self.listen_host, self.listen_port) 74 | 75 | def stop_server(self): 76 | """Stop the MCP server""" 77 | logger.debug('Stopping background thread') 78 | self.server_thread.stop() 79 | logger.info('MCP Server stopped') 80 | 81 | def menu_server_control(self, bv: BinaryView): 82 | if self.server_running(): 83 | self.stop_server() 84 | else: 85 | self.start_server() 86 | 87 | def menu_debug_bvs_status(self, bv: BinaryView): 88 | """Check opened BinaryViews status""" 89 | for name, bv in list(self.bvs.items()): 90 | logger.debug('name=%s, bv=%s', name, bv) 91 | logger.debug('bv.file.analysis_changed=%s', bv.file.analysis_changed) 92 | 93 | 94 | # Global plugin instance 95 | plugin = MCPServerPlugin() 96 | 97 | 98 | def plugin_init(): 99 | # Binary Ninja has log filter in GUI, always output debug logs 100 | setup_logging(logging.DEBUG, setup_for_plugin=True) 101 | # Register global handler for all BinaryView events 102 | BinaryViewType.add_binaryview_initial_analysis_completion_event( 103 | plugin.on_binaryview_initial_analysis_completion 104 | ) 105 | 106 | PluginCommand.register( 107 | 'MCPServer\\Start/Stop Server', 'Start/Stop Server', plugin.menu_server_control 108 | ) 109 | 110 | PluginCommand.register( 111 | 'MCPServer\\Debug Menu', 'Check BinaryView Status', plugin.menu_debug_bvs_status 112 | ) 113 | logger.debug('Plugin is loaded!') 114 | -------------------------------------------------------------------------------- /README.zh.md: -------------------------------------------------------------------------------- 1 | # Another™ MCP Server for Binary Ninja 2 | 3 |
4 | 5 | Binary Ninja 的 MCP(模型上下文协议)服务器 6 | 7 | [![PyPI][pypi-badge]][pypi-url] [![Apache licensed][license-badge]][license-url] 8 | [![Python Version][python-badge]][python-url] 9 | [![GitHub Discussions][discussions-badge]][discussions-url] 10 | 11 |
12 | 13 | [English](README.md) | 中文 14 | 15 | [pypi-badge]: https://img.shields.io/pypi/v/binaryninja-mcp.svg 16 | [pypi-url]: https://pypi.org/project/binaryninja-mcp/ 17 | [license-badge]: https://img.shields.io/pypi/l/binaryninja-mcp.svg 18 | [license-url]: https://github.com/MCPPhalanx/binaryninja-mcp/blob/main/LICENSE 19 | [python-badge]: https://img.shields.io/pypi/pyversions/binaryninja-mcp.svg 20 | [python-url]: https://www.python.org/downloads/ 21 | [discussions-badge]: 22 | https://img.shields.io/github/discussions/MCPPhalanx/binaryninja-mcp 23 | [discussions-url]: https://github.com/MCPPhalanx/binaryninja-mcp/discussions 24 | 25 | # 演示 26 | 27 | [tests/binary/beleaf.elf](tests/binary/beleaf.elf) 文件取自 28 | [CSAW'19: Beleaf - Nightmare](https://guyinatuxedo.github.io/03-beginner_re/csaw19_beleaf/index.html)。 29 | 您也可以从上述链接找到完整的解题过程! 30 | 31 | ![demo](docs/demo-1.jpg) 32 | 33 | ## ... 为什么是“又一款”? 34 | 35 | 参见: 36 | [与现有插件的关键区别(英文)](https://github.com/Vector35/community-plugins/issues/305) 37 | 38 | # 安装 39 | 40 | ## 服务器设置 41 | 42 | 有两种方式运行 MCP 服务器: 43 | 44 | 1. **通过 Binary Ninja UI 插件**: 45 | 46 | - 通过 Binary Ninja 的插件管理器安装本插件 47 | - 首次加载二进制文件时, MCP 服务器将自动启动 48 | - 自动启动功能可通过 `Settings - MCP Server - Auto Start` 配置 49 | - 监听端口可通过 `Settings - MCP Server - Server port number` 配置 50 | - 所有打开的文件都将作为独立资源暴露,详见下 51 | 方[可用资源](#提供给-mcp-客户端的可用资源)章节 52 | 53 | 2. **通过 Binary Ninja Headless (无界面)模式**: 54 | ```bash 55 | uvx binaryninja-mcp install-api # 只需运行一次 56 | uvx binaryninja-mcp server <文件名> [文件名]... 57 | ``` 58 | - `文件名` 可以是任意二进制文件或 BNDB 文件,与 UI 模式类似,所有打开的文件都 59 | 将对 MCP 客户端可用 60 | - 服务器默认运行在 7000 端口 61 | - 使用 `--port` 参数指定其他端口 62 | 63 | ## MCP 客户端设置 64 | 65 | 1. **Claude Desktop(stdio 中继客户端)**:配置客户端通过 stdio 传输使用内置中继 66 | 连接 67 | 68 | ```json 69 | { 70 | "mcpServers": { 71 | "binaryninja": { 72 | "command": "uvx", 73 | "args": ["binaryninja-mcp", "client"] 74 | } 75 | } 76 | } 77 | ``` 78 | 79 | 2. **Cherry Studio**: 80 | - **SSE 模式(推荐)**:URL: `http://localhost:7000/sse` 81 | - **stdio 模式**: 82 | - 命令:`uvx` 83 | - 参数: 84 | ``` 85 | binaryninja-mcp 86 | client 87 | ``` 88 | 89 | 若需使用非默认端口,请在服务器和客户端命令中添加 `--port 12345` 参数。 90 | 91 | # 提供给 MCP 客户端的可用工具 92 | 93 | MCP 服务器提供以下工具: 94 | 95 | - `rename_symbol`:重命名函数或数据变量 96 | - `pseudo_c`:获取指定函数的伪 C 代码 97 | - `pseudo_rust`:获取指定函数的伪 Rust 代码 98 | - `high_level_il`:获取指定函数的高级中间语言(HLIL) 99 | - `medium_level_il`:获取指定函数的中级中间语言(MLIL) 100 | - `disassembly`:获取函数或指定范围的汇编代码 101 | - `update_analysis_and_wait`:更新二进制分析并等待完成 102 | - `get_triage_summary`:获取 Binary Ninja 概要视图的基本信息 103 | - `get_imports`:获取导入符号字典 104 | - `get_exports`:获取导出符号字典 105 | - `get_segments`:获取内存段列表 106 | - `get_sections`:获取二进制区段列表 107 | - `get_strings`:获取二进制文件中发现的字符串列表 108 | - `get_functions`:获取函数列表 109 | - `get_data_variables`:获取数据变量列表 110 | 111 | # 提供给 MCP 客户端的可用资源 112 | 113 | MCP 资源可通过以下格式的 URI 访问:`binaryninja://{文件名}/{资源类型}` 114 | 115 | 服务器为每个二进制文件提供以下资源类型: 116 | 117 | - `triage_summary`:来自 Binary Ninja 概要视图的基本信息 118 | - `imports`:导入符号/函数字典 119 | - `exports`:导出符号/函数字典 120 | - `segments`:内存段列表 121 | - `sections`:二进制区段列表 122 | - `strings`:二进制文件中发现的字符串列表 123 | - `functions`:函数列表 124 | - `data_variables`:数据变量列表 125 | 126 | # 开发 127 | 128 | 推荐使用 [uv](https://github.com/astral-sh/uv) 作为本项目的包管理工具。 129 | 130 | ## 克隆仓库到 Binary Ninja 插件目录 131 | 132 | ```powershell 133 | git clone https://github.com/MCPPhalanx/binaryninja-mcp.git "${env:APPDATA}\Binary Ninja\plugins\MCPPhalanx_binaryninja_mcp" 134 | ``` 135 | 136 | ## 配置 Python 环境 137 | 138 | 需要手动将 Binary Ninja API 安装到虚拟环境中。 139 | 140 | ```bash 141 | uv venv 142 | uv sync --dev 143 | # 安装 binaryninja API 144 | binaryninja-mcp install-api 145 | # 检查 API 是否正确安装 146 | uv run python -c 'import binaryninja as bn; assert bn._init_plugins() is None; assert bn.core_ui_enabled() is not None; print("BN API check PASSED!!")' 147 | ``` 148 | 149 | ## 为开发配置 MCP 客户端 150 | 151 | 对于使用 stdio 传输的 MCP 客户端(如 Claude Desktop),将工作目录更改为开发文件 152 | 夹。 153 | 154 | ```json 155 | { 156 | "mcpServers": { 157 | "binaryninja": { 158 | "command": "uv", 159 | "args": [ 160 | "--directory", 161 | "C:/path/to/binaryninja-mcp", 162 | "run", 163 | "binaryninja-mcp", 164 | "client" 165 | ] 166 | } 167 | } 168 | } 169 | ``` 170 | 171 | 支持 SSE 的 MCP 客户端可使用:`http://localhost:7000/sse` 进行连接。 172 | 173 | ## 构建 174 | 175 | ```bash 176 | uv build 177 | ``` 178 | 179 | ## 测试 180 | 181 | ```bash 182 | pytest 183 | # 更新测试快照 184 | pytest --snapshot-update 185 | ``` 186 | 187 | ## 版本推进 188 | 189 | PyPI 包版本自动从 Binary Ninja 的 `plugin.json`(使用 package.json 格式)生成, 190 | 保持 BN 插件与 PyPI 包版本一致。 191 | 192 | ```bash 193 | # 升级 alpha 版本 194 | uvx hatch version a 195 | 196 | # 升级正式版本 197 | uvx hatch version minor,rc 198 | uvx hatch version release 199 | ``` 200 | 201 | 参考:[Versioning - Hatch](https://hatch.pypa.io/1.12/version/) 202 | 203 | ## 发布 204 | 205 | ```bash 206 | uv publish 207 | ``` 208 | 209 | # 许可协议 210 | 211 | [Apache 2.0](LICENSE) 212 | -------------------------------------------------------------------------------- /src/binaryninja_mcp/resources.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, List 2 | 3 | import binaryninja as bn 4 | 5 | 6 | class MCPResource: 7 | """Resource handler for Binary Ninja MCP resources""" 8 | 9 | def __init__(self, bv: bn.BinaryView): 10 | """Initialize with a Binary Ninja BinaryView""" 11 | self.bv = bv 12 | 13 | def triage_summary(self) -> Dict[str, Any]: 14 | """Get basic information as shown in BinaryNinja Triage view""" 15 | result = { 16 | 'file_metadata': { 17 | 'filename': self.bv.file.filename, 18 | 'file_size': self.bv.length, 19 | 'view_type': self.bv.view_type, 20 | }, 21 | 'binary_info': { 22 | 'platform': str(self.bv.platform), 23 | 'entry_point': hex(self.bv.entry_point), 24 | 'base_address': hex(self.bv.start), 25 | 'end_address': hex(self.bv.end), 26 | 'endianness': self.bv.endianness.name, 27 | 'address_size': self.bv.address_size, 28 | }, 29 | 'statistics': { 30 | 'function_count': len(list(self.bv.functions)), 31 | 'string_count': len(list(self.bv.strings)), 32 | 'segment_count': len(self.bv.segments), 33 | 'section_count': len(self.bv.sections), 34 | }, 35 | } 36 | 37 | # Add architecture info if available 38 | if hasattr(self.bv, 'arch') and self.bv.arch: 39 | result['binary_info']['architecture'] = self.bv.arch.name 40 | 41 | return result 42 | 43 | def imports(self) -> Dict[str, List[Dict[str, Any]]]: 44 | """Get dictionary of imported symbols or functions with properties""" 45 | result: Dict[str, List[Dict[str, Any]]] = {} 46 | 47 | for sym in self.bv.get_symbols_of_type(bn.SymbolType.ImportedFunctionSymbol): 48 | module = sym.namespace or 'unknown' 49 | if module not in result: 50 | result[module] = [] 51 | 52 | result[module].append( 53 | { 54 | 'name': sym.name, 55 | 'address': hex(sym.address), 56 | 'type': str(sym.type), 57 | 'ordinal': sym.ordinal if hasattr(sym, 'ordinal') else None, 58 | } 59 | ) 60 | 61 | for sym in self.bv.get_symbols_of_type(bn.SymbolType.ImportedDataSymbol): 62 | module = sym.namespace or 'unknown' 63 | if module not in result: 64 | result[module] = [] 65 | 66 | result[module].append( 67 | { 68 | 'name': sym.name, 69 | 'address': hex(sym.address), 70 | 'type': str(sym.type), 71 | 'ordinal': sym.ordinal if hasattr(sym, 'ordinal') else None, 72 | } 73 | ) 74 | 75 | return result 76 | 77 | def exports(self) -> List[Dict[str, Any]]: 78 | """Get dictionary of exported symbols or functions with properties""" 79 | result = [] 80 | 81 | for sym in self.bv.get_symbols_of_type(bn.SymbolType.FunctionSymbol): 82 | if sym.binding == bn.SymbolBinding.GlobalBinding: 83 | result.append( 84 | { 85 | 'name': sym.name, 86 | 'address': hex(sym.address), 87 | 'type': str(sym.type), 88 | 'ordinal': sym.ordinal if hasattr(sym, 'ordinal') else None, 89 | } 90 | ) 91 | 92 | for sym in self.bv.get_symbols_of_type(bn.SymbolType.DataSymbol): 93 | if sym.binding == bn.SymbolBinding.GlobalBinding: 94 | result.append( 95 | { 96 | 'name': sym.name, 97 | 'address': hex(sym.address), 98 | 'type': str(sym.type), 99 | 'ordinal': sym.ordinal if hasattr(sym, 'ordinal') else None, 100 | } 101 | ) 102 | 103 | return result 104 | 105 | def segments(self) -> List[Dict[str, Any]]: 106 | """Get list of memory segments""" 107 | result = [] 108 | 109 | for segment in self.bv.segments: 110 | result.append( 111 | { 112 | 'start': hex(segment.start), 113 | 'end': hex(segment.end), 114 | 'length': segment.length, 115 | 'data_offset': segment.data_offset, 116 | 'data_length': segment.data_length, 117 | 'data_end': segment.data_end, 118 | 'readable': segment.readable, 119 | 'writable': segment.writable, 120 | 'executable': segment.executable, 121 | } 122 | ) 123 | 124 | return result 125 | 126 | def sections(self) -> List[Dict[str, Any]]: 127 | """Get list of binary sections""" 128 | result = [] 129 | 130 | for section in self.bv.sections.values(): 131 | result.append( 132 | { 133 | 'name': section.name, 134 | 'start': hex(section.start), 135 | 'end': hex(section.end), 136 | 'length': section.length, 137 | 'type': section.type, 138 | 'align': section.align, 139 | 'entry_size': section.entry_size, 140 | 'linked_section': section.linked_section, 141 | 'info_section': section.info_section, 142 | 'info_data': section.info_data, 143 | } 144 | ) 145 | 146 | return result 147 | 148 | def strings(self) -> List[Dict[str, Any]]: 149 | """Get list of strings found in the binary""" 150 | result = [] 151 | 152 | for string in self.bv.strings: 153 | result.append( 154 | { 155 | 'value': string.value, 156 | 'start': hex(string.start), 157 | 'length': string.length, 158 | 'type': str(string.type), 159 | } 160 | ) 161 | 162 | return result 163 | 164 | def functions(self) -> List[Dict[str, Any]]: 165 | """Get list of functions""" 166 | result = [] 167 | 168 | for function in self.bv.functions: 169 | result.append( 170 | { 171 | 'name': function.name, 172 | 'start': hex(function.start), 173 | 'symbol': { 174 | 'name': function.symbol.name, 175 | 'type': str(function.symbol.type), 176 | 'short_name': function.symbol.short_name, 177 | } 178 | if function.symbol 179 | else None, 180 | 'parameter_count': len(function.parameter_vars), 181 | 'return_type': str(function.return_type) if function.return_type else None, 182 | 'has_prototype': function.has_user_type, 183 | 'is_imported': function.symbol.type == bn.SymbolType.ImportedFunctionSymbol 184 | if function.symbol 185 | else False, 186 | 'is_thunk': function.is_thunk, 187 | 'basic_block_count': len(list(function.basic_blocks)), 188 | } 189 | ) 190 | 191 | return result 192 | 193 | def data_variables(self) -> List[Dict[str, Any]]: 194 | """Get list of data variables""" 195 | result = [] 196 | 197 | for var_addr in self.bv.data_vars: 198 | var = self.bv.data_vars[var_addr] 199 | result.append( 200 | { 201 | 'address': hex(var_addr), 202 | 'type': str(var.type) if var.type else None, 203 | 'auto_discovered': var.auto_discovered, 204 | 'symbol': { 205 | 'name': var.symbol.name, 206 | 'type': str(var.symbol.type), 207 | 'short_name': var.symbol.short_name, 208 | } 209 | if var.symbol 210 | else None, 211 | } 212 | ) 213 | 214 | return result 215 | -------------------------------------------------------------------------------- /tests/test_mcp_tools.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from mcp.shared.memory import create_connected_server_and_client_session as memory_session 3 | 4 | from binaryninja_mcp.server import create_mcp_server 5 | 6 | # Address of main function in the test binary 7 | TEST_BINARY_NAME = 'beleaf.elf.bndb' 8 | TEST_BINARY_NAME_INVALID = 'nonexist.elf' 9 | TEST_FUNC = 'main' 10 | TEST_FUNC_ADDR = '0x000008a1' 11 | TEST_FUNC_ADDR_INVALID = '0xINVALID' 12 | 13 | MCP_SERVER_HOST = 'localhost' 14 | 15 | pytestmark = pytest.mark.anyio 16 | 17 | 18 | @pytest.fixture 19 | def anyio_backend(): 20 | return 'trio' 21 | 22 | 23 | def pytest_generate_tests(metafunc: pytest.Metafunc): 24 | if 'filename' in metafunc.fixturenames: 25 | metafunc.parametrize('filename', [TEST_BINARY_NAME, TEST_BINARY_NAME_INVALID]) 26 | if 'function_name' in metafunc.fixturenames: 27 | metafunc.parametrize('function_name', [TEST_FUNC]) 28 | if 'function_address' in metafunc.fixturenames: 29 | metafunc.parametrize('function_address', [TEST_FUNC_ADDR, TEST_FUNC_ADDR_INVALID]) 30 | 31 | 32 | @pytest.fixture 33 | async def mcp_server(bvs): 34 | return create_mcp_server(bvs)._mcp_server 35 | 36 | 37 | async def test_list_tools(mcp_server, snapshot): 38 | """Test listing available tools""" 39 | async with memory_session(mcp_server) as client: 40 | tools = await client.list_tools() 41 | assert tools.tools == snapshot 42 | 43 | 44 | async def test_list_filename(mcp_server, snapshot): 45 | """Test listing filenames""" 46 | async with memory_session(mcp_server) as client: 47 | result = await client.call_tool('list_filename') 48 | assert result.content[0].text == snapshot 49 | 50 | 51 | async def test_get_triage_summary(mcp_server, filename, snapshot): 52 | """Test getting triage summary with valid filename""" 53 | async with memory_session(mcp_server) as client: 54 | result = await client.call_tool('get_triage_summary', {'filename': filename}) 55 | assert result.content[0].text == snapshot 56 | 57 | 58 | async def test_get_imports(mcp_server, filename, snapshot): 59 | """Test getting imports with valid filename""" 60 | async with memory_session(mcp_server) as client: 61 | result = await client.call_tool('get_imports', {'filename': filename}) 62 | assert result.content[0].text == snapshot 63 | 64 | 65 | async def test_get_exports(mcp_server, filename, snapshot): 66 | """Test getting exports with valid filename""" 67 | async with memory_session(mcp_server) as client: 68 | result = await client.call_tool('get_exports', {'filename': filename}) 69 | assert result.content[0].text == snapshot 70 | 71 | 72 | async def test_get_segments(mcp_server, filename, snapshot): 73 | """Test getting segments with valid filename""" 74 | async with memory_session(mcp_server) as client: 75 | result = await client.call_tool('get_segments', {'filename': filename}) 76 | assert result.content[0].text == snapshot 77 | 78 | 79 | async def test_get_sections(mcp_server, filename, snapshot): 80 | """Test getting sections with valid filename""" 81 | async with memory_session(mcp_server) as client: 82 | result = await client.call_tool('get_sections', {'filename': filename}) 83 | assert result.content[0].text == snapshot 84 | 85 | 86 | async def test_get_strings(mcp_server, filename, snapshot): 87 | """Test getting strings with valid filename""" 88 | async with memory_session(mcp_server) as client: 89 | result = await client.call_tool('get_strings', {'filename': filename}) 90 | assert result.content[0].text == snapshot 91 | 92 | 93 | async def test_get_functions(mcp_server, filename, snapshot): 94 | """Test getting functions with valid filename""" 95 | async with memory_session(mcp_server) as client: 96 | result = await client.call_tool('get_functions', {'filename': filename}) 97 | assert result.content[0].text == snapshot 98 | 99 | 100 | async def test_get_data_variables(mcp_server, filename, snapshot): 101 | """Test getting data variables with valid filename""" 102 | async with memory_session(mcp_server) as client: 103 | result = await client.call_tool('get_data_variables', {'filename': filename}) 104 | assert result.content[0].text == snapshot 105 | 106 | 107 | async def test_rename_symbol(mcp_server, filename, function_address, snapshot): 108 | """Test renaming a symbol with valid filename and address""" 109 | async with memory_session(mcp_server) as client: 110 | result = await client.call_tool( 111 | 'rename_symbol', 112 | { 113 | 'filename': filename, 114 | 'address_or_name': function_address, 115 | 'new_name': 'test_renamed_function', 116 | }, 117 | ) 118 | assert result.content[0].text == snapshot 119 | 120 | 121 | async def test_pseudo_c(mcp_server, filename, function_address, snapshot): 122 | """Test getting pseudo C code with valid filename and address""" 123 | async with memory_session(mcp_server) as client: 124 | result = await client.call_tool( 125 | 'pseudo_c', {'filename': filename, 'address_or_name': function_address} 126 | ) 127 | assert result.content[0].text == snapshot 128 | 129 | 130 | async def test_pseudo_rust(mcp_server, filename, function_address, snapshot): 131 | """Test getting pseudo Rust code with valid filename and address""" 132 | async with memory_session(mcp_server) as client: 133 | result = await client.call_tool( 134 | 'pseudo_rust', {'filename': filename, 'address_or_name': function_address} 135 | ) 136 | assert result.content[0].text == snapshot 137 | 138 | 139 | async def test_high_level_il(mcp_server, filename, function_address, snapshot): 140 | """Test getting high level IL with valid filename and address""" 141 | async with memory_session(mcp_server) as client: 142 | result = await client.call_tool( 143 | 'high_level_il', {'filename': filename, 'address_or_name': function_address} 144 | ) 145 | assert result.content[0].text == snapshot 146 | 147 | 148 | async def test_medium_level_il(mcp_server, filename, function_address, snapshot): 149 | """Test getting medium level IL with valid filename and address""" 150 | async with memory_session(mcp_server) as client: 151 | result = await client.call_tool( 152 | 'medium_level_il', {'filename': filename, 'address_or_name': function_address} 153 | ) 154 | assert result.content[0].text == snapshot 155 | 156 | 157 | async def test_disassembly(mcp_server, filename, function_address, snapshot): 158 | """Test getting disassembly with valid filename and address""" 159 | async with memory_session(mcp_server) as client: 160 | result = await client.call_tool( 161 | 'disassembly', {'filename': filename, 'address_or_name': function_address} 162 | ) 163 | assert result.content[0].text == snapshot 164 | 165 | 166 | async def test_disassembly_with_length(mcp_server, filename, function_address, snapshot): 167 | """Test getting disassembly with valid filename, address, and length""" 168 | async with memory_session(mcp_server) as client: 169 | result = await client.call_tool( 170 | 'disassembly', {'filename': filename, 'address_or_name': function_address, 'length': 16} 171 | ) 172 | assert result.content[0].text == snapshot 173 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Another™ MCP Server for Binary Ninja 2 | 3 |
4 | 5 | The MCP (Model Context Protocol) Server for Binary Ninja 6 | 7 | [![PyPI][pypi-badge]][pypi-url] [![Apache licensed][license-badge]][license-url] 8 | [![Python Version][python-badge]][python-url] 9 | [![GitHub Discussions][discussions-badge]][discussions-url] 10 | 11 |
12 | 13 | English | [中文](README.zh.md) 14 | 15 | [pypi-badge]: https://img.shields.io/pypi/v/binaryninja-mcp.svg 16 | [pypi-url]: https://pypi.org/project/binaryninja-mcp/ 17 | [license-badge]: https://img.shields.io/pypi/l/binaryninja-mcp.svg 18 | [license-url]: https://github.com/MCPPhalanx/binaryninja-mcp/blob/main/LICENSE 19 | [python-badge]: https://img.shields.io/pypi/pyversions/binaryninja-mcp.svg 20 | [python-url]: https://www.python.org/downloads/ 21 | [discussions-badge]: 22 | https://img.shields.io/github/discussions/MCPPhalanx/binaryninja-mcp 23 | [discussions-url]: https://github.com/MCPPhalanx/binaryninja-mcp/discussions 24 | 25 | # Demo 26 | 27 | The [tests/binary/beleaf.elf](tests/binary/beleaf.elf) is taken from 28 | [CSAW'19: Beleaf - Nightmare](https://guyinatuxedo.github.io/03-beginner_re/csaw19_beleaf/index.html). 29 | You can also find the complete writeup from the link above! 30 | 31 | ![demo](docs/demo-1.jpg) 32 | 33 | ## ... but why _Another_? 34 | 35 | See: 36 | [Key Differences from the Existing Plugin](https://github.com/Vector35/community-plugins/issues/305) 37 | 38 | # Installation 39 | 40 | ## Server Setup 41 | 42 | There are two ways to run the MCP server: 43 | 44 | 1. **Binary Ninja UI Plugin**: 45 | 46 | - Install the plugin via Binary Ninja's plugin manager 47 | - The MCP server will start automatically when first file is loaded. 48 | - Auto start is configurable via `Settings - MCP Server - Auto Start` 49 | - Listen port is configurable via 50 | `Settings - MCP Server - Server port number` 51 | - All opened files are exposed to separate resources, see 52 | [Available Resources](README.md#available-resources) section below 53 | 54 | 2. **Binary Ninja Headless Mode**: 55 | ```bash 56 | uvx binaryninja-mcp install-api # only run once 57 | uvx binaryninja-mcp server [filename]... 58 | ``` 59 | - `filename` could be any binary files or BNDB, like in UI mode, all opened 60 | files are available to the MCP client. 61 | - Server runs on default port 7000 62 | - Use `--port` flag to specify a different port 63 | 64 | ## MCP Client Setup 65 | 66 | 1. **Claude Desktop (stdio relay client)**: Configure the client to connect via 67 | stdio transport using built-in relay. 68 | 69 | ```json 70 | { 71 | "mcpServers": { 72 | "binaryninja": { 73 | "command": "uvx", 74 | "args": ["binaryninja-mcp", "client"] 75 | } 76 | } 77 | } 78 | ``` 79 | 80 | 2. **Cherry Studio**: 81 | - **SSE endpoint** (recommanded): URL: `http://localhost:7000/sse` 82 | - **stdio client**: 83 | - Command: `uvx` 84 | - Arguments: 85 | ``` 86 | binaryninja-mcp 87 | client 88 | ``` 89 | 90 | Add `--port 12345` to both server and client command line if you prefer to run 91 | MCP server on port other than default. 92 | 93 | # Available Tools for MCP Clients 94 | 95 | The MCP server provides the following tools: 96 | 97 | - `rename_symbol`: Rename a function or a data variable 98 | - `pseudo_c`: Get pseudo C code of a specified function 99 | - `pseudo_rust`: Get pseudo Rust code of a specified function 100 | - `high_level_il`: Get high level IL of a specified function 101 | - `medium_level_il`: Get medium level IL of a specified function 102 | - `disassembly`: Get disassembly of a function or specified range 103 | - `update_analysis_and_wait`: Update analysis for the binary and wait for 104 | completion 105 | - `get_triage_summary`: Get basic information from BinaryNinja Triage view 106 | - `get_imports`: Get dictionary of imported symbols 107 | - `get_exports`: Get dictionary of exported symbols 108 | - `get_segments`: Get list of memory segments 109 | - `get_sections`: Get list of binary sections 110 | - `get_strings`: Get list of strings found in the binary 111 | - `get_functions`: Get list of functions 112 | - `get_data_variables`: Get list of data variables 113 | 114 | # Available Resources for MCP Clients 115 | 116 | MCP Resources can be accessed via URIs in the format: 117 | `binaryninja://{filename}/{resource_type}` 118 | 119 | The server provides these resource types for each binary: 120 | 121 | - `triage_summary`: Basic information from BinaryNinja Triage view 122 | - `imports`: Dictionary of imported symbols/functions 123 | - `exports`: Dictionary of exported symbols/functions 124 | - `segments`: List of memory segments 125 | - `sections`: List of binary sections 126 | - `strings`: List of strings found in the binary 127 | - `functions`: List of functions 128 | - `data_variables`: List of data variables 129 | 130 | # Development 131 | 132 | [uv](https://github.com/astral-sh/uv) is the recommanded package management tool 133 | for this project. 134 | 135 | ## Clone directory to Binary Ninja Plugin Directory 136 | 137 | ```powershell 138 | git clone https://github.com/MCPPhalanx/binaryninja-mcp.git "${env:APPDATA}\Binary Ninja\plugins\MCPPhalanx_binaryninja_mcp" 139 | ``` 140 | 141 | ## Setup Python Environment 142 | 143 | Binary Ninja API must be installed into virtualenv manually. 144 | 145 | ```bash 146 | uv venv 147 | uv sync --dev 148 | # install binaryninja API 149 | binaryninja-mcp install-api 150 | # check API is correctly installed 151 | uv run python -c 'import binaryninja as bn; assert bn._init_plugins() is None; assert bn.core_ui_enabled() is not None; print("BN API check PASSED!!")' 152 | ``` 153 | 154 | ## Setup MCP Client for Development 155 | 156 | For MCP clients with stdio transport like Claude Desktop, change working 157 | directory to development folder. 158 | 159 | ```json 160 | { 161 | "mcpServers": { 162 | "binaryninja": { 163 | "command": "uv", 164 | "args": [ 165 | "--directory", 166 | "C:/path/to/binaryninja-mcp", 167 | "run", 168 | "binaryninja-mcp", 169 | "client" 170 | ] 171 | } 172 | } 173 | } 174 | ``` 175 | 176 | SSE-enabled MCP clients can connect using: `http://localhost:7000/sse` 177 | 178 | ## Build 179 | 180 | ```bash 181 | uv build 182 | ``` 183 | 184 | ## Test 185 | 186 | ```bash 187 | pytest 188 | # To update test snapshots: 189 | pytest --snapshot-update 190 | ``` 191 | 192 | ## Version Bump 193 | 194 | The PyPI package version is automatically derived from Binary Ninja's 195 | `plugin.json` (using package.json format), maintaining version consistency 196 | between the BN plugin and PyPI package. 197 | 198 | ```bash 199 | # bump alpha version 200 | uvx hatch version a 201 | 202 | # bump release version 203 | uvx hatch version minor,rc 204 | uvx hatch version release 205 | ``` 206 | 207 | See: [Versioning - Hatch](https://hatch.pypa.io/1.12/version/) 208 | 209 | ## Release 210 | 211 | ```bash 212 | uv publish 213 | ``` 214 | 215 | # License 216 | 217 | [Apache 2.0](LICENSE) 218 | -------------------------------------------------------------------------------- /src/binaryninja_mcp/cli.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | 4 | import click 5 | 6 | from binaryninja_mcp.consts import DEFAULT_PORT 7 | from binaryninja_mcp.log import setup_logging 8 | from binaryninja_mcp.utils import ( 9 | disable_binaryninja_user_plugins, 10 | find_binaryninja_path, 11 | ) 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | @click.group() 17 | @click.option('-v', '--verbose', count=True, help='Enable verbose debug logging') 18 | def cli(verbose): 19 | """MCP CLI tool for Binary Ninja""" 20 | log_level, third_party_log_level = { 21 | 0: (logging.INFO, logging.WARNING), 22 | 1: (logging.DEBUG, logging.WARNING), 23 | }.get(verbose, (logging.DEBUG, logging.DEBUG)) 24 | setup_logging(log_level=log_level, third_party_log_level=third_party_log_level) 25 | 26 | 27 | @cli.command() 28 | @click.option('--listen-host', default='localhost', help='SSE bind address') 29 | @click.option('-p', '--listen-port', default=DEFAULT_PORT, help='SSE server port') 30 | @click.argument('filename') 31 | def server(listen_host, listen_port, filename): 32 | """Start an MCP server for the given binary file""" 33 | from binaryninja import load 34 | 35 | from binaryninja_mcp.server import SSEServerThread, create_mcp_server 36 | 37 | disable_binaryninja_user_plugins() 38 | 39 | # Load the binary view 40 | bv = load(filename) 41 | if not bv: 42 | logger.error('Failed to load binary: %s', filename) 43 | return 44 | 45 | mcp = create_mcp_server([bv]) 46 | 47 | # Run SSE server 48 | logger.info('Starting MCP server for %s on port %d', filename, listen_port) 49 | 50 | # Why separate thread? 51 | # because it's easier to maintain a single interface to SSE lifecycle management. 52 | thread = SSEServerThread(mcp.sse_app(), listen_host, listen_port) 53 | 54 | try: 55 | thread.start() 56 | while True: 57 | # thread.join will block Ctrl-C, use plain old time.sleep 58 | time.sleep(1000) 59 | except KeyboardInterrupt: 60 | logger.warning('Exiting...') 61 | finally: 62 | thread.stop() 63 | 64 | 65 | @cli.command() 66 | @click.option('--host', default='localhost', help='SSE server host') 67 | @click.option('--port', default=DEFAULT_PORT, help='SSE server port') 68 | @click.option( 69 | '--retry-interval', default=3, help='Retry interval in seconds when server disconnects' 70 | ) 71 | def client(host, port, retry_interval): 72 | """Connect to an MCP SSE server and relay to stdio""" 73 | 74 | import anyio 75 | import mcp.types as types 76 | from mcp.client.sse import sse_client 77 | from mcp.server.stdio import stdio_server 78 | from mcp.shared.session import RequestResponder 79 | 80 | async def message_handler( 81 | message: RequestResponder[types.ServerRequest, types.ClientResult] 82 | | types.ServerNotification 83 | | Exception, 84 | ) -> None: 85 | if isinstance(message, Exception): 86 | logger.error('Error: %s', message) 87 | return 88 | 89 | async def run_client(retry_seconds): 90 | # Create stdio server to relay messages 91 | async with stdio_server() as (stdio_read, stdio_write): 92 | while True: # Infinite retry loop 93 | try: 94 | # Connect to SSE server 95 | url = f'http://{host}:{port}/sse' 96 | logger.info('Connecting to MCP server at %s', url) 97 | 98 | # Connect to SSE server 99 | async with sse_client(url) as (sse_read, sse_write): 100 | logger.info('Connected to MCP server at %s:%d', host, port) 101 | 102 | # Create a disconnection event to signal when either connection is broken 103 | disconnection_event = anyio.Event() 104 | 105 | # Create a proxy to relay messages between stdio and SSE 106 | # Forward messages from stdio to SSE 107 | async def forward_stdio_to_sse(): 108 | try: 109 | while True: 110 | message = await stdio_read.receive() 111 | logger.debug('STDIO -> SSE: %s', message) 112 | await sse_write.send(message) 113 | except Exception as e: 114 | logger.error('Error forwarding stdio to SSE: %s', e) 115 | # Signal disconnection and exit 116 | disconnection_event.set() 117 | 118 | # Forward messages from SSE to stdio 119 | async def forward_sse_to_stdio(): 120 | try: 121 | while True: 122 | message = await sse_read.receive() 123 | logger.debug('SSE -> STDIO: %s', message) 124 | await stdio_write.send(message) 125 | except Exception as e: 126 | logger.error('Error forwarding SSE to stdio: %s', e) 127 | # Signal disconnection and exit 128 | disconnection_event.set() 129 | 130 | # Function to wait for disconnection and retry 131 | async def wait_for_disconnection(): 132 | await disconnection_event.wait() 133 | # Once disconnection is detected, just return to exit the task group 134 | logger.warning('Server disconnected, attempting to reconnect...') 135 | 136 | # Run all tasks concurrently 137 | async with anyio.create_task_group() as tg: 138 | tg.start_soon(forward_stdio_to_sse) 139 | tg.start_soon(forward_sse_to_stdio) 140 | logger.debug('STDIO-SSE relay client started') 141 | tg.start_soon(wait_for_disconnection) 142 | 143 | # If we get here, the task group has exited due to disconnection 144 | # We'll retry connecting in the next loop iteration 145 | 146 | except Exception as e: 147 | if isinstance(e, KeyboardInterrupt): 148 | raise # Re-raise KeyboardInterrupt to be caught by the outer try/except 149 | logger.warning(f'Connection error: {e}. Retrying in {retry_seconds} seconds...') 150 | await anyio.sleep(retry_seconds) # Wait before retrying 151 | 152 | try: 153 | # Use anyio.run with trio backend as in the reference code 154 | anyio.run(run_client, retry_interval, backend='trio') 155 | except KeyboardInterrupt: 156 | logger.info('\nDisconnected') 157 | except Exception as e: 158 | if e.__class__.__name__ == 'ExceptionGroup': 159 | logger.error('anyio job error: %s', repr(e.exceptions)) 160 | else: 161 | logger.error('Connection error: %s', e) 162 | # Exit with error code 163 | raise SystemExit(1) 164 | 165 | 166 | @cli.command() 167 | @click.option( 168 | '--binja-path', 169 | type=click.Path(exists=True), 170 | help='Custom Binary Ninja install path', 171 | ) 172 | @click.option('--silent', is_flag=True, help='Run in non-interactive mode') 173 | @click.option('--uninstall', is_flag=True, help='Uninstall instead of install') 174 | @click.option('--force', is_flag=True, help='Force installation') 175 | @click.option('--install-on-root', is_flag=True, help='Install to system Python') 176 | @click.option('--install-on-usersite', is_flag=True, help='Install to user sitepackage') 177 | def install_api(binja_path, silent, uninstall, force, install_on_root, install_on_usersite): 178 | """Install/uninstall Binary Ninja API""" 179 | install_path = find_binaryninja_path(binja_path) 180 | if not install_path: 181 | logger.error( 182 | 'Could not find Binary Ninja install directory, please provide the path via --binja-path' 183 | ) 184 | raise SystemExit(1) 185 | 186 | install_script = install_path / 'scripts/install_api.py' 187 | # Import from found script 188 | import importlib.util 189 | 190 | spec = importlib.util.spec_from_file_location('install_api', install_script) 191 | module = importlib.util.module_from_spec(spec) 192 | spec.loader.exec_module(module) 193 | 194 | if uninstall: 195 | result = module.uninstall() 196 | else: 197 | result = module.install( 198 | interactive=not silent, 199 | on_root=install_on_root, 200 | on_pyenv=not install_on_usersite, 201 | force=force, 202 | ) 203 | 204 | if not result: 205 | logger.error('Operation failed') 206 | raise SystemExit(1) 207 | logger.info('Operation succeeded') 208 | 209 | 210 | if __name__ == '__main__': 211 | cli() 212 | -------------------------------------------------------------------------------- /src/binaryninja_mcp/tools.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import logging 3 | from typing import Any, Optional 4 | 5 | import binaryninja as bn 6 | 7 | from binaryninja_mcp.resources import MCPResource 8 | 9 | # Set up logger 10 | logger = logging.getLogger('binaryninja_mcp.tools') 11 | 12 | 13 | def handle_exceptions(func): 14 | """Decorator to handle exceptions in tool methods 15 | 16 | Logs the error and re-raises the exception 17 | """ 18 | 19 | @functools.wraps(func) 20 | def wrapper(*args, **kwargs): 21 | try: 22 | return func(*args, **kwargs) 23 | except Exception as e: 24 | logger.error(f'Error in {func.__name__}: {str(e)}') 25 | raise 26 | 27 | return wrapper 28 | 29 | 30 | class MCPTools: 31 | """Tool handler for Binary Ninja MCP tools""" 32 | 33 | def __init__(self, bv: bn.BinaryView): 34 | """Initialize with a Binary Ninja BinaryView""" 35 | self.bv = bv 36 | self.resource = MCPResource(bv) 37 | 38 | def resolve_symbol(self, address_or_name: str) -> Optional[int]: 39 | """Resolve a symbol name or address to a numeric address 40 | 41 | Args: 42 | address_or_name: Either a hex address string or symbol name 43 | 44 | Returns: 45 | Numeric address if found, None otherwise 46 | """ 47 | try: 48 | return int(address_or_name, 16) 49 | except ValueError: 50 | # Search functions 51 | for func in self.bv.functions: 52 | if func.name == address_or_name: 53 | return func.start 54 | # Search data variables 55 | for addr, var in self.bv.data_vars.items(): 56 | if var.name == address_or_name: 57 | return addr 58 | return None 59 | 60 | @handle_exceptions 61 | def rename_symbol(self, address_or_name: str, new_name: str) -> str: 62 | """Rename a function or a data variable 63 | 64 | Args: 65 | address_or_name: Address (hex string) or name of the symbol 66 | new_name: New name for the symbol 67 | 68 | Returns: 69 | Success message string 70 | """ 71 | # Convert hex string to int 72 | addr = self.resolve_symbol(address_or_name) 73 | if addr is None: 74 | raise ValueError( 75 | f"No function or data variable found with name/address '{address_or_name}'" 76 | ) 77 | 78 | # Check if address is a function 79 | func = self.bv.get_function_at(addr) 80 | if func: 81 | old_name = func.name 82 | func.name = new_name 83 | return f"Successfully renamed function at {hex(addr)} from '{old_name}' to '{new_name}'" 84 | 85 | # Check if address is a data variable 86 | if addr in self.bv.data_vars: 87 | var = self.bv.data_vars[addr] 88 | old_name = var.name if hasattr(var, 'name') else 'unnamed' 89 | 90 | # Create a symbol at this address with the new name 91 | self.bv.define_user_symbol(bn.Symbol(bn.SymbolType.DataSymbol, addr, new_name)) 92 | 93 | return f"Successfully renamed data variable at {hex(addr)} from '{old_name}' to '{new_name}'" 94 | 95 | raise ValueError( 96 | f"No function or data variable found with name/address '{address_or_name}'" 97 | ) 98 | 99 | @handle_exceptions 100 | def pseudo_c(self, address_or_name: str) -> str: 101 | """Get pseudo C code of a specified function 102 | 103 | Args: 104 | address_or_name: Address (hex string) or name of the function 105 | 106 | Returns: 107 | Pseudo C code as string 108 | """ 109 | addr = self.resolve_symbol(address_or_name) 110 | if addr is None: 111 | raise ValueError(f"No function found with name/address '{address_or_name}'") 112 | 113 | func = self.bv.get_function_at(addr) 114 | if not func: 115 | raise ValueError(f'No function found at address {hex(addr)}') 116 | 117 | lines = [] 118 | settings = bn.DisassemblySettings() 119 | settings.set_option(bn.DisassemblyOption.ShowAddress, False) 120 | settings.set_option(bn.DisassemblyOption.WaitForIL, True) 121 | obj = bn.LinearViewObject.language_representation(self.bv, settings) 122 | cursor_end = bn.LinearViewCursor(obj) 123 | cursor_end.seek_to_address(func.highest_address) 124 | body = self.bv.get_next_linear_disassembly_lines(cursor_end) 125 | cursor_end.seek_to_address(func.highest_address) 126 | header = self.bv.get_previous_linear_disassembly_lines(cursor_end) 127 | 128 | for line in header: 129 | lines.append(f'{str(line)}\n') 130 | 131 | for line in body: 132 | lines.append(f'{str(line)}\n') 133 | 134 | return ''.join(lines) 135 | 136 | @handle_exceptions 137 | def pseudo_rust(self, address_or_name: str) -> str: 138 | """Get pseudo Rust code of a specified function 139 | 140 | Args: 141 | address_or_name: Address (hex string) or name of the function 142 | 143 | Returns: 144 | Pseudo Rust code as string 145 | """ 146 | addr = self.resolve_symbol(address_or_name) 147 | if addr is None: 148 | raise ValueError(f"No function found with name/address '{address_or_name}'") 149 | 150 | func = self.bv.get_function_at(addr) 151 | if not func: 152 | raise ValueError(f'No function found at address {hex(addr)}') 153 | 154 | lines = [] 155 | settings = bn.DisassemblySettings() 156 | settings.set_option(bn.DisassemblyOption.ShowAddress, False) 157 | settings.set_option(bn.DisassemblyOption.WaitForIL, True) 158 | obj = bn.LinearViewObject.language_representation(self.bv, settings, language='Pseudo Rust') 159 | cursor_end = bn.LinearViewCursor(obj) 160 | cursor_end.seek_to_address(func.highest_address) 161 | body = self.bv.get_next_linear_disassembly_lines(cursor_end) 162 | cursor_end.seek_to_address(func.highest_address) 163 | header = self.bv.get_previous_linear_disassembly_lines(cursor_end) 164 | 165 | for line in header: 166 | lines.append(f'{str(line)}\n') 167 | 168 | for line in body: 169 | lines.append(f'{str(line)}\n') 170 | 171 | return ''.join(lines) 172 | 173 | @handle_exceptions 174 | def high_level_il(self, address_or_name: str) -> str: 175 | """Get high level IL of a specified function 176 | 177 | Args: 178 | address_or_name: Address (hex string) or name of the function 179 | 180 | Returns: 181 | HLIL as string 182 | """ 183 | addr = self.resolve_symbol(address_or_name) 184 | if addr is None: 185 | raise ValueError(f"No function found with name/address '{address_or_name}'") 186 | 187 | func = self.bv.get_function_at(addr) 188 | if not func: 189 | raise ValueError(f'No function found at address {hex(addr)}') 190 | 191 | # Get HLIL 192 | hlil = func.hlil 193 | if not hlil: 194 | raise ValueError(f'Failed to get HLIL for function at {hex(addr)}') 195 | 196 | # Format the HLIL output 197 | lines = [] 198 | for instruction in hlil.instructions: 199 | lines.append(f'{instruction.address:#x}: {instruction}\n') 200 | 201 | return ''.join(lines) 202 | 203 | @handle_exceptions 204 | def medium_level_il(self, address_or_name: str) -> str: 205 | """Get medium level IL of a specified function 206 | 207 | Args: 208 | address_or_name: Address (hex string) or name of the function 209 | 210 | Returns: 211 | MLIL as string 212 | """ 213 | addr = self.resolve_symbol(address_or_name) 214 | if addr is None: 215 | raise ValueError(f"No function found with name/address '{address_or_name}'") 216 | 217 | func = self.bv.get_function_at(addr) 218 | if not func: 219 | raise ValueError(f'No function found at address {hex(addr)}') 220 | 221 | # Get MLIL 222 | mlil = func.mlil 223 | if not mlil: 224 | raise ValueError(f'Failed to get MLIL for function at {hex(addr)}') 225 | 226 | # Format the MLIL output 227 | lines = [] 228 | for instruction in mlil.instructions: 229 | lines.append(f'{instruction.address:#x}: {instruction}\n') 230 | 231 | return ''.join(lines) 232 | 233 | @handle_exceptions 234 | def disassembly(self, address_or_name: str, length: Optional[int] = None) -> str: 235 | """Get disassembly of a function or specified range 236 | 237 | Args: 238 | address_or_name: Address (hex string) or name to start disassembly 239 | length: Optional length of bytes to disassemble 240 | 241 | Returns: 242 | Disassembly as string 243 | """ 244 | addr = self.resolve_symbol(address_or_name) 245 | if addr is None: 246 | raise ValueError(f"No symbol found with name/address '{address_or_name}'") 247 | 248 | # If length is provided, disassemble that range 249 | if length is not None: 250 | disasm = [] 251 | # Get instruction lengths instead of assuming 4-byte instructions 252 | current_addr = addr 253 | remaining_length = length 254 | 255 | while remaining_length > 0 and current_addr < self.bv.end: 256 | # Get instruction length at this address 257 | instr_length = self.bv.get_instruction_length(current_addr) 258 | if instr_length == 0: 259 | instr_length = 1 # Fallback to 1 byte if instruction length is unknown 260 | 261 | # Get disassembly at this address 262 | tokens = self.bv.get_disassembly(current_addr) 263 | if tokens: 264 | disasm.append(f'{hex(current_addr)}: {tokens}') 265 | 266 | current_addr += instr_length 267 | remaining_length -= instr_length 268 | 269 | if remaining_length <= 0: 270 | break 271 | 272 | if not disasm: 273 | raise ValueError( 274 | f'Failed to disassemble at address {hex(addr)} with length {length}' 275 | ) 276 | 277 | return '\n'.join(disasm) 278 | 279 | # Otherwise, try to get function disassembly 280 | func = self.bv.get_function_at(addr) 281 | if not func: 282 | raise ValueError(f'No function found at address {hex(addr)}') 283 | 284 | # Get function disassembly using linear disassembly 285 | result_lines = [] 286 | settings = bn.DisassemblySettings() 287 | settings.set_option(bn.DisassemblyOption.ShowAddress, True) 288 | 289 | # Use single_function_disassembly which is specifically for disassembling a single function 290 | obj = bn.LinearViewObject.single_function_disassembly(func, settings) 291 | cursor = bn.LinearViewCursor(obj) 292 | cursor.seek_to_begin() 293 | 294 | # Get all lines until we reach the end 295 | while not cursor.after_end: 296 | lines = self.bv.get_next_linear_disassembly_lines(cursor) 297 | if not lines: 298 | break 299 | for line in lines: 300 | result_lines.append(str(line)) 301 | 302 | if not result_lines: 303 | raise ValueError(f'Failed to disassemble function at {hex(addr)}') 304 | 305 | return '\n'.join(result_lines) 306 | 307 | # Resource access tools 308 | @handle_exceptions 309 | def get_triage_summary(self) -> Any: 310 | """Get basic information as shown in BinaryNinja Triage view""" 311 | return self.resource.triage_summary() 312 | 313 | @handle_exceptions 314 | def get_imports(self) -> dict: 315 | """Get dictionary of imported symbols""" 316 | return self.resource.imports() 317 | 318 | @handle_exceptions 319 | def get_exports(self) -> dict: 320 | """Get dictionary of exported symbols""" 321 | return self.resource.exports() 322 | 323 | @handle_exceptions 324 | def get_segments(self) -> list: 325 | """Get list of memory segments""" 326 | return self.resource.segments() 327 | 328 | @handle_exceptions 329 | def get_sections(self) -> list: 330 | """Get list of binary sections""" 331 | return self.resource.sections() 332 | 333 | @handle_exceptions 334 | def get_strings(self) -> list: 335 | """Get list of strings found in the binary""" 336 | return self.resource.strings() 337 | 338 | @handle_exceptions 339 | def get_functions(self) -> list: 340 | """Get list of functions""" 341 | return self.resource.functions() 342 | 343 | @handle_exceptions 344 | def get_data_variables(self) -> list: 345 | """Get list of data variables""" 346 | return self.resource.data_variables() 347 | 348 | @handle_exceptions 349 | def update_analysis_and_wait(self) -> str: 350 | """Update analysis for the binary and wait for it to complete 351 | 352 | Returns: 353 | Success message string 354 | """ 355 | # Start the analysis update 356 | self.bv.update_analysis_and_wait() 357 | return f'Analysis updated successfully for {self.bv.file.filename}' 358 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/binaryninja_mcp/server.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from contextlib import asynccontextmanager 3 | from dataclasses import dataclass, field 4 | from threading import Event, Thread 5 | from typing import AsyncIterator, Dict, List, Optional 6 | 7 | import anyio 8 | import binaryninja as bn 9 | from anyio import to_thread 10 | from hypercorn.config import Config 11 | from hypercorn.trio import serve 12 | from mcp.server.fastmcp import Context, FastMCP 13 | 14 | from binaryninja_mcp.resources import MCPResource 15 | from binaryninja_mcp.tools import MCPTools 16 | from binaryninja_mcp.utils import bv_name 17 | 18 | logger = logging.getLogger(__name__) 19 | 20 | 21 | @dataclass 22 | class BNContext: 23 | """Context holding loaded BinaryViews with automatic name deduplication""" 24 | 25 | bvs: Dict[str, bn.BinaryView] = field(default_factory=dict) 26 | 27 | def add_bv(self, bv: bn.BinaryView, name: Optional[str] = None) -> str: 28 | """Add a BinaryView to the context with automatic name deduplication 29 | 30 | Args: 31 | bv: The BinaryView to add 32 | name: Optional name to use (defaults to filename) 33 | 34 | Returns: 35 | The name used for the BinaryView 36 | """ 37 | if name is None: 38 | name = bv_name(bv) 39 | 40 | # Sanitize name for URL usage 41 | invalid_chars = '/\\:*?"<>| ' 42 | for c in invalid_chars: 43 | name = name.replace(c, '_') 44 | name = name.strip('_.') 45 | if not name: 46 | name = 'unnamed' 47 | 48 | # Deduplicate name if needed 49 | base_name = name 50 | counter = 1 51 | while name in self.bvs: 52 | name = f'{base_name}_{counter}' 53 | counter += 1 54 | 55 | self.bvs[name] = bv 56 | logger.debug("Added BinaryView %s as '%s'", bv, name) 57 | return name 58 | 59 | def get_bv(self, name: str) -> Optional[bn.BinaryView]: 60 | """Get a BinaryView by name 61 | 62 | Args: 63 | name: The name of the BinaryView 64 | 65 | Returns: 66 | The BinaryView if found, None otherwise 67 | """ 68 | logger.debug('Looking up BinaryView: %s', name) 69 | bv = self.bvs.get(name) 70 | if not bv: 71 | logger.error('BinaryView not found: %s', name) 72 | raise KeyError( 73 | f'filename not found: {name}, currently opened: {" , ".join(self.bvs.keys())}' 74 | ) 75 | return bv 76 | 77 | 78 | @asynccontextmanager 79 | async def lifespan(server: FastMCP) -> AsyncIterator[BNContext]: 80 | """Application lifecycle manager with initial BinaryViews""" 81 | context = BNContext() 82 | 83 | # Add initial BinaryViews from server configuration 84 | for bv in getattr(server, 'initial_bvs', []): 85 | context.add_bv(bv) 86 | 87 | try: 88 | yield context 89 | finally: 90 | logger.debug('Cleaning up BNContext') 91 | context.bvs.clear() 92 | 93 | 94 | def create_mcp_server(initial_bvs: Optional[List[bn.BinaryView]] = None, **mcp_settings) -> FastMCP: 95 | """Initialize MCP server with optional initial BinaryViews 96 | 97 | Args: 98 | initial_bvs: Optional list of BinaryViews to initialize the server with 99 | 100 | Returns: 101 | Configured MCP server instance 102 | """ 103 | mcp = FastMCP( 104 | name='BinaryNinja', 105 | version='1.0.0', 106 | description='MCP server for Binary Ninja analysis', 107 | lifespan=lifespan, 108 | **mcp_settings, 109 | ) 110 | 111 | # Store initial BinaryViews for use in lifespan 112 | mcp.initial_bvs = initial_bvs or [] 113 | 114 | # Resource handlers 115 | @mcp.resource('binaryninja://{filename}/triage_summary') 116 | def resource_get_triage_summary(filename: str) -> dict: 117 | """Get basic information as shown in BinaryNinja Triage view""" 118 | bnctx: BNContext = mcp.get_context().request_context.lifespan_context 119 | bv = bnctx.get_bv(filename) 120 | resource = MCPResource(bv) 121 | return resource.triage_summary() 122 | 123 | @mcp.resource('binaryninja://{filename}/imports') 124 | def resource_get_imports(filename: str) -> dict: 125 | """Get dictionary of imported symbols or functions with properties""" 126 | bnctx: BNContext = mcp.get_context().request_context.lifespan_context 127 | bv = bnctx.get_bv(filename) 128 | resource = MCPResource(bv) 129 | return resource.imports() 130 | 131 | @mcp.resource('binaryninja://{filename}/exports') 132 | def resource_get_exports(filename: str) -> dict: 133 | """Get dictionary of exported symbols or functions with properties""" 134 | bnctx: BNContext = mcp.get_context().request_context.lifespan_context 135 | bv = bnctx.get_bv(filename) 136 | resource = MCPResource(bv) 137 | return resource.exports() 138 | 139 | @mcp.resource('binaryninja://{filename}/segments') 140 | def resource_get_segments(filename: str) -> list: 141 | """Get list of memory segments""" 142 | bnctx: BNContext = mcp.get_context().request_context.lifespan_context 143 | bv = bnctx.get_bv(filename) 144 | resource = MCPResource(bv) 145 | return resource.segments() 146 | 147 | @mcp.resource('binaryninja://{filename}/sections') 148 | def resource_get_sections(filename: str) -> list: 149 | """Get list of binary sections""" 150 | bnctx: BNContext = mcp.get_context().request_context.lifespan_context 151 | bv = bnctx.get_bv(filename) 152 | resource = MCPResource(bv) 153 | return resource.sections() 154 | 155 | @mcp.resource('binaryninja://{filename}/strings') 156 | def resource_get_strings(filename: str) -> list: 157 | """Get list of strings found in the binary""" 158 | bnctx: BNContext = mcp.get_context().request_context.lifespan_context 159 | bv = bnctx.get_bv(filename) 160 | resource = MCPResource(bv) 161 | return resource.strings() 162 | 163 | @mcp.resource('binaryninja://{filename}/functions') 164 | def resource_get_functions(filename: str) -> list: 165 | """Get list of functions""" 166 | bnctx: BNContext = mcp.get_context().request_context.lifespan_context 167 | bv = bnctx.get_bv(filename) 168 | resource = MCPResource(bv) 169 | return resource.functions() 170 | 171 | @mcp.resource('binaryninja://{filename}/data_variables') 172 | def resource_get_data_variables(filename: str) -> list: 173 | """Get list of data variables""" 174 | bnctx: BNContext = mcp.get_context().request_context.lifespan_context 175 | bv = bnctx.get_bv(filename) 176 | resource = MCPResource(bv) 177 | return resource.data_variables() 178 | 179 | # Tool handlers 180 | @mcp.tool() 181 | def list_filename(ctx: Context) -> List[str]: 182 | """List file names of all opened files""" 183 | bnctx: BNContext = ctx.request_context.lifespan_context 184 | return list(bnctx.bvs.keys()) 185 | 186 | @mcp.tool() 187 | def get_triage_summary(filename: str, ctx: Context) -> dict: 188 | """Get basic information as shown in BinaryNinja Triage view""" 189 | bnctx: BNContext = ctx.request_context.lifespan_context 190 | bv = bnctx.get_bv(filename) 191 | tools = MCPTools(bv) 192 | return tools.get_triage_summary() 193 | 194 | @mcp.tool() 195 | def get_imports(filename: str, ctx: Context) -> dict: 196 | """Get dictionary of imported symbols""" 197 | bnctx: BNContext = ctx.request_context.lifespan_context 198 | bv = bnctx.get_bv(filename) 199 | tools = MCPTools(bv) 200 | return tools.get_imports() 201 | 202 | @mcp.tool() 203 | def get_exports(filename: str, ctx: Context) -> dict: 204 | """Get dictionary of exported symbols""" 205 | bnctx: BNContext = ctx.request_context.lifespan_context 206 | bv = bnctx.get_bv(filename) 207 | tools = MCPTools(bv) 208 | return tools.get_exports() 209 | 210 | @mcp.tool() 211 | def get_segments(filename: str, ctx: Context) -> list: 212 | """Get list of memory segments""" 213 | bnctx: BNContext = ctx.request_context.lifespan_context 214 | bv = bnctx.get_bv(filename) 215 | tools = MCPTools(bv) 216 | return tools.get_segments() 217 | 218 | @mcp.tool() 219 | def get_sections(filename: str, ctx: Context) -> list: 220 | """Get list of binary sections""" 221 | bnctx: BNContext = ctx.request_context.lifespan_context 222 | bv = bnctx.get_bv(filename) 223 | tools = MCPTools(bv) 224 | return tools.get_sections() 225 | 226 | @mcp.tool() 227 | def get_strings(filename: str, ctx: Context) -> list: 228 | """Get list of strings found in the binary""" 229 | bnctx: BNContext = ctx.request_context.lifespan_context 230 | bv = bnctx.get_bv(filename) 231 | tools = MCPTools(bv) 232 | return tools.get_strings() 233 | 234 | @mcp.tool() 235 | def get_functions(filename: str, ctx: Context) -> list: 236 | """Get list of functions""" 237 | bnctx: BNContext = ctx.request_context.lifespan_context 238 | bv = bnctx.get_bv(filename) 239 | tools = MCPTools(bv) 240 | return tools.get_functions() 241 | 242 | @mcp.tool() 243 | def get_data_variables(filename: str, ctx: Context) -> list: 244 | """Get list of data variables""" 245 | bnctx: BNContext = ctx.request_context.lifespan_context 246 | bv = bnctx.get_bv(filename) 247 | tools = MCPTools(bv) 248 | return tools.get_data_variables() 249 | 250 | @mcp.tool() 251 | def rename_symbol(filename: str, address_or_name: str, new_name: str, ctx: Context) -> str: 252 | """Rename a function or a data variable""" 253 | bnctx: BNContext = ctx.request_context.lifespan_context 254 | bv = bnctx.get_bv(filename) 255 | tools = MCPTools(bv) 256 | return tools.rename_symbol(address_or_name, new_name) 257 | 258 | @mcp.tool() 259 | def pseudo_c(filename: str, address_or_name: str, ctx: Context) -> str: 260 | """Get pseudo C code of a specified function""" 261 | bnctx: BNContext = ctx.request_context.lifespan_context 262 | bv = bnctx.get_bv(filename) 263 | tools = MCPTools(bv) 264 | return tools.pseudo_c(address_or_name) 265 | 266 | @mcp.tool() 267 | def pseudo_rust(filename: str, address_or_name: str, ctx: Context) -> str: 268 | """Get pseudo Rust code of a specified function""" 269 | bnctx: BNContext = ctx.request_context.lifespan_context 270 | bv = bnctx.get_bv(filename) 271 | tools = MCPTools(bv) 272 | return tools.pseudo_rust(address_or_name) 273 | 274 | @mcp.tool() 275 | def high_level_il(filename: str, address_or_name: str, ctx: Context) -> str: 276 | """Get high level IL of a specified function""" 277 | bnctx: BNContext = ctx.request_context.lifespan_context 278 | bv = bnctx.get_bv(filename) 279 | tools = MCPTools(bv) 280 | return tools.high_level_il(address_or_name) 281 | 282 | @mcp.tool() 283 | def medium_level_il(filename: str, address_or_name: str, ctx: Context) -> str: 284 | """Get medium level IL of a specified function""" 285 | bnctx: BNContext = ctx.request_context.lifespan_context 286 | bv = bnctx.get_bv(filename) 287 | tools = MCPTools(bv) 288 | return tools.medium_level_il(address_or_name) 289 | 290 | @mcp.tool() 291 | def disassembly( 292 | filename: str, address_or_name: str, ctx: Context, length: Optional[int] = None 293 | ) -> str: 294 | """Get disassembly of a function or specified range""" 295 | bnctx: BNContext = ctx.request_context.lifespan_context 296 | bv = bnctx.get_bv(filename) 297 | tools = MCPTools(bv) 298 | return tools.disassembly(address_or_name, length) 299 | 300 | @mcp.tool() 301 | def update_analysis_and_wait(filename: str, ctx: Context) -> str: 302 | """Update analysis for the binary and wait for it to complete""" 303 | bnctx: BNContext = ctx.request_context.lifespan_context 304 | bv = bnctx.get_bv(filename) 305 | tools = MCPTools(bv) 306 | return tools.update_analysis_and_wait() 307 | 308 | return mcp 309 | 310 | 311 | class SSEServerThread(Thread): 312 | def __init__(self, asgiapp, host, port): 313 | super().__init__(name='SSE Server Thread', daemon=True) 314 | self.app = asgiapp 315 | self.config = Config() 316 | self.config.bind = [f'{host}:{port}'] 317 | 318 | def run(self): 319 | self.shutdown_signal = Event() 320 | return anyio.run(self.arun, backend='trio') 321 | 322 | def stop(self): 323 | self.shutdown_signal.set() 324 | 325 | # When running event loop from a thread, anyio.open_signal_receiver does not work 326 | # (it only works from main thread). We have to use our own way to send signals. 327 | # In order to send signal from a synchronous thread to an asynchronous task, 328 | # I choose to use anyio.to_thread helper with threading.Event to bridge between those two worlds. 329 | 330 | async def arun(self): 331 | return await serve(self.app, self.config, shutdown_trigger=self._shutdown_trigger) 332 | 333 | async def _shutdown_trigger(self): 334 | logger.debug('Start listening for shutdown event') 335 | await to_thread.run_sync(self.shutdown_signal.wait) 336 | logger.debug('Shutdown event triggered') 337 | -------------------------------------------------------------------------------- /tests/__snapshots__/test_mcp_tools.ambr: -------------------------------------------------------------------------------- 1 | # serializer version: 1 2 | # name: test_disassembly[beleaf.elf.bndb-0x000008a1] 3 | ''' 4 | 5 | int32_t main(int32_t argc, char** argv, char** envp) 6 | 7 | 000008a1 push rbp 8 | 000008a2 mov rbp, rsp 9 | 000008a5 sub rsp, 0xc0 10 | 000008ac mov dword [rbp-0xb4], edi 11 | 000008b2 mov qword [rbp-0xc0], rsi 12 | 000008b9 mov rax, qword [fs:0x28] 13 | 000008c2 mov qword [rbp-0x8], rax 14 | 000008c6 xor eax, eax 15 | 000008c8 lea rdi, [rel data_a64] {"Enter the flag\n>>> "} 16 | 000008cf mov eax, 0x0 17 | 000008d4 call printf 18 | 000008d9 lea rax, [rbp-0x90] 19 | 000008e0 mov rsi, rax 20 | 000008e3 lea rdi, [rel data_a78] 21 | 000008ea mov eax, 0x0 22 | 000008ef call __isoc99_scanf 23 | 000008f4 lea rax, [rbp-0x90] 24 | 000008fb mov rdi, rax 25 | 000008fe call strlen 26 | 00000903 mov qword [rbp-0xa0], rax 27 | 0000090a cmp qword [rbp-0xa0], 0x20 28 | 00000912 ja 0x92a 29 | 30 | 00000914 lea rdi, [rel data_a7b] {"Incorrect!"} 31 | 0000091b call puts 32 | 00000920 mov edi, 0x1 33 | 00000925 call exit 34 | { Does not return } 35 | 36 | 0000092a mov qword [rbp-0xa8], 0x0 37 | 00000935 jmp 0x99d 38 | 39 | 00000937 lea rdx, [rbp-0x90] 40 | 0000093e mov rax, qword [rbp-0xa8] 41 | 00000945 add rax, rdx 42 | 00000948 movzx eax, byte [rax] 43 | 0000094b movsx eax, al 44 | 0000094e mov edi, eax 45 | 00000950 call sub_7fa 46 | 00000955 mov qword [rbp-0x98], rax 47 | 0000095c mov rax, qword [rbp-0xa8] 48 | 00000963 lea rdx, [rax*8] 49 | 0000096b lea rax, [rel data_2014e0] 50 | 00000972 mov rax, qword [rdx+rax] 51 | 00000976 cmp qword [rbp-0x98], rax 52 | 0000097d je 0x995 53 | 54 | 0000097f lea rdi, [rel data_a7b] {"Incorrect!"} 55 | 00000986 call puts 56 | 0000098b mov edi, 0x1 57 | 00000990 call exit 58 | { Does not return } 59 | 60 | 00000995 add qword [rbp-0xa8], 0x1 61 | 62 | 0000099d mov rax, qword [rbp-0xa8] 63 | 000009a4 cmp rax, qword [rbp-0xa0] 64 | 000009ab jb 0x937 65 | 66 | 000009ad lea rdi, [rel data_a86] {"Correct!"} 67 | 000009b4 call puts 68 | 000009b9 mov eax, 0x0 69 | 000009be mov rcx, qword [rbp-0x8] 70 | 000009c2 xor rcx, qword [fs:0x28] 71 | 000009cb je 0x9d2 72 | 73 | 000009cd call __stack_chk_fail 74 | { Does not return } 75 | 76 | 000009d2 leave 77 | 000009d3 retn 78 | 79 | ''' 80 | # --- 81 | # name: test_disassembly[beleaf.elf.bndb-0xINVALID] 82 | "Error executing tool disassembly: No symbol found with name/address '0xINVALID'" 83 | # --- 84 | # name: test_disassembly[nonexist.elf-0x000008a1] 85 | "Error executing tool disassembly: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 86 | # --- 87 | # name: test_disassembly[nonexist.elf-0xINVALID] 88 | "Error executing tool disassembly: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 89 | # --- 90 | # name: test_disassembly_with_length[beleaf.elf.bndb-0x000008a1] 91 | ''' 92 | 0x8a1: push rbp 93 | 0x8a2: mov rbp, rsp 94 | 0x8a5: sub rsp, 0xc0 95 | 0x8ac: mov dword [rbp-0xb4], edi 96 | ''' 97 | # --- 98 | # name: test_disassembly_with_length[beleaf.elf.bndb-0xINVALID] 99 | "Error executing tool disassembly: No symbol found with name/address '0xINVALID'" 100 | # --- 101 | # name: test_disassembly_with_length[nonexist.elf-0x000008a1] 102 | "Error executing tool disassembly: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 103 | # --- 104 | # name: test_disassembly_with_length[nonexist.elf-0xINVALID] 105 | "Error executing tool disassembly: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 106 | # --- 107 | # name: test_get_data_variables[beleaf.elf.bndb] 108 | '{"address": "0x0", "type": "struct Elf64_Header", "auto_discovered": true, "symbol": {"name": "__elf_header", "type": "SymbolType.DataSymbol", "short_name": "__elf_header"}}' 109 | # --- 110 | # name: test_get_data_variables[nonexist.elf] 111 | "Error executing tool get_data_variables: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 112 | # --- 113 | # name: test_get_exports[beleaf.elf.bndb] 114 | '{"name": "_start", "address": "0x6f0", "type": "SymbolType.FunctionSymbol", "ordinal": 0}' 115 | # --- 116 | # name: test_get_exports[nonexist.elf] 117 | "Error executing tool get_exports: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 118 | # --- 119 | # name: test_get_functions[beleaf.elf.bndb] 120 | '{"name": "_init", "start": "0x650", "symbol": {"name": "_init", "type": "SymbolType.FunctionSymbol", "short_name": "_init"}, "parameter_count": 0, "return_type": null, "has_prototype": false, "is_imported": false, "is_thunk": false, "basic_block_count": 3}' 121 | # --- 122 | # name: test_get_functions[nonexist.elf] 123 | "Error executing tool get_functions: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 124 | # --- 125 | # name: test_get_imports[beleaf.elf.bndb] 126 | "{'BNINTERNALNAMESPACE': [{'name': 'puts', 'address': '0x680', 'type': 'SymbolType.ImportedFunctionSymbol', 'ordinal': 0}, {'name': 'strlen', 'address': '0x690', 'type': 'SymbolType.ImportedFunctionSymbol', 'ordinal': 0}, {'name': '__stack_chk_fail', 'address': '0x6a0', 'type': 'SymbolType.ImportedFunctionSymbol', 'ordinal': 0}, {'name': 'printf', 'address': '0x6b0', 'type': 'SymbolType.ImportedFunctionSymbol', 'ordinal': 0}, {'name': '__isoc99_scanf', 'address': '0x6c0', 'type': 'SymbolType.ImportedFunctionSymbol', 'ordinal': 0}, {'name': 'exit', 'address': '0x6d0', 'type': 'SymbolType.ImportedFunctionSymbol', 'ordinal': 0}, {'name': '__cxa_finalize', 'address': '0x6e0', 'type': 'SymbolType.ImportedFunctionSymbol', 'ordinal': 0}]}" 127 | # --- 128 | # name: test_get_imports[nonexist.elf] 129 | "Error executing tool get_imports: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 130 | # --- 131 | # name: test_get_sections[beleaf.elf.bndb] 132 | '{"name": ".bss", "start": "0x2015e8", "end": "0x2015f0", "length": 8, "type": "NOBITS", "align": 1, "entry_size": 0, "linked_section": "", "info_section": "", "info_data": 0}' 133 | # --- 134 | # name: test_get_sections[nonexist.elf] 135 | "Error executing tool get_sections: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 136 | # --- 137 | # name: test_get_segments[beleaf.elf.bndb] 138 | '{"start": "0x0", "end": "0xc00", "length": 3072, "data_offset": 0, "data_length": 3072, "data_end": 3072, "readable": true, "writable": false, "executable": true}' 139 | # --- 140 | # name: test_get_segments[nonexist.elf] 141 | "Error executing tool get_segments: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 142 | # --- 143 | # name: test_get_strings[beleaf.elf.bndb] 144 | '{"value": "@8\\t@", "start": "0x34", "length": 8, "type": "StringType.Utf16String"}' 145 | # --- 146 | # name: test_get_strings[nonexist.elf] 147 | "Error executing tool get_strings: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 148 | # --- 149 | # name: test_get_triage_summary[beleaf.elf.bndb] 150 | '{"file_metadata": {"filename": "tests/binary/beleaf.elf.bndb", "file_size": 2102904, "view_type": "ELF"}, "binary_info": {"platform": "linux-x86_64", "entry_point": "0x6f0", "base_address": "0x0", "end_address": "0x201678", "endianness": "LittleEndian", "address_size": 8, "architecture": "x86_64"}, "statistics": {"function_count": 25, "string_count": 30, "segment_count": 5, "section_count": 26}}' 151 | # --- 152 | # name: test_get_triage_summary[nonexist.elf] 153 | "Error executing tool get_triage_summary: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 154 | # --- 155 | # name: test_high_level_il[beleaf.elf.bndb-0x000008a1] 156 | ''' 157 | 0x8ac: int32_t argc_1 = argc 158 | 0x8b2: char** argv_1 = argv 159 | 0x8b9: void* fsbase 160 | 0x8b9: int64_t rax = *(fsbase + 0x28) 161 | 0x8d4: printf("Enter the flag\n>>> ") 162 | 0x8ef: void var_98 163 | 0x8ef: __isoc99_scanf("%s", &var_98) 164 | 0x8fe: uint64_t rax_3 = strlen(&var_98) 165 | 0x912: if (rax_3 u<= 0x20) 166 | 0x91b: puts("Incorrect!") 167 | 0x925: exit(1) 168 | 0x925: noreturn 169 | 0x92a: int64_t i = 0 170 | 0x9ab: while (i u< rax_3) 171 | 0x97d: if (sub_7fa(*(i + &var_98)) != *((i << 3) + &data_2014e0)) 172 | 0x986: puts("Incorrect!") 173 | 0x990: exit(1) 174 | 0x990: noreturn 175 | 0x995: i += 1 176 | 0x9b4: puts("Correct!") 177 | 0x9cb: if (rax == *(fsbase + 0x28)) 178 | 0x9d3: return 0 179 | 0x9cd: __stack_chk_fail() 180 | 0x9cd: noreturn 181 | 182 | ''' 183 | # --- 184 | # name: test_high_level_il[beleaf.elf.bndb-0xINVALID] 185 | "Error executing tool high_level_il: No function found with name/address '0xINVALID'" 186 | # --- 187 | # name: test_high_level_il[nonexist.elf-0x000008a1] 188 | "Error executing tool high_level_il: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 189 | # --- 190 | # name: test_high_level_il[nonexist.elf-0xINVALID] 191 | "Error executing tool high_level_il: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 192 | # --- 193 | # name: test_list_filename 194 | 'beleaf.elf.bndb' 195 | # --- 196 | # name: test_list_tools 197 | list([ 198 | Tool(name='list_filename', description='List file names of all opened files', inputSchema={'properties': {}, 'title': 'list_filenameArguments', 'type': 'object'}), 199 | Tool(name='get_triage_summary', description='Get basic information as shown in BinaryNinja Triage view', inputSchema={'properties': {'filename': {'title': 'Filename', 'type': 'string'}}, 'required': ['filename'], 'title': 'get_triage_summaryArguments', 'type': 'object'}), 200 | Tool(name='get_imports', description='Get dictionary of imported symbols', inputSchema={'properties': {'filename': {'title': 'Filename', 'type': 'string'}}, 'required': ['filename'], 'title': 'get_importsArguments', 'type': 'object'}), 201 | Tool(name='get_exports', description='Get dictionary of exported symbols', inputSchema={'properties': {'filename': {'title': 'Filename', 'type': 'string'}}, 'required': ['filename'], 'title': 'get_exportsArguments', 'type': 'object'}), 202 | Tool(name='get_segments', description='Get list of memory segments', inputSchema={'properties': {'filename': {'title': 'Filename', 'type': 'string'}}, 'required': ['filename'], 'title': 'get_segmentsArguments', 'type': 'object'}), 203 | Tool(name='get_sections', description='Get list of binary sections', inputSchema={'properties': {'filename': {'title': 'Filename', 'type': 'string'}}, 'required': ['filename'], 'title': 'get_sectionsArguments', 'type': 'object'}), 204 | Tool(name='get_strings', description='Get list of strings found in the binary', inputSchema={'properties': {'filename': {'title': 'Filename', 'type': 'string'}}, 'required': ['filename'], 'title': 'get_stringsArguments', 'type': 'object'}), 205 | Tool(name='get_functions', description='Get list of functions', inputSchema={'properties': {'filename': {'title': 'Filename', 'type': 'string'}}, 'required': ['filename'], 'title': 'get_functionsArguments', 'type': 'object'}), 206 | Tool(name='get_data_variables', description='Get list of data variables', inputSchema={'properties': {'filename': {'title': 'Filename', 'type': 'string'}}, 'required': ['filename'], 'title': 'get_data_variablesArguments', 'type': 'object'}), 207 | Tool(name='rename_symbol', description='Rename a function or a data variable', inputSchema={'properties': {'filename': {'title': 'Filename', 'type': 'string'}, 'address_or_name': {'title': 'Address Or Name', 'type': 'string'}, 'new_name': {'title': 'New Name', 'type': 'string'}}, 'required': ['filename', 'address_or_name', 'new_name'], 'title': 'rename_symbolArguments', 'type': 'object'}), 208 | Tool(name='pseudo_c', description='Get pseudo C code of a specified function', inputSchema={'properties': {'filename': {'title': 'Filename', 'type': 'string'}, 'address_or_name': {'title': 'Address Or Name', 'type': 'string'}}, 'required': ['filename', 'address_or_name'], 'title': 'pseudo_cArguments', 'type': 'object'}), 209 | Tool(name='pseudo_rust', description='Get pseudo Rust code of a specified function', inputSchema={'properties': {'filename': {'title': 'Filename', 'type': 'string'}, 'address_or_name': {'title': 'Address Or Name', 'type': 'string'}}, 'required': ['filename', 'address_or_name'], 'title': 'pseudo_rustArguments', 'type': 'object'}), 210 | Tool(name='high_level_il', description='Get high level IL of a specified function', inputSchema={'properties': {'filename': {'title': 'Filename', 'type': 'string'}, 'address_or_name': {'title': 'Address Or Name', 'type': 'string'}}, 'required': ['filename', 'address_or_name'], 'title': 'high_level_ilArguments', 'type': 'object'}), 211 | Tool(name='medium_level_il', description='Get medium level IL of a specified function', inputSchema={'properties': {'filename': {'title': 'Filename', 'type': 'string'}, 'address_or_name': {'title': 'Address Or Name', 'type': 'string'}}, 'required': ['filename', 'address_or_name'], 'title': 'medium_level_ilArguments', 'type': 'object'}), 212 | Tool(name='disassembly', description='Get disassembly of a function or specified range', inputSchema={'properties': {'filename': {'title': 'Filename', 'type': 'string'}, 'address_or_name': {'title': 'Address Or Name', 'type': 'string'}, 'length': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'title': 'Length'}}, 'required': ['filename', 'address_or_name'], 'title': 'disassemblyArguments', 'type': 'object'}), 213 | Tool(name='update_analysis_and_wait', description='Update analysis for the binary and wait for it to complete', inputSchema={'properties': {'filename': {'title': 'Filename', 'type': 'string'}}, 'required': ['filename'], 'title': 'update_analysis_and_waitArguments', 'type': 'object'}), 214 | ]) 215 | # --- 216 | # name: test_medium_level_il[beleaf.elf.bndb-0x000008a1] 217 | ''' 218 | 0x8ac: argc_1 = argc 219 | 0x8b2: argv_1 = argv 220 | 0x8b9: rax = [fsbase + 0x28].q 221 | 0x8c2: var_10 = rax 222 | 0x8cf: rax_1 = 0 223 | 0x8d4: 0x6b0("Enter the flag\n>>> ") 224 | 0x8e0: rsi = &var_98 225 | 0x8ea: rax_2 = 0 226 | 0x8ef: 0x6c0("%s", rsi) 227 | 0x8fb: rdi = &var_98 228 | 0x8fe: rax_3 = 0x690(rdi) 229 | 0x903: var_a8 = rax_3 230 | 0x912: if (var_a8 u> 0x20) then 13 @ 0x92a else 15 @ 0x91b 231 | 0x92a: i = 0 232 | 0x935: goto 18 @ 0x99d 233 | 0x91b: 0x680("Incorrect!") 234 | 0x925: 0x6d0(1) 235 | 0x925: noreturn 236 | 0x99d: rax_11 = i 237 | 0x9ab: if (rax_11 u< var_a8) then 20 @ 0x93e else 31 @ 0x9b4 238 | 0x93e: rax_4 = i 239 | 0x945: rax_5 = rax_4 + &var_98 240 | 0x948: rax_6 = [rax_5].b 241 | 0x94b: rax_7 = rax_6 242 | 0x94e: rdi_1 = rax_7 243 | 0x950: rax_8 = 0x7fa(rdi_1) 244 | 0x955: var_a0_1 = rax_8 245 | 0x95c: rax_9 = i 246 | 0x963: rdx = rax_9 << 3 247 | 0x972: rax_10 = [rdx + 0x2014e0].q 248 | 0x97d: if (var_a0_1 == rax_10) then 36 @ 0x995 else 38 @ 0x986 249 | 0x9b4: 0x680("Correct!") 250 | 0x9b9: rax_12 = 0 251 | 0x9be: rcx = var_10 252 | 0x9c2: rcx_1 = rcx ^ [fsbase + 0x28].q 253 | 0x9cb: if (rcx_1 == 0) then 41 @ 0x9d3 else 42 @ 0x9cd 254 | 0x995: i = i + 1 255 | 0x995: goto 18 @ 0x99d 256 | 0x986: 0x680("Incorrect!") 257 | 0x990: 0x6d0(1) 258 | 0x990: noreturn 259 | 0x9d3: return 0 260 | 0x9cd: 0x6a0() 261 | 0x9cd: noreturn 262 | 263 | ''' 264 | # --- 265 | # name: test_medium_level_il[beleaf.elf.bndb-0xINVALID] 266 | "Error executing tool medium_level_il: No function found with name/address '0xINVALID'" 267 | # --- 268 | # name: test_medium_level_il[nonexist.elf-0x000008a1] 269 | "Error executing tool medium_level_il: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 270 | # --- 271 | # name: test_medium_level_il[nonexist.elf-0xINVALID] 272 | "Error executing tool medium_level_il: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 273 | # --- 274 | # name: test_pseudo_c[beleaf.elf.bndb-0x000008a1] 275 | ''' 276 | 277 | int32_t main(int32_t argc, char** argv, char** envp) 278 | 279 | { 280 | int32_t argc_1 = argc; 281 | char** argv_1 = argv; 282 | void* fsbase; 283 | int64_t rax = *(fsbase + 0x28); 284 | printf("Enter the flag\n>>> "); 285 | void var_98; 286 | __isoc99_scanf("%s", &var_98); 287 | uint64_t rax_3 = strlen(&var_98); 288 | 289 | if (rax_3 <= 0x20) 290 | { 291 | puts("Incorrect!"); 292 | exit(1); 293 | /* no return */ 294 | } 295 | 296 | for (int64_t i = 0; i < rax_3; i += 1) 297 | { 298 | if (sub_7fa(*(i + &var_98)) != *((i << 3) + &data_2014e0)) 299 | { 300 | puts("Incorrect!"); 301 | exit(1); 302 | /* no return */ 303 | } 304 | } 305 | 306 | puts("Correct!"); 307 | 308 | if (rax == *(fsbase + 0x28)) 309 | return 0; 310 | 311 | __stack_chk_fail(); 312 | /* no return */ 313 | } 314 | 315 | 316 | ''' 317 | # --- 318 | # name: test_pseudo_c[beleaf.elf.bndb-0xINVALID] 319 | "Error executing tool pseudo_c: No function found with name/address '0xINVALID'" 320 | # --- 321 | # name: test_pseudo_c[nonexist.elf-0x000008a1] 322 | "Error executing tool pseudo_c: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 323 | # --- 324 | # name: test_pseudo_c[nonexist.elf-0xINVALID] 325 | "Error executing tool pseudo_c: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 326 | # --- 327 | # name: test_pseudo_rust[beleaf.elf.bndb-0x000008a1] 328 | ''' 329 | 330 | fn main(argc: i32, argv: *mut *mut i8, envp: *mut *mut i8) -> i32 331 | 332 | { 333 | let argc_1: i32 = argc; 334 | let argv_1: *mut *mut i8 = argv; 335 | let fsbase: *mut c_void; 336 | let rax: i64 = *fsbase.byte_offset(0x28); 337 | printf("Enter the flag\n>>> "); 338 | let mut var_98: (); 339 | __isoc99_scanf("%s", &var_98); 340 | let rax_3: u64 = strlen(&var_98); 341 | 342 | if rax_3 <= 0x20 { 343 | puts("Incorrect!"); 344 | exit(1); 345 | /* no return */ 346 | } 347 | 348 | for i in 0..rax_3 { 349 | if sub_7fa(*(i + &var_98)) != *((i << 3) + &data_2014e0) { 350 | puts("Incorrect!"); 351 | exit(1); 352 | /* no return */ 353 | } 354 | } 355 | 356 | puts("Correct!"); 357 | 358 | if rax == *fsbase.byte_offset(0x28) { 359 | return 0; 360 | } 361 | 362 | __stack_chk_fail(); 363 | /* no return */ 364 | } 365 | 366 | 367 | ''' 368 | # --- 369 | # name: test_pseudo_rust[beleaf.elf.bndb-0xINVALID] 370 | "Error executing tool pseudo_rust: No function found with name/address '0xINVALID'" 371 | # --- 372 | # name: test_pseudo_rust[nonexist.elf-0x000008a1] 373 | "Error executing tool pseudo_rust: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 374 | # --- 375 | # name: test_pseudo_rust[nonexist.elf-0xINVALID] 376 | "Error executing tool pseudo_rust: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 377 | # --- 378 | # name: test_rename_symbol[beleaf.elf.bndb-0x000008a1] 379 | "Successfully renamed function at 0x8a1 from 'main' to 'test_renamed_function'" 380 | # --- 381 | # name: test_rename_symbol[beleaf.elf.bndb-0xINVALID] 382 | "Error executing tool rename_symbol: No function or data variable found with name/address '0xINVALID'" 383 | # --- 384 | # name: test_rename_symbol[nonexist.elf-0x000008a1] 385 | "Error executing tool rename_symbol: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 386 | # --- 387 | # name: test_rename_symbol[nonexist.elf-0xINVALID] 388 | "Error executing tool rename_symbol: 'filename not found: nonexist.elf, currently opened: beleaf.elf.bndb , beleaf.elf'" 389 | # --- 390 | -------------------------------------------------------------------------------- /tests/__snapshots__/test_tools.ambr: -------------------------------------------------------------------------------- 1 | # serializer version: 1 2 | # name: test_disassembly_function 3 | ''' 4 | 5 | int32_t main(int32_t argc, char** argv, char** envp) 6 | 7 | 000008a1 push rbp 8 | 000008a2 mov rbp, rsp 9 | 000008a5 sub rsp, 0xc0 10 | 000008ac mov dword [rbp-0xb4], edi 11 | 000008b2 mov qword [rbp-0xc0], rsi 12 | 000008b9 mov rax, qword [fs:0x28] 13 | 000008c2 mov qword [rbp-0x8], rax 14 | 000008c6 xor eax, eax 15 | 000008c8 lea rdi, [rel data_a64] {"Enter the flag\n>>> "} 16 | 000008cf mov eax, 0x0 17 | 000008d4 call printf 18 | 000008d9 lea rax, [rbp-0x90] 19 | 000008e0 mov rsi, rax 20 | 000008e3 lea rdi, [rel data_a78] 21 | 000008ea mov eax, 0x0 22 | 000008ef call __isoc99_scanf 23 | 000008f4 lea rax, [rbp-0x90] 24 | 000008fb mov rdi, rax 25 | 000008fe call strlen 26 | 00000903 mov qword [rbp-0xa0], rax 27 | 0000090a cmp qword [rbp-0xa0], 0x20 28 | 00000912 ja 0x92a 29 | 30 | 00000914 lea rdi, [rel data_a7b] {"Incorrect!"} 31 | 0000091b call puts 32 | 00000920 mov edi, 0x1 33 | 00000925 call exit 34 | { Does not return } 35 | 36 | 0000092a mov qword [rbp-0xa8], 0x0 37 | 00000935 jmp 0x99d 38 | 39 | 00000937 lea rdx, [rbp-0x90] 40 | 0000093e mov rax, qword [rbp-0xa8] 41 | 00000945 add rax, rdx 42 | 00000948 movzx eax, byte [rax] 43 | 0000094b movsx eax, al 44 | 0000094e mov edi, eax 45 | 00000950 call sub_7fa 46 | 00000955 mov qword [rbp-0x98], rax 47 | 0000095c mov rax, qword [rbp-0xa8] 48 | 00000963 lea rdx, [rax*8] 49 | 0000096b lea rax, [rel data_2014e0] 50 | 00000972 mov rax, qword [rdx+rax] 51 | 00000976 cmp qword [rbp-0x98], rax 52 | 0000097d je 0x995 53 | 54 | 0000097f lea rdi, [rel data_a7b] {"Incorrect!"} 55 | 00000986 call puts 56 | 0000098b mov edi, 0x1 57 | 00000990 call exit 58 | { Does not return } 59 | 60 | 00000995 add qword [rbp-0xa8], 0x1 61 | 62 | 0000099d mov rax, qword [rbp-0xa8] 63 | 000009a4 cmp rax, qword [rbp-0xa0] 64 | 000009ab jb 0x937 65 | 66 | 000009ad lea rdi, [rel data_a86] {"Correct!"} 67 | 000009b4 call puts 68 | 000009b9 mov eax, 0x0 69 | 000009be mov rcx, qword [rbp-0x8] 70 | 000009c2 xor rcx, qword [fs:0x28] 71 | 000009cb je 0x9d2 72 | 73 | 000009cd call __stack_chk_fail 74 | { Does not return } 75 | 76 | 000009d2 leave 77 | 000009d3 retn 78 | 79 | ''' 80 | # --- 81 | # name: test_disassembly_range 82 | ''' 83 | 0x8a1: push rbp 84 | 0x8a2: mov rbp, rsp 85 | 0x8a5: sub rsp, 0xc0 86 | 0x8ac: mov dword [rbp-0xb4], edi 87 | ''' 88 | # --- 89 | # name: test_get_data_variables 90 | list([ 91 | dict({ 92 | 'address': '0x0', 93 | 'auto_discovered': True, 94 | 'symbol': dict({ 95 | 'name': '__elf_header', 96 | 'short_name': '__elf_header', 97 | 'type': 'SymbolType.DataSymbol', 98 | }), 99 | 'type': 'struct Elf64_Header', 100 | }), 101 | dict({ 102 | 'address': '0x40', 103 | 'auto_discovered': True, 104 | 'symbol': dict({ 105 | 'name': '__elf_program_headers', 106 | 'short_name': '__elf_program_headers', 107 | 'type': 'SymbolType.DataSymbol', 108 | }), 109 | 'type': 'struct Elf64_ProgramHeader[0x9]', 110 | }), 111 | dict({ 112 | 'address': '0x238', 113 | 'auto_discovered': True, 114 | 'symbol': dict({ 115 | 'name': '__elf_interp', 116 | 'short_name': '__elf_interp', 117 | 'type': 'SymbolType.DataSymbol', 118 | }), 119 | 'type': 'char[0x1c]', 120 | }), 121 | dict({ 122 | 'address': '0x2b8', 123 | 'auto_discovered': True, 124 | 'symbol': dict({ 125 | 'name': '__elf_symbol_table', 126 | 'short_name': '__elf_symbol_table', 127 | 'type': 'SymbolType.DataSymbol', 128 | }), 129 | 'type': 'struct Elf64_Sym[0xc]', 130 | }), 131 | dict({ 132 | 'address': '0x3d9', 133 | 'auto_discovered': True, 134 | 'symbol': None, 135 | 'type': 'char[0xa]', 136 | }), 137 | dict({ 138 | 'address': '0x3e3', 139 | 'auto_discovered': True, 140 | 'symbol': None, 141 | 'type': 'char[0x5]', 142 | }), 143 | dict({ 144 | 'address': '0x3e8', 145 | 'auto_discovered': True, 146 | 'symbol': None, 147 | 'type': 'char[0xf]', 148 | }), 149 | dict({ 150 | 'address': '0x3f7', 151 | 'auto_discovered': True, 152 | 'symbol': None, 153 | 'type': 'char[0x5]', 154 | }), 155 | dict({ 156 | 'address': '0x3fc', 157 | 'auto_discovered': True, 158 | 'symbol': None, 159 | 'type': 'char[0x11]', 160 | }), 161 | dict({ 162 | 'address': '0x40d', 163 | 'auto_discovered': True, 164 | 'symbol': None, 165 | 'type': 'char[0x7]', 166 | }), 167 | dict({ 168 | 'address': '0x414', 169 | 'auto_discovered': True, 170 | 'symbol': None, 171 | 'type': 'char[0x7]', 172 | }), 173 | dict({ 174 | 'address': '0x41b', 175 | 'auto_discovered': True, 176 | 'symbol': None, 177 | 'type': 'char[0xf]', 178 | }), 179 | dict({ 180 | 'address': '0x42a', 181 | 'auto_discovered': True, 182 | 'symbol': None, 183 | 'type': 'char[0x12]', 184 | }), 185 | dict({ 186 | 'address': '0x43c', 187 | 'auto_discovered': True, 188 | 'symbol': None, 189 | 'type': 'char[0xa]', 190 | }), 191 | dict({ 192 | 'address': '0x446', 193 | 'auto_discovered': True, 194 | 'symbol': None, 195 | 'type': 'char[0xa]', 196 | }), 197 | dict({ 198 | 'address': '0x450', 199 | 'auto_discovered': True, 200 | 'symbol': None, 201 | 'type': 'char[0xc]', 202 | }), 203 | dict({ 204 | 'address': '0x45c', 205 | 'auto_discovered': True, 206 | 'symbol': None, 207 | 'type': 'char[0x1c]', 208 | }), 209 | dict({ 210 | 'address': '0x478', 211 | 'auto_discovered': True, 212 | 'symbol': None, 213 | 'type': 'char[0xf]', 214 | }), 215 | dict({ 216 | 'address': '0x487', 217 | 'auto_discovered': True, 218 | 'symbol': None, 219 | 'type': 'char[0x1a]', 220 | }), 221 | dict({ 222 | 'address': '0x500', 223 | 'auto_discovered': True, 224 | 'symbol': dict({ 225 | 'name': '__elf_rela_table', 226 | 'short_name': '__elf_rela_table', 227 | 'type': 'SymbolType.DataSymbol', 228 | }), 229 | 'type': 'struct Elf64_Rela[0x8]', 230 | }), 231 | dict({ 232 | 'address': '0x650', 233 | 'auto_discovered': True, 234 | 'symbol': dict({ 235 | 'name': '_init', 236 | 'short_name': '_init', 237 | 'type': 'SymbolType.FunctionSymbol', 238 | }), 239 | 'type': None, 240 | }), 241 | dict({ 242 | 'address': '0x670', 243 | 'auto_discovered': True, 244 | 'symbol': None, 245 | 'type': None, 246 | }), 247 | dict({ 248 | 'address': '0x680', 249 | 'auto_discovered': True, 250 | 'symbol': dict({ 251 | 'name': 'puts', 252 | 'short_name': 'puts', 253 | 'type': 'SymbolType.ImportedFunctionSymbol', 254 | }), 255 | 'type': None, 256 | }), 257 | dict({ 258 | 'address': '0x690', 259 | 'auto_discovered': True, 260 | 'symbol': dict({ 261 | 'name': 'strlen', 262 | 'short_name': 'strlen', 263 | 'type': 'SymbolType.ImportedFunctionSymbol', 264 | }), 265 | 'type': None, 266 | }), 267 | dict({ 268 | 'address': '0x6a0', 269 | 'auto_discovered': True, 270 | 'symbol': dict({ 271 | 'name': '__stack_chk_fail', 272 | 'short_name': '__stack_chk_fail', 273 | 'type': 'SymbolType.ImportedFunctionSymbol', 274 | }), 275 | 'type': None, 276 | }), 277 | dict({ 278 | 'address': '0x6b0', 279 | 'auto_discovered': True, 280 | 'symbol': dict({ 281 | 'name': 'printf', 282 | 'short_name': 'printf', 283 | 'type': 'SymbolType.ImportedFunctionSymbol', 284 | }), 285 | 'type': None, 286 | }), 287 | dict({ 288 | 'address': '0x6c0', 289 | 'auto_discovered': True, 290 | 'symbol': dict({ 291 | 'name': '__isoc99_scanf', 292 | 'short_name': '__isoc99_scanf', 293 | 'type': 'SymbolType.ImportedFunctionSymbol', 294 | }), 295 | 'type': None, 296 | }), 297 | dict({ 298 | 'address': '0x6d0', 299 | 'auto_discovered': True, 300 | 'symbol': dict({ 301 | 'name': 'exit', 302 | 'short_name': 'exit', 303 | 'type': 'SymbolType.ImportedFunctionSymbol', 304 | }), 305 | 'type': None, 306 | }), 307 | dict({ 308 | 'address': '0x6e0', 309 | 'auto_discovered': True, 310 | 'symbol': dict({ 311 | 'name': '__cxa_finalize', 312 | 'short_name': '__cxa_finalize', 313 | 'type': 'SymbolType.ImportedFunctionSymbol', 314 | }), 315 | 'type': None, 316 | }), 317 | dict({ 318 | 'address': '0x720', 319 | 'auto_discovered': True, 320 | 'symbol': dict({ 321 | 'name': 'deregister_tm_clones', 322 | 'short_name': 'deregister_tm_clones', 323 | 'type': 'SymbolType.LibraryFunctionSymbol', 324 | }), 325 | 'type': None, 326 | }), 327 | dict({ 328 | 'address': '0x760', 329 | 'auto_discovered': True, 330 | 'symbol': dict({ 331 | 'name': 'register_tm_clones', 332 | 'short_name': 'register_tm_clones', 333 | 'type': 'SymbolType.LibraryFunctionSymbol', 334 | }), 335 | 'type': None, 336 | }), 337 | dict({ 338 | 'address': '0x7fa', 339 | 'auto_discovered': True, 340 | 'symbol': None, 341 | 'type': None, 342 | }), 343 | dict({ 344 | 'address': '0x8a1', 345 | 'auto_discovered': True, 346 | 'symbol': dict({ 347 | 'name': 'main', 348 | 'short_name': 'main', 349 | 'type': 'SymbolType.FunctionSymbol', 350 | }), 351 | 'type': None, 352 | }), 353 | dict({ 354 | 'address': '0x9e0', 355 | 'auto_discovered': True, 356 | 'symbol': dict({ 357 | 'name': 'init', 358 | 'short_name': 'init', 359 | 'type': 'SymbolType.FunctionSymbol', 360 | }), 361 | 'type': None, 362 | }), 363 | dict({ 364 | 'address': '0xa50', 365 | 'auto_discovered': True, 366 | 'symbol': dict({ 367 | 'name': 'fini', 368 | 'short_name': 'fini', 369 | 'type': 'SymbolType.FunctionSymbol', 370 | }), 371 | 'type': None, 372 | }), 373 | dict({ 374 | 'address': '0xa64', 375 | 'auto_discovered': True, 376 | 'symbol': None, 377 | 'type': 'char const[0x14]', 378 | }), 379 | dict({ 380 | 'address': '0xa78', 381 | 'auto_discovered': True, 382 | 'symbol': None, 383 | 'type': None, 384 | }), 385 | dict({ 386 | 'address': '0xa7b', 387 | 'auto_discovered': True, 388 | 'symbol': None, 389 | 'type': 'char const[0xb]', 390 | }), 391 | dict({ 392 | 'address': '0xa86', 393 | 'auto_discovered': True, 394 | 'symbol': None, 395 | 'type': 'char const[0x9]', 396 | }), 397 | dict({ 398 | 'address': '0x200d90', 399 | 'auto_discovered': True, 400 | 'symbol': dict({ 401 | 'name': 'init_array', 402 | 'short_name': 'init_array', 403 | 'type': 'SymbolType.DataSymbol', 404 | }), 405 | 'type': 'void (*[0x1])()', 406 | }), 407 | dict({ 408 | 'address': '0x200d98', 409 | 'auto_discovered': True, 410 | 'symbol': dict({ 411 | 'name': 'fini_array', 412 | 'short_name': 'fini_array', 413 | 'type': 'SymbolType.DataSymbol', 414 | }), 415 | 'type': 'void (*[0x1])()', 416 | }), 417 | dict({ 418 | 'address': '0x200da0', 419 | 'auto_discovered': True, 420 | 'symbol': dict({ 421 | 'name': '__elf_dynamic_table', 422 | 'short_name': '__elf_dynamic_table', 423 | 'type': 'SymbolType.DataSymbol', 424 | }), 425 | 'type': 'struct Elf64_Dyn[0x1b]', 426 | }), 427 | dict({ 428 | 'address': '0x200f98', 429 | 'auto_discovered': True, 430 | 'symbol': None, 431 | 'type': 'int64_t', 432 | }), 433 | dict({ 434 | 'address': '0x200fa0', 435 | 'auto_discovered': True, 436 | 'symbol': None, 437 | 'type': 'int64_t', 438 | }), 439 | dict({ 440 | 'address': '0x200fa8', 441 | 'auto_discovered': True, 442 | 'symbol': dict({ 443 | 'name': 'puts', 444 | 'short_name': 'puts', 445 | 'type': 'SymbolType.ImportAddressSymbol', 446 | }), 447 | 'type': 'int32_t (* const)(char const* str)', 448 | }), 449 | dict({ 450 | 'address': '0x200fb0', 451 | 'auto_discovered': True, 452 | 'symbol': dict({ 453 | 'name': 'strlen', 454 | 'short_name': 'strlen', 455 | 'type': 'SymbolType.ImportAddressSymbol', 456 | }), 457 | 'type': 'uint64_t (* const)(char const*)', 458 | }), 459 | dict({ 460 | 'address': '0x200fb8', 461 | 'auto_discovered': True, 462 | 'symbol': dict({ 463 | 'name': '__stack_chk_fail', 464 | 'short_name': '__stack_chk_fail', 465 | 'type': 'SymbolType.ImportAddressSymbol', 466 | }), 467 | 'type': 'void (* const)() __noreturn', 468 | }), 469 | dict({ 470 | 'address': '0x200fc0', 471 | 'auto_discovered': True, 472 | 'symbol': dict({ 473 | 'name': 'printf', 474 | 'short_name': 'printf', 475 | 'type': 'SymbolType.ImportAddressSymbol', 476 | }), 477 | 'type': 'int32_t (* const)(char const* format, ...)', 478 | }), 479 | dict({ 480 | 'address': '0x200fc8', 481 | 'auto_discovered': True, 482 | 'symbol': dict({ 483 | 'name': '__isoc99_scanf', 484 | 'short_name': '__isoc99_scanf', 485 | 'type': 'SymbolType.ImportAddressSymbol', 486 | }), 487 | 'type': 'int32_t (* const)(char const* format, ...)', 488 | }), 489 | dict({ 490 | 'address': '0x200fd0', 491 | 'auto_discovered': True, 492 | 'symbol': dict({ 493 | 'name': 'exit', 494 | 'short_name': 'exit', 495 | 'type': 'SymbolType.ImportAddressSymbol', 496 | }), 497 | 'type': 'void (* const)(int32_t status) __noreturn', 498 | }), 499 | dict({ 500 | 'address': '0x200fd8', 501 | 'auto_discovered': True, 502 | 'symbol': dict({ 503 | 'name': '_ITM_deregisterTMCloneTable', 504 | 'short_name': '_ITM_deregisterTMCloneTable', 505 | 'type': 'SymbolType.ImportAddressSymbol', 506 | }), 507 | 'type': 'int64_t (* const)()', 508 | }), 509 | dict({ 510 | 'address': '0x200fe0', 511 | 'auto_discovered': True, 512 | 'symbol': dict({ 513 | 'name': '__libc_start_main', 514 | 'short_name': '__libc_start_main', 515 | 'type': 'SymbolType.ImportAddressSymbol', 516 | }), 517 | 'type': 'void (* const)(int32_t (* main)(int32_t argc, char** argv, char** envp), int32_t argc, char** ubp_av, void (* init)(), void (* fini)(), void (* rtld_fini)(), void* stack_end) __noreturn', 518 | }), 519 | dict({ 520 | 'address': '0x200fe8', 521 | 'auto_discovered': True, 522 | 'symbol': dict({ 523 | 'name': '__gmon_start__', 524 | 'short_name': '__gmon_start__', 525 | 'type': 'SymbolType.ImportAddressSymbol', 526 | }), 527 | 'type': 'int64_t (* const)()', 528 | }), 529 | dict({ 530 | 'address': '0x200ff0', 531 | 'auto_discovered': True, 532 | 'symbol': dict({ 533 | 'name': '_ITM_registerTMCloneTable', 534 | 'short_name': '_ITM_registerTMCloneTable', 535 | 'type': 'SymbolType.ImportAddressSymbol', 536 | }), 537 | 'type': 'int64_t (* const)()', 538 | }), 539 | dict({ 540 | 'address': '0x200ff8', 541 | 'auto_discovered': True, 542 | 'symbol': dict({ 543 | 'name': '__cxa_finalize', 544 | 'short_name': '__cxa_finalize', 545 | 'type': 'SymbolType.ImportAddressSymbol', 546 | }), 547 | 'type': 'void (* const)(void* d)', 548 | }), 549 | dict({ 550 | 'address': '0x201008', 551 | 'auto_discovered': True, 552 | 'symbol': None, 553 | 'type': 'void*', 554 | }), 555 | dict({ 556 | 'address': '0x201020', 557 | 'auto_discovered': True, 558 | 'symbol': None, 559 | 'type': 'wchar32[0x7]', 560 | }), 561 | dict({ 562 | 'address': '0x2014e0', 563 | 'auto_discovered': True, 564 | 'symbol': None, 565 | 'type': None, 566 | }), 567 | dict({ 568 | 'address': '0x2015e8', 569 | 'auto_discovered': True, 570 | 'symbol': None, 571 | 'type': 'char', 572 | }), 573 | dict({ 574 | 'address': '0x2015f0', 575 | 'auto_discovered': True, 576 | 'symbol': dict({ 577 | 'name': '_ITM_deregisterTMCloneTable', 578 | 'short_name': '_ITM_deregisterTMCloneTable', 579 | 'type': 'SymbolType.ExternalSymbol', 580 | }), 581 | 'type': None, 582 | }), 583 | dict({ 584 | 'address': '0x2015f8', 585 | 'auto_discovered': True, 586 | 'symbol': dict({ 587 | 'name': '_ITM_registerTMCloneTable', 588 | 'short_name': '_ITM_registerTMCloneTable', 589 | 'type': 'SymbolType.ExternalSymbol', 590 | }), 591 | 'type': None, 592 | }), 593 | dict({ 594 | 'address': '0x201600', 595 | 'auto_discovered': True, 596 | 'symbol': dict({ 597 | 'name': '__cxa_finalize', 598 | 'short_name': '__cxa_finalize', 599 | 'type': 'SymbolType.ExternalSymbol', 600 | }), 601 | 'type': None, 602 | }), 603 | dict({ 604 | 'address': '0x201608', 605 | 'auto_discovered': True, 606 | 'symbol': dict({ 607 | 'name': '__gmon_start__', 608 | 'short_name': '__gmon_start__', 609 | 'type': 'SymbolType.ExternalSymbol', 610 | }), 611 | 'type': None, 612 | }), 613 | dict({ 614 | 'address': '0x201610', 615 | 'auto_discovered': True, 616 | 'symbol': dict({ 617 | 'name': '__isoc99_scanf', 618 | 'short_name': '__isoc99_scanf', 619 | 'type': 'SymbolType.ExternalSymbol', 620 | }), 621 | 'type': None, 622 | }), 623 | dict({ 624 | 'address': '0x201618', 625 | 'auto_discovered': True, 626 | 'symbol': dict({ 627 | 'name': '__libc_start_main', 628 | 'short_name': '__libc_start_main', 629 | 'type': 'SymbolType.ExternalSymbol', 630 | }), 631 | 'type': None, 632 | }), 633 | dict({ 634 | 'address': '0x201620', 635 | 'auto_discovered': True, 636 | 'symbol': dict({ 637 | 'name': '__stack_chk_fail', 638 | 'short_name': '__stack_chk_fail', 639 | 'type': 'SymbolType.ExternalSymbol', 640 | }), 641 | 'type': None, 642 | }), 643 | dict({ 644 | 'address': '0x201628', 645 | 'auto_discovered': True, 646 | 'symbol': dict({ 647 | 'name': 'exit', 648 | 'short_name': 'exit', 649 | 'type': 'SymbolType.ExternalSymbol', 650 | }), 651 | 'type': None, 652 | }), 653 | dict({ 654 | 'address': '0x201630', 655 | 'auto_discovered': True, 656 | 'symbol': dict({ 657 | 'name': 'printf', 658 | 'short_name': 'printf', 659 | 'type': 'SymbolType.ExternalSymbol', 660 | }), 661 | 'type': None, 662 | }), 663 | dict({ 664 | 'address': '0x201638', 665 | 'auto_discovered': True, 666 | 'symbol': dict({ 667 | 'name': 'puts', 668 | 'short_name': 'puts', 669 | 'type': 'SymbolType.ExternalSymbol', 670 | }), 671 | 'type': None, 672 | }), 673 | dict({ 674 | 'address': '0x201640', 675 | 'auto_discovered': True, 676 | 'symbol': dict({ 677 | 'name': 'strlen', 678 | 'short_name': 'strlen', 679 | 'type': 'SymbolType.ExternalSymbol', 680 | }), 681 | 'type': None, 682 | }), 683 | dict({ 684 | 'address': '0x201650', 685 | 'auto_discovered': True, 686 | 'symbol': dict({ 687 | 'name': '__builtin_memcpy', 688 | 'short_name': '__builtin_memcpy', 689 | 'type': 'SymbolType.SymbolicFunctionSymbol', 690 | }), 691 | 'type': None, 692 | }), 693 | dict({ 694 | 'address': '0x201658', 695 | 'auto_discovered': True, 696 | 'symbol': dict({ 697 | 'name': '__builtin_memset', 698 | 'short_name': '__builtin_memset', 699 | 'type': 'SymbolType.SymbolicFunctionSymbol', 700 | }), 701 | 'type': None, 702 | }), 703 | dict({ 704 | 'address': '0x201660', 705 | 'auto_discovered': True, 706 | 'symbol': dict({ 707 | 'name': '__builtin_strcpy', 708 | 'short_name': '__builtin_strcpy', 709 | 'type': 'SymbolType.SymbolicFunctionSymbol', 710 | }), 711 | 'type': None, 712 | }), 713 | dict({ 714 | 'address': '0x201668', 715 | 'auto_discovered': True, 716 | 'symbol': dict({ 717 | 'name': '__builtin_strncpy', 718 | 'short_name': '__builtin_strncpy', 719 | 'type': 'SymbolType.SymbolicFunctionSymbol', 720 | }), 721 | 'type': None, 722 | }), 723 | dict({ 724 | 'address': '0x201670', 725 | 'auto_discovered': True, 726 | 'symbol': dict({ 727 | 'name': '__builtin_wcscpy', 728 | 'short_name': '__builtin_wcscpy', 729 | 'type': 'SymbolType.SymbolicFunctionSymbol', 730 | }), 731 | 'type': None, 732 | }), 733 | ]) 734 | # --- 735 | # name: test_get_exports 736 | list([ 737 | dict({ 738 | 'address': '0x6f0', 739 | 'name': '_start', 740 | 'ordinal': 0, 741 | 'type': 'SymbolType.FunctionSymbol', 742 | }), 743 | dict({ 744 | 'address': '0x7b0', 745 | 'name': '_FINI_0', 746 | 'ordinal': 0, 747 | 'type': 'SymbolType.FunctionSymbol', 748 | }), 749 | dict({ 750 | 'address': '0x7f0', 751 | 'name': '_INIT_0', 752 | 'ordinal': 0, 753 | 'type': 'SymbolType.FunctionSymbol', 754 | }), 755 | ]) 756 | # --- 757 | # name: test_get_functions 758 | list([ 759 | dict({ 760 | 'basic_block_count': 3, 761 | 'has_prototype': False, 762 | 'is_imported': False, 763 | 'is_thunk': False, 764 | 'name': '_init', 765 | 'parameter_count': 0, 766 | 'return_type': None, 767 | 'start': '0x650', 768 | 'symbol': dict({ 769 | 'name': '_init', 770 | 'short_name': '_init', 771 | 'type': 'SymbolType.FunctionSymbol', 772 | }), 773 | }), 774 | dict({ 775 | 'basic_block_count': 1, 776 | 'has_prototype': False, 777 | 'is_imported': False, 778 | 'is_thunk': False, 779 | 'name': 'sub_670', 780 | 'parameter_count': 0, 781 | 'return_type': 'int64_t', 782 | 'start': '0x670', 783 | 'symbol': dict({ 784 | 'name': 'sub_670', 785 | 'short_name': 'sub_670', 786 | 'type': 'SymbolType.FunctionSymbol', 787 | }), 788 | }), 789 | dict({ 790 | 'basic_block_count': 1, 791 | 'has_prototype': False, 792 | 'is_imported': True, 793 | 'is_thunk': False, 794 | 'name': 'puts', 795 | 'parameter_count': 1, 796 | 'return_type': 'int32_t', 797 | 'start': '0x680', 798 | 'symbol': dict({ 799 | 'name': 'puts', 800 | 'short_name': 'puts', 801 | 'type': 'SymbolType.ImportedFunctionSymbol', 802 | }), 803 | }), 804 | dict({ 805 | 'basic_block_count': 1, 806 | 'has_prototype': False, 807 | 'is_imported': False, 808 | 'is_thunk': False, 809 | 'name': 'sub_686', 810 | 'parameter_count': 0, 811 | 'return_type': 'int64_t', 812 | 'start': '0x686', 813 | 'symbol': dict({ 814 | 'name': 'sub_686', 815 | 'short_name': 'sub_686', 816 | 'type': 'SymbolType.FunctionSymbol', 817 | }), 818 | }), 819 | dict({ 820 | 'basic_block_count': 1, 821 | 'has_prototype': False, 822 | 'is_imported': True, 823 | 'is_thunk': False, 824 | 'name': 'strlen', 825 | 'parameter_count': 1, 826 | 'return_type': 'uint64_t', 827 | 'start': '0x690', 828 | 'symbol': dict({ 829 | 'name': 'strlen', 830 | 'short_name': 'strlen', 831 | 'type': 'SymbolType.ImportedFunctionSymbol', 832 | }), 833 | }), 834 | dict({ 835 | 'basic_block_count': 1, 836 | 'has_prototype': False, 837 | 'is_imported': False, 838 | 'is_thunk': False, 839 | 'name': 'sub_696', 840 | 'parameter_count': 0, 841 | 'return_type': 'int64_t', 842 | 'start': '0x696', 843 | 'symbol': dict({ 844 | 'name': 'sub_696', 845 | 'short_name': 'sub_696', 846 | 'type': 'SymbolType.FunctionSymbol', 847 | }), 848 | }), 849 | dict({ 850 | 'basic_block_count': 1, 851 | 'has_prototype': False, 852 | 'is_imported': True, 853 | 'is_thunk': False, 854 | 'name': '__stack_chk_fail', 855 | 'parameter_count': 0, 856 | 'return_type': None, 857 | 'start': '0x6a0', 858 | 'symbol': dict({ 859 | 'name': '__stack_chk_fail', 860 | 'short_name': '__stack_chk_fail', 861 | 'type': 'SymbolType.ImportedFunctionSymbol', 862 | }), 863 | }), 864 | dict({ 865 | 'basic_block_count': 1, 866 | 'has_prototype': False, 867 | 'is_imported': False, 868 | 'is_thunk': False, 869 | 'name': 'sub_6a6', 870 | 'parameter_count': 0, 871 | 'return_type': 'int64_t', 872 | 'start': '0x6a6', 873 | 'symbol': dict({ 874 | 'name': 'sub_6a6', 875 | 'short_name': 'sub_6a6', 876 | 'type': 'SymbolType.FunctionSymbol', 877 | }), 878 | }), 879 | dict({ 880 | 'basic_block_count': 1, 881 | 'has_prototype': False, 882 | 'is_imported': True, 883 | 'is_thunk': False, 884 | 'name': 'printf', 885 | 'parameter_count': 1, 886 | 'return_type': 'int32_t', 887 | 'start': '0x6b0', 888 | 'symbol': dict({ 889 | 'name': 'printf', 890 | 'short_name': 'printf', 891 | 'type': 'SymbolType.ImportedFunctionSymbol', 892 | }), 893 | }), 894 | dict({ 895 | 'basic_block_count': 1, 896 | 'has_prototype': False, 897 | 'is_imported': False, 898 | 'is_thunk': False, 899 | 'name': 'sub_6b6', 900 | 'parameter_count': 0, 901 | 'return_type': 'int64_t', 902 | 'start': '0x6b6', 903 | 'symbol': dict({ 904 | 'name': 'sub_6b6', 905 | 'short_name': 'sub_6b6', 906 | 'type': 'SymbolType.FunctionSymbol', 907 | }), 908 | }), 909 | dict({ 910 | 'basic_block_count': 1, 911 | 'has_prototype': False, 912 | 'is_imported': True, 913 | 'is_thunk': False, 914 | 'name': '__isoc99_scanf', 915 | 'parameter_count': 1, 916 | 'return_type': 'int32_t', 917 | 'start': '0x6c0', 918 | 'symbol': dict({ 919 | 'name': '__isoc99_scanf', 920 | 'short_name': '__isoc99_scanf', 921 | 'type': 'SymbolType.ImportedFunctionSymbol', 922 | }), 923 | }), 924 | dict({ 925 | 'basic_block_count': 1, 926 | 'has_prototype': False, 927 | 'is_imported': False, 928 | 'is_thunk': False, 929 | 'name': 'sub_6c6', 930 | 'parameter_count': 0, 931 | 'return_type': 'int64_t', 932 | 'start': '0x6c6', 933 | 'symbol': dict({ 934 | 'name': 'sub_6c6', 935 | 'short_name': 'sub_6c6', 936 | 'type': 'SymbolType.FunctionSymbol', 937 | }), 938 | }), 939 | dict({ 940 | 'basic_block_count': 1, 941 | 'has_prototype': False, 942 | 'is_imported': True, 943 | 'is_thunk': False, 944 | 'name': 'exit', 945 | 'parameter_count': 1, 946 | 'return_type': None, 947 | 'start': '0x6d0', 948 | 'symbol': dict({ 949 | 'name': 'exit', 950 | 'short_name': 'exit', 951 | 'type': 'SymbolType.ImportedFunctionSymbol', 952 | }), 953 | }), 954 | dict({ 955 | 'basic_block_count': 1, 956 | 'has_prototype': False, 957 | 'is_imported': False, 958 | 'is_thunk': False, 959 | 'name': 'sub_6d6', 960 | 'parameter_count': 0, 961 | 'return_type': 'int64_t', 962 | 'start': '0x6d6', 963 | 'symbol': dict({ 964 | 'name': 'sub_6d6', 965 | 'short_name': 'sub_6d6', 966 | 'type': 'SymbolType.FunctionSymbol', 967 | }), 968 | }), 969 | dict({ 970 | 'basic_block_count': 1, 971 | 'has_prototype': False, 972 | 'is_imported': True, 973 | 'is_thunk': False, 974 | 'name': '__cxa_finalize', 975 | 'parameter_count': 1, 976 | 'return_type': None, 977 | 'start': '0x6e0', 978 | 'symbol': dict({ 979 | 'name': '__cxa_finalize', 980 | 'short_name': '__cxa_finalize', 981 | 'type': 'SymbolType.ImportedFunctionSymbol', 982 | }), 983 | }), 984 | dict({ 985 | 'basic_block_count': 1, 986 | 'has_prototype': False, 987 | 'is_imported': False, 988 | 'is_thunk': False, 989 | 'name': '_start', 990 | 'parameter_count': 3, 991 | 'return_type': None, 992 | 'start': '0x6f0', 993 | 'symbol': dict({ 994 | 'name': '_start', 995 | 'short_name': '_start', 996 | 'type': 'SymbolType.FunctionSymbol', 997 | }), 998 | }), 999 | dict({ 1000 | 'basic_block_count': 4, 1001 | 'has_prototype': False, 1002 | 'is_imported': False, 1003 | 'is_thunk': False, 1004 | 'name': 'deregister_tm_clones', 1005 | 'parameter_count': 0, 1006 | 'return_type': None, 1007 | 'start': '0x720', 1008 | 'symbol': dict({ 1009 | 'name': 'deregister_tm_clones', 1010 | 'short_name': 'deregister_tm_clones', 1011 | 'type': 'SymbolType.LibraryFunctionSymbol', 1012 | }), 1013 | }), 1014 | dict({ 1015 | 'basic_block_count': 4, 1016 | 'has_prototype': False, 1017 | 'is_imported': False, 1018 | 'is_thunk': False, 1019 | 'name': 'register_tm_clones', 1020 | 'parameter_count': 0, 1021 | 'return_type': None, 1022 | 'start': '0x760', 1023 | 'symbol': dict({ 1024 | 'name': 'register_tm_clones', 1025 | 'short_name': 'register_tm_clones', 1026 | 'type': 'SymbolType.LibraryFunctionSymbol', 1027 | }), 1028 | }), 1029 | dict({ 1030 | 'basic_block_count': 5, 1031 | 'has_prototype': False, 1032 | 'is_imported': False, 1033 | 'is_thunk': False, 1034 | 'name': '_FINI_0', 1035 | 'parameter_count': 0, 1036 | 'return_type': None, 1037 | 'start': '0x7b0', 1038 | 'symbol': dict({ 1039 | 'name': '_FINI_0', 1040 | 'short_name': '_FINI_0', 1041 | 'type': 'SymbolType.FunctionSymbol', 1042 | }), 1043 | }), 1044 | dict({ 1045 | 'basic_block_count': 1, 1046 | 'has_prototype': False, 1047 | 'is_imported': False, 1048 | 'is_thunk': False, 1049 | 'name': '_INIT_0', 1050 | 'parameter_count': 0, 1051 | 'return_type': None, 1052 | 'start': '0x7f0', 1053 | 'symbol': dict({ 1054 | 'name': '_INIT_0', 1055 | 'short_name': '_INIT_0', 1056 | 'type': 'SymbolType.FunctionSymbol', 1057 | }), 1058 | }), 1059 | dict({ 1060 | 'basic_block_count': 10, 1061 | 'has_prototype': False, 1062 | 'is_imported': False, 1063 | 'is_thunk': False, 1064 | 'name': 'sub_7fa', 1065 | 'parameter_count': 1, 1066 | 'return_type': 'int64_t', 1067 | 'start': '0x7fa', 1068 | 'symbol': dict({ 1069 | 'name': 'sub_7fa', 1070 | 'short_name': 'sub_7fa', 1071 | 'type': 'SymbolType.FunctionSymbol', 1072 | }), 1073 | }), 1074 | dict({ 1075 | 'basic_block_count': 10, 1076 | 'has_prototype': False, 1077 | 'is_imported': False, 1078 | 'is_thunk': False, 1079 | 'name': 'main', 1080 | 'parameter_count': 3, 1081 | 'return_type': 'int32_t', 1082 | 'start': '0x8a1', 1083 | 'symbol': dict({ 1084 | 'name': 'main', 1085 | 'short_name': 'main', 1086 | 'type': 'SymbolType.FunctionSymbol', 1087 | }), 1088 | }), 1089 | dict({ 1090 | 'basic_block_count': 4, 1091 | 'has_prototype': False, 1092 | 'is_imported': False, 1093 | 'is_thunk': False, 1094 | 'name': 'init', 1095 | 'parameter_count': 0, 1096 | 'return_type': None, 1097 | 'start': '0x9e0', 1098 | 'symbol': dict({ 1099 | 'name': 'init', 1100 | 'short_name': 'init', 1101 | 'type': 'SymbolType.FunctionSymbol', 1102 | }), 1103 | }), 1104 | dict({ 1105 | 'basic_block_count': 1, 1106 | 'has_prototype': False, 1107 | 'is_imported': False, 1108 | 'is_thunk': False, 1109 | 'name': 'fini', 1110 | 'parameter_count': 0, 1111 | 'return_type': None, 1112 | 'start': '0xa50', 1113 | 'symbol': dict({ 1114 | 'name': 'fini', 1115 | 'short_name': 'fini', 1116 | 'type': 'SymbolType.FunctionSymbol', 1117 | }), 1118 | }), 1119 | dict({ 1120 | 'basic_block_count': 1, 1121 | 'has_prototype': False, 1122 | 'is_imported': False, 1123 | 'is_thunk': False, 1124 | 'name': '_fini', 1125 | 'parameter_count': 0, 1126 | 'return_type': 'int64_t', 1127 | 'start': '0xa54', 1128 | 'symbol': dict({ 1129 | 'name': '_fini', 1130 | 'short_name': '_fini', 1131 | 'type': 'SymbolType.FunctionSymbol', 1132 | }), 1133 | }), 1134 | ]) 1135 | # --- 1136 | # name: test_get_imports 1137 | dict({ 1138 | 'BNINTERNALNAMESPACE': list([ 1139 | dict({ 1140 | 'address': '0x680', 1141 | 'name': 'puts', 1142 | 'ordinal': 0, 1143 | 'type': 'SymbolType.ImportedFunctionSymbol', 1144 | }), 1145 | dict({ 1146 | 'address': '0x690', 1147 | 'name': 'strlen', 1148 | 'ordinal': 0, 1149 | 'type': 'SymbolType.ImportedFunctionSymbol', 1150 | }), 1151 | dict({ 1152 | 'address': '0x6a0', 1153 | 'name': '__stack_chk_fail', 1154 | 'ordinal': 0, 1155 | 'type': 'SymbolType.ImportedFunctionSymbol', 1156 | }), 1157 | dict({ 1158 | 'address': '0x6b0', 1159 | 'name': 'printf', 1160 | 'ordinal': 0, 1161 | 'type': 'SymbolType.ImportedFunctionSymbol', 1162 | }), 1163 | dict({ 1164 | 'address': '0x6c0', 1165 | 'name': '__isoc99_scanf', 1166 | 'ordinal': 0, 1167 | 'type': 'SymbolType.ImportedFunctionSymbol', 1168 | }), 1169 | dict({ 1170 | 'address': '0x6d0', 1171 | 'name': 'exit', 1172 | 'ordinal': 0, 1173 | 'type': 'SymbolType.ImportedFunctionSymbol', 1174 | }), 1175 | dict({ 1176 | 'address': '0x6e0', 1177 | 'name': '__cxa_finalize', 1178 | 'ordinal': 0, 1179 | 'type': 'SymbolType.ImportedFunctionSymbol', 1180 | }), 1181 | ]), 1182 | }) 1183 | # --- 1184 | # name: test_get_sections 1185 | list([ 1186 | dict({ 1187 | 'align': 1, 1188 | 'end': '0x2015f0', 1189 | 'entry_size': 0, 1190 | 'info_data': 0, 1191 | 'info_section': '', 1192 | 'length': 8, 1193 | 'linked_section': '', 1194 | 'name': '.bss', 1195 | 'start': '0x2015e8', 1196 | 'type': 'NOBITS', 1197 | }), 1198 | dict({ 1199 | 'align': 32, 1200 | 'end': '0x2015e8', 1201 | 'entry_size': 0, 1202 | 'info_data': 0, 1203 | 'info_section': '', 1204 | 'length': 1512, 1205 | 'linked_section': '', 1206 | 'name': '.data', 1207 | 'start': '0x201000', 1208 | 'type': 'PROGBITS', 1209 | }), 1210 | dict({ 1211 | 'align': 8, 1212 | 'end': '0x200f90', 1213 | 'entry_size': 16, 1214 | 'info_data': 0, 1215 | 'info_section': '', 1216 | 'length': 496, 1217 | 'linked_section': '.dynstr', 1218 | 'name': '.dynamic', 1219 | 'start': '0x200da0', 1220 | 'type': 'DYNAMIC', 1221 | }), 1222 | dict({ 1223 | 'align': 1, 1224 | 'end': '0x4a1', 1225 | 'entry_size': 0, 1226 | 'info_data': 0, 1227 | 'info_section': '', 1228 | 'length': 201, 1229 | 'linked_section': '', 1230 | 'name': '.dynstr', 1231 | 'start': '0x3d8', 1232 | 'type': 'STRTAB', 1233 | }), 1234 | dict({ 1235 | 'align': 8, 1236 | 'end': '0x3d8', 1237 | 'entry_size': 24, 1238 | 'info_data': 1, 1239 | 'info_section': '', 1240 | 'length': 288, 1241 | 'linked_section': '.dynstr', 1242 | 'name': '.dynsym', 1243 | 'start': '0x2b8', 1244 | 'type': 'DYNSYM', 1245 | }), 1246 | dict({ 1247 | 'align': 8, 1248 | 'end': '0xc00', 1249 | 'entry_size': 0, 1250 | 'info_data': 0, 1251 | 'info_section': '', 1252 | 'length': 296, 1253 | 'linked_section': '', 1254 | 'name': '.eh_frame', 1255 | 'start': '0xad8', 1256 | 'type': 'PROGBITS', 1257 | }), 1258 | dict({ 1259 | 'align': 4, 1260 | 'end': '0xad4', 1261 | 'entry_size': 0, 1262 | 'info_data': 0, 1263 | 'info_section': '', 1264 | 'length': 68, 1265 | 'linked_section': '', 1266 | 'name': '.eh_frame_hdr', 1267 | 'start': '0xa90', 1268 | 'type': 'PROGBITS', 1269 | }), 1270 | dict({ 1271 | 'align': 1, 1272 | 'end': '0x201648', 1273 | 'entry_size': 0, 1274 | 'info_data': 0, 1275 | 'info_section': '', 1276 | 'length': 88, 1277 | 'linked_section': '', 1278 | 'name': '.extern', 1279 | 'start': '0x2015f0', 1280 | 'type': '', 1281 | }), 1282 | dict({ 1283 | 'align': 4, 1284 | 'end': '0xa5d', 1285 | 'entry_size': 0, 1286 | 'info_data': 0, 1287 | 'info_section': '', 1288 | 'length': 9, 1289 | 'linked_section': '', 1290 | 'name': '.fini', 1291 | 'start': '0xa54', 1292 | 'type': 'PROGBITS', 1293 | }), 1294 | dict({ 1295 | 'align': 8, 1296 | 'end': '0x200da0', 1297 | 'entry_size': 8, 1298 | 'info_data': 0, 1299 | 'info_section': '', 1300 | 'length': 8, 1301 | 'linked_section': '', 1302 | 'name': '.fini_array', 1303 | 'start': '0x200d98', 1304 | 'type': '', 1305 | }), 1306 | dict({ 1307 | 'align': 8, 1308 | 'end': '0x2b4', 1309 | 'entry_size': 0, 1310 | 'info_data': 0, 1311 | 'info_section': '', 1312 | 'length': 28, 1313 | 'linked_section': '.dynsym', 1314 | 'name': '.gnu.hash', 1315 | 'start': '0x298', 1316 | 'type': '', 1317 | }), 1318 | dict({ 1319 | 'align': 2, 1320 | 'end': '0x4ba', 1321 | 'entry_size': 2, 1322 | 'info_data': 0, 1323 | 'info_section': '', 1324 | 'length': 24, 1325 | 'linked_section': '.dynsym', 1326 | 'name': '.gnu.version', 1327 | 'start': '0x4a2', 1328 | 'type': '', 1329 | }), 1330 | dict({ 1331 | 'align': 8, 1332 | 'end': '0x500', 1333 | 'entry_size': 0, 1334 | 'info_data': 1, 1335 | 'info_section': '', 1336 | 'length': 64, 1337 | 'linked_section': '.dynstr', 1338 | 'name': '.gnu.version_r', 1339 | 'start': '0x4c0', 1340 | 'type': '', 1341 | }), 1342 | dict({ 1343 | 'align': 8, 1344 | 'end': '0x201000', 1345 | 'entry_size': 8, 1346 | 'info_data': 0, 1347 | 'info_section': '', 1348 | 'length': 112, 1349 | 'linked_section': '', 1350 | 'name': '.got', 1351 | 'start': '0x200f90', 1352 | 'type': 'PROGBITS', 1353 | }), 1354 | dict({ 1355 | 'align': 4, 1356 | 'end': '0x667', 1357 | 'entry_size': 0, 1358 | 'info_data': 0, 1359 | 'info_section': '', 1360 | 'length': 23, 1361 | 'linked_section': '', 1362 | 'name': '.init', 1363 | 'start': '0x650', 1364 | 'type': 'PROGBITS', 1365 | }), 1366 | dict({ 1367 | 'align': 8, 1368 | 'end': '0x200d98', 1369 | 'entry_size': 8, 1370 | 'info_data': 0, 1371 | 'info_section': '', 1372 | 'length': 8, 1373 | 'linked_section': '', 1374 | 'name': '.init_array', 1375 | 'start': '0x200d90', 1376 | 'type': '', 1377 | }), 1378 | dict({ 1379 | 'align': 1, 1380 | 'end': '0x254', 1381 | 'entry_size': 0, 1382 | 'info_data': 0, 1383 | 'info_section': '', 1384 | 'length': 28, 1385 | 'linked_section': '', 1386 | 'name': '.interp', 1387 | 'start': '0x238', 1388 | 'type': 'PROGBITS', 1389 | }), 1390 | dict({ 1391 | 'align': 4, 1392 | 'end': '0x274', 1393 | 'entry_size': 0, 1394 | 'info_data': 0, 1395 | 'info_section': '', 1396 | 'length': 32, 1397 | 'linked_section': '', 1398 | 'name': '.note.ABI-tag', 1399 | 'start': '0x254', 1400 | 'type': 'NOTE', 1401 | }), 1402 | dict({ 1403 | 'align': 4, 1404 | 'end': '0x298', 1405 | 'entry_size': 0, 1406 | 'info_data': 0, 1407 | 'info_section': '', 1408 | 'length': 36, 1409 | 'linked_section': '', 1410 | 'name': '.note.gnu.build-id', 1411 | 'start': '0x274', 1412 | 'type': 'NOTE', 1413 | }), 1414 | dict({ 1415 | 'align': 16, 1416 | 'end': '0x6e0', 1417 | 'entry_size': 16, 1418 | 'info_data': 0, 1419 | 'info_section': '', 1420 | 'length': 112, 1421 | 'linked_section': '', 1422 | 'name': '.plt', 1423 | 'start': '0x670', 1424 | 'type': 'PROGBITS', 1425 | }), 1426 | dict({ 1427 | 'align': 8, 1428 | 'end': '0x6e8', 1429 | 'entry_size': 8, 1430 | 'info_data': 0, 1431 | 'info_section': '', 1432 | 'length': 8, 1433 | 'linked_section': '', 1434 | 'name': '.plt.got', 1435 | 'start': '0x6e0', 1436 | 'type': 'PROGBITS', 1437 | }), 1438 | dict({ 1439 | 'align': 8, 1440 | 'end': '0x5c0', 1441 | 'entry_size': 24, 1442 | 'info_data': 0, 1443 | 'info_section': '', 1444 | 'length': 192, 1445 | 'linked_section': '.dynsym', 1446 | 'name': '.rela.dyn', 1447 | 'start': '0x500', 1448 | 'type': 'RELA', 1449 | }), 1450 | dict({ 1451 | 'align': 8, 1452 | 'end': '0x650', 1453 | 'entry_size': 24, 1454 | 'info_data': 22, 1455 | 'info_section': '', 1456 | 'length': 144, 1457 | 'linked_section': '.dynsym', 1458 | 'name': '.rela.plt', 1459 | 'start': '0x5c0', 1460 | 'type': 'RELA', 1461 | }), 1462 | dict({ 1463 | 'align': 4, 1464 | 'end': '0xa8f', 1465 | 'entry_size': 0, 1466 | 'info_data': 0, 1467 | 'info_section': '', 1468 | 'length': 47, 1469 | 'linked_section': '', 1470 | 'name': '.rodata', 1471 | 'start': '0xa60', 1472 | 'type': 'PROGBITS', 1473 | }), 1474 | dict({ 1475 | 'align': 1, 1476 | 'end': '0x201678', 1477 | 'entry_size': 0, 1478 | 'info_data': 0, 1479 | 'info_section': '', 1480 | 'length': 40, 1481 | 'linked_section': '', 1482 | 'name': '.synthetic_builtins', 1483 | 'start': '0x201650', 1484 | 'type': '', 1485 | }), 1486 | dict({ 1487 | 'align': 16, 1488 | 'end': '0xa52', 1489 | 'entry_size': 0, 1490 | 'info_data': 0, 1491 | 'info_section': '', 1492 | 'length': 866, 1493 | 'linked_section': '', 1494 | 'name': '.text', 1495 | 'start': '0x6f0', 1496 | 'type': 'PROGBITS', 1497 | }), 1498 | ]) 1499 | # --- 1500 | # name: test_get_segments 1501 | list([ 1502 | dict({ 1503 | 'data_end': 3072, 1504 | 'data_length': 3072, 1505 | 'data_offset': 0, 1506 | 'end': '0xc00', 1507 | 'executable': True, 1508 | 'length': 3072, 1509 | 'readable': True, 1510 | 'start': '0x0', 1511 | 'writable': False, 1512 | }), 1513 | dict({ 1514 | 'data_end': 2102760, 1515 | 'data_length': 2136, 1516 | 'data_offset': 3472, 1517 | 'end': '0x2015e8', 1518 | 'executable': False, 1519 | 'length': 2136, 1520 | 'readable': True, 1521 | 'start': '0x200d90', 1522 | 'writable': True, 1523 | }), 1524 | dict({ 1525 | 'data_end': 2102760, 1526 | 'data_length': 0, 1527 | 'data_offset': 0, 1528 | 'end': '0x2015f0', 1529 | 'executable': False, 1530 | 'length': 8, 1531 | 'readable': True, 1532 | 'start': '0x2015e8', 1533 | 'writable': True, 1534 | }), 1535 | dict({ 1536 | 'data_end': 2102768, 1537 | 'data_length': 0, 1538 | 'data_offset': 0, 1539 | 'end': '0x201648', 1540 | 'executable': False, 1541 | 'length': 88, 1542 | 'readable': False, 1543 | 'start': '0x2015f0', 1544 | 'writable': False, 1545 | }), 1546 | dict({ 1547 | 'data_end': 2102864, 1548 | 'data_length': 0, 1549 | 'data_offset': 0, 1550 | 'end': '0x201678', 1551 | 'executable': False, 1552 | 'length': 40, 1553 | 'readable': False, 1554 | 'start': '0x201650', 1555 | 'writable': False, 1556 | }), 1557 | ]) 1558 | # --- 1559 | # name: test_get_strings 1560 | list([ 1561 | dict({ 1562 | 'length': 8, 1563 | 'start': '0x34', 1564 | 'type': 'StringType.Utf16String', 1565 | 'value': '@8\t@', 1566 | }), 1567 | dict({ 1568 | 'length': 27, 1569 | 'start': '0x238', 1570 | 'type': 'StringType.AsciiString', 1571 | 'value': '/lib64/ld-linux-x86-64.so.2', 1572 | }), 1573 | dict({ 1574 | 'length': 7, 1575 | 'start': '0x28e', 1576 | 'type': 'StringType.AsciiString', 1577 | 'value': 'g@:lo+6', 1578 | }), 1579 | dict({ 1580 | 'length': 9, 1581 | 'start': '0x3d9', 1582 | 'type': 'StringType.AsciiString', 1583 | 'value': 'libc.so.6', 1584 | }), 1585 | dict({ 1586 | 'length': 4, 1587 | 'start': '0x3e3', 1588 | 'type': 'StringType.AsciiString', 1589 | 'value': 'exit', 1590 | }), 1591 | dict({ 1592 | 'length': 14, 1593 | 'start': '0x3e8', 1594 | 'type': 'StringType.AsciiString', 1595 | 'value': '__isoc99_scanf', 1596 | }), 1597 | dict({ 1598 | 'length': 4, 1599 | 'start': '0x3f7', 1600 | 'type': 'StringType.AsciiString', 1601 | 'value': 'puts', 1602 | }), 1603 | dict({ 1604 | 'length': 16, 1605 | 'start': '0x3fc', 1606 | 'type': 'StringType.AsciiString', 1607 | 'value': '__stack_chk_fail', 1608 | }), 1609 | dict({ 1610 | 'length': 6, 1611 | 'start': '0x40d', 1612 | 'type': 'StringType.AsciiString', 1613 | 'value': 'printf', 1614 | }), 1615 | dict({ 1616 | 'length': 6, 1617 | 'start': '0x414', 1618 | 'type': 'StringType.AsciiString', 1619 | 'value': 'strlen', 1620 | }), 1621 | dict({ 1622 | 'length': 14, 1623 | 'start': '0x41b', 1624 | 'type': 'StringType.AsciiString', 1625 | 'value': '__cxa_finalize', 1626 | }), 1627 | dict({ 1628 | 'length': 17, 1629 | 'start': '0x42a', 1630 | 'type': 'StringType.AsciiString', 1631 | 'value': '__libc_start_main', 1632 | }), 1633 | dict({ 1634 | 'length': 9, 1635 | 'start': '0x43c', 1636 | 'type': 'StringType.AsciiString', 1637 | 'value': 'GLIBC_2.7', 1638 | }), 1639 | dict({ 1640 | 'length': 9, 1641 | 'start': '0x446', 1642 | 'type': 'StringType.AsciiString', 1643 | 'value': 'GLIBC_2.4', 1644 | }), 1645 | dict({ 1646 | 'length': 11, 1647 | 'start': '0x450', 1648 | 'type': 'StringType.AsciiString', 1649 | 'value': 'GLIBC_2.2.5', 1650 | }), 1651 | dict({ 1652 | 'length': 27, 1653 | 'start': '0x45c', 1654 | 'type': 'StringType.AsciiString', 1655 | 'value': '_ITM_deregisterTMCloneTable', 1656 | }), 1657 | dict({ 1658 | 'length': 14, 1659 | 'start': '0x478', 1660 | 'type': 'StringType.AsciiString', 1661 | 'value': '__gmon_start__', 1662 | }), 1663 | dict({ 1664 | 'length': 25, 1665 | 'start': '0x487', 1666 | 'type': 'StringType.AsciiString', 1667 | 'value': '_ITM_registerTMCloneTable', 1668 | }), 1669 | dict({ 1670 | 'length': 4, 1671 | 'start': '0x671', 1672 | 'type': 'StringType.AsciiString', 1673 | 'value': '5"\t ', 1674 | }), 1675 | dict({ 1676 | 'length': 4, 1677 | 'start': '0x677', 1678 | 'type': 'StringType.AsciiString', 1679 | 'value': '%$\t ', 1680 | }), 1681 | dict({ 1682 | 'length': 4, 1683 | 'start': '0x681', 1684 | 'type': 'StringType.AsciiString', 1685 | 'value': '%"\t ', 1686 | }), 1687 | dict({ 1688 | 'length': 4, 1689 | 'start': '0x6b1', 1690 | 'type': 'StringType.AsciiString', 1691 | 'value': ''' 1692 | % 1693 | 1694 | ''', 1695 | }), 1696 | dict({ 1697 | 'length': 5, 1698 | 'start': '0x9e0', 1699 | 'type': 'StringType.AsciiString', 1700 | 'value': 'AWAVI', 1701 | }), 1702 | dict({ 1703 | 'length': 5, 1704 | 'start': '0x9e7', 1705 | 'type': 'StringType.AsciiString', 1706 | 'value': 'AUATL', 1707 | }), 1708 | dict({ 1709 | 'length': 10, 1710 | 'start': '0xa3a', 1711 | 'type': 'StringType.AsciiString', 1712 | 'value': '[]A\\A]A^A_', 1713 | }), 1714 | dict({ 1715 | 'length': 19, 1716 | 'start': '0xa64', 1717 | 'type': 'StringType.AsciiString', 1718 | 'value': ''' 1719 | Enter the flag 1720 | >>> 1721 | ''', 1722 | }), 1723 | dict({ 1724 | 'length': 10, 1725 | 'start': '0xa7b', 1726 | 'type': 'StringType.AsciiString', 1727 | 'value': 'Incorrect!', 1728 | }), 1729 | dict({ 1730 | 'length': 8, 1731 | 'start': '0xa86', 1732 | 'type': 'StringType.AsciiString', 1733 | 'value': 'Correct!', 1734 | }), 1735 | dict({ 1736 | 'length': 5, 1737 | 'start': '0xb3f', 1738 | 'type': 'StringType.AsciiString', 1739 | 'value': ';*3$"', 1740 | }), 1741 | dict({ 1742 | 'length': 28, 1743 | 'start': '0x201020', 1744 | 'type': 'StringType.Utf32String', 1745 | 'value': 'wf{_ny}', 1746 | }), 1747 | ]) 1748 | # --- 1749 | # name: test_get_triage_summary 1750 | dict({ 1751 | 'binary_info': dict({ 1752 | 'address_size': 8, 1753 | 'architecture': 'x86_64', 1754 | 'base_address': '0x0', 1755 | 'end_address': '0x201678', 1756 | 'endianness': 'LittleEndian', 1757 | 'entry_point': '0x6f0', 1758 | 'platform': 'linux-x86_64', 1759 | }), 1760 | 'file_metadata': dict({ 1761 | 'file_size': 2102904, 1762 | 'filename': 'tests/binary/beleaf.elf.bndb', 1763 | 'view_type': 'ELF', 1764 | }), 1765 | 'statistics': dict({ 1766 | 'function_count': 25, 1767 | 'section_count': 26, 1768 | 'segment_count': 5, 1769 | 'string_count': 30, 1770 | }), 1771 | }) 1772 | # --- 1773 | # name: test_high_level_il 1774 | ''' 1775 | 0x8ac: int32_t argc_1 = argc 1776 | 0x8b2: char** argv_1 = argv 1777 | 0x8b9: void* fsbase 1778 | 0x8b9: int64_t rax = *(fsbase + 0x28) 1779 | 0x8d4: printf("Enter the flag\n>>> ") 1780 | 0x8ef: void var_98 1781 | 0x8ef: __isoc99_scanf("%s", &var_98) 1782 | 0x8fe: uint64_t rax_3 = strlen(&var_98) 1783 | 0x912: if (rax_3 u<= 0x20) 1784 | 0x91b: puts("Incorrect!") 1785 | 0x925: exit(1) 1786 | 0x925: noreturn 1787 | 0x92a: int64_t i = 0 1788 | 0x9ab: while (i u< rax_3) 1789 | 0x97d: if (sub_7fa(*(i + &var_98)) != *((i << 3) + &data_2014e0)) 1790 | 0x986: puts("Incorrect!") 1791 | 0x990: exit(1) 1792 | 0x990: noreturn 1793 | 0x995: i += 1 1794 | 0x9b4: puts("Correct!") 1795 | 0x9cb: if (rax == *(fsbase + 0x28)) 1796 | 0x9d3: return 0 1797 | 0x9cd: __stack_chk_fail() 1798 | 0x9cd: noreturn 1799 | 1800 | ''' 1801 | # --- 1802 | # name: test_medium_level_il 1803 | ''' 1804 | 0x8ac: argc_1 = argc 1805 | 0x8b2: argv_1 = argv 1806 | 0x8b9: rax = [fsbase + 0x28].q 1807 | 0x8c2: var_10 = rax 1808 | 0x8cf: rax_1 = 0 1809 | 0x8d4: 0x6b0("Enter the flag\n>>> ") 1810 | 0x8e0: rsi = &var_98 1811 | 0x8ea: rax_2 = 0 1812 | 0x8ef: 0x6c0("%s", rsi) 1813 | 0x8fb: rdi = &var_98 1814 | 0x8fe: rax_3 = 0x690(rdi) 1815 | 0x903: var_a8 = rax_3 1816 | 0x912: if (var_a8 u> 0x20) then 13 @ 0x92a else 15 @ 0x91b 1817 | 0x92a: i = 0 1818 | 0x935: goto 18 @ 0x99d 1819 | 0x91b: 0x680("Incorrect!") 1820 | 0x925: 0x6d0(1) 1821 | 0x925: noreturn 1822 | 0x99d: rax_11 = i 1823 | 0x9ab: if (rax_11 u< var_a8) then 20 @ 0x93e else 31 @ 0x9b4 1824 | 0x93e: rax_4 = i 1825 | 0x945: rax_5 = rax_4 + &var_98 1826 | 0x948: rax_6 = [rax_5].b 1827 | 0x94b: rax_7 = rax_6 1828 | 0x94e: rdi_1 = rax_7 1829 | 0x950: rax_8 = 0x7fa(rdi_1) 1830 | 0x955: var_a0_1 = rax_8 1831 | 0x95c: rax_9 = i 1832 | 0x963: rdx = rax_9 << 3 1833 | 0x972: rax_10 = [rdx + 0x2014e0].q 1834 | 0x97d: if (var_a0_1 == rax_10) then 36 @ 0x995 else 38 @ 0x986 1835 | 0x9b4: 0x680("Correct!") 1836 | 0x9b9: rax_12 = 0 1837 | 0x9be: rcx = var_10 1838 | 0x9c2: rcx_1 = rcx ^ [fsbase + 0x28].q 1839 | 0x9cb: if (rcx_1 == 0) then 41 @ 0x9d3 else 42 @ 0x9cd 1840 | 0x995: i = i + 1 1841 | 0x995: goto 18 @ 0x99d 1842 | 0x986: 0x680("Incorrect!") 1843 | 0x990: 0x6d0(1) 1844 | 0x990: noreturn 1845 | 0x9d3: return 0 1846 | 0x9cd: 0x6a0() 1847 | 0x9cd: noreturn 1848 | 1849 | ''' 1850 | # --- 1851 | # name: test_pseudo_c 1852 | ''' 1853 | 1854 | int32_t main(int32_t argc, char** argv, char** envp) 1855 | 1856 | { 1857 | int32_t argc_1 = argc; 1858 | char** argv_1 = argv; 1859 | void* fsbase; 1860 | int64_t rax = *(fsbase + 0x28); 1861 | printf("Enter the flag\n>>> "); 1862 | void var_98; 1863 | __isoc99_scanf("%s", &var_98); 1864 | uint64_t rax_3 = strlen(&var_98); 1865 | 1866 | if (rax_3 <= 0x20) 1867 | { 1868 | puts("Incorrect!"); 1869 | exit(1); 1870 | /* no return */ 1871 | } 1872 | 1873 | for (int64_t i = 0; i < rax_3; i += 1) 1874 | { 1875 | if (sub_7fa(*(i + &var_98)) != *((i << 3) + &data_2014e0)) 1876 | { 1877 | puts("Incorrect!"); 1878 | exit(1); 1879 | /* no return */ 1880 | } 1881 | } 1882 | 1883 | puts("Correct!"); 1884 | 1885 | if (rax == *(fsbase + 0x28)) 1886 | return 0; 1887 | 1888 | __stack_chk_fail(); 1889 | /* no return */ 1890 | } 1891 | 1892 | 1893 | ''' 1894 | # --- 1895 | # name: test_pseudo_rust 1896 | ''' 1897 | 1898 | fn main(argc: i32, argv: *mut *mut i8, envp: *mut *mut i8) -> i32 1899 | 1900 | { 1901 | let argc_1: i32 = argc; 1902 | let argv_1: *mut *mut i8 = argv; 1903 | let fsbase: *mut c_void; 1904 | let rax: i64 = *fsbase.byte_offset(0x28); 1905 | printf("Enter the flag\n>>> "); 1906 | let mut var_98: (); 1907 | __isoc99_scanf("%s", &var_98); 1908 | let rax_3: u64 = strlen(&var_98); 1909 | 1910 | if rax_3 <= 0x20 { 1911 | puts("Incorrect!"); 1912 | exit(1); 1913 | /* no return */ 1914 | } 1915 | 1916 | for i in 0..rax_3 { 1917 | if sub_7fa(*(i + &var_98)) != *((i << 3) + &data_2014e0) { 1918 | puts("Incorrect!"); 1919 | exit(1); 1920 | /* no return */ 1921 | } 1922 | } 1923 | 1924 | puts("Correct!"); 1925 | 1926 | if rax == *fsbase.byte_offset(0x28) { 1927 | return 0; 1928 | } 1929 | 1930 | __stack_chk_fail(); 1931 | /* no return */ 1932 | } 1933 | 1934 | 1935 | ''' 1936 | # --- 1937 | # name: test_rename_symbol_function 1938 | "Successfully renamed function at 0x8a1 from 'main' to 'new_function_name'" 1939 | # --- 1940 | # name: test_update_analysis_and_wait 1941 | 'Analysis updated successfully for tests/binary/beleaf.elf.bndb' 1942 | # --- 1943 | --------------------------------------------------------------------------------