Skip to content

Latest commit

 

History

History
executable file
·
377 lines (275 loc) · 16.1 KB

File metadata and controls

executable file
·
377 lines (275 loc) · 16.1 KB

RePythonNET-MCP

An MCP (Model Context Protocol) server for static analysis and patching of .NET binaries.

Uses dnlib for IL parsing and editing, and the ILSpy/dnSpy decompiler engine for C# source reconstruction. Exposes 38 tools over a standard MCP interface, making it easy to integrate with any MCP-compatible LLM agent.

Designed to run as a standalone Docker container, and also ships with integration files for SARA — an internal automated malware reverse engineering system developed at Sekoia.io.


Features

  • Decompile any .NET class or method to readable C#
  • Enumerate types, methods, fields, imports, resources, entry point
  • Analyse IL opcodes, call graphs, callers, string literals
  • Detect P/Invoke declarations, reflection usage, large byte arrays
  • Rename obfuscated classes, methods, and fields (in-memory, non-destructive)
  • Patch IL instructions: replace strings, NOP out instructions, bypass checks, invert branches, patch static fields
  • Save the modified binary to disk
  • Upload binaries via HTTP, or load directly from a URL
  • Multi-session: analyse several binaries in parallel with separate analysis_id identifiers

Architecture

┌─────────────────────────────────┐
│  Any MCP client / LLM agent     │
│  (curl, Python, SARA, etc.)     │
└──────────────┬──────────────────┘
               │  JSON-RPC over HTTP
               ▼
┌─────────────────────────────────┐
│  pythonnet-mcp  (port 8001)     │
│                                 │
│  server.py                      │
│  ├── FastMCP (38 tools)         │
│  ├── POST /upload               │
│  └── GET  /download             │
│                                 │
│  Runtime: Python + Mono         │
│  .NET libs: dnlib, ILSpy/dnSpy  │
└─────────────────────────────────┘

The server is stateless across requests but holds in-memory sessions keyed by analysis_id. Each session stores the loaded dnlib module and a lazily-initialised ILSpy decompiler instance.


Quick Start

Docker (recommended)

# Create the required directories (first time only)
mkdir -p data/samples data/pythonnet_projects logs

# Build and run
docker compose -f docker-compose.yml up --build

# The server listens on http://localhost:8001

Direct (without Docker)

Prerequisites: Python 3.11+, mono-complete, and the .NET assemblies in dll/.

pip install -r requirements.txt

# Override default paths to avoid needing write access to /data/
export PYTHONNET_PROJECTS_DIR=./data/pythonnet_projects
export SAMPLES_DIR=./data/samples
mkdir -p data/pythonnet_projects data/samples

python server.py --host 0.0.0.0 --port 8001

CLI options:

Option Default Description
--host 0.0.0.0 Bind address
--port 8001 Bind port
--binary (none) Optional binary to pre-load at startup

Claude Desktop Configuration

The file scripts/wrapper.py is a wrapper to put on your host. Then configure Claude Desktop (claude_desktop_config.json) to execute this file.

{
  "mcpServers": {
    "pythonnet": {
      "command": "<path to python>",
      "args": [
        "<path to wrapper.py>"
      ]
    }
  },
}

Usage

Upload a binary

You first needs to upload the file using the /upload endpoint:

curl -F file=@malware.dll http://localhost:8001/upload
# {"path": "/data/pythonnet_projects/_uploads/malware.dll", "size_bytes": 45056}

You can then ask Claude Desktop to use this MCP service to load and analyze the binary. You can use pythonnet_list_uploaded_files to list already uploaded files.

Call an MCP tool (raw JSON-RPC)

curl -s -X POST http://localhost:8001/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "pythonnet_load_binary",
      "arguments": {
        "binary_path": "/data/pythonnet_projects/_uploads/malware.dll",
        "analysis_id": "session1"
      }
    }
  }'

Session isolation: append ?session_id=<value> to the URL to keep sessions independent when running parallel analyses from multiple clients.

List available tools

curl -s -X POST http://localhost:8001/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

Download a file

# Download a patched binary produced by pythonnet_save_binary
curl "http://localhost:8001/download?path=/data/pythonnet_projects/session1/malware_patched.dll" \
  -o malware_patched.dll

Downloads are restricted to files inside /data/pythonnet_projects/ and /data/samples/.


Tools Reference

All tool names are prefixed with pythonnet_. The analysis_id parameter defaults to "default" and identifies the session.

Loading

