Source code for pwndbg_lldb_mcp

#!/usr/bin/env python3
"""
pwndbg-lldb MCP Server

An MCP (Model Context Protocol) server that exposes pwndbg commands running
under LLDB as tools for AI assistants. This enables AI-driven binary analysis,
exploit development, and reverse engineering through pwndbg's enhanced
debugging capabilities.

pwndbg-lldb documentation:
  https://pwndbg.re/2025.05.30/reference/pwndbg/dbg/lldb/

Based on the conventions established by lldb-mcp:
  https://github.com/stass/lldb-mcp

Architecture:
  - Communicates with LLDB+pwndbg via a PTY (pseudo-terminal) pair
  - Each debugging session is isolated with a unique UUID
  - All I/O is async with 30-second timeouts for pwndbg commands
    (longer than plain LLDB since pwndbg commands do more work)
  - The generic `pwndbg_command` tool serves as an escape hatch for
    any command not explicitly exposed as a dedicated tool
"""

from __future__ import annotations

import os
import re
import sys
import uuid
import asyncio
import subprocess
import pty
import fcntl
import termios
import struct
import argparse
from typing import Dict, List, Optional, Any, AsyncIterator, Callable, Awaitable
from dataclasses import dataclass, field
from contextlib import asynccontextmanager
from collections import deque
import time
import json

from mcp.server.fastmcp import FastMCP, Context

# ---------------------------------------------------------------------------
# Debug logging
# ---------------------------------------------------------------------------

DEBUG = False


