#
tokens: 5807/50000 3/3 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── ida-mcp-server.py
├── LICENSE
├── README.md
└── requirements.txt
```

# Files

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
[![MseeP.ai Security Assessment Badge](https://mseep.net/pr/taida957789-ida-mcp-server-plugin-badge.png)](https://mseep.ai/app/taida957789-ida-mcp-server-plugin)

# IDA Pro MCP Server

IDA Pro MCP Server is a plugin that allows remote querying and control of IDA Pro through the Model Context Protocol (MCP) interface. This plugin enables AI assistants (such as Claude) to interact directly with IDA Pro for binary analysis tasks.

## Overview

This server provides a series of tools that allow AI assistants to perform the following operations:
- Get byte data from specific addresses
- Get disassembly code
- Get decompiled pseudocode
- Query function names
- Get segment information
- List all functions
- Find cross-references
- Get import/export tables
- Get entry points
- Define/undefine functions
- Get various data types (dword, word, byte, qword, float, double, string)
- Get all strings in the binary file
- Get the length of the instruction at the specified address

## Installation

> **Note:** This plugin is designed for and tested with IDA Pro version 9.0+.

1. Ensure Python and related dependencies are installed:

```bash
pip install -r requirements.txt
```

2. Copy the `ida-mcp-server.py` file to the IDA Pro plugins directory:
   - Windows: `%Programfiles%\IDA Pro 9.0\plugins\`
   - Linux: `~/.idapro/plugins/`
   - macOS: `~/Library/Application Support/IDA Pro/plugins/`

## Configure Claude / VSCode

Add the following configuration to the `mcp.json` file in Claude or VSCode:

```json
{
  "mcpServers": {
    "IDAPro": {
      "url": "http://127.0.0.1:3000/sse",
      "type": "sse"
    }
  }
}
```

## Usage

1. Open a binary file in IDA Pro
2. The plugin will automatically load and start the MCP server locally (port 3000)
3. Connect your AI assistant (e.g., Claude) to this server
4. Use the AI assistant to perform binary analysis tasks

## Available Analysis Tools

IDA Pro MCP Server provides the following tools:

- `get_bytes`: Get bytes at a specified address
- `get_disasm`: Get disassembly at a specified address
- `get_decompiled_func`: Get pseudocode of the function containing the specified address
- `get_function_name`: Get function name at a specified address
- `get_segments`: Get all segment information
- `get_functions`: Get all functions in the binary
- `get_xrefs_to`: Get all cross-references to a specified address
- `get_imports`: Get all imported functions
- `get_exports`: Get all exported functions
- `get_entry_point`: Get the entry point of the binary
- `make_function`: Create a function at a specified address
- `undefine_function`: Undefine a function at a specified address
- `get_dword_at`: Get the dword at a specified address
- `get_word_at`: Get the word at a specified address
- `get_byte_at`: Get the byte at a specified address
- `get_qword_at`: Get the qword at a specified address
- `get_float_at`: Get the float at a specified address
- `get_double_at`: Get the double at a specified address
- `get_string_at`: Get the string at a specified address
- `get_string_list`: Get all strings in the binary
- `get_strings`: Get all strings in the binary (with addresses)

## Best Practices

When analyzing binary files, it's recommended to follow these steps:

1. Examine the entry point
2. Analyze the import table
3. Review strings
4. Track key API calls
5. Identify main functional blocks
6. Analyze control flow
7. Identify malicious behaviors
8. Analyze algorithms and encryption routines
9. Document analysis results
10. Use advanced techniques

## License

MIT License

Copyright (c) 2023 

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

```

--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------

```
mcp==1.5.0
starlette==0.46.1
sse-starlette==2.2.1
uvicorn==0.34.0
typing_extensions==4.12.2
pydantic==2.10.6
pydantic_core==2.27.2
anyio==4.9.0
idna==3.10
sniffio==1.3.1
httpcore==1.0.7
httpx==0.28.1
httpx-sse==0.4.0
h11==0.14.0
click==8.1.8

```

--------------------------------------------------------------------------------
/ida-mcp-server.py:
--------------------------------------------------------------------------------