Tool Key Parameters Description
pythonnet_load_binary binary_path, analysis_id Load a .NET binary from disk. Copies it to a project directory before opening so the original is never modified.
pythonnet_load_from_url url, analysis_id Download a .NET binary from HTTP/HTTPS and load it.
pythonnet_unload_binary analysis_id Unload a binary and free its memory.
pythonnet_list_uploaded_files extension List binaries in the upload staging area and the samples directory.
pythonnet_list_active_sessions List all active and on-disk analysis sessions.

Enumeration

Tool Key Parameters Description
pythonnet_list_all_classes analysis_id, namespace_filter List all types defined in the binary.
pythonnet_list_all_methods analysis_id, class_filter List all methods that have a body.
pythonnet_list_all_methods_inside_class class_name, analysis_id List all methods (including abstract/interface) inside a specific class.

Decompilation

Tool Key Parameters Description
pythonnet_decompile_class class_name, analysis_id Decompile an entire class to C# source.
pythonnet_decompile_method class_name, method_name, analysis_id, method_index Decompile a single method. Use method_index to disambiguate overloads.
pythonnet_decompile_all_classes analysis_id Decompile every class in the binary at once.

Metadata

Tool Key Parameters Description
pythonnet_get_module_info analysis_id Assembly name, version, culture, runtime version, target framework.
pythonnet_get_entry_point analysis_id Managed entry point (Main or EntryPoint metadata).
pythonnet_get_imports analysis_id All external assemblies and types referenced by the binary.
pythonnet_get_fields class_name, analysis_id All fields of a class with their types and visibility.
pythonnet_get_custom_attributes class_name, analysis_id, method_name Custom attributes on a class or one of its methods.
pythonnet_get_resources analysis_id List all embedded resources.
pythonnet_extract_resource resource_name, analysis_id Extract a raw embedded resource to a file.

IL Analysis

Tool Key Parameters Description
pythonnet_get_opcodes class_name, method_name, analysis_id, method_index Dump the raw IL instruction listing of a method.
pythonnet_get_method_calls class_name, method_name, analysis_id, method_index List all outgoing call/callvirt instructions from a method.
pythonnet_get_callers class_name, method_name, analysis_id Find all methods that call a given method (reverse call graph).
pythonnet_get_call_graph class_name, method_name, analysis_id, max_depth BFS call graph from a root method (default max depth: 5, internal calls only).
pythonnet_find_dead_code analysis_id Find methods that are never called from within the binary.

String and Name Search

Tool Key Parameters Description
pythonnet_get_strings analysis_id, min_length List all unique ldstr string literals across all method bodies.
pythonnet_search_string search_string, analysis_id Find all methods that reference a given string literal.
pythonnet_search_method_by_name method_name, analysis_id Search for methods by partial name match across all classes.

Behavioral Indicators

Tool Key Parameters Description
pythonnet_find_pinvoke analysis_id, dll_filter List all P/Invoke (DllImport) declarations.
pythonnet_find_reflection_calls analysis_id Find all uses of System.Reflection (Assembly.Load, GetMethod, Invoke, etc.).
pythonnet_find_large_byte_arrays analysis_id, min_size Find methods that allocate large byte arrays (potential payload buffers; default min: 256 bytes).

Renaming (Deobfuscation)

Changes are in-memory only. Call pythonnet_save_binary to persist.

Tool Key Parameters Description
pythonnet_rename_class class_name, new_name, analysis_id, new_namespace Rename a class and optionally its namespace.
pythonnet_rename_method class_name, method_name, new_method_name, analysis_id, method_index Rename a method.
pythonnet_rename_field class_name, field_name, new_field_name, analysis_id Rename a field.

IL Patching

Changes are in-memory only. Call pythonnet_save_binary to persist.

Tool Key Parameters Description
pythonnet_patch_ldstr class_name, method_name, il_offset, new_string, analysis_id Replace the string operand of an ldstr instruction at a given IL offset.
pythonnet_nop_instructions class_name, method_name, il_offsets, analysis_id Replace one or more instructions with nop.
pythonnet_patch_return class_name, method_name, return_value, analysis_id Replace a method body with a minimal return (useful to bypass a check).
pythonnet_patch_branch class_name, method_name, il_offset, mode, analysis_id Flip (invert), force (always/never), or NOP a conditional branch.
pythonnet_set_field_constant class_name, field_name, new_value, analysis_id Patch the initialization value of a static field in .cctor.
pythonnet_save_binary analysis_id, output_path Write the modified module to disk.

Volumes and Paths