[docs] def debug_log(message: str) -> None: """Print debug messages only when DEBUG flag is enabled.""" if DEBUG: print(f"[DEBUG] {message}")
# --------------------------------------------------------------------------- # Progress / logging callback infrastructure # --------------------------------------------------------------------------- # Callback signature: async fn(chunk: str, elapsed: float) -> None ProgressCallback = Callable[[str, float], Awaitable[None]] async def _noop_callback(chunk: str, elapsed: float) -> None: """Default no-op callback when no progress reporting is needed.""" pass
[docs] def make_progress_callback(ctx: Context) -> ProgressCallback: """Build a progress callback that sends MCP notifications via Context. Uses ctx.report_progress() if a progress token is available, and always sends intermediate output via ctx.info() logging notifications. Gracefully degrades: if no progress token exists, only logging is used. If logging fails, the error is silently ignored to avoid disrupting the debugger command execution. """ start_time = time.monotonic() bytes_received = 0 async def callback(chunk: str, elapsed: float) -> None: nonlocal bytes_received bytes_received += len(chunk) try: # Always send as log notification (fire-and-forget) await ctx.info(f"[debugger] {chunk.rstrip()}") except Exception: pass # logging is best-effort try: # Send progress if the client provided a progress token await ctx.report_progress( progress=elapsed, total=None, message=f"Waiting for debugger ({elapsed:.1f}s, {bytes_received} bytes received)", ) except Exception: pass # progress is best-effort return callback
# --------------------------------------------------------------------------- # Debugger event types for Phase 2 background monitoring # ---------------------------------------------------------------------------
[docs] @dataclass class DebuggerEvent: """A debugger event detected from PTY output.""" timestamp: float event_type: str # "breakpoint_hit", "signal", "crash", "exited", "stopped", "output" summary: str raw_output: str
[docs] def to_dict(self) -> dict: return { "timestamp": self.timestamp, "event_type": self.event_type, "summary": self.summary, "raw_output": self.raw_output, }
# Patterns used to classify debugger stop events from PTY output _EVENT_PATTERNS = [ (re.compile(r"stop reason = breakpoint", re.IGNORECASE), "breakpoint_hit", "Breakpoint hit"), (re.compile(r"stop reason = watchpoint", re.IGNORECASE), "watchpoint_hit", "Watchpoint triggered"), (re.compile(r"stop reason = signal (SIG\w+)", re.IGNORECASE), "signal", "Signal received: {0}"), (re.compile(r"stop reason = EXC_BAD_ACCESS", re.IGNORECASE), "crash", "Crash: EXC_BAD_ACCESS"), (re.compile(r"stop reason = EXC_CRASH", re.IGNORECASE), "crash", "Crash: EXC_CRASH"), (re.compile(r"Process \d+ exited with status = (\d+)", re.IGNORECASE), "exited", "Process exited with status {0}"), (re.compile(r"Process \d+ crashed", re.IGNORECASE), "crash", "Process crashed"), (re.compile(r"stop reason = step", re.IGNORECASE), "stopped", "Stopped after step"), (re.compile(r"stop reason = trace", re.IGNORECASE), "stopped", "Stopped: trace"), (re.compile(r"stop reason = plan complete", re.IGNORECASE), "stopped", "Stopped: plan complete"), ]
[docs] def classify_debugger_output(output: str) -> Optional[DebuggerEvent]: """Classify PTY output into a DebuggerEvent if it matches a known pattern.""" for pattern, event_type, summary_template in _EVENT_PATTERNS: match = pattern.search(output) if match: groups = match.groups() summary = summary_template.format(*groups) if groups else summary_template return DebuggerEvent( timestamp=time.time(), event_type=event_type, summary=summary, raw_output=output, ) return None
# --------------------------------------------------------------------------- # LLDB + pwndbg Session # ---------------------------------------------------------------------------
[docs] class PwndbgSession: """Manages a single LLDB process with pwndbg loaded. Communication happens over a PTY pair: the parent process holds the master fd and writes commands / reads output, while the LLDB child process uses the slave fd as its terminal. The prompt pattern used to detect command completion is "(pwndbg-lldb)" which is what pwndbg sets when running under LLDB. We also fall back to "(lldb)" for the initial startup before pwndbg fully initializes. """ # pwndbg under LLDB may use either prompt depending on initialization state PROMPT_PATTERNS = [b"(pwndbg-lldb)", b"pwndbg>", b"pwndbg-lldb>", b"(lldb)"] COMMAND_TIMEOUT = 30.0 # seconds — pwndbg commands can be slow def __init__( self, session_id: str, lldb_path: str, working_dir: Optional[str] = None, pwndbg_path: Optional[str] = None, ): self.id = session_id self.lldb_path = lldb_path self.working_dir = working_dir or os.getcwd() self.pwndbg_path = pwndbg_path self.process = None self.master_fd = None self.slave_fd = None self.target = None self.ready = False # Phase 1: progress callback support self._last_output: str = "" # Phase 2: event log for background monitoring self.event_log: deque[DebuggerEvent] = deque(maxlen=200) self._monitor_task: Optional[asyncio.Task] = None self._monitor_active = False # Subscribers notified when events are appended (set of async callables) self._event_subscribers: List[Callable[[DebuggerEvent], Awaitable[None]]] = []
[docs] async def start(self) -> str: """Start the LLDB process with pwndbg loaded via PTY. Creates a pseudo-terminal pair, spawns LLDB, disables terminal echo, and waits for the initial prompt. If a pwndbg_path is provided, it will be loaded via 'command script import'. """ debug_log(f"Starting LLDB+pwndbg process with path: {self.lldb_path}") # Create a pseudo-terminal pair self.master_fd, self.slave_fd = pty.openpty() debug_log(f"Created PTY pair: master={self.master_fd}, slave={self.slave_fd}") # Disable terminal echo old_settings = termios.tcgetattr(self.slave_fd) new_settings = termios.tcgetattr(self.slave_fd) new_settings[3] = new_settings[3] & ~termios.ECHO termios.tcsetattr(self.slave_fd, termios.TCSADRAIN, new_settings) # Build the LLDB launch command cmd = [self.lldb_path] self.process = await asyncio.create_subprocess_exec( *cmd, stdin=self.slave_fd, stdout=self.slave_fd, stderr=self.slave_fd, cwd=self.working_dir, ) debug_log(f"LLDB process created with PID: {self.process.pid}") # Close slave end in parent process os.close(self.slave_fd) self.slave_fd = None # Make the master fd non-blocking flags = fcntl.fcntl(self.master_fd, fcntl.F_GETFL) fcntl.fcntl(self.master_fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) # Wait for initial LLDB prompt debug_log("Waiting for initial prompt...") output = await self.read_until_prompt() debug_log("Initial prompt received") self.ready = True # Load pwndbg if path is provided if self.pwndbg_path: debug_log(f"Loading pwndbg from: {self.pwndbg_path}") pwndbg_output = await self.execute_command( f'command script import "{self.pwndbg_path}"' ) output += pwndbg_output # Send version command to confirm functionality debug_log("Sending version command") version_output = await self.execute_command("version") output += version_output return output
def _check_ready(self) -> None: """Raise RuntimeError if the session is not in a usable state.""" if not self.ready: raise RuntimeError("pwndbg session is not ready") if not self.process: raise RuntimeError("pwndbg session is not ready: no process") if self.process.returncode is not None: raise RuntimeError( f"pwndbg session is not ready: process terminated, code: {self.process.returncode}" )
[docs] async def execute_command(self, command: str) -> str: """Execute a command in the LLDB+pwndbg session and return output. Writes the command string to the PTY master fd and reads until a known prompt pattern appears or the timeout is reached. """ self._check_ready() debug_log(f"Executing command: {command}") os.write(self.master_fd, f"{command}\n".encode()) output = await self.read_until_prompt() self._last_output = output self._record_event(output) return output
[docs] async def execute_command_with_progress( self, command: str, callback: ProgressCallback = _noop_callback, timeout_override: Optional[float] = None, ) -> str: """Execute a command with progress callbacks on intermediate PTY output. Like execute_command, but invokes `callback(chunk, elapsed)` each time new data arrives from the PTY. This enables MCP progress notifications and logging during long-running debugger operations (run, continue, etc.). Args: command: The LLDB/pwndbg command to execute. callback: Async callable invoked with (chunk_text, elapsed_seconds). timeout_override: Override the default COMMAND_TIMEOUT for this call. """ self._check_ready() timeout = timeout_override if timeout_override is not None else self.COMMAND_TIMEOUT debug_log(f"Executing command (with progress): {command}") os.write(self.master_fd, f"{command}\n".encode()) output = await self._read_until_prompt_with_callback(callback, timeout) self._last_output = output self._record_event(output) return output
[docs] async def read_until_prompt(self) -> str: """Read from the PTY until a known prompt pattern is detected. Implements a polling loop with non-blocking reads and a global timeout. Returns accumulated output as a UTF-8 string. """ return await self._read_until_prompt_with_callback(_noop_callback, self.COMMAND_TIMEOUT)
async def _read_until_prompt_with_callback( self, callback: ProgressCallback, timeout: float, ) -> str: """Core PTY read loop with optional progress callback. Args: callback: Invoked with (decoded_chunk, elapsed_seconds) on each read. timeout: Maximum seconds to wait for a prompt. """ if not self.master_fd: raise RuntimeError("PTY not initialized") buffer = b"" start_time = asyncio.get_event_loop().time() last_callback_time = start_time debug_log("Starting to read until prompt") while True: current_time = asyncio.get_event_loop().time() elapsed = current_time - start_time if elapsed > timeout: debug_log(f"Timeout after {elapsed:.1f}s") result = buffer.decode("utf-8", errors="replace") + "\n[Timeout waiting for response]" try: await callback(f"[Timeout after {elapsed:.1f}s]", elapsed) except Exception: pass return result if self.process and self.process.returncode is not None: debug_log(f"Process terminated with code: {self.process.returncode}") if buffer: return buffer.decode("utf-8", errors="replace") raise RuntimeError(f"LLDB process terminated with code {self.process.returncode}") try: chunk = os.read(self.master_fd, 4096) if chunk: debug_log(f"Read {len(chunk)} bytes") buffer += chunk # Invoke progress callback with decoded chunk decoded = chunk.decode("utf-8", errors="replace") try: await callback(decoded, elapsed) except Exception: pass # callback errors must not disrupt PTY reads # Check for any known prompt pattern for pattern in self.PROMPT_PATTERNS: if pattern in buffer: debug_log(f"Found prompt: {pattern}") return buffer.decode("utf-8", errors="replace") except BlockingIOError: # No data available — send a heartbeat callback every 2 seconds if current_time - last_callback_time >= 2.0: try: await callback("", elapsed) except Exception: pass last_callback_time = current_time await asyncio.sleep(0.1) except Exception as e: debug_log(f"Error reading from PTY: {e}") if buffer: return buffer.decode("utf-8", errors="replace") + f"\n[Error: {e}]" raise RuntimeError(f"Error reading from LLDB: {e}") # ------------------------------------------------------------------ # Event recording (Phase 2) # ------------------------------------------------------------------ def _record_event(self, output: str) -> None: """Classify output and append to event log if it matches a pattern.""" event = classify_debugger_output(output) if event: self.event_log.append(event) debug_log(f"Recorded event: {event.event_type}{event.summary}") # Notify subscribers asynchronously (fire-and-forget) for sub in self._event_subscribers: asyncio.ensure_future(self._safe_notify(sub, event)) @staticmethod async def _safe_notify(sub: Callable[[DebuggerEvent], Awaitable[None]], event: DebuggerEvent) -> None: try: await sub(event) except Exception as e: debug_log(f"Event subscriber error: {e}")
[docs] def get_recent_events(self, limit: int = 50, event_type: Optional[str] = None) -> List[DebuggerEvent]: """Return recent events, optionally filtered by type.""" events = list(self.event_log) if event_type: events = [e for e in events if e.event_type == event_type] return events[-limit:]
# ------------------------------------------------------------------ # Background PTY monitor (Phase 2) # ------------------------------------------------------------------
[docs] def start_monitor(self) -> None: """Start the background PTY monitor task. The monitor reads PTY output when no command is actively executing, classifying and recording debugger events (breakpoints, signals, etc.). This is a best-effort system — if the monitor fails, it does not affect normal command execution. """ if self._monitor_task and not self._monitor_task.done(): return # already running self._monitor_active = True self._monitor_task = asyncio.ensure_future(self._background_monitor()) debug_log(f"Background monitor started for session {self.id}")
[docs] def stop_monitor(self) -> None: """Stop the background PTY monitor.""" self._monitor_active = False if self._monitor_task and not self._monitor_task.done(): self._monitor_task.cancel() debug_log(f"Background monitor stopped for session {self.id}")
async def _background_monitor(self) -> None: """Background loop that reads stray PTY output between commands. This catches events like async breakpoint hits or signals that occur when no tool call is actively reading the PTY. Output is classified into DebuggerEvents and appended to the event log. """ while self._monitor_active and self.master_fd is not None: try: if self.process and self.process.returncode is not None: # Process died — record it and stop event = DebuggerEvent( timestamp=time.time(), event_type="exited", summary=f"LLDB process terminated with code {self.process.returncode}", raw_output="", ) self.event_log.append(event) for sub in self._event_subscribers: asyncio.ensure_future(self._safe_notify(sub, event)) break try: chunk = os.read(self.master_fd, 4096) if chunk: decoded = chunk.decode("utf-8", errors="replace") debug_log(f"[monitor] Read {len(chunk)} bytes: {decoded[:80]!r}") event = classify_debugger_output(decoded) if event: self.event_log.append(event) for sub in self._event_subscribers: asyncio.ensure_future(self._safe_notify(sub, event)) except BlockingIOError: pass # no data — normal except OSError: break # fd closed except asyncio.CancelledError: break except Exception as e: debug_log(f"[monitor] Error: {e}") await asyncio.sleep(0.5) debug_log(f"Background monitor exited for session {self.id}")
[docs] async def cleanup(self): """Clean up the LLDB+pwndbg session and all associated resources.""" debug_log("Cleaning up pwndbg session") # Stop background monitor first self.stop_monitor() try: if self.master_fd is not None: try: os.write(self.master_fd, b"quit\n") await asyncio.sleep(0.5) except Exception as e: debug_log(f"Error sending quit: {e}") if self.process and self.process.returncode is None: debug_log("Terminating LLDB process") self.process.terminate() try: await asyncio.wait_for(self.process.wait(), 2.0) except asyncio.TimeoutError: debug_log("Force killing LLDB process") self.process.kill() await self.process.wait() if self.master_fd is not None: os.close(self.master_fd) self.master_fd = None if self.slave_fd is not None: os.close(self.slave_fd) self.slave_fd = None except Exception as e: debug_log(f"Error during cleanup: {e}") finally: self.process = None self.ready = False self._event_subscribers.clear() debug_log("pwndbg session cleanup completed")
# --------------------------------------------------------------------------- # Application context & server setup # ---------------------------------------------------------------------------
[docs] @dataclass class AppContext: """Holds all active pwndbg sessions keyed by UUID.""" sessions: Dict[str, PwndbgSession]
[docs] @asynccontextmanager async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]: """Manage application lifecycle — create context on startup, clean up on shutdown.""" sessions: Dict[str, PwndbgSession] = {} try: yield AppContext(sessions=sessions) finally: for session_id, session in list(sessions.items()): await session.cleanup() sessions.pop(session_id, None)
mcp = FastMCP("pwndbg-lldb-mcp", lifespan=app_lifespan)
[docs] def get_session(ctx: Context, session_id: str) -> PwndbgSession: """Retrieve a session by ID or raise ValueError if not found.""" sessions = ctx.request_context.lifespan_context.sessions if session_id not in sessions: raise ValueError(f"No active pwndbg session with ID: {session_id}") return sessions[session_id]
def _get_sessions_from_lifespan(ctx: Context) -> Dict[str, PwndbgSession]: """Get the sessions dict from any context.""" return ctx.request_context.lifespan_context.sessions # --------------------------------------------------------------------------- # Phase 2: MCP Resources for debugger event streams # ---------------------------------------------------------------------------
[docs] @mcp.resource("debugger://sessions") def resource_sessions_list(ctx: Context) -> str: """List all active debugging sessions as a JSON resource.""" try: sessions = _get_sessions_from_lifespan(ctx) result = [] for sid, s in sessions.items(): result.append({ "session_id": sid, "target": s.target, "working_dir": s.working_dir, "ready": s.ready, "event_count": len(s.event_log), "monitor_active": s._monitor_active, }) return json.dumps(result, indent=2) except Exception: return json.dumps([])
[docs] @mcp.resource("debugger://session/{session_id}/events") def resource_session_events(session_id: str, ctx: Context) -> str: """Recent debugger events for a session (breakpoints, signals, crashes, exits). Returns the last 50 events as a JSON array. Each event includes: timestamp, event_type, summary, and raw_output. Subscribe to this resource to receive notifications when new events occur. """ try: session = get_session(ctx, session_id) events = session.get_recent_events(limit=50) return json.dumps([e.to_dict() for e in events], indent=2) except (ValueError, Exception): return json.dumps([])
[docs] @mcp.resource("debugger://session/{session_id}/state") def resource_session_state(session_id: str, ctx: Context) -> str: """Current state of a debugging session. Returns session metadata including: target, working directory, process status, last output excerpt, and recent event summary. """ try: session = get_session(ctx, session_id) process_alive = ( session.process is not None and session.process.returncode is None ) recent_events = session.get_recent_events(limit=5) state = { "session_id": session.id, "target": session.target, "working_dir": session.working_dir, "ready": session.ready, "process_alive": process_alive, "process_returncode": session.process.returncode if session.process else None, "monitor_active": session._monitor_active, "total_events": len(session.event_log), "recent_events": [ {"event_type": e.event_type, "summary": e.summary, "timestamp": e.timestamp} for e in recent_events ], "last_output_excerpt": session._last_output[:500] if session._last_output else None, } return json.dumps(state, indent=2) except (ValueError, Exception): return json.dumps({"error": f"Session {session_id} not found"})
async def _notify_resource_updated(ctx: Context, uri: str) -> None: """Send a resource-updated notification to the client (best-effort). This is a no-op if the client doesn't support resource subscriptions. """ try: await ctx.session.send_resource_updated(uri) except Exception: pass # resource notifications are optional async def _notify_session_resources(ctx: Context, session_id: str) -> None: """Notify that a session's event and state resources have been updated.""" await _notify_resource_updated(ctx, f"debugger://session/{session_id}/events") await _notify_resource_updated(ctx, f"debugger://session/{session_id}/state") # --------------------------------------------------------------------------- # Phase 2: Event query tool # ---------------------------------------------------------------------------
[docs] @mcp.tool() async def pwndbg_get_events( ctx: Context, session_id: str, limit: int = 50, event_type: str = None, ) -> str: """Query debugger events recorded for a session. Returns events such as breakpoint hits, signals, crashes, and process exits detected during command execution or by the background monitor. Args: session_id: The UUID of the session. limit: Maximum number of events to return (default 50). event_type: Filter by event type (e.g. "breakpoint_hit", "signal", "crash", "exited", "stopped"). None returns all types. """ try: session = get_session(ctx, session_id) events = session.get_recent_events(limit=limit, event_type=event_type) if not events: return "No events recorded." result = json.dumps([e.to_dict() for e in events], indent=2) return f"Events ({len(events)}):\n{result}" except ValueError as e: return str(e) except Exception as e: return f"Failed to get events: {e}"
[docs] @mcp.tool() async def pwndbg_start_monitor(ctx: Context, session_id: str) -> str: """Start the background event monitor for a session. The monitor watches for asynchronous debugger events (breakpoint hits, signals, crashes) between tool calls. Events are recorded in the session's event log and trigger MCP resource-updated notifications. This is optional — all core debugging functionality works without the monitor. The monitor is useful for long-running programs where events may occur outside of active tool calls. Args: session_id: The UUID of the session. """ try: session = get_session(ctx, session_id) # Register a subscriber that sends resource-updated notifications async def notify_on_event(event: DebuggerEvent) -> None: try: await ctx.session.send_resource_updated( f"debugger://session/{session_id}/events" ) await ctx.session.send_resource_updated( f"debugger://session/{session_id}/state" ) except Exception: pass # notifications are best-effort session._event_subscribers.append(notify_on_event) session.start_monitor() return f"Background monitor started for session {session_id}. Events will be recorded and resource notifications sent." except ValueError as e: return str(e) except Exception as e: return f"Failed to start monitor: {e}"
[docs] @mcp.tool() async def pwndbg_stop_monitor(ctx: Context, session_id: str) -> str: """Stop the background event monitor for a session. Args: session_id: The UUID of the session. """ try: session = get_session(ctx, session_id) session.stop_monitor() return f"Background monitor stopped for session {session_id}. {len(session.event_log)} events recorded." except ValueError as e: return str(e) except Exception as e: return f"Failed to stop monitor: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ SESSION MANAGEMENT TOOLS ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_start( ctx: Context, lldb_path: str = "lldb", working_dir: str = None, pwndbg_path: str = None, ) -> str: """Start a new LLDB session with pwndbg loaded. Spawns an LLDB process, optionally loads pwndbg via `command script import`, and returns a session ID for subsequent commands. Args: lldb_path: Path to the LLDB binary (default: "lldb"). working_dir: Working directory for the session. pwndbg_path: Path to pwndbg's lldbinit.py entry point. If provided, pwndbg will be loaded automatically via `command script import`. See: https://pwndbg.re/2025.05.30/reference/pwndbg/dbg/lldb/ """ session_id = str(uuid.uuid4()) debug_log(f"Starting new session: {session_id}") try: # Verify lldb is reachable try: proc = await asyncio.create_subprocess_exec( lldb_path, "--version", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=5.0) if proc.returncode != 0: return f"Failed to start: invalid lldb path '{lldb_path}'. {stderr.decode('utf-8', errors='replace').strip()}" except asyncio.TimeoutError: return f"Failed to start: timeout verifying lldb path '{lldb_path}'" except Exception as e: return f"Failed to start: invalid lldb path '{lldb_path}'. {e}" working_dir = working_dir or os.getcwd() session = PwndbgSession( session_id=session_id, lldb_path=lldb_path, working_dir=working_dir, pwndbg_path=pwndbg_path, ) try: output = await asyncio.wait_for(session.start(), timeout=30.0) except asyncio.TimeoutError: await session.cleanup() return "Failed to start: timeout initializing session" except Exception as e: await session.cleanup() return f"Failed to start: {e}" ctx.request_context.lifespan_context.sessions[session_id] = session return f"pwndbg session started with ID: {session_id}\n\nOutput:\n{output}" except Exception as e: return f"Failed to start: {e}"
[docs] @mcp.tool() async def pwndbg_terminate(ctx: Context, session_id: str) -> str: """Terminate a pwndbg session and free all resources. Args: session_id: The UUID of the session to terminate. """ try: session = get_session(ctx, session_id) await session.cleanup() ctx.request_context.lifespan_context.sessions.pop(session_id, None) return f"pwndbg session terminated: {session_id}" except ValueError as e: return str(e) except Exception as e: return f"Failed to terminate session: {e}"
[docs] @mcp.tool() def pwndbg_list_sessions(ctx: Context) -> str: """List all active pwndbg sessions with their IDs, targets, and working directories.""" sessions = ctx.request_context.lifespan_context.sessions if not sessions: return "No active sessions." info = [] for sid, s in sessions.items(): info.append(f" {sid} target={s.target or 'none'} cwd={s.working_dir}") return f"Active sessions ({len(sessions)}):\n" + "\n".join(info)
[docs] @mcp.tool() async def pwndbg_command(ctx: Context, session_id: str, command: str) -> str: """Execute an arbitrary LLDB or pwndbg command (escape hatch). Use this for any command not exposed as a dedicated tool. Both native LLDB commands and pwndbg-registered commands are supported. Args: session_id: The UUID of the session. command: The full command string to execute. See: https://pwndbg.re/2025.05.30/reference/pwndbg/dbg/lldb/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(command) return f"Command: {command}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed to execute command: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ PROGRAM LOADING TOOLS ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_load( ctx: Context, session_id: str, program: str, arguments: List[str] = None ) -> str: """Load a program into the debugger. Sets the target executable and optional arguments. The program path is resolved relative to the session's working directory if not absolute. Args: session_id: The UUID of the session. program: Path to the executable. arguments: Optional list of program arguments. """ try: session = get_session(ctx, session_id) if session.working_dir and not os.path.isabs(program): program = os.path.join(session.working_dir, program) output = await session.execute_command(f'file "{program}"') if arguments: args_str = " ".join(f'"{arg}"' for arg in arguments) args_output = await session.execute_command( f"settings set -- target.run-args {args_str}" ) output += f"\n{args_output}" session.target = program return f"Program loaded: {program}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed to load program: {e}"
[docs] @mcp.tool() async def pwndbg_attach(ctx: Context, session_id: str, pid: int) -> str: """Attach to a running process by PID. Args: session_id: The UUID of the session. pid: Process ID to attach to. """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"process attach -p {pid}") return f"Attached to process {pid}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed to attach: {e}"
[docs] @mcp.tool() async def pwndbg_load_core( ctx: Context, session_id: str, program: str, core_path: str ) -> str: """Load a core dump file for post-mortem analysis. Args: session_id: The UUID of the session. program: Path to the executable that generated the core. core_path: Path to the core dump file. """ try: session = get_session(ctx, session_id) file_output = await session.execute_command(f'file "{program}"') core_output = await session.execute_command(f'target core "{core_path}"') bt_output = await session.execute_command("bt") return ( f"Core loaded: {core_path}\n\nOutput:\n{file_output}\n{core_output}" f"\n\nBacktrace:\n{bt_output}" ) except ValueError as e: return str(e) except Exception as e: return f"Failed to load core: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ EXECUTION CONTROL TOOLS ║ # ║ ║ # ║ pwndbg execution commands: ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/start/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/next/ ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_run(ctx: Context, session_id: str, timeout: float = 30.0) -> str: """Run the loaded program from the beginning. Blocks until the process stops (breakpoint, signal, exit) or times out. Streams intermediate debugger output as MCP progress/log notifications. Args: session_id: The UUID of the session. timeout: Maximum seconds to wait for the process to stop (default 30). """ try: session = get_session(ctx, session_id) callback = make_progress_callback(ctx) output = await session.execute_command_with_progress("run", callback, timeout) await _notify_session_resources(ctx, session_id) return f"Running program\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed to run: {e}"
[docs] @mcp.tool() async def pwndbg_entry(ctx: Context, session_id: str, timeout: float = 30.0) -> str: """Start the program and stop at its ELF entry point address. pwndbg command: entry Source: pwndbg/commands/start.py Category: Start Unlike 'start' (GDB-only), 'entry' works on LLDB. It sets a temporary breakpoint at the binary's entry point and runs. Args: session_id: The UUID of the session. timeout: Maximum seconds to wait (default 30). """ try: session = get_session(ctx, session_id) callback = make_progress_callback(ctx) output = await session.execute_command_with_progress("entry", callback, timeout) await _notify_session_resources(ctx, session_id) return f"Stopped at entry point\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed to run to entry: {e}"
[docs] @mcp.tool() async def pwndbg_continue(ctx: Context, session_id: str, timeout: float = 30.0) -> str: """Continue program execution until the next breakpoint or exit. Blocks until the process stops or times out. Streams intermediate debugger output as MCP progress/log notifications. Args: session_id: The UUID of the session. timeout: Maximum seconds to wait for the process to stop (default 30). """ try: session = get_session(ctx, session_id) callback = make_progress_callback(ctx) output = await session.execute_command_with_progress("continue", callback, timeout) await _notify_session_resources(ctx, session_id) return f"Continued execution\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed to continue: {e}"
[docs] @mcp.tool() async def pwndbg_step(ctx: Context, session_id: str, instructions: bool = False) -> str: """Step into the next source line or instruction. Args: session_id: The UUID of the session. instructions: If True, step a single machine instruction (si) instead of a source line (s). """ try: session = get_session(ctx, session_id) cmd = "si" if instructions else "s" output = await session.execute_command(cmd) kind = "instruction" if instructions else "line" return f"Stepped {kind}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed to step: {e}"
[docs] @mcp.tool() async def pwndbg_next(ctx: Context, session_id: str, instructions: bool = False) -> str: """Step over the next source line or instruction (does not enter calls). Args: session_id: The UUID of the session. instructions: If True, step over a single machine instruction (ni) instead of a source line (n). """ try: session = get_session(ctx, session_id) cmd = "ni" if instructions else "n" output = await session.execute_command(cmd) kind = "instruction" if instructions else "function call" return f"Stepped over {kind}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed to step over: {e}"
[docs] @mcp.tool() async def pwndbg_finish(ctx: Context, session_id: str, timeout: float = 30.0) -> str: """Execute until the current function returns. Blocks until the function returns or times out. Streams intermediate debugger output as MCP progress/log notifications. Args: session_id: The UUID of the session. timeout: Maximum seconds to wait (default 30). """ try: session = get_session(ctx, session_id) callback = make_progress_callback(ctx) output = await session.execute_command_with_progress("finish", callback, timeout) return f"Finished current function\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed to finish: {e}"
[docs] @mcp.tool() async def pwndbg_kill(ctx: Context, session_id: str) -> str: """Kill the running process. Args: session_id: The UUID of the session. """ try: session = get_session(ctx, session_id) output = await session.execute_command("process kill") return f"Killed process\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed to kill: {e}"
[docs] @mcp.tool() async def pwndbg_nextjmp(ctx: Context, session_id: str, timeout: float = 30.0) -> str: """Break at the next jump instruction. pwndbg command: nextjmp (alias: nextjump) Source: pwndbg/commands/next.py Category: Step/Next/Continue Continues execution until the next jump-type instruction (jmp, je, jne, etc.) is reached, which is useful for tracing control flow decisions. Args: session_id: The UUID of the session. timeout: Maximum seconds to wait (default 30). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/next/ """ try: session = get_session(ctx, session_id) callback = make_progress_callback(ctx) output = await session.execute_command_with_progress("nextjmp", callback, timeout) return f"Stopped at next jump\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_nextcall( ctx: Context, session_id: str, symbol_regex: str = None, timeout: float = 30.0 ) -> str: """Break at the next call instruction, optionally filtered by symbol regex. pwndbg command: nextcall Source: pwndbg/commands/next.py Category: Step/Next/Continue Args: session_id: The UUID of the session. symbol_regex: Optional regex to match the call target symbol name. timeout: Maximum seconds to wait (default 30). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/next/ """ try: session = get_session(ctx, session_id) cmd = "nextcall" if symbol_regex: cmd += f" {symbol_regex}" callback = make_progress_callback(ctx) output = await session.execute_command_with_progress(cmd, callback, timeout) return f"Stopped at next call\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_nextret(ctx: Context, session_id: str, timeout: float = 30.0) -> str: """Break at the next return-like instruction (ret, retf, iret, sysret). pwndbg command: nextret Source: pwndbg/commands/next.py Category: Step/Next/Continue Args: session_id: The UUID of the session. timeout: Maximum seconds to wait (default 30). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/next/ """ try: session = get_session(ctx, session_id) callback = make_progress_callback(ctx) output = await session.execute_command_with_progress("nextret", callback, timeout) return f"Stopped at next return\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_stepret(ctx: Context, session_id: str, timeout: float = 30.0) -> str: """Step to the next return instruction (single-steps until ret is found). pwndbg command: stepret Source: pwndbg/commands/next.py Category: Step/Next/Continue Unlike nextret which sets a breakpoint, this command single-steps through every instruction until a return instruction is reached. Args: session_id: The UUID of the session. timeout: Maximum seconds to wait (default 30). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/next/ """ try: session = get_session(ctx, session_id) callback = make_progress_callback(ctx) output = await session.execute_command_with_progress("stepret", callback, timeout) return f"Stepped to return\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_nextproginstr(ctx: Context, session_id: str, timeout: float = 30.0) -> str: """Break at the next instruction belonging to the running program. pwndbg command: nextproginstr Source: pwndbg/commands/next.py Category: Step/Next/Continue Useful for skipping over library code to reach the next instruction that belongs to the main binary. Args: session_id: The UUID of the session. timeout: Maximum seconds to wait (default 30). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/next/ """ try: session = get_session(ctx, session_id) callback = make_progress_callback(ctx) output = await session.execute_command_with_progress("nextproginstr", callback, timeout) return f"Stopped at next program instruction\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_stepover(ctx: Context, session_id: str, addr: int = None, timeout: float = 30.0) -> str: """Set a breakpoint on the instruction after the current one and continue. pwndbg command: stepover (alias: so) Source: pwndbg/commands/next.py Category: Step/Next/Continue This is pwndbg's enhanced step-over that works at the instruction level by setting a breakpoint on the next instruction address. Args: session_id: The UUID of the session. addr: Optional address to step over at (defaults to current PC). timeout: Maximum seconds to wait (default 30). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/next/ """ try: session = get_session(ctx, session_id) cmd = "stepover" if addr is not None: cmd += f" {hex(addr)}" callback = make_progress_callback(ctx) output = await session.execute_command_with_progress(cmd, callback, timeout) return f"Stepped over instruction\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_nextsyscall(ctx: Context, session_id: str, timeout: float = 30.0) -> str: """Break at the next syscall instruction (without taking branches). pwndbg command: nextsyscall (alias: nextsc) Source: pwndbg/commands/next.py Category: Step/Next/Continue Args: session_id: The UUID of the session. timeout: Maximum seconds to wait (default 30). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/next/ """ try: session = get_session(ctx, session_id) callback = make_progress_callback(ctx) output = await session.execute_command_with_progress("nextsyscall", callback, timeout) return f"Stopped at next syscall\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_stepsyscall(ctx: Context, session_id: str, timeout: float = 30.0) -> str: """Step to the next syscall instruction (follows branches). pwndbg command: stepsyscall (alias: stepsc) Source: pwndbg/commands/next.py Category: Step/Next/Continue Unlike nextsyscall, this follows branches by single-stepping through all instructions until a syscall is found. Args: session_id: The UUID of the session. timeout: Maximum seconds to wait (default 30). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/next/ """ try: session = get_session(ctx, session_id) callback = make_progress_callback(ctx) output = await session.execute_command_with_progress("stepsyscall", callback, timeout) return f"Stepped to next syscall\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_stepuntilasm( ctx: Context, session_id: str, mnemonic: str, op_str: str = "", timeout: float = 60.0 ) -> str: """Step until a specific assembly instruction is reached. pwndbg command: stepuntilasm Source: pwndbg/commands/next.py Category: Step/Next/Continue Single-steps until an instruction matching the given mnemonic (and optionally operand string) is found. This can be slow, so the default timeout is 60s. Args: session_id: The UUID of the session. mnemonic: The instruction mnemonic to match (e.g. "syscall", "call", "mov"). op_str: Optional operand string to match. timeout: Maximum seconds to wait (default 60). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/next/ """ try: session = get_session(ctx, session_id) cmd = f"stepuntilasm {mnemonic}" if op_str: cmd += f" {op_str}" callback = make_progress_callback(ctx) output = await session.execute_command_with_progress(cmd, callback, timeout) return f"Stepped to matching instruction: {mnemonic} {op_str}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ BREAKPOINT & WATCHPOINT TOOLS ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_set_breakpoint( ctx: Context, session_id: str, location: str, condition: str = None ) -> str: """Set a breakpoint at the given location, optionally with a condition. Args: session_id: The UUID of the session. location: Function name, address, or file:line to break at. condition: Optional condition expression for the breakpoint. """ try: session = get_session(ctx, session_id) output = await session.execute_command(f'breakpoint set --name "{location}"') if condition: match = re.search(r"Breakpoint (\d+):", output) if match: bp_num = match.group(1) cond_out = await session.execute_command( f'breakpoint modify -c "{condition}" {bp_num}' ) output += f"\n{cond_out}" return f"Breakpoint set at: {location}{' with condition: ' + condition if condition else ''}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed to set breakpoint: {e}"
[docs] @mcp.tool() async def pwndbg_breakpoint_list(ctx: Context, session_id: str) -> str: """List all breakpoints in the current session. Args: session_id: The UUID of the session. """ try: session = get_session(ctx, session_id) output = await session.execute_command("breakpoint list") return f"Breakpoints:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed to list breakpoints: {e}"
[docs] @mcp.tool() async def pwndbg_breakpoint_delete( ctx: Context, session_id: str, breakpoint_id: int ) -> str: """Delete a breakpoint by its ID number. Args: session_id: The UUID of the session. breakpoint_id: The numeric breakpoint ID to delete. """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"breakpoint delete {breakpoint_id}") return f"Deleted breakpoint {breakpoint_id}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed to delete breakpoint: {e}"
# --------------------------------------------------------------------------- # Enhanced breakpoint, batch, and run-until-stop tools # ---------------------------------------------------------------------------
[docs] @mcp.tool() async def pwndbg_set_breakpoint_advanced( ctx: Context, session_id: str, address: str = None, name: str = None, module: str = None, offset: str = None, auto_continue: bool = False, bp_name: str = None, condition: str = None, one_shot: bool = False, ) -> str: """Set a breakpoint with full control over address, module, auto-continue, and naming. Supports all common breakpoint styles: - By function name: name="CreateXmlReader" - By address: address="0x10027cacc" - By module+offset: module="Xmllite", offset="0x3cf8" - By file+line: address="file.c:42" Args: session_id: The UUID of the session. address: Address or file:line expression. Mutually exclusive with name. name: Symbol name to break on. Mutually exclusive with address. module: Restrict breakpoint to this module (e.g., "Xmllite"). offset: Module-relative offset (hex string). Requires module to be set. auto_continue: If True, breakpoint auto-continues (counts hits without stopping). bp_name: Assign a human-readable name to the breakpoint for later reference. condition: Optional condition expression (breakpoint only fires if true). one_shot: If True, breakpoint is deleted after first hit. """ try: session = get_session(ctx, session_id) # Build the breakpoint set command if module and offset: cmd = f"breakpoint set -s {module} -a {offset}" elif address: cmd = f"breakpoint set -a {address}" elif name: cmd = f"breakpoint set -n {name}" else: return "Error: must specify address, name, or module+offset" if auto_continue: cmd += " --auto-continue true" if one_shot: cmd += " --one-shot true" output = await session.execute_command(cmd) # Extract breakpoint ID from output bp_id = None match = re.search(r"Breakpoint (\d+):", output) if match: bp_id = int(match.group(1)) # Apply name and condition if breakpoint was created if bp_id is not None: if bp_name: name_out = await session.execute_command( f"breakpoint modify -N {bp_name} {bp_id}" ) output += f"\n{name_out}" if condition: cond_out = await session.execute_command( f'breakpoint modify -c "{condition}" {bp_id}' ) output += f"\n{cond_out}" return f"Breakpoint created (id={bp_id})\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed to set breakpoint: {e}"
[docs] @mcp.tool() async def pwndbg_breakpoint_list_parsed(ctx: Context, session_id: str) -> str: """List all breakpoints with structured, machine-parseable output. Returns a JSON-formatted list of breakpoints, each with: id, name, address, module, resolved, hit_count, auto_continue, enabled This is more reliable than parsing raw `breakpoint list` text output, especially for module-relative breakpoints which have inconsistent formatting across lldb versions. Args: session_id: The UUID of the session. """ try: session = get_session(ctx, session_id) raw = await session.execute_command("breakpoint list -f") breakpoints = [] current_bp = None for line in raw.split("\n"): line = line.strip() # Match top-level breakpoint line: # 1: name = 'X', locations = N, resolved = N, hit count = N # 1: address = Module[0x1234], locations = N ... bp_match = re.match( r"^(\d+):\s+(.*?)(?:,\s+locations?\s*=\s*(\d+))?" r"(?:,\s+resolved\s*=\s*(\d+))?" r"(?:,\s+hit count\s*=\s*(\d+))?", line, ) if bp_match: if current_bp: breakpoints.append(current_bp) bp_id = int(bp_match.group(1)) rest = bp_match.group(2) or "" current_bp = { "id": bp_id, "description": rest.strip(), "locations": int(bp_match.group(3)) if bp_match.group(3) else 0, "resolved": int(bp_match.group(4)) if bp_match.group(4) else 0, "hit_count": int(bp_match.group(5)) if bp_match.group(5) else 0, "auto_continue": "auto-continue" in line, "enabled": "disabled" not in line, "addresses": [], } # Extract name from description if present name_match = re.search(r"name\s*=\s*'([^']*)'", rest) if name_match: current_bp["name"] = name_match.group(1) continue # Match location sub-line: # 1.1: where = Module`Symbol, address = 0x..., resolved, hit count = N loc_match = re.match( r"^\d+\.\d+:\s+(?:where\s*=\s*(.*?),\s+)?" r"address\s*=\s*(0x[0-9a-fA-F]+)" r"(?:.*hit count\s*=\s*(\d+))?", line, ) if loc_match and current_bp: loc = { "where": loc_match.group(1) or "", "address": loc_match.group(2), "hit_count": int(loc_match.group(3)) if loc_match.group(3) else 0, } current_bp["addresses"].append(loc) # Use location hit count if top-level is 0 if current_bp["hit_count"] == 0 and loc["hit_count"] > 0: current_bp["hit_count"] = loc["hit_count"] if current_bp: breakpoints.append(current_bp) import json result = json.dumps(breakpoints, indent=2) return f"Breakpoints ({len(breakpoints)} total):\n\n{result}" except ValueError as e: return str(e) except Exception as e: return f"Failed to list breakpoints: {e}"
[docs] @mcp.tool() async def pwndbg_command_batch( ctx: Context, session_id: str, commands: list[str] ) -> str: """Execute multiple LLDB/pwndbg commands sequentially in a single call. This avoids multiple MCP round-trips for setup operations like setting several breakpoints, configuring settings, or running a sequence of inspection commands. Args: session_id: The UUID of the session. commands: List of command strings to execute in order. Returns: Combined output from all commands, with clear separators. """ try: session = get_session(ctx, session_id) results = [] for i, cmd in enumerate(commands): output = await session.execute_command(cmd) results.append(f"[{i+1}/{len(commands)}] {cmd}\n{output}") return "\n".join(results) except ValueError as e: return str(e) except Exception as e: return f"Failed in batch execution: {e}"
[docs] @mcp.tool() async def pwndbg_run_until_stop( ctx: Context, session_id: str, action: str = "run", timeout: float = 30.0, ) -> str: """Run or continue the program and block until it stops or exits. Waits until the process reaches a stopped state (breakpoint, crash, signal) or exits, then returns the stop reason along with register context. Streams intermediate debugger output as MCP progress/log notifications so the client can observe execution in real-time. Args: session_id: The UUID of the session. action: "run" to start from beginning, "continue" to resume. timeout: Maximum seconds to wait for the process to stop. """ try: session = get_session(ctx, session_id) callback = make_progress_callback(ctx) effective_timeout = max(timeout + 5.0, session.COMMAND_TIMEOUT) # Execute the run/continue command with progress streaming output = await session.execute_command_with_progress( action, callback, effective_timeout ) # Check if the output indicates the process is still running # (some prompts return before the process stops) if "Process" in output and "running" in output.lower(): extra = await session._read_until_prompt_with_callback( callback, effective_timeout ) output += extra # Now gather state info if the process stopped (not exited) if "stop reason" in output or "stopped" in output.lower(): bt_output = await session.execute_command("bt -c 5") reg_output = await session.execute_command( "register read x0 x1 x2 x3 pc lr sp" ) output += f"\n\nBacktrace:\n{bt_output}\n\nRegisters:\n{reg_output}" await _notify_session_resources(ctx, session_id) return f"Process state after '{action}':\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed to {action}: {e}"
[docs] @mcp.tool() async def pwndbg_watchpoint( ctx: Context, session_id: str, expression: str, watch_type: str = "write" ) -> str: """Set a watchpoint on a memory address or variable. Args: session_id: The UUID of the session. expression: The variable or address expression to watch. watch_type: Type of access to watch — "read", "write", or "read_write". """ try: session = get_session(ctx, session_id) watch_options = {"read": "r", "write": "w", "read_write": "rw"} option = watch_options.get(watch_type, "w") output = await session.execute_command( f"watchpoint set expression -- {expression} -w {option}" ) return f"Watchpoint set on {expression} (type: {watch_type})\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed to set watchpoint: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ CONTEXT & DISPLAY TOOLS ║ # ║ ║ # ║ pwndbg's context display is the flagship feature — it shows registers, ║ # ║ disassembly, stack, backtrace, and more in a unified view. ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/context/ ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_context( ctx: Context, session_id: str, sections: str = None ) -> str: """Display the pwndbg context — registers, disassembly, stack, backtrace, etc. pwndbg command: context (alias: ctx) Source: pwndbg/commands/context.py Category: Context This is pwndbg's signature command. It shows a unified view of the current debugger state including registers, nearby disassembly, stack contents, backtrace, and any additional context sections configured by the user. Args: session_id: The UUID of the session. sections: Optional space-separated list of sections to show (e.g. "regs disasm stack backtrace"). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/context/ """ try: session = get_session(ctx, session_id) cmd = "context" if sections: cmd += f" {sections}" output = await session.execute_command(cmd) return f"Context:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed to get context: {e}"
[docs] @mcp.tool() async def pwndbg_contextoutput( ctx: Context, session_id: str, section: str, value: str ) -> str: """Configure where a context section's output is sent. pwndbg command: contextoutput (alias: ctx-out) Source: pwndbg/commands/context.py Category: Context Args: session_id: The UUID of the session. section: The context section name (e.g. "regs", "disasm", "stack"). value: The output target or configuration value. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/context/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"contextoutput {section} {value}") return f"Context output configured: {section}{value}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_contextwatch( ctx: Context, session_id: str, expression: str, cmd: str = None ) -> str: """Add a watched expression to the context display. pwndbg command: contextwatch (alias: ctx-watch, cwatch) Source: pwndbg/commands/context.py Category: Context Adds an expression that will be evaluated and displayed every time the context refreshes. Args: session_id: The UUID of the session. expression: The expression to watch (e.g. "$rax", "*(int*)$rsp"). cmd: Optional command to use for display (e.g. "hexdump", "telescope"). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/context/ """ try: session = get_session(ctx, session_id) command = f"contextwatch {expression}" if cmd: command += f" --cmd {cmd}" output = await session.execute_command(command) return f"Added context watch: {expression}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_contextunwatch(ctx: Context, session_id: str, num: int) -> str: """Remove a watched expression from the context display. pwndbg command: contextunwatch (alias: ctx-unwatch, cunwatch) Source: pwndbg/commands/context.py Category: Context Args: session_id: The UUID of the session. num: The watch number to remove. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/context/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"contextunwatch {num}") return f"Removed context watch {num}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ MEMORY INSPECTION TOOLS ║ # ║ ║ # ║ pwndbg memory commands: ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/telescope/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/hexdump/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/vmmap/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/search/ ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_telescope( ctx: Context, session_id: str, address: str = None, count: int = None, reverse: bool = False, ) -> str: """Recursively dereference pointers starting at an address (default: $sp). pwndbg command: telescope Source: pwndbg/commands/telescope.py Category: Memory Telescope is one of pwndbg's most useful commands. It reads pointer-sized values from memory and follows the chain of dereferences, showing the ultimate value (string, address, symbol, etc.). Args: session_id: The UUID of the session. address: Starting address or register (default: $sp). count: Number of pointer-sized entries to show. reverse: If True, show entries in reverse order. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/telescope/ """ try: session = get_session(ctx, session_id) cmd = "telescope" if address: cmd += f" {address}" if count is not None: cmd += f" {count}" if reverse: cmd += " --reverse" output = await session.execute_command(cmd) return f"Telescope{' at ' + address if address else ''}:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_stack( ctx: Context, session_id: str, count: int = 8, offset: int = 0 ) -> str: """Show stack contents using telescope-style dereference display. pwndbg command: stack Source: pwndbg/commands/telescope.py Category: Stack Equivalent to `telescope $sp` with extra stack-aware formatting. Args: session_id: The UUID of the session. count: Number of entries to show (default: 8). offset: Offset from $sp in pointer-sized units. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/telescope/ """ try: session = get_session(ctx, session_id) cmd = f"stack {count} {offset}" output = await session.execute_command(cmd) return f"Stack:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_stackf(ctx: Context, session_id: str) -> str: """Show the entire current stack frame contents. pwndbg command: stackf Source: pwndbg/commands/telescope.py Category: Stack Dereferences the stack from $sp to $bp, showing the entire current stack frame. Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/telescope/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("stackf") return f"Stack frame:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_hexdump( ctx: Context, session_id: str, address: str = None, count: int = None, code: str = None, ) -> str: """Hex dump memory at the specified address. pwndbg command: hexdump Source: pwndbg/commands/hexdump.py Category: Memory Shows memory in canonical hex+ASCII format, similar to xxd/hexdump. Args: session_id: The UUID of the session. address: Address or register to dump from (default: $sp). count: Number of bytes to dump. code: Output format — "py" for Python bytes literal, "c" for C array. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/hexdump/ """ try: session = get_session(ctx, session_id) cmd = "hexdump" if address: cmd += f" {address}" if count is not None: cmd += f" {count}" if code: cmd += f" --code {code}" output = await session.execute_command(cmd) return f"Hexdump{' at ' + address if address else ''}:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_vmmap( ctx: Context, session_id: str, filter_str: str = None, writable: bool = False, executable: bool = False, ) -> str: """Print the virtual memory map of the process. pwndbg command: vmmap (aliases: lm, address, vprot, libs) Source: pwndbg/commands/vmmap.py Category: Memory Shows all memory regions with their start/end addresses, permissions, and mapped file names. Can be filtered by address, module name, or permission flags. Args: session_id: The UUID of the session. filter_str: Optional filter — an address or module name substring. writable: If True, only show writable regions. executable: If True, only show executable regions. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/vmmap/ """ try: session = get_session(ctx, session_id) cmd = "vmmap" if filter_str: cmd += f" {filter_str}" if writable: cmd += " --writable" if executable: cmd += " --executable" output = await session.execute_command(cmd) return f"Virtual memory map:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_xinfo(ctx: Context, session_id: str, address: str = None) -> str: """Show extended information about an address — offsets from useful locations. pwndbg command: xinfo Source: pwndbg/commands/xinfo.py Category: Memory Displays what memory region the address belongs to, along with offsets from the base of the containing page, binary, stack, heap, etc. Args: session_id: The UUID of the session. address: Address to inspect (default: $pc). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/xinfo/ """ try: session = get_session(ctx, session_id) cmd = "xinfo" if address: cmd += f" {address}" output = await session.execute_command(cmd) return f"Extended info:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_distance( ctx: Context, session_id: str, a: str, b: str = None ) -> str: """Calculate the distance between two addresses. pwndbg command: distance Source: pwndbg/commands/distance.py Category: Memory If only one argument is given, prints the offset from the address's page base. Useful for calculating offsets for exploits. Args: session_id: The UUID of the session. a: First address or expression. b: Optional second address or expression. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/distance/ """ try: session = get_session(ctx, session_id) cmd = f"distance {a}" if b: cmd += f" {b}" output = await session.execute_command(cmd) return f"Distance:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_probeleak( ctx: Context, session_id: str, address: str = None, count: int = 0x40, max_distance: int = 0, ) -> str: """Pointer-scan memory for possible information leaks. pwndbg command: probeleak Source: pwndbg/commands/probeleak.py Category: Memory Scans memory at the given address for values that look like pointers into known regions (stack, heap, libc, binary, etc.), which could indicate exploitable information leaks. Args: session_id: The UUID of the session. address: Address to start scanning (default: $sp). count: Number of bytes to scan (default: 0x40). max_distance: Maximum distance for pointer matching (0 = unlimited). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/probeleak/ """ try: session = get_session(ctx, session_id) cmd = "probeleak" if address: cmd += f" {address}" cmd += f" {count}" if max_distance: cmd += f" {max_distance}" output = await session.execute_command(cmd) return f"Leak probe:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_leakfind( ctx: Context, session_id: str, address: str = None, page_name: str = None, max_depth: int = 4, ) -> str: """Attempt to find a pointer leak chain from a starting address. pwndbg command: leakfind Source: pwndbg/commands/leakfind.py Category: Memory Walks pointer chains to find paths from a controlled region to interesting targets (libc, stack, etc.). Extremely useful for exploit development. Args: session_id: The UUID of the session. address: Starting address (default: $sp). page_name: Target page name to reach. max_depth: Maximum chain depth (default: 4). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/leakfind/ """ try: session = get_session(ctx, session_id) cmd = "leakfind" if address: cmd += f" {address}" if page_name: cmd += f" --page-name {page_name}" cmd += f" --max-depth {max_depth}" output = await session.execute_command(cmd) return f"Leak chain search:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_p2p(ctx: Context, session_id: str, mapping_names: str = None) -> str: """Pointer-to-pointer chain search across memory mappings. pwndbg command: p2p Source: pwndbg/commands/p2p.py Category: Memory Finds chains of pointers between different memory regions, useful for discovering pivot chains in exploit development. Args: session_id: The UUID of the session. mapping_names: Optional comma-separated list of mapping name ranges to search. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/p2p/ """ try: session = get_session(ctx, session_id) cmd = "p2p" if mapping_names: cmd += f" {mapping_names}" output = await session.execute_command(cmd) return f"Pointer-to-pointer chains:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_mprotect( ctx: Context, session_id: str, addr: str, length: int, prot: str ) -> str: """Call the mprotect syscall to change memory permissions. pwndbg command: mprotect Source: pwndbg/commands/mprotect.py Category: Memory Directly invokes mprotect(2) on the target process. Useful for making regions writable or executable during exploit development. Args: session_id: The UUID of the session. addr: Address of the memory region. length: Length of the region in bytes. prot: Protection flags (e.g. "7" for rwx, "5" for r-x). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/mprotect/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"mprotect {addr} {length} {prot}") return f"mprotect result:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_mmap( ctx: Context, session_id: str, addr: str = "0", length: int = 0x1000, prot: str = "7", flags: str = "0x22", ) -> str: """Call the mmap syscall to allocate new memory in the target process. pwndbg command: mmap Source: pwndbg/commands/mmap.py Category: Memory Directly invokes mmap(2) on the target. Useful for creating executable shellcode regions or scratch memory during exploitation. Args: session_id: The UUID of the session. addr: Desired address (0 for OS-chosen). length: Size of mapping (default: 0x1000). prot: Protection flags (default: "7" = rwx). flags: mmap flags (default: "0x22" = MAP_PRIVATE | MAP_ANONYMOUS). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/mmap/ """ try: session = get_session(ctx, session_id) output = await session.execute_command( f"mmap {addr} {length} {prot} {flags}" ) return f"mmap result:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ REGISTER & CPU STATE TOOLS ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_info_registers( ctx: Context, session_id: str, register: str = None ) -> str: """Display CPU registers. Args: session_id: The UUID of the session. register: Optional specific register name to read. """ try: session = get_session(ctx, session_id) cmd = "register read" if register: cmd += f" {register}" output = await session.execute_command(cmd) return f"Registers{' (' + register + ')' if register else ''}:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_cpsr(ctx: Context, session_id: str, cpsr_value: str = None) -> str: """Display ARM CPSR / xPSR / PSTATE register bits. pwndbg command: cpsr (aliases: xpsr, pstate) Source: pwndbg/commands/cpsr.py Category: Register Arch: ARM, AArch64 only Decodes the ARM condition flags register into individual flag bits with human-readable names. Args: session_id: The UUID of the session. cpsr_value: Optional CPSR value to decode (default: read from register). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/cpsr/ """ try: session = get_session(ctx, session_id) cmd = "cpsr" if cpsr_value: cmd += f" {cpsr_value}" output = await session.execute_command(cmd) return f"CPSR:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_setflag( ctx: Context, session_id: str, flag: str, value: int ) -> str: """Modify a CPU flag in the flags register. pwndbg command: setflag (alias: flag) Source: pwndbg/commands/flags.py Category: Register Allows setting individual flag bits (ZF, CF, SF, OF, etc.) without modifying the entire flags register. Args: session_id: The UUID of the session. flag: Flag name (e.g. "ZF", "CF", "SF", "OF"). value: Value to set (0 or 1). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/flags/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"setflag {flag} {value}") return f"Set flag {flag}={value}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ DISASSEMBLY TOOLS ║ # ║ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/nearpc/ ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_nearpc( ctx: Context, session_id: str, address: str = None, lines: int = None, emulate: bool = False, ) -> str: """Disassemble instructions near the PC with enhanced annotation. pwndbg command: nearpc (aliases: pdisass, u) Source: pwndbg/commands/nearpc.py Category: Disassemble pwndbg's enhanced disassembler that shows resolved symbols, register values, memory dereferences, and branch target annotations inline with the disassembly. Args: session_id: The UUID of the session. address: Address to disassemble at (default: $pc). lines: Number of instructions to show. emulate: If True, emulate instructions to show predicted register values. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/nearpc/ """ try: session = get_session(ctx, session_id) cmd = "nearpc" if address: cmd += f" {address}" if lines is not None: cmd += f" {lines}" if emulate: cmd += " --force-emulate" output = await session.execute_command(cmd) return f"Disassembly:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_emulate( ctx: Context, session_id: str, address: str = None, lines: int = None ) -> str: """Disassemble with instruction emulation to predict register/memory state. pwndbg command: emulate Source: pwndbg/commands/nearpc.py Category: Disassemble Like nearpc but with emulation enabled by default. Shows what registers and memory values would be after each instruction executes, without actually executing them. Args: session_id: The UUID of the session. address: Address to start emulation (default: $pc). lines: Number of instructions to emulate. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/nearpc/ """ try: session = get_session(ctx, session_id) cmd = "emulate" if address: cmd += f" {address}" if lines is not None: cmd += f" {lines}" output = await session.execute_command(cmd) return f"Emulated disassembly:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_disassemble( ctx: Context, session_id: str, location: str = None, count: int = 10 ) -> str: """Disassemble code using LLDB's native disassembler. Args: session_id: The UUID of the session. location: Function name or address to disassemble. count: Number of instructions (default: 10). """ try: session = get_session(ctx, session_id) cmd = "disassemble" if location: cmd += f" --name {location}" cmd += f" -c {count}" output = await session.execute_command(cmd) return f"Disassembly{' of ' + location if location else ''}:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ STACK & ARGUMENTS TOOLS ║ # ║ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/argv/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/retaddr/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/dumpargs/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/canary/ ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_argc(ctx: Context, session_id: str) -> str: """Print the argument count (argc) of the running program. pwndbg command: argc Source: pwndbg/commands/argv.py Category: Linux/libc/ELF Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/argv/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("argc") return f"Argument count:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_argv(ctx: Context, session_id: str, index: int = None) -> str: """Print the argument vector (argv) of the running program. pwndbg command: argv Source: pwndbg/commands/argv.py Category: Linux/libc/ELF Args: session_id: The UUID of the session. index: Optional specific argv index to print. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/argv/ """ try: session = get_session(ctx, session_id) cmd = "argv" if index is not None: cmd += f" {index}" output = await session.execute_command(cmd) return f"Arguments:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_envp(ctx: Context, session_id: str, name: str = None) -> str: """Print environment variables of the running program. pwndbg command: envp (aliases: env, environ) Source: pwndbg/commands/argv.py Category: Linux/libc/ELF Args: session_id: The UUID of the session. name: Optional specific environment variable name. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/argv/ """ try: session = get_session(ctx, session_id) cmd = "envp" if name: cmd += f" {name}" output = await session.execute_command(cmd) return f"Environment:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_dumpargs(ctx: Context, session_id: str, force: bool = False) -> str: """Dump determined arguments for the current call/syscall instruction. pwndbg command: dumpargs (alias: args) Source: pwndbg/commands/dumpargs.py Category: Misc Automatically detects the calling convention and displays function arguments with their resolved values at the current call site. Args: session_id: The UUID of the session. force: If True, force argument dumping even if not at a call site. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/dumpargs/ """ try: session = get_session(ctx, session_id) cmd = "dumpargs" if force: cmd += " --force" output = await session.execute_command(cmd) return f"Function arguments:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_retaddr(ctx: Context, session_id: str) -> str: """Print stack addresses that contain return addresses. pwndbg command: retaddr Source: pwndbg/commands/retaddr.py Category: Stack Scans the stack for values that look like return addresses (pointers into executable regions), useful for finding ROP pivot targets. Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/retaddr/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("retaddr") return f"Return addresses on stack:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_canary(ctx: Context, session_id: str, show_all: bool = False) -> str: """Display the stack canary value. pwndbg command: canary Source: pwndbg/commands/canary.py Category: Stack Shows the current stack canary (stack guard) value. The canary is used by stack-smashing protection (SSP / -fstack-protector) to detect buffer overflows. Args: session_id: The UUID of the session. show_all: If True, show canary for all threads. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/canary/ """ try: session = get_session(ctx, session_id) cmd = "canary" if show_all: cmd += " --all" output = await session.execute_command(cmd) return f"Stack canary:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_sigreturn( ctx: Context, session_id: str, address: str = None ) -> str: """Display the SigreturnFrame at a specific address. pwndbg command: sigreturn Source: pwndbg/commands/sigreturn.py Category: Misc Arch: x86-64, i386, aarch64, arm Parses and displays a sigreturn frame structure, which is used in SROP (Sigreturn-Oriented Programming) exploits. Args: session_id: The UUID of the session. address: Address of the sigreturn frame (default: auto-detect). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/sigreturn/ """ try: session = get_session(ctx, session_id) cmd = "sigreturn" if address: cmd += f" {address}" output = await session.execute_command(cmd) return f"Sigreturn frame:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_valist( ctx: Context, session_id: str, addr: str, count: int = 8 ) -> str: """Dump the arguments of a va_list (variadic argument list). pwndbg command: valist Source: pwndbg/commands/valist.py Category: Misc Args: session_id: The UUID of the session. addr: Address of the va_list structure. count: Number of arguments to dump (default: 8). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/valist/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"valist {addr} {count}") return f"va_list arguments:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ ELF / BINARY ANALYSIS TOOLS ║ # ║ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/elf/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/checksec/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/got/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/pie/ ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_checksec(ctx: Context, session_id: str, file: str = None) -> str: """Check binary security properties (RELRO, NX, canary, PIE, RPATH, etc.). pwndbg command: checksec Source: pwndbg/commands/checksec.py Category: Misc Analyzes the ELF binary for security mitigations. Shows RELRO level, stack canary, NX (non-executable stack), PIE (position-independent), RPATH/RUNPATH, Fortify, and other compiler/linker security features. Args: session_id: The UUID of the session. file: Optional path to check (default: loaded binary). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/checksec/ """ try: session = get_session(ctx, session_id) cmd = "checksec" if file: cmd += f" {file}" output = await session.execute_command(cmd) return f"Security checks:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_elfsections( ctx: Context, session_id: str, no_rebase: bool = False ) -> str: """Print ELF section mappings from the binary header. pwndbg command: elfsections Source: pwndbg/commands/elf.py Category: Linux/libc/ELF Shows all ELF sections (.text, .data, .bss, .got, .plt, etc.) with their addresses, sizes, and flags. Args: session_id: The UUID of the session. no_rebase: If True, show file offsets instead of rebased addresses. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/elf/ """ try: session = get_session(ctx, session_id) cmd = "elfsections" if no_rebase: cmd += " --no-rebase" output = await session.execute_command(cmd) return f"ELF sections:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_gotplt(ctx: Context, session_id: str) -> str: """Print symbols found in the .got.plt section. pwndbg command: gotplt Source: pwndbg/commands/elf.py Category: Linux/libc/ELF Shows the GOT/PLT entries with their current resolved values. Useful for identifying which library functions have been resolved by the dynamic linker. Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/elf/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("gotplt") return f"GOT/PLT:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_plt( ctx: Context, session_id: str, all_symbols: bool = False ) -> str: """Print symbols found in Procedure Linkage Table sections. pwndbg command: plt Source: pwndbg/commands/elf.py Category: Linux/libc/ELF Args: session_id: The UUID of the session. all_symbols: If True, show all PLT symbols including internal ones. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/elf/ """ try: session = get_session(ctx, session_id) cmd = "plt" if all_symbols: cmd += " --all-symbols" output = await session.execute_command(cmd) return f"PLT entries:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_got( ctx: Context, session_id: str, filter_str: str = "" ) -> str: """Show the state of the Global Offset Table. pwndbg command: got Source: pwndbg/commands/got.py Category: Linux/libc/ELF Displays GOT entries with their current values, showing which entries point to the PLT stub (unresolved) vs actual library addresses (resolved). Args: session_id: The UUID of the session. filter_str: Optional filter string to match symbol names. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/got/ """ try: session = get_session(ctx, session_id) cmd = "got" if filter_str: cmd += f" {filter_str}" output = await session.execute_command(cmd) return f"GOT entries:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_piebase( ctx: Context, session_id: str, offset: int = 0, module: str = "" ) -> str: """Calculate the virtual address from a PIE-relative offset. pwndbg command: piebase Source: pwndbg/commands/pie.py Category: Linux/libc/ELF For PIE binaries, converts a file offset (RVA) to a runtime virtual address by adding the PIE base. Args: session_id: The UUID of the session. offset: Offset from PIE base to calculate (default: 0 = show base). module: Optional module name (default: main binary). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/pie/ """ try: session = get_session(ctx, session_id) cmd = f"piebase {offset}" if module: cmd += f" {module}" output = await session.execute_command(cmd) return f"PIE base + offset:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_linkmap(ctx: Context, session_id: str) -> str: """Show the dynamic linker's link map (loaded shared objects). pwndbg command: linkmap Source: pwndbg/commands/linkmap.py Category: Linux/libc/ELF Displays the linked list of loaded shared objects maintained by the dynamic linker (ld.so), showing base addresses and file paths. Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/linkmap/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("linkmap") return f"Link map:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_dt( ctx: Context, session_id: str, typename: str, address: str = None ) -> str: """Dump type information, optionally overlaid on a memory address. pwndbg command: dt Source: pwndbg/commands/dt.py Category: Misc Displays the fields, offsets, and sizes of a struct/type. If an address is provided, reads the memory at that address and displays actual values for each field. Args: session_id: The UUID of the session. typename: The type name to inspect (e.g. "struct malloc_chunk"). address: Optional memory address to overlay the type onto. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/dt/ """ try: session = get_session(ctx, session_id) cmd = f"dt {typename}" if address: cmd += f" {address}" output = await session.execute_command(cmd) return f"Type dump: {typename}\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ HEAP ANALYSIS TOOLS (ptmalloc2 / glibc) ║ # ║ ║ # ║ pwndbg's heap commands provide deep insight into glibc's ptmalloc2 ║ # ║ allocator state. These are essential for heap exploitation. ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_heap( ctx: Context, session_id: str, addr: str = None, verbose: bool = False ) -> str: """Iteratively print chunks on a heap. pwndbg command: heap Source: pwndbg/commands/ptmalloc2.py Category: GLibc ptmalloc2 Heap Walks the heap and prints each chunk's metadata (size, flags, fd/bk pointers for freed chunks). Defaults to the current thread's active heap. Args: session_id: The UUID of the session. addr: Optional arena or heap address. verbose: If True, show extended chunk details. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ """ try: session = get_session(ctx, session_id) cmd = "heap" if addr: cmd += f" {addr}" if verbose: cmd += " --verbose" output = await session.execute_command(cmd) return f"Heap chunks:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_arena(ctx: Context, session_id: str, addr: str = None) -> str: """Print the contents of a malloc arena. pwndbg command: arena Source: pwndbg/commands/ptmalloc2.py Category: GLibc ptmalloc2 Heap Shows the malloc_state structure fields including top chunk, bins, system_mem, and other arena metadata. Defaults to the current thread's arena. Args: session_id: The UUID of the session. addr: Optional arena address (default: current thread's arena). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ """ try: session = get_session(ctx, session_id) cmd = "arena" if addr: cmd += f" {addr}" output = await session.execute_command(cmd) return f"Arena:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_arenas(ctx: Context, session_id: str) -> str: """List all arenas in the process. pwndbg command: arenas Source: pwndbg/commands/ptmalloc2.py Category: GLibc ptmalloc2 Heap Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("arenas") return f"Arenas:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_bins( ctx: Context, session_id: str, addr: str = None, tcache_addr: str = None ) -> str: """Print all bin contents — fast, small, large, unsorted, and tcache. pwndbg command: bins Source: pwndbg/commands/ptmalloc2.py Category: GLibc ptmalloc2 Heap Displays a unified view of all bin types in the ptmalloc2 allocator. Args: session_id: The UUID of the session. addr: Optional arena address. tcache_addr: Optional tcache address. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ """ try: session = get_session(ctx, session_id) cmd = "bins" if addr: cmd += f" {addr}" if tcache_addr: cmd += f" {tcache_addr}" output = await session.execute_command(cmd) return f"All bins:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_fastbins( ctx: Context, session_id: str, addr: str = None, verbose: bool = False ) -> str: """Print the contents of an arena's fastbins. pwndbg command: fastbins Source: pwndbg/commands/ptmalloc2.py Category: GLibc ptmalloc2 Heap Fastbins are singly-linked LIFO free lists for small allocations (up to 0x80 bytes on 64-bit). Shows each fastbin index with its chain. Args: session_id: The UUID of the session. addr: Optional arena address. verbose: If True, show extended details. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ """ try: session = get_session(ctx, session_id) cmd = "fastbins" if addr: cmd += f" {addr}" if verbose: cmd += " --verbose" output = await session.execute_command(cmd) return f"Fastbins:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_unsortedbin( ctx: Context, session_id: str, addr: str = None, verbose: bool = False ) -> str: """Print the contents of an arena's unsorted bin. pwndbg command: unsortedbin Source: pwndbg/commands/ptmalloc2.py Category: GLibc ptmalloc2 Heap The unsorted bin is a doubly-linked list where freed chunks go before being sorted into small/large bins. A key target for heap exploits. Args: session_id: The UUID of the session. addr: Optional arena address. verbose: If True, show extended details. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ """ try: session = get_session(ctx, session_id) cmd = "unsortedbin" if addr: cmd += f" {addr}" if verbose: cmd += " --verbose" output = await session.execute_command(cmd) return f"Unsorted bin:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_smallbins( ctx: Context, session_id: str, addr: str = None, verbose: bool = False ) -> str: """Print the contents of an arena's small bins. pwndbg command: smallbins Source: pwndbg/commands/ptmalloc2.py Category: GLibc ptmalloc2 Heap Small bins hold chunks from 0x20 to 0x3F0 bytes (64-bit) in doubly-linked lists sorted by size. Args: session_id: The UUID of the session. addr: Optional arena address. verbose: If True, show extended details. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ """ try: session = get_session(ctx, session_id) cmd = "smallbins" if addr: cmd += f" {addr}" if verbose: cmd += " --verbose" output = await session.execute_command(cmd) return f"Small bins:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_largebins( ctx: Context, session_id: str, addr: str = None, verbose: bool = False ) -> str: """Print the contents of an arena's large bins. pwndbg command: largebins Source: pwndbg/commands/ptmalloc2.py Category: GLibc ptmalloc2 Heap Large bins hold chunks >= 0x400 bytes (64-bit) in doubly-linked lists sorted by size within each bin. Args: session_id: The UUID of the session. addr: Optional arena address. verbose: If True, show extended details. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ """ try: session = get_session(ctx, session_id) cmd = "largebins" if addr: cmd += f" {addr}" if verbose: cmd += " --verbose" output = await session.execute_command(cmd) return f"Large bins:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_tcache(ctx: Context, session_id: str, addr: str = None) -> str: """Print tcache contents for the current thread. pwndbg command: tcache Source: pwndbg/commands/ptmalloc2.py Category: GLibc ptmalloc2 Heap Thread-local caching (tcache) was introduced in glibc 2.26. Each thread has 64 singly-linked bins for small allocations, providing fast thread-local allocation without arena locks. Args: session_id: The UUID of the session. addr: Optional tcache address. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ """ try: session = get_session(ctx, session_id) cmd = "tcache" if addr: cmd += f" {addr}" output = await session.execute_command(cmd) return f"Tcache:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_tcachebins( ctx: Context, session_id: str, addr: str = None, verbose: bool = False ) -> str: """Print tcache bin entries (free list chains per size class). pwndbg command: tcachebins Source: pwndbg/commands/ptmalloc2.py Category: GLibc ptmalloc2 Heap Args: session_id: The UUID of the session. addr: Optional tcache address. verbose: If True, show extended details. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ """ try: session = get_session(ctx, session_id) cmd = "tcachebins" if addr: cmd += f" {addr}" if verbose: cmd += " --verbose" output = await session.execute_command(cmd) return f"Tcache bins:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_malloc_chunk(ctx: Context, session_id: str, addr: str) -> str: """Display detailed information about a specific malloc chunk. pwndbg command: malloc_chunk Source: pwndbg/commands/ptmalloc2.py Category: GLibc ptmalloc2 Heap Shows the chunk header fields (prev_size, size, flags) and for freed chunks, the fd/bk pointers. Args: session_id: The UUID of the session. addr: Address of the malloc chunk. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"malloc_chunk {addr}") return f"Malloc chunk at {addr}:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_top_chunk(ctx: Context, session_id: str, addr: str = None) -> str: """Print information about the top chunk (wilderness) of an arena. pwndbg command: top_chunk Source: pwndbg/commands/ptmalloc2.py Category: GLibc ptmalloc2 Heap The top chunk is the last chunk in the heap, used to service allocations when no suitable freed chunk is available. Args: session_id: The UUID of the session. addr: Optional arena address. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ """ try: session = get_session(ctx, session_id) cmd = "top_chunk" if addr: cmd += f" {addr}" output = await session.execute_command(cmd) return f"Top chunk:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_mp(ctx: Context, session_id: str) -> str: """Print the mp_ (malloc parameters) struct contents. pwndbg command: mp Source: pwndbg/commands/ptmalloc2.py Category: GLibc ptmalloc2 Heap Shows global malloc tuning parameters like mmap_threshold, trim_threshold, top_pad, etc. Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("mp") return f"Malloc parameters:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_vis_heap_chunks( ctx: Context, session_id: str, addr: str = None ) -> str: """Visualize heap chunks with a colorful graphical representation. pwndbg command: vis_heap_chunks (alias: vis) Source: pwndbg/commands/ptmalloc2.py Category: GLibc ptmalloc2 Heap Renders heap chunks as a visual map with color-coded regions showing chunk boundaries, headers, and data. One of pwndbg's most distinctive features for heap analysis. Args: session_id: The UUID of the session. addr: Optional starting address. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ """ try: session = get_session(ctx, session_id) cmd = "vis_heap_chunks" if addr: cmd += f" {addr}" output = await session.execute_command(cmd) return f"Heap visualization:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_try_free(ctx: Context, session_id: str, addr: str) -> str: """Simulate what would happen if free() were called on an address. pwndbg command: try_free Source: pwndbg/commands/ptmalloc2.py Category: GLibc ptmalloc2 Heap Walks through glibc's free() logic and reports which checks would pass or fail. Invaluable for debugging heap exploits — shows exactly why a crafted chunk would or wouldn't pass free()'s validation. Args: session_id: The UUID of the session. addr: Address to simulate freeing. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"try_free {addr}") return f"Simulated free({addr}):\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_find_fake_fast( ctx: Context, session_id: str, target_address: str ) -> str: """Find fake fastbin chunk candidates near a target address. pwndbg command: find_fake_fast Source: pwndbg/commands/ptmalloc2.py Category: GLibc ptmalloc2 Heap Searches memory near the target for byte sequences that could be interpreted as valid fastbin chunk headers. Used to find targets for fastbin attacks (e.g. overwriting __malloc_hook). Args: session_id: The UUID of the session. target_address: Address to search near. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"find_fake_fast {target_address}") return f"Fake fastbin candidates near {target_address}:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_hi( ctx: Context, session_id: str, addr: str, verbose: bool = False ) -> str: """Display heap information for a specific chunk address. pwndbg command: hi Source: pwndbg/commands/ptmalloc2.py Category: GLibc ptmalloc2 Heap Shows which bin a chunk belongs to, its neighbors, and allocation status. Args: session_id: The UUID of the session. addr: Address of the chunk. verbose: If True, show extended details. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ptmalloc2/ """ try: session = get_session(ctx, session_id) cmd = f"hi {addr}" if verbose: cmd += " --verbose" output = await session.execute_command(cmd) return f"Heap info for {addr}:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ THREAD TOOLS ║ # ║ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/tls/ ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_thread_list(ctx: Context, session_id: str) -> str: """List all threads in the current process (LLDB native). Args: session_id: The UUID of the session. """ try: session = get_session(ctx, session_id) output = await session.execute_command("thread list") return f"Threads:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_thread_select(ctx: Context, session_id: str, thread_id: int) -> str: """Select a specific thread and show its backtrace. Args: session_id: The UUID of the session. thread_id: The thread index to select. """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"thread select {thread_id}") bt_output = await session.execute_command("bt") return f"Selected thread {thread_id}\n\n{output}\n\nBacktrace:\n{bt_output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_threads(ctx: Context, session_id: str) -> str: """List all threads with pwndbg's enhanced formatting. pwndbg command: threads Source: pwndbg/commands/tls.py Category: Linux/libc/ELF Shows threads with their IDs, names, and current PC locations using pwndbg's enhanced display format. Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/tls/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("threads") return f"Threads (pwndbg):\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_tls(ctx: Context, session_id: str) -> str: """Print the Thread Local Storage (TLS) base address. pwndbg command: tls Source: pwndbg/commands/tls.py Category: Linux/libc/ELF Shows the TLS base address and optionally the full TLS structure contents. The TLS contains thread-local variables, the stack canary, and other per-thread data. Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/tls/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("tls") return f"TLS info:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ EXPLOIT DEVELOPMENT UTILITIES ║ # ║ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/cyclic/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/rop/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/patch/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/asm/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/xor/ ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_cyclic( ctx: Context, session_id: str, length: int = None, lookup: str = None, detect: bool = False, count: int = 100, ) -> str: """Generate or look up a cyclic (De Bruijn) pattern for offset calculation. pwndbg command: cyclic Source: pwndbg/commands/cyclic.py Category: Misc Generates patterns where every N-byte subsequence is unique, making it easy to determine crash offsets. Can also look up a value in the pattern to find the offset. Args: session_id: The UUID of the session. length: Length of pattern to generate (mutually exclusive with lookup). lookup: Value to look up in the pattern (finds offset). detect: If True, auto-detect the crash offset from registers. count: Pattern element count (default: 100). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/cyclic/ """ try: session = get_session(ctx, session_id) cmd = "cyclic" if detect: cmd += " --detect" elif lookup: cmd += f" --lookup {lookup}" elif length is not None: cmd += f" {length}" else: cmd += f" {count}" output = await session.execute_command(cmd) return f"Cyclic pattern:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_rop( ctx: Context, session_id: str, grep: str = None ) -> str: """Find ROP gadgets using ROPgadget. pwndbg command: rop (alias: ropgadget) Source: pwndbg/commands/rop.py Category: Integrations Searches the loaded binary for useful ROP gadgets. Requires ROPgadget to be installed. Args: session_id: The UUID of the session. grep: Optional regex to filter gadgets. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/rop/ """ try: session = get_session(ctx, session_id) cmd = "rop" if grep: cmd += f" --grep {grep}" output = await session.execute_command(cmd) return f"ROP gadgets:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_onegadget( ctx: Context, session_id: str, verbose: bool = False ) -> str: """Find one-gadget (magic gadget) RCE gadgets in libc. pwndbg command: onegadget Source: pwndbg/commands/onegadget.py Category: Linux/libc/ELF Arch: x86-64, i386, aarch64 Searches for single-gadget code paths in libc that directly call execve("/bin/sh", ...). These are the holy grail for exploitation since overwriting a single function pointer gives a shell. Args: session_id: The UUID of the session. verbose: If True, show constraint details. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/onegadget/ """ try: session = get_session(ctx, session_id) cmd = "onegadget" if verbose: cmd += " --verbose" output = await session.execute_command(cmd) return f"One-gadgets:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_patch( ctx: Context, session_id: str, address: str, instruction: str ) -> str: """Patch an instruction at the given address with new code or bytes. pwndbg command: patch Source: pwndbg/commands/patch.py Category: Misc Assembles the given instruction and writes the bytes at the target address. Useful for live-patching binaries during analysis. Args: session_id: The UUID of the session. address: Address to patch. instruction: Assembly instruction or hex bytes to write. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/patch/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"patch {address} {instruction}") return f"Patched at {address}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_patch_list(ctx: Context, session_id: str) -> str: """List all applied patches. pwndbg command: patch-list Source: pwndbg/commands/patch.py Category: Misc Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/patch/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("patch-list") return f"Applied patches:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_patch_revert(ctx: Context, session_id: str, address: str) -> str: """Revert a patch at the given address. pwndbg command: patch-revert Source: pwndbg/commands/patch.py Category: Misc Args: session_id: The UUID of the session. address: Address of the patch to revert. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/patch/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"patch-revert {address}") return f"Reverted patch at {address}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_asm( ctx: Context, session_id: str, shellcode: str, format: str = "hex" ) -> str: """Assemble shellcode into bytes. pwndbg command: asm Source: pwndbg/commands/asm.py Category: Misc Assembles the given assembly code and outputs the resulting bytes in the requested format. Args: session_id: The UUID of the session. shellcode: Assembly code to assemble (e.g. "nop; ret"). format: Output format — "hex" or "string" (default: "hex"). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/asm/ """ try: session = get_session(ctx, session_id) cmd = f"asm --format {format} {shellcode}" output = await session.execute_command(cmd) return f"Assembled:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_xor( ctx: Context, session_id: str, address: str, key: str, count: int ) -> str: """XOR memory at the given address with a key. pwndbg command: xor Source: pwndbg/commands/xor.py Category: Memory XORs `count` bytes at `address` with the repeating key byte(s). Useful for decoding XOR-encoded payloads in memory. Args: session_id: The UUID of the session. address: Address of the data to XOR. key: XOR key (hex string). count: Number of bytes to XOR. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/xor/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"xor {address} {key} {count}") return f"XOR applied:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_spray( ctx: Context, session_id: str, addr: str, length: int = 0 ) -> str: """Spray memory with cyclic pattern values. pwndbg command: spray Source: pwndbg/commands/spray.py Category: Misc Writes cyclic() generated values to memory, useful for identifying which offset in a buffer overwrites a target. Args: session_id: The UUID of the session. addr: Address to start spraying. length: Number of bytes to spray (0 = auto). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/spray/ """ try: session = get_session(ctx, session_id) cmd = f"spray {addr}" if length: cmd += f" {length}" output = await session.execute_command(cmd) return f"Spray result:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_hex2ptr(ctx: Context, session_id: str, hex_string: str) -> str: """Convert a space-separated hex string to a little-endian address. pwndbg command: hex2ptr Source: pwndbg/commands/hex2ptr.py Category: Misc Args: session_id: The UUID of the session. hex_string: Hex bytes separated by spaces (e.g. "41 42 43 44"). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/hex2ptr/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"hex2ptr {hex_string}") return f"Pointer value:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_parse_seccomp(ctx: Context, session_id: str, addr: str) -> str: """Parse a seccomp BPF filter from memory and dump its rules. pwndbg command: parse-seccomp Source: pwndbg/commands/parse_seccomp.py Category: Linux/libc/ELF Reads a struct sock_fprog from memory and disassembles the BPF filter program to show which syscalls are allowed/denied. Args: session_id: The UUID of the session. addr: Address of the sock_fprog structure. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/parse_seccomp/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"parse-seccomp {addr}") return f"Seccomp filter:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ PROCESS INFORMATION TOOLS ║ # ║ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/procinfo/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/aslr/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/auxv/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/libcinfo/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/errno/ ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_pid(ctx: Context, session_id: str) -> str: """Get the PID of the running process. pwndbg command: pid (alias: getpid) Source: pwndbg/commands/procinfo.py Category: Process Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/procinfo/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("pid") return f"PID:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_procinfo(ctx: Context, session_id: str) -> str: """Display detailed process information. pwndbg command: procinfo Source: pwndbg/commands/procinfo.py Category: Process Shows process details including PID, executable path, architecture, endianness, and other metadata. Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/procinfo/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("procinfo") return f"Process info:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_aslr(ctx: Context, session_id: str, state: str = None) -> str: """Check or set the ASLR status. pwndbg command: aslr Source: pwndbg/commands/aslr.py Category: Linux/libc/ELF Without arguments, shows the current ASLR setting. With "on" or "off", changes it for the current debugging session. Args: session_id: The UUID of the session. state: Optional "on" or "off" to change ASLR state. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/aslr/ """ try: session = get_session(ctx, session_id) cmd = "aslr" if state: cmd += f" {state}" output = await session.execute_command(cmd) return f"ASLR:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_auxv(ctx: Context, session_id: str) -> str: """Print the ELF Auxiliary Vector. pwndbg command: auxv Source: pwndbg/commands/auxv.py Category: Linux/libc/ELF Shows the auxiliary vector passed to the process by the kernel, containing info like page size, entry point, platform, UID/GID, etc. Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/auxv/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("auxv") return f"Auxiliary vector:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_libcinfo(ctx: Context, session_id: str) -> str: """Show information about the loaded libc (version, build, offsets). pwndbg command: libcinfo Source: pwndbg/commands/libcinfo.py Category: Linux/libc/ELF Displays the libc version, build ID, and paths. Useful for identifying the exact libc for exploit development. Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/libcinfo/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("libcinfo") return f"Libc info:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_errno(ctx: Context, session_id: str, err: int = None) -> str: """Convert errno to its string representation. pwndbg command: errno Source: pwndbg/commands/errno.py Category: Linux/libc/ELF Without arguments, shows the current errno value. With a number, shows the name and description for that error code. Args: session_id: The UUID of the session. err: Optional error code to look up. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/errno/ """ try: session = get_session(ctx, session_id) cmd = "errno" if err is not None: cmd += f" {err}" output = await session.execute_command(cmd) return f"Errno:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_strings( ctx: Context, session_id: str, n: int = 4 ) -> str: """Extract ASCII strings from readable memory pages. pwndbg command: strings Source: pwndbg/commands/strings.py Category: Linux/libc/ELF Scans all readable memory pages for printable ASCII strings, similar to the `strings` Unix utility but operating on the live process memory. Args: session_id: The UUID of the session. n: Minimum string length (default: 4). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/strings/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"strings -n {n}") return f"Strings:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_hijack_fd( ctx: Context, session_id: str, fdnum: int, newfile: str ) -> str: """Replace a file descriptor of the debugged process. pwndbg command: hijack-fd Source: pwndbg/commands/hijack_fd.py Category: Misc Redirects an open file descriptor to a new file or socket, useful for redirecting stdin/stdout/stderr during exploitation. Args: session_id: The UUID of the session. fdnum: File descriptor number to replace. newfile: New file path or socket specification. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/hijack_fd/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"hijack-fd {fdnum} {newfile}") return f"Hijacked fd {fdnum}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ WINDBG COMPATIBILITY COMMANDS ║ # ║ ║ # ║ pwndbg provides WinDbg-style memory dump and edit commands for users ║ # ║ familiar with that debugger. ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/windbg/ ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_db( ctx: Context, session_id: str, address: str, count: int = 64 ) -> str: """Dump N bytes at address (WinDbg-style). pwndbg command: db Source: pwndbg/commands/windbg.py Category: WinDbg Args: session_id: The UUID of the session. address: Address to dump. count: Number of bytes (default: 64). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/windbg/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"db {address} {count}") return f"Bytes at {address}:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_dw( ctx: Context, session_id: str, address: str, count: int = 32 ) -> str: """Dump N words (2-byte) at address (WinDbg-style). pwndbg command: dw Source: pwndbg/commands/windbg.py Category: WinDbg Args: session_id: The UUID of the session. address: Address to dump. count: Number of words (default: 32). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/windbg/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"dw {address} {count}") return f"Words at {address}:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_dd( ctx: Context, session_id: str, address: str, count: int = 16 ) -> str: """Dump N dwords (4-byte) at address (WinDbg-style). pwndbg command: dd Source: pwndbg/commands/windbg.py Category: WinDbg Args: session_id: The UUID of the session. address: Address to dump. count: Number of dwords (default: 16). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/windbg/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"dd {address} {count}") return f"DWORDs at {address}:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_dq( ctx: Context, session_id: str, address: str, count: int = 8 ) -> str: """Dump N qwords (8-byte) at address (WinDbg-style). pwndbg command: dq Source: pwndbg/commands/windbg.py Category: WinDbg Args: session_id: The UUID of the session. address: Address to dump. count: Number of qwords (default: 8). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/windbg/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"dq {address} {count}") return f"QWORDs at {address}:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_dc( ctx: Context, session_id: str, address: str, count: int = 8 ) -> str: """Hexdump with ASCII at address (WinDbg-style). pwndbg command: dc Source: pwndbg/commands/windbg.py Category: WinDbg Args: session_id: The UUID of the session. address: Address to dump. count: Number of entries (default: 8). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/windbg/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"dc {address} {count}") return f"Hex+ASCII at {address}:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_eb( ctx: Context, session_id: str, address: str, data: str ) -> str: """Write hex bytes at address (WinDbg-style). pwndbg command: eb Source: pwndbg/commands/windbg.py Category: WinDbg Args: session_id: The UUID of the session. address: Address to write to. data: Space-separated hex bytes (e.g. "90 90 90"). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/windbg/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"eb {address} {data}") return f"Wrote bytes at {address}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_ed( ctx: Context, session_id: str, address: str, data: str ) -> str: """Write hex dwords at address (WinDbg-style). pwndbg command: ed Source: pwndbg/commands/windbg.py Category: WinDbg Args: session_id: The UUID of the session. address: Address to write to. data: Space-separated hex dwords. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/windbg/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"ed {address} {data}") return f"Wrote dwords at {address}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_eq( ctx: Context, session_id: str, address: str, data: str ) -> str: """Write hex qwords at address (WinDbg-style). pwndbg command: eq Source: pwndbg/commands/windbg.py Category: WinDbg Args: session_id: The UUID of the session. address: Address to write to. data: Space-separated hex qwords. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/windbg/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"eq {address} {data}") return f"Wrote qwords at {address}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_dds(ctx: Context, session_id: str, addr: str) -> str: """Dump pointers and resolve symbols at address (WinDbg-style). pwndbg command: dds (aliases: kd, dps, dqs) Source: pwndbg/commands/windbg.py Category: WinDbg Shows pointer-sized values with symbol resolution, similar to telescope but in WinDbg format. Args: session_id: The UUID of the session. addr: Address to dump. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/windbg/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"dds {addr}") return f"Pointers+symbols at {addr}:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_da( ctx: Context, session_id: str, address: str, max_len: int = 256 ) -> str: """Dump a string at address (WinDbg-style). pwndbg command: da Source: pwndbg/commands/windbg.py Category: WinDbg Args: session_id: The UUID of the session. address: Address of the string. max_len: Maximum length to display (default: 256). See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/windbg/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"da {address} {max_len}") return f"String at {address}:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_ln(ctx: Context, session_id: str, value: str = None) -> str: """List the symbols nearest to the provided value. pwndbg command: ln Source: pwndbg/commands/windbg.py Category: WinDbg Args: session_id: The UUID of the session. value: Address or value to look up symbols near. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/windbg/ """ try: session = get_session(ctx, session_id) cmd = "ln" if value: cmd += f" {value}" output = await session.execute_command(cmd) return f"Nearest symbols:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ DARWIN / macOS TOOLS ║ # ║ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/commpage/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/plist/ ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_commpage( ctx: Context, session_id: str, verbose: bool = False ) -> str: """Dump values from the macOS commpage. pwndbg command: commpage Source: pwndbg/commands/commpage.py Category: Darwin/libsystem/Mach-O The commpage is a shared memory page on macOS that contains kernel-provided data accessible from userspace without syscalls (CPU features, timestamps, etc.). Args: session_id: The UUID of the session. verbose: If True, show all commpage fields. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/commpage/ """ try: session = get_session(ctx, session_id) cmd = "commpage" if verbose: cmd += " --verbose" output = await session.execute_command(cmd) return f"Commpage:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_plist( ctx: Context, session_id: str, path: str, next_field: str, count: int = None ) -> str: """Dump elements of a linked list structure. pwndbg command: plist Source: pwndbg/commands/plist.py Category: Misc Walks a linked list starting at `path`, following the `next` field, and displaying each element. Args: session_id: The UUID of the session. path: Starting address or expression for the list head. next_field: Name of the 'next' pointer field. count: Optional maximum number of elements to show. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/plist/ """ try: session = get_session(ctx, session_id) cmd = f"plist {path} {next_field}" if count is not None: cmd += f" --count {count}" output = await session.execute_command(cmd) return f"Linked list:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ PWNDBG CONFIGURATION & META TOOLS ║ # ║ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/config/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/pwndbg_/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/tips/ ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_config( ctx: Context, session_id: str, filter_pattern: str = None ) -> str: """Show or set pwndbg configuration options. pwndbg command: config Source: pwndbg/commands/config.py Category: Pwndbg Without arguments, shows all pwndbg configuration options. With a filter, shows only matching options. Args: session_id: The UUID of the session. filter_pattern: Optional pattern to filter config options. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/config/ """ try: session = get_session(ctx, session_id) cmd = "config" if filter_pattern: cmd += f" {filter_pattern}" output = await session.execute_command(cmd) return f"Configuration:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_theme( ctx: Context, session_id: str, filter_pattern: str = None ) -> str: """Show or set pwndbg theme/color configuration. pwndbg command: theme Source: pwndbg/commands/config.py Category: Pwndbg Args: session_id: The UUID of the session. filter_pattern: Optional pattern to filter theme options. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/config/ """ try: session = get_session(ctx, session_id) cmd = "theme" if filter_pattern: cmd += f" {filter_pattern}" output = await session.execute_command(cmd) return f"Theme:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_heap_config( ctx: Context, session_id: str, filter_pattern: str = None ) -> str: """Show heap-related pwndbg configuration. pwndbg command: heap-config Source: pwndbg/commands/config.py Category: Pwndbg Args: session_id: The UUID of the session. filter_pattern: Optional pattern to filter config options. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/config/ """ try: session = get_session(ctx, session_id) cmd = "heap-config" if filter_pattern: cmd += f" {filter_pattern}" output = await session.execute_command(cmd) return f"Heap config:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_pwndbg( ctx: Context, session_id: str, filter_pattern: str = None ) -> str: """List all available pwndbg commands. pwndbg command: pwndbg Source: pwndbg/commands/pwndbg_.py Category: Pwndbg Shows a categorized list of all registered pwndbg commands with brief descriptions. Args: session_id: The UUID of the session. filter_pattern: Optional pattern to filter commands. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/pwndbg_/ """ try: session = get_session(ctx, session_id) cmd = "pwndbg" if filter_pattern: cmd += f" {filter_pattern}" output = await session.execute_command(cmd) return f"pwndbg commands:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_tips(ctx: Context, session_id: str) -> str: """Show pwndbg usage tips. pwndbg command: tips Source: pwndbg/commands/tips.py Category: Misc Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/tips/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("tips") return f"Tips:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_bugreport(ctx: Context, session_id: str) -> str: """Generate a bug report with environment information. pwndbg command: bugreport Source: pwndbg/commands/version.py Category: Pwndbg Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/version/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("bugreport") return f"Bug report:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_comm( ctx: Context, session_id: str, addr: str = None, comment: str = None ) -> str: """Add or view comments on assembly addresses. pwndbg command: comm Source: pwndbg/commands/comments.py Category: Misc Without arguments, lists all comments. With an address and comment, annotates that address. Args: session_id: The UUID of the session. addr: Optional address to comment. comment: Optional comment text to add. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/comments/ """ try: session = get_session(ctx, session_id) cmd = "comm" if addr: cmd += f" {addr}" if comment: cmd += f" {comment}" output = await session.execute_command(cmd) return f"Comments:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ LLDB NATIVE INSPECTION TOOLS ║ # ║ ║ # ║ These wrap LLDB-native commands that complement pwndbg's features. ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_print(ctx: Context, session_id: str, expression: str) -> str: """Print value of an expression (LLDB native). Args: session_id: The UUID of the session. expression: The expression to evaluate and print. """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"p {expression}") return f"Print {expression}:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_examine( ctx: Context, session_id: str, expression: str, format: str = "x", count: int = 1, ) -> str: """Examine memory at an address with a specified format (LLDB native). Args: session_id: The UUID of the session. expression: Address expression to examine. format: Format — x (hex), d (decimal), u (unsigned), o (octal), t (binary), i (instruction), c (char), f (float), s (string). count: Number of elements to display. """ try: session = get_session(ctx, session_id) fmt_map = {"x": "x", "d": "d", "u": "u", "o": "o", "t": "t", "i": "i", "c": "c", "f": "f", "s": "s"} lldb_fmt = fmt_map.get(format, "x") output = await session.execute_command( f"memory read -f {lldb_fmt} -c {count} {expression}" ) return f"Memory at {expression} (fmt={format}, n={count}):\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_backtrace( ctx: Context, session_id: str, full: bool = False, limit: int = None ) -> str: """Show call stack backtrace (LLDB native). Args: session_id: The UUID of the session. full: If True, show all frames including library frames. limit: Optional limit on frame count. """ try: session = get_session(ctx, session_id) cmd = "bt" if full: cmd += " all" if limit is not None: cmd += f" -c {limit}" output = await session.execute_command(cmd) return f"Backtrace:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_frame_info( ctx: Context, session_id: str, frame_index: int = 0 ) -> str: """Get detailed information about a stack frame (LLDB native). Args: session_id: The UUID of the session. frame_index: Frame index (0 = current frame). """ try: session = get_session(ctx, session_id) frame_out = await session.execute_command(f"frame select {frame_index}") vars_out = await session.execute_command("frame variable") src_out = await session.execute_command("source list") return ( f"Frame {frame_index}:\n\n{frame_out}\n\nVariables:\n{vars_out}" f"\n\nSource:\n{src_out}" ) except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_expression(ctx: Context, session_id: str, expression: str) -> str: """Evaluate an expression in the current frame (LLDB native). Args: session_id: The UUID of the session. expression: The expression to evaluate. """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"expression -- {expression}") return f"Expression: {expression}\n\nOutput:\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_process_info(ctx: Context, session_id: str) -> str: """Get LLDB process status and info (LLDB native). Args: session_id: The UUID of the session. """ try: session = get_session(ctx, session_id) status = await session.execute_command("process status") info = await session.execute_command("process info") return f"Process status:\n\n{status}\n\nDetails:\n{info}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_help(ctx: Context, session_id: str, command: str = None) -> str: """Get help for a command (LLDB native or pwndbg). Args: session_id: The UUID of the session. command: Optional command name to get help for. """ try: session = get_session(ctx, session_id) cmd = "help" if command: cmd += f" {command}" output = await session.execute_command(cmd) return f"Help{' for ' + command if command else ''}:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ INTEGRATIONS & EXTERNAL TOOLS ║ # ║ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/radare2/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/rizin/ ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_r2(ctx: Context, session_id: str, args: str = "") -> str: """Execute a radare2 command on the current binary. pwndbg command: r2 Source: pwndbg/commands/radare2.py Category: Integrations Requires radare2 (r2) to be installed. Passes the command to r2 for analysis of the loaded binary. Args: session_id: The UUID of the session. args: radare2 command arguments. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/radare2/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"r2 {args}") return f"Radare2:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_rz(ctx: Context, session_id: str, args: str = "") -> str: """Execute a Rizin command on the current binary. pwndbg command: rz Source: pwndbg/commands/rizin.py Category: Integrations Requires Rizin to be installed. Args: session_id: The UUID of the session. args: Rizin command arguments. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/rizin/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"rz {args}") return f"Rizin:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ KERNEL DEBUGGING TOOLS ║ # ║ ║ # ║ pwndbg provides extensive kernel debugging support. These commands ║ # ║ work when debugging the Linux kernel via QEMU/KVM or kgdb. ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/kbase/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/kchecksec/ ║ # ║ https://pwndbg.re/2025.05.30/reference/pwndbg/commands/paging/ ║ # ╚═══════════════════════════════════════════════════════════════════════════╝
[docs] @mcp.tool() async def pwndbg_kbase(ctx: Context, session_id: str) -> str: """Show the kernel base address. pwndbg command: kbase Source: pwndbg/commands/kbase.py Category: Kernel Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/kbase/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("kbase") return f"Kernel base:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_kchecksec(ctx: Context, session_id: str) -> str: """Check kernel security configuration (KASLR, SMEP, SMAP, etc.). pwndbg command: kchecksec Source: pwndbg/commands/kchecksec.py Category: Kernel Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/kchecksec/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("kchecksec") return f"Kernel security:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_kcmdline(ctx: Context, session_id: str) -> str: """Show the kernel command line. pwndbg command: kcmdline Source: pwndbg/commands/kcmdline.py Category: Kernel Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/kcmdline/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("kcmdline") return f"Kernel cmdline:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_kconfig( ctx: Context, session_id: str, filter_str: str = None ) -> str: """Show kernel config options. pwndbg command: kconfig Source: pwndbg/commands/kconfig.py Category: Kernel Args: session_id: The UUID of the session. filter_str: Optional pattern to filter config entries. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/kconfig/ """ try: session = get_session(ctx, session_id) cmd = "kconfig" if filter_str: cmd += f" {filter_str}" output = await session.execute_command(cmd) return f"Kernel config:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_kdmesg(ctx: Context, session_id: str) -> str: """Show kernel log messages (dmesg). pwndbg command: kdmesg Source: pwndbg/commands/kdmesg.py Category: Kernel Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/kdmesg/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("kdmesg") return f"Kernel messages:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_kmod(ctx: Context, session_id: str, filter_str: str = None) -> str: """Show loaded kernel modules. pwndbg command: kmod Source: pwndbg/commands/kmod.py Category: Kernel Args: session_id: The UUID of the session. filter_str: Optional pattern to filter modules. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/kmod/ """ try: session = get_session(ctx, session_id) cmd = "kmod" if filter_str: cmd += f" {filter_str}" output = await session.execute_command(cmd) return f"Kernel modules:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_ksyscalls(ctx: Context, session_id: str) -> str: """Show syscall table information. pwndbg command: ksyscalls Source: pwndbg/commands/ksyscalls.py Category: Kernel Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ksyscalls/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("ksyscalls") return f"Syscall table:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_slab(ctx: Context, session_id: str, cache_name: str = None) -> str: """Display kernel slab cache information. pwndbg command: slab Source: pwndbg/commands/slab.py Category: Kernel Args: session_id: The UUID of the session. cache_name: Optional slab cache name to inspect. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/slab/ """ try: session = get_session(ctx, session_id) cmd = "slab" if cache_name: cmd += f" {cache_name}" output = await session.execute_command(cmd) return f"Slab caches:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_pagewalk(ctx: Context, session_id: str, address: str) -> str: """Walk page tables for a virtual address. pwndbg command: pagewalk Source: pwndbg/commands/paging.py Category: Kernel Shows the full page table walk: PGD → P4D → PUD → PMD → PTE, with the physical frame number and page flags at each level. Args: session_id: The UUID of the session. address: Virtual address to walk. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/paging/ """ try: session = get_session(ctx, session_id) output = await session.execute_command(f"pagewalk {address}") return f"Page table walk for {address}:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_ktask(ctx: Context, session_id: str, pid: int = None) -> str: """Show kernel task/process information. pwndbg command: ktask Source: pwndbg/commands/ktask.py Category: Kernel Args: session_id: The UUID of the session. pid: Optional PID to show info for. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/ktask/ """ try: session = get_session(ctx, session_id) cmd = "ktask" if pid is not None: cmd += f" {pid}" output = await session.execute_command(cmd) return f"Kernel task:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
[docs] @mcp.tool() async def pwndbg_kversion(ctx: Context, session_id: str) -> str: """Show kernel version. pwndbg command: kversion Source: pwndbg/commands/kversion.py Category: Kernel Args: session_id: The UUID of the session. See: https://pwndbg.re/2025.05.30/reference/pwndbg/commands/kversion/ """ try: session = get_session(ctx, session_id) output = await session.execute_command("kversion") return f"Kernel version:\n\n{output}" except ValueError as e: return str(e) except Exception as e: return f"Failed: {e}"
# ╔═══════════════════════════════════════════════════════════════════════════╗ # ║ MAIN ENTRY POINT ║ # ╚═══════════════════════════════════════════════════════════════════════════╝ if __name__ == "__main__": parser = argparse.ArgumentParser(description="pwndbg-lldb MCP Server") parser.add_argument("--debug", action="store_true", help="Enable debug logging") args = parser.parse_args() if args.debug: DEBUG = True debug_log("Debug logging enabled") try: mcp.run() except KeyboardInterrupt: debug_log("pwndbg-lldb-mcp server stopped")