```python
import glob
import json
import os
import ida_bytes
import ida_ua
import ida_funcs
import ida_hexrays
import ida_name
import ida_segment
import idautils
import idc
import ida_idaapi
import ida_kernwin
import idaapi
import threading

from typing import Dict, List, Optional, Any, Tuple
from datetime import datetime
from functools import wraps
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.routing import Mount, Route
from starlette.responses import Response
from mcp.server import Server
from mcp.server.sse import SseServerTransport
from mcp.server import FastMCP
import uvicorn


# Initialize FastMCP server for IDA tools
mcp = FastMCP("IDA MCP Server", port=3000)

# 封裝函數執行在主線程的裝飾器
def execute_on_main_thread(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        result = []
        exception = []
        
        def run_function():
            try:
                result.append(f(*args, **kwargs))
            except Exception as e:
                exception.append(e)
            return 0
        
        ida_kernwin.execute_sync(run_function, ida_kernwin.MFF_FAST)
        
        if exception:
            raise exception[0]
        return result[0]
    return wrapper


@mcp.tool()
@execute_on_main_thread
def get_bytes(ea: int, size: int) -> List[int]:
    """Get bytes at specified address.

    Args:
        ea: Effective address to read from
        size: Number of bytes to read
    """
    try:
        result = [ida_bytes.get_byte(ea + i) for i in range(size)]
        return result
    except Exception as e:
        print(f"Error in get_bytes: {str(e)}")
        return {"error": str(e)}


@mcp.tool()
@execute_on_main_thread
def get_disasm(ea: int) -> str:
    """Get disassembly at specified address.

    Args:
        ea: Effective address to disassemble
    """
    return idc.generate_disasm_line(ea, 0)


@mcp.tool()
@execute_on_main_thread
def get_decompiled_func(ea: int) -> Dict[str, Any]:
    """Get decompiled pseudocode of function containing address.

    Args:
        ea: Effective address within the function
    """
    try:
        func = ida_funcs.get_func(ea)
        if not func:
            return {"error": "No function found at address"}

        decompiler = ida_hexrays.decompile(func.start_ea)
        if not decompiler:
            return {"error": "Failed to decompile function"}

        return {"code": str(decompiler)}
    except Exception as e:
        return {"error": str(e)}


@mcp.tool()
@execute_on_main_thread
def get_function_name(ea: int) -> str:
    """Get function name at specified address.

    Args:
        ea: Effective address of the function
    """
    return ida_name.get_name(ea)


@mcp.tool()
@execute_on_main_thread
def get_segments() -> List[Dict[str, Any]]:
    """Get all segments information.

    @return: List of segments (start, end, name, class, perm, bitness, align, comb, type, sel, flags)
    """
    segments = []
    n = 0
    seg = ida_segment.getnseg(n)
    while seg:
        segments.append(
            {
                "start": seg.start_ea,
                "end": seg.end_ea,
                "name": ida_segment.get_segm_name(seg),
                "class": ida_segment.get_segm_class(seg),
                "perm": seg.perm,
                "bitness": seg.bitness,
                "align": seg.align,
                "comb": seg.comb,
                "type": seg.type,
                "sel": seg.sel,
                "flags": seg.flags,
            }
        )
        n += 1
        seg = ida_segment.getnseg(n)
    return segments


@mcp.tool()
@execute_on_main_thread
def get_functions() -> List[Dict[str, Any]]:
    """Get all functions in the binary."""
    functions = []
    for func_ea in idautils.Functions():
        func_name = ida_name.get_name(func_ea)
        functions.append({"address": func_ea, "name": func_name})
    return functions


@mcp.tool()
@execute_on_main_thread
def get_xrefs_to(ea: int) -> List[Dict[str, Any]]:
    """Get all cross references to specified address.

    Args:
        ea: Effective address to find references to
    """
    xrefs = []
    for xref in idautils.XrefsTo(ea, 0):
        xrefs.append({"from": xref.frm, "type": xref.type})
    return xrefs


@mcp.tool()
@execute_on_main_thread
def get_imports() -> dict[str, list[tuple[int, str, int]]]:
    """Get all imports in the binary.

    Args:
        None

    Returns:
        A dictionary where the keys are module names and the values are lists of tuples.
        Each tuple contains the address of the imported function, the name of the function,
        and the ordinal value of the function.
    """
    tree = {}
    nimps = idaapi.get_import_module_qty()

    for i in range(0, nimps):
        name = idaapi.get_import_module_name(i)
        if not name:
            continue
        # Create a list for imported names
        items = []

        def imports_names_cb(ea, name, ord):
            items.append((ea, "" if not name else name, ord))
            # True -> Continue enumeration
            return True

        # Enum imported entries in this module
        idaapi.enum_import_names(i, imports_names_cb)

        if name not in tree:
            tree[name] = []
        tree[name].extend(items)

    return tree


@mcp.tool()
@execute_on_main_thread
def get_exports() -> List[Tuple[int, int, int, str]]:
    """Get all exports in the binary.

    @return: List of tuples (index, ordinal, ea, name)
    """
    return list(idautils.Entries())


@mcp.tool()
@execute_on_main_thread
def get_entry_point() -> int:
    """Get the entry point of the binary."""
    try:
        import ida_ida
        return ida_ida.inf_get_start_ea()
    except (ImportError, AttributeError):
        try:
            # Alternative method: idc.get_inf_attr to get
            import idc
            return idc.get_inf_attr(idc.INF_START_EA)
        except (ImportError, AttributeError):
            # Last alternative method: use cvar.inf
            return idaapi.cvar.inf.start_ea


@mcp.tool()
@execute_on_main_thread
def make_function(ea: int) -> None:
    """Make a function at specified address."""
    ida_funcs.add_func(ea)


@mcp.tool()
@execute_on_main_thread
def undefine_function(ea: int) -> None:
    """Undefine a function at specified address."""
    ida_funcs.del_func(ea)


@mcp.tool()
@execute_on_main_thread
def get_dword_at(ea: int) -> int:
    """Get the dword at specified address."""
    return idc.get_dword(ea)


@mcp.tool()
@execute_on_main_thread
def get_word_at(ea: int) -> int:
    """Get the word at specified address."""
    return idc.get_word(ea)


@mcp.tool()
@execute_on_main_thread
def get_byte_at(ea: int) -> int:
    """Get the byte at specified address."""
    return idc.get_byte(ea)


@mcp.tool()
@execute_on_main_thread
def get_qword_at(ea: int) -> int:
    """Get the qword at specified address."""
    return idc.get_qword(ea)


@mcp.tool()
@execute_on_main_thread
def get_float_at(ea: int) -> float:
    """Get the float at specified address."""
    return idc.get_float(ea)


@mcp.tool()
@execute_on_main_thread
def get_double_at(ea: int) -> float:
    """Get the double at specified address."""
    return idc.get_double(ea)


@mcp.tool()
@execute_on_main_thread
def get_string_at(ea: int) -> str:
    """Get the string at specified address."""
    return idc.get_strlit_contents(ea)


@mcp.tool()
@execute_on_main_thread
def get_strings():
    strings = []
    for s in idautils.Strings():
        strings.append({"address": s.ea, "string": str(s)})
    return strings

@mcp.tool()
@execute_on_main_thread
def get_current_file_path():
    return idc.get_input_file_path()

@mcp.tool()
@execute_on_main_thread
def list_files_with_relative_path(relative_path: str = ""):
    base_dir = os.path.dirname(idc.get_input_file_path())
    if  ':' in relative_path or '..' in relative_path or '//' in relative_path:
        return json.dumps({"error": "Invalid relative path"})
    if relative_path is None or relative_path == "":
        return glob.glob(os.path.join(base_dir, "*"))
    else:
        return glob.glob(os.path.join(base_dir, relative_path, "*"))

@mcp.tool()
@execute_on_main_thread
def read_file(relative_path: str):
    base_dir = os.path.dirname(idc.get_input_file_path())
    if  ':' in relative_path or '..' in relative_path or '//' in relative_path:
        return json.dumps({"error": "Invalid relative path"})
    if relative_path is "":
        return json.dumps({"error": "Relative path is required"})
    with open(os.path.join(base_dir, relative_path), "r") as f:
        return f.read()

@mcp.tool()
@execute_on_main_thread
def write_file(relative_path: str, content: str):
    base_dir = os.path.dirname(idc.get_input_file_path())
    if  ':' in relative_path or '..' in relative_path or '//' in relative_path:
        return json.dumps({"error": "Invalid relative path"})
    if relative_path is "":
        return json.dumps({"error": "Relative path is required"})
    with open(os.path.join(base_dir, relative_path), "w") as f:
        f.write(content)

@mcp.tool()
@execute_on_main_thread
def read_binary(relative_path: str):
    base_dir = os.path.dirname(idc.get_input_file_path())
    if  ':' in relative_path or '..' in relative_path or '//' in relative_path:
        return json.dumps({"error": "Invalid relative path"})
    if relative_path is "":
        return json.dumps({"error": "Relative path is required"})
    with open(os.path.join(base_dir, relative_path), "rb") as f:
        return f.read()

@mcp.tool()
@execute_on_main_thread
def write_binary(relative_path: str , content: bytes):
    base_dir = os.path.dirname(idc.get_input_file_path())
    if  ':' in relative_path or '..' in relative_path or '//' in relative_path:
        return json.dumps({"error": "Invalid relative path"})
    if relative_path is "":
        return json.dumps({"error": "Relative path is required"})
    with open(os.path.join(base_dir, relative_path), "wb") as f:
        f.write(content)    

@mcp.tool()
@execute_on_main_thread
def eval_pythoni(script: str):
    return eval(script)

@mcp.tool()
@execute_on_main_thread
def get_instruction_length(address: int) -> int:
    """
    Retrieves the length (in bytes) of the instruction at the specified address.

    Args:
        address: The address of the instruction.

    Returns:
        The length (in bytes) of the instruction.  Returns 0 if the instruction cannot be decoded.
    """
    try:
        # Create an insn_t object to store instruction information.
        insn = ida_ua.insn_t()

        # Decode the instruction.
        length = ida_ua.decode_insn(insn, address)
        if length == 0:
            print(f"Failed to decode instruction at address {hex(address)}")
            return 0

        return length
    except Exception as e:
        print(f"Error getting instruction length: {str(e)}")
        return 0

@mcp.prompt()
def binary_analysis_strategy() -> str:
    """
    Guild for analyzing the binary
    """
    return (
        "IDA Pro MCP Server Tools and Best Practices:\n\n."
        "Tools: \n"
        "- get_bytes: Get bytes at specified address.\n"
        "- get_disasm: Get disassembly at specified address.\n"
        "- get_decompiled_func: Get decompiled pseudocode of function containing address.\n"
        "- get_function_name: Get function name at specified address.\n"
        "- get_segments: Get all segments information.\n"
        "- get_functions: Get all functions in the binary.\n"
        "- get_xrefs_to: Get all cross references to a specified address.\n"
        "- get_imports: Get all imports in the binary.\n"
        "- get_exports: Get all exports in the binary.\n"
        "- get_entry_point: Get the entry point of the binary.\n"
        "- make_function: Make a function at specified address.\n"
        "- undefine_function: Undefine a function at specified address.\n"
        "- get_dword_at: Get the dword at specified address.\n"
        "- get_word_at: Get the word at specified address.\n"
        "- get_byte_at: Get the byte at specified address.\n"
        "- get_qword_at: Get the qword at specified address.\n"
        "- get_float_at: Get the float at specified address.\n"
        "- get_double_at: Get the double at specified address.\n"
        "- get_string_at: Get the string at specified address.\n"
        "- get_strings: Get all strings in the binary.\n"
        "- get_current_file_path: Get the current path of the binary.\n"
        "- list_files_with_relative_path: List all files in the specified relative path in the current directory.\n"
        "- read_file: Read the content of a file.\n"
        "- write_file: Write content to a file.\n"
        "- read_binary: Read the content of a binary file.\n"
        "- write_binary: Write content to a binary file.\n"
        "- eval_python: Evaluate a Python script in IDA Pro.\n"
        "- get_instruction_length: Get the length of the instruction at the specified address.\n"
        "Best Practices: \n"
        "- Initial Analysis Phase\n"
        "   1. Examine the Entry Point\n"
        "       - Use the get_entry_point() tool to locate the program's entry point\n"
        "       - Analyze the code at the entry point to understand the program's startup flow\n"
        "       - Look for unusual instructions or jumps\n"
        "   2. Analyze Import Table\n"
        "       - Use the get_imports() tool to view all imported functions\n"
        "       - Look for suspicious API functions, such as:\n"
        "           - File operations: CreateFile, WriteFile\n"
        "           - Network communication: socket, connect, InternetOpen\n"
        "           - Process manipulation: CreateProcess, VirtualAlloc\n"
        "           - Registry operations: RegOpenKey, RegSetValue\n"
        "           - Cryptography related: CryptEncrypt, CryptDecrypt\n"
        "   3. Review Strings\n"
        "       - Use the get_strings() tool to obtain all strings\n"
        "       - Pay attention to IP addresses, URLs, domain names, file paths\n"
        "       - Look for encrypted or obfuscated string patterns\n"
        "       - Analyze command line parameters and error messages\n"
        "   4. In-Depth Analysis Phase\n"
        "       - Track Key API Calls\n"
        "           - Use get_xrefs_to() to find cross-references to suspicious imported functions\n"
        "           - Use get_decompiled_func() to analyze functions that call these APIs\n"
        "           - Analyze how parameters and return values are handled\n"
        "   5. Identify Main Functional Blocks\n"
        "       - Use get_functions() to get a list of all functions\n"
        "       - Sort functions by size and complexity\n"
        "       - Decompile and analyze large, complex functions\n"
        "       - Look for suspicious function names or unnamed functions\n"
        "   6. Analyze Control Flow\n"
        "       - Observe conditional branches and loop structures\n"
        "       - Analyze function call graphs and execution paths\n"
        "       - Look for anti-debugging and anti-VM detection techniques\n"
        "       - Pay attention to unusual jumps and callback mechanisms\n"
        "   7. Identifying Malicious Behaviors\n"
        "       - Identify Common Malicious Functionality\n"
        "           - Persistence mechanisms: Registry modifications, startup items, service creation\n"
        "           - Data theft: File searching, keylogging, screen capturing\n"
        "           - Communication features: C&C communication, data exfiltration channels\n"
        "           - Evasion techniques: Obfuscation, packing, anti-analysis checks\n"
        "           - Destructive behaviors: File encryption, system damage\n"
        "   8. Analyze Algorithms and Encryption Routines\n"
        "       - Identify encryption and decryption functions\n"
        "       - Look for hardcoded keys and cryptographic constants\n"
        "       - Analyze how data is processed in memory\n"
        "   9. Analyze Network Communication\n"
        "       - Identify network communication functions\n"
        "       - Look for IP addresses, URLs, and domain names\n"
        "       - Analyze how data is sent and received over the network\n"
        "   10. Dump payloads if there is any decryption or encoding\n"
        "       - Generate decryption or decodeing script in python and ida python\n"
        "       - Use eval_python to execute the script in IDA Pro\n"
        "       - Dump payloads in the current directory\n"
        "   11. Document Analysis Results\n"
        "       - Add comments to key functions\n"
        "       - Rename functions to reflect their actual functionality\n"
        "       - Create a logical structure diagram of the code\n"
        "   12. Using Advanced Techniques\n"
        "       - Use IDA Pro's advanced features like IDAPython scripting, IDA SDK, and IDA API\n"
        "       - Implement custom analysis scripts to automate repetitive tasks\n"
        "       - Explore IDA Pro's plugin ecosystem for additional analysis capabilities\n"
    )


def create_starlette_app(mcp_server: Server, *, debug: bool = False) -> Starlette:
    """Create a Starlette application that can serve the provided mcp server with SSE."""

    middleware = [
        Middleware(
            CORSMiddleware,
            allow_origins=["*"],
            allow_methods=["*"],
            allow_headers=["*"],
        )
    ]
    return Starlette(
        debug=debug,
        middleware=middleware,
        routes=[
            Mount("/", app=mcp.sse_app()),
        ],
    )


class ModelContextProtocolPlugin(ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_FIX | ida_idaapi.PLUGIN_HIDE
    comment = "IDA Model Context Protocol Server"
    help = "Provides REST API and SSE for IDA Pro analysis"
    wanted_name = "IDA MCP Server"
    wanted_hotkey = ""

    def init(self):
        try:
            print("Initializing IDA Model Context Protocol Server...")
            # app = create_starlette_app(mcp, debug=True)

            def run_server():
                try:
                    # 設置將異常轉換為 JSON 響應
                    mcp.run(transport="sse")
                    # uvicorn.run(app, host="localhost", port=3000, log_level="debug")
                except Exception as e:
                    print(f"Server error: {str(e)}")

            server_thread = threading.Thread(target=run_server)
            server_thread.daemon = True
            server_thread.start()
            print("Server started successfully!")
            return ida_idaapi.PLUGIN_KEEP
        except Exception as e:
            print(f"Failed to start server: {str(e)}")
            return ida_idaapi.PLUGIN_SKIP

    def run(self, arg):
        pass

    def term(self):
        print("Terminating IDA Model Context Protocol Server...")


def PLUGIN_ENTRY():
    return ModelContextProtocolPlugin()


if __name__ == "__main__":
    PLUGIN_ENTRY()

```