Skip to main content
Glama
pwndbg.py10.4 kB
""" Pwndbg tools for MCP server Provides MCP tool implementations for GDB/pwndbg commands. Each tool returns immediate results suitable for LLM interaction. """ import logging from typing import Dict, Any, Optional, List, Tuple from pwnomcp.gdb import GdbController from pwnomcp.state import SessionState logger = logging.getLogger(__name__) class PwndbgTools: """MCP tools for pwndbg interaction""" def __init__(self, gdb_controller: GdbController, session_state: SessionState): """ Initialize pwndbg tools Args: gdb_controller: GDB controller instance session_state: Session state manager """ self.gdb = gdb_controller self.session = session_state # Ensure GDB is initialized once lazily to avoid startup cost unless used try: self.gdb.initialize() except Exception: # Defer errors to first actual call to avoid failing construction logger.exception("GDB initialize() failed during tool init; will retry on demand") def execute(self, command: str) -> Dict[str, Any]: """Execute arbitrary GDB/pwndbg command and return raw responses""" logger.info(f"Execute tool: {command}") self.gdb.initialize() result = self.gdb.execute_command(command) self.session.update_state(result["state"]) self.session.record_command(command, result) return result def set_file(self, binary_path: str) -> Dict[str, Any]: """Set the file to debug; return raw responses""" logger.info(f"Set file: {binary_path}") self.gdb.initialize() result = self.gdb.set_file(binary_path) if result.get("success"): self.session.binary_path = binary_path self.session.binary_loaded = True self.session.update_state(result["state"]) self.session.record_command(result.get("command", f"-file-exec-and-symbols {binary_path}"), result) return result def attach(self, pid: int) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]: """Attach to an existing process; return raw responses""" logger.info(f"Attach to pid: {pid}") self.gdb.initialize() result, context = self.gdb.attach(pid) if result.get("success"): self.session.pid = pid self.session.update_state(result["state"]) self.session.record_command(result.get("command", f"-target-attach {pid}"), result) return result, context def run(self, args: str = "", start: bool = False) -> Dict[str, Any]: """Run the loaded binary; return raw responses""" logger.info(f"Run with args: '{args}'") self.gdb.initialize() if not self.session.binary_loaded: return {"command": "-exec-run", "responses": [], "success": False, "state": self.gdb.get_state(), "error": "No binary loaded. Use set_file first."} result = self.gdb.run(args, start=start) self.session.update_state(result["state"]) self.session.record_command(result.get("command", "-exec-run"), result) return result def finish(self) -> Dict[str, Any]: """Run until current function finishes; return raw responses""" logger.info("Finish current function") self.gdb.initialize() result = self.gdb.finish() self.session.update_state(result["state"]) self.session.record_command(result.get("command", "-exec-finish"), result) return result def jump(self, locspec: str) -> Dict[str, Any]: """Jump to a specific location; return raw responses""" logger.info(f"Jump to {locspec}") self.gdb.initialize() result = self.gdb.jump(locspec) self.session.update_state(result["state"]) self.session.record_command(result.get("command", f"-exec-jump {locspec}"), result) return result def return_from_function(self) -> Dict[str, Any]: """Force return from current function; return raw responses""" logger.info("Force return from current function") self.gdb.initialize() result = self.gdb.return_from_function() self.session.update_state(result["state"]) self.session.record_command(result.get("command", "-exec-return"), result) return result def until(self, locspec: Optional[str] = None) -> Dict[str, Any]: """Run until a location or next source line; return raw responses""" logger.info(f"Until {locspec if locspec else '[next line]'}") self.gdb.initialize() result = self.gdb.until(locspec) self.session.update_state(result["state"]) self.session.record_command(result.get("command", "-exec-until"), result) return result def step_control(self, command: str) -> Dict[str, Any]: """Execute stepping commands (c, n, s, ni, si); return raw responses""" logger.info(f"Step control: {command}") self.gdb.initialize() command_map = { "c": "continue", "n": "next", "s": "step", "ni": "nexti", "si": "stepi", } actual = command_map.get(command, command) current_state = self.gdb.get_state() if current_state != "stopped": return {"command": actual, "responses": [], "success": False, "state": current_state, "error": f"Cannot execute '{command}' in state '{current_state}'"} if actual == "continue": result = self.gdb.continue_execution() elif actual == "next": result = self.gdb.next() elif actual == "step": result = self.gdb.step() elif actual == "nexti": result = self.gdb.nexti() elif actual == "stepi": result = self.gdb.stepi() else: return {"command": actual, "responses": [], "success": False, "state": current_state, "error": f"Unknown step command '{command}'"} self.session.update_state(result["state"]) self.session.record_command(result.get("command", actual), result) return result def get_context(self, context_type: str = "all") -> Dict[str, Any]: """Get debugging context (registers, stack, disassembly, etc.)""" logger.info(f"Get context: {context_type}") self.gdb.initialize() if self.gdb.get_state() != "stopped": return {"command": f"context {context_type}", "responses": [], "success": False, "state": self.gdb.get_state(), "error": f"Cannot get context while inferior is {self.gdb.get_state()}"} if context_type == "all": # Prefer fast MI-based snapshot to avoid slow pwndbg rendering aggregated = self.gdb.get_quick_context() self.session.record_command("context all (quick)", aggregated) return aggregated else: result = self.gdb.get_context(context_type) self.session.record_command(f"context {context_type}", result) return result def set_breakpoint(self, location: str, condition: Optional[str] = None) -> Dict[str, Any]: """Set a breakpoint; return raw responses""" logger.info(f"Set breakpoint at {location}") result = self.gdb.set_breakpoint(location, condition) self.session.record_command(result.get("command", "-break-insert"), result) return result def list_breakpoints(self) -> Dict[str, Any]: """List all breakpoints; return raw responses""" logger.info("List breakpoints") result = self.gdb.list_breakpoints() self.session.record_command(result.get("command", "-break-list"), result) return result def delete_breakpoint(self, number: int) -> Dict[str, Any]: """Delete a breakpoint; return raw responses""" logger.info(f"Delete breakpoint #{number}") result = self.gdb.delete_breakpoint(number) self.session.record_command(result.get("command", f"-break-delete {number}"), result) return result def toggle_breakpoint(self, number: int, enable: bool) -> Dict[str, Any]: """Enable or disable a breakpoint; return raw responses""" action = "enable" if enable else "disable" logger.info(f"{action} breakpoint #{number}") result = self.gdb.enable_breakpoint(number) if enable else self.gdb.disable_breakpoint(number) self.session.record_command(result.get("command", f"-break-{action} {number}"), result) return result def _get_full_context(self) -> Dict[str, Any]: """Get complete debugging context (raw responses per context)""" contexts = {} for ctx_type in ["regs", "stack", "disasm", "code", "backtrace"]: contexts[ctx_type] = self.gdb.get_context(ctx_type) return contexts def get_memory(self, address: str, size: int = 64, format: str = "hex") -> Dict[str, Any]: """Read memory at specified address; return raw responses""" logger.info(f"Read memory at {address}, {size} bytes as {format}") self.gdb.initialize() if format == "hex": # Use fast MI bytes read and return raw bytes; caller can format result = self.gdb.read_memory_bytes(address, size) self.session.record_command(f"-data-read-memory-bytes {address} {size}", result) return result elif format == "string": # For C-string, classic command is fine (needs pwndbg/pretty print) cmd = f"x/s {address}" result = self.gdb.execute_command(cmd) self.session.record_command(cmd, result) return result else: # Generic bytes read via MI grid as fallback word_size = 1 nr_rows = size nr_cols = 1 result = self.gdb.read_memory_mi(address, "x", word_size, nr_rows, nr_cols) self.session.record_command(f"-data-read-memory {address} x {word_size} {nr_rows} {nr_cols}", result) return result def get_session_info(self) -> Dict[str, Any]: """Get current session information (no GDB sync/parsing)""" return { "session": self.session.to_dict(), "gdb_state": self.gdb.get_state() }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/0xSensei/pwno-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server