Path (in container) Purpose Notes
/data/samples/ Read-only input samples Mount your binary directory here
/data/pythonnet_projects/ Per-session working directories Created automatically; writable
/data/pythonnet_projects/_uploads/ HTTP upload staging area Used by the /upload endpoint

When running standalone, adjust the volume mounts in docker-compose.yml to your local directory layout.


Configuration

Environment Variable Default Description
WEB_SERVER_PORT 8001 Port the server binds to
PYTHONNET_PROJECTS_DIR /data/pythonnet_projects Working directory for analysis sessions (uploads, patched binaries, extracted resources)
SAMPLES_DIR /data/samples Read-only input directory scanned by pythonnet_list_uploaded_files

Dependencies

Python packages (see requirements.txt):

  • pythonnet ≥ 3.0 — Python/.NET CLR interop
  • fastmcp — MCP server framework
  • mcp ≥ 1.0 — MCP SDK
  • click, requests, aiofiles

System (installed in the Docker image): mono-complete

.NET assemblies (bundled in dll/):

Assembly License Source
dnlib.dll MIT 0xd4d/dnlib
ICSharpCode.Decompiler.dll MIT icsharpcode/ILSpy
ICSharpCode.NRefactory*.dll MIT ILSpy / NRefactory
dnSpy.Decompiler*.dll GPL-3.0 dnSpy/dnSpy
dnSpy.Contracts.Logic.dll GPL-3.0 dnSpy/dnSpy

License note: The dnSpy assemblies are GPL-3.0. If you distribute a modified version of this project that includes those files, the GPL-3.0 terms apply to the distribution. Using the service as-is (running the Docker container) is not affected.


SARA Integration

SARA is an internal automated malware reverse engineering system developed at Sekoia.io. It uses this service as its .NET analysis backend. Two additional files support that integration:

tools.py — MCP client

A lightweight HTTP client that runs inside the SARA process and proxies tool calls to this server. It:

  • Auto-detects whether it is running inside Docker (uses http://pythonnet-mcp:8001/mcp) or on the host (uses http://localhost:8001/mcp), and respects the PYTHONNET_MCP_URL environment variable.
  • Fetches tool definitions from the server at runtime (tools/list) with a 5-minute cache — the server is the single source of truth for schemas.
  • Normalises legacy relative paths (data/samples/file.dll/data/samples/file.dll) before forwarding requests.
  • Exposes PythonnetToolRegistry, the class that SARA's service registry instantiates per analysis run.
from services.pythonnet.tools import PythonnetToolRegistry

registry = PythonnetToolRegistry(session_id="sara-abc12")
result = registry.execute_tool("pythonnet_list_all_classes", {"analysis_id": "sara-abc12"})

policy.py — Tool usage policy

Controls tool call limits and deduplication inside SARA's LangGraph analysis loop. Not needed outside SARA.

Tool Limit
pythonnet_load_binary 1 per session
pythonnet_list_all_classes 3 + lock_clears
pythonnet_list_all_methods 3 + lock_clears
pythonnet_list_all_methods_inside_class 20 + (lock_clears × 5)
pythonnet_decompile_class 50 + (lock_clears × 10)
pythonnet_decompile_method unlimited
pythonnet_rename_class 20 + lock_clears
pythonnet_rename_method 50 + (lock_clears × 10)

lock_clears is incremented by SARA's loop when it detects the LLM is stuck, progressively relaxing limits.

Registration in SARA

In core/services_registry.py:

def _make_pythonnet_toolset(session_id: str):
    from services.pythonnet.tools import PythonnetToolRegistry
    return PythonnetToolRegistry(session_id=session_id)

SERVICE_FACTORIES["pythonnet"] = _make_pythonnet_toolset

PIPELINES["pythonnet"] = {
    "name": "Pythonnet",
    "description": ".NET assembly decompilation and patching",
    "supported_formats": ["EXE", "DLL", ".NET"],
    "capabilities": ["decompile", "patch", "rename"],
    "services": ["pythonnet"],
    "available": _pythonnet_available,
    "loader_tool": "pythonnet_load_binary",
}

Volume layout in SARA

When running inside SARA's docker-compose-sara-dev.yml:

  • /data/samples is mounted read-only in the pythonnet-mcp container. The server never writes there.
  • All write operations (working copies, patched binaries, extracted resources) go to /data/pythonnet_projects/{analysis_id}/.

License

The Python source files in this project are released under the MIT License — see LICENSE.

The bundled .NET assemblies in dll/ are subject to their own licenses — see the Dependencies section above, in particular the GPL-3.0 notice for the dnSpy assemblies.