DEV Community

lupingQAQ
lupingQAQ

Posted on

Introducing ntobjmanager-mcp: stateful Windows RPC research for AI agents

ntobjmanager-mcp: a stateful MCP server for Windows RPC research

A Model Context Protocol (MCP) server that gives an AI agent live, stateful access to Windows RPC attack-surface research, built on James Forshaw's NtObjectManager (NtCoreLib).

Why it exists

Most "let an LLM drive PowerShell" setups are stateless: every tool call spawns a fresh shell, so the RpcServer object you just parsed, the client you just connected, and any session variables are gone when the call returns. That breaks the workflows RPC research depends on: auth handshakes, context-handle chains, anything where step two needs the object from step one.

ntobjmanager-mcp keeps a single PowerShell engine alive for the whole session and exposes it as 22 fixed tools.

Two things a generic PowerShell MCP cannot do

1. Stateful RPC connections. A persistent powershell.exe keeps parsed RpcServer objects and connected RPC clients alive across tool calls: rpc_connect once, rpc_call many times. Auth handshakes, context-handle chains, and session variables survive.

2. CVE methodology as fixed tools. The standard hunting workflows from 2024-2026 public research ship as one-click tools instead of prompt-engineering.

Architecture

AI Agent (Claude Code / OpenCode / any MCP client)
     |  MCP (stdio, 22 tools)
     v
server.py -- snippets.py (PS templates, @@TOKEN@@ + ps_str escape)
     |
     v
ps_engine.py -- persistent powershell.exe (base64 + __MCP_DONE__)
     |            state: $RPCMCP = @{ Servers; Clients; vars }
     v
NtObjectManager / NtCoreLib  -->  RPC runtime (ALPC / pipe / TCP)
Enter fullscreen mode Exit fullscreen mode

Tool matrix (22)

Core stateful pipeline

Tool Purpose
rpc_parse(file, symbol_path?) Parse a PE for RPC servers, cache (keys file_N)
rpc_state() Cached servers + live sessions
rpc_get_interface(key) Procedures, NDR params, context handles, strictness
rpc_query_endpoints(ifid?, search_binding?, find_alpc_port?) Endpoint-mapper query (local or remote)
rpc_running_servers(pid?/service?) Live process/service enumeration
rpc_connect(session, key, binding?, auth?) Generate + connect a client (stateful)
rpc_methods(session) Signatures with opnum mapping
rpc_call(session, method, args_json, store_as?) Reflection invoke; {"__var__"} passes stored objects
rpc_disconnect(session) Drop session

2024-2026 CVE methodology tools

Tool Methodology source
rpc_scan_context_handles(paths) Context-handle type confusion - CVE-2025-48815 pattern (whereisk0shl 2026)
rpc_inventory(paths?, limit?) Attack-surface inventory + EPM cross-check - MS-RPC-Fuzzer phase 1 (CVE-2025-26651)
rpc_fuzz(session, dry_run=True) Primitive-only default-value fuzzing with ok/denied/error classification - dry-run by default
rpc_find_hijackable() Unregistered interfaces of stopped services - EPM poisoning / RPC-Racer (CVE-2025-49760 / 59200 / 59230)
rpc_etw_unreachable(duration, trigger_script?) Clients calling dead servers - PhantomRPC (Kaspersky 2026), admin required
rpc_interface_security(key) ALPC security descriptor / anonymous-ACE audit - MS-NRPC null session (SafeBreach / Securelist 2025)
rpc_decode_flags(flags) RpcServerRegisterIf3 flag bitmask decoding
rpc_format_client(key) Export the generated C# client source (offline grep workflow)
rpc_new_struct(session, type, store_as) Build NDR complex types as session vars
rpc_alpc_squat(name, duration) ALPC port squat + connection capture (race validation primitive)
rpc_accessible_tasks() User-startable tasks (Dark-Elevator chain material, CVE-2026-66804 pattern)
rpc_vars / rpc_clear_cache Session-variable and cache management (eviction cap 150)

Every tool call is appended to output/mcp_audit.log, so at the end you have a full trace of what the agent actually did.

Quick start

# 1) Prerequisites (one-time)
Install-Module NtObjectManager -Scope CurrentUser -Force
pip install -r requirements.txt            # mcp>=1.2.0 (1.x / 2.x compatible)

# 2) Verify - three suites
python tests\smoke_test.py                 # 17 checks (live MCP stdio round-trip)
python tests\var_test.py                   # 10 checks (store_as / __var__ mechanics)
python tests\audit.py                      # 43 checks (edge cases, hostile paths, concurrency)

# 3) Run the server
python server.py                           # stdio MCP
Enter fullscreen mode Exit fullscreen mode

Add it to an MCP client

Claude Code:

claude mcp add ntobjectmanager-rpc -- python C:\path\to\ntobjmanager-mcp\server.py
Enter fullscreen mode Exit fullscreen mode

Any MCP client (OpenCode opencode.json):

{
  "mcp": {
    "ntobjectmanager-rpc": {
      "type": "local",
      "command": ["python", "C:\\path\\to\\ntobjmanager-mcp\\server.py"],
      "enabled": true
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

How it works

The agent speaks MCP over stdio to server.py, which compiles PowerShell snippets and hands them to a persistent powershell.exe (base64-encoded, with a completion sentinel). All state lives in one PowerShell variable, $RPCMCP = @{ Servers; Clients; vars }. NtObjectManager and NtCoreLib do the actual RPC work underneath, over ALPC, named pipe, or TCP.

Repo and docs: https://github.com/lupingQAQ/ntobjmanager-mcp

Top comments (0)