MCPInstall

pyghidra-scripting

Write and run Python (PyGhidra) code inside the Ghidra session that ReVa's MCP server is already attached to, using the five ReVa scripting tools — run-script, list-scripts, read-script, write-script, edit-script. Use this whenever the user asks to execute Python against the current program, reach for the Ghidra Flat API directly, write a custom analysis pass, automate something the other ReVa tools don't expose, or persist a .py script in Ghidra's scripts directory. Also use when an existing ReVa MCP tool can't do what's needed and the right answer is "drop into PyGhidra for one call." Do NOT use this skill for plain ReVa tool calls that already have a dedicated MCP tool (use that tool instead); do NOT use it to build standalone Python programs that run pyghidra in their own process (the run-script tool runs inside the ReVa-hosted Ghidra).

Installs
86
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/cyberkaida/reverse-engineering-assistant --skill pyghidra-scripting
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads pyghidra-scripting automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

PyGhidra scripting via ReVa

ReVa exposes five MCP tools under the scripts provider that let an assistant write, edit, run, and inspect Python scripts inside the same Ghidra session ReVa is serving. The Python code runs under PyGhidra (CPython 3 + JPype), so it has the full Ghidra Java API available — the FlatProgramAPI, DecompInterface, FunctionManager, everything.

This skill teaches when to reach for these tools, how to structure the Python you send to run-script, and the pitfalls that bite in practice.

The five tools at a glance

ToolPurposeUse when
run-scriptExecute Python against a program. Inline code, or by scriptPath / scriptName.The dedicated ReVa MCP tools don't cover what you need, or you want one tight pass over the program.
list-scriptsEnumerate scripts across registered directories. Filterable by name/path; paginated.Discovering what's already saved before writing a new one.
read-scriptcat -n view of a script with offset / limit and a truncated flag.Reading an existing script before editing it. The numbered output is what edit-script lines up against.
write-scriptCreate a new .py file (or full overwrite with overwrite: true). User-writeable dirs only.Saving a reusable script. Never reach for this just to bundle inline code — use run-script with code for one-shot use.
edit-scriptold_string → new_string replacement. Errors if old_string matches multiple places unless replace_all: true.Iterating on an existing script. Pair with read-script to see line context first.

Reach-for-it triggers

run-script is the escape hatch. Use it when:

  • You need to run a custom predicate over every function/symbol/instruction and the existing list/find tools don't filter the right way.
  • You need to combine multiple Ghidra API calls into one operation that would otherwise take many MCP round-trips.
  • You want to use a Ghidra API ReVa doesn't expose — e.g. DecompInterface low-level features, PCode analysis, custom AddressSet arithmetic, the data flow API directly, BinaryReader, etc.
  • You're prototyping; once it works and is reusable, save it with write-script.

Don't use run-script when a dedicated ReVa MCP tool already does the job — those tools have stable schemas and structured output that's easier to reason about than free-form stdout. The decompiler / functions / strings / symbols / xrefs tools cover the common path.

Runtime gate: PyGhidra-only

run-script only works when Ghidra was launched under PyGhidra:

  • ✅ mcp-reva (Claude CLI / stdio mode)
  • ✅ pyghidra-gui (GUI mode launched with PyGhidra)
  • ✅ reva_headless_server.py (headless mode via PyGhidra)
  • ❌ Plain ghidraRun — PyGhidra is not wired in; you'll get a PyGhidraNotAvailableException error response with launch guidance.

If a run-script call comes back with that error, the right move is to tell the user how to relaunch (don't keep retrying). The other four tools (list-scripts, read-script, write-script, edit-script) work in every mode because they're plain file operations.

The execution contract for run-script

This is the contract your inline code (or saved script) runs under. Internalise it — most failures come from getting one of these wrong.

Pre-bound globals. The script runs as a Ghidra PyGhidra script, so the standard GhidraScript globals are already defined: currentProgram, currentAddress, currentSelection, currentHighlight, monitor, state, plus FlatProgramAPI helpers like toAddr, getFunctionAt, getFunctionContaining, getSymbolAt, getInstructionAt, getDataAt, getReferencesTo, getReferencesFrom, createLabel, setEOLComment, find, findBytes, clearListing. You do not need to import or set these up. currentProgram is the program identified by the programPath argument.

No automatic transaction. Unlike GhidraScripts run from inside Ghidra's GUI, ReVa's run-script deliberately does not open a transaction around your code. Any mutation (rename, retype, comment, label, create function, etc.) must be wrapped manually:

tx = currentProgram.startTransaction("Describe the edit")
try:
    # ... do stuff that mutates state ...
    currentProgram.endTransaction(tx, True)   # commit
except Exception:
    currentProgram.endTransaction(tx, False)  # roll back
    raise

This is intentional — auto-wrapping at the tool layer would create nested-transaction footguns when scripts already handle their own. Read-only scripts (printing, counting, dumping) need no transaction at all.

Inline header. When you pass code, the tool prepends # @runtime PyGhidra\n automatically. Don't add it yourself.

Cancellation is cooperative. The configured timeout (default 60s, overridable per-call via timeoutSeconds) only fires if your code yields to the monitor. In long loops, call monitor.isCancelled() regularly and break:

fm = currentProgram.getFunctionManager()
for func in fm.getFunctions(True):
    if monitor.isCancelled():
        break
    # ... work ...

A tight Python loop without a monitor check will run past the timeout. The result will then come back with timedOut: true and partial output but the Ghidra session may still be busy for a moment.

Output is captured and capped. stdout and stderr are each capped at 64K chars by default (configurable via SCRIPT_OUTPUT_CHAR_LIMIT). If you blow the cap, the result has stdoutTruncated: true or stderrTruncated: true. Design output for an LLM consumer — terse, structured, line-per-record. Reach for JSON if downstream parsing matters:

import json
results = []
for func in currentProgram.getFunctionManager().getFunctions(True):
    if monitor.isCancelled(): break
    if some_predicate(func):
        results.append({"addr": str(func.getEntryPoint()), "name": func.getName()})
print(json.dumps(results))

Result schema. run-script returns:

{
  "success": true|false,
  "programPath": "/binary.exe",
  "stdout": "...",
  "stderr": "...",
  "stdoutTruncated": false,
  "stderrTruncated": false,
  "durationMs": 1234,
  "timedOut": false,
  "scriptSource": {"type": "inline|path|name", "value": "..."},
  "error": "ClassName: message"   // only when the executor itself threw
}

success is false if the script raised a Python exception (detected via the Traceback (most recent call last) marker in stderr), if it hit the timeout, or if the executor threw. Read stderr to see the actual traceback — Python exceptions don't surface as MCP errors, they come back in stderr with success: false.

What you can call from inside

The script has the full Ghidra/PyGhidra surface. The most commonly useful entry points:

# Program structure
prog     = currentProgram                             # the Program object
listing  = prog.getListing()                          # CodeUnits, data, comments
memory   = prog.getMemory()                           # blocks, bytes
fm       = prog.getFunctionManager()                  # functions
st       = prog.getSymbolTable()                      # symbols/labels
rm       = prog.getReferenceManager()                 # xrefs
dtm      = prog.getDataTypeManager()                  # data types

# Flat-API one-liners (already global)
addr     = toAddr(0x401000)
func     = getFunctionAt(addr) or getFunctionContaining(addr)
sym      = getSymbolAt(addr)
instr    = getInstructionAt(addr)
xrefs_to = getReferencesTo(addr)

# Java classes — import as normal Python after PyGhidra is up (it is, when run-script runs)
from ghidra.program.model.symbol import SourceType
from ghidra.app.decompiler import DecompInterface, DecompileOptions

For everything else: see references/flat-api.md for the categorised Flat-API cheat-sheet, and references/jpype-interop.md for the Java-via-Python idioms (arrays, iterators, sign extension, type stubs).

Workflow patterns

Inline one-shot (most common)

run-script(programPath="/bin", code="""
fm = currentProgram.getFunctionManager()
total = 0
huge = []
for f in fm.getFunctions(True):
    if monitor.isCancelled(): break
    size = f.getBody().getNumAddresses()
    total += size
    if size > 4096:
        huge.append((str(f.getEntryPoint()), f.getName(), size))
print(f'total bytes: {total}')
for addr, name, size in huge:
    print(f'{addr} {size:6d} {name}')
""")

Read the stdout, draw conclusions, move on. Nothing persisted.

Iterate then save

  1. Send a draft via run-script with inline code.
  2. When it works, call write-script with scriptName="MyAnalysis.py" and the final code. (Pick a descriptive name — list-scripts will return it.)
  3. Next time, run-script with scriptName="MyAnalysis.py".

Edit-in-place on a saved script

  1. list-scripts (or remember the name).
  2. read-script — note the line numbers around what you want to change.
  3. edit-script with old_string set to a uniquely identifying slice (include enough surrounding context that there's exactly one match). The tool errors out on ambiguity unless you pass replace_all: true.
  4. Re-run via run-script(scriptName=...).

edit-script is preferred over re-writing the whole file — it preserves the rest of the script unchanged and is what the user can review as a diff.

Pitfalls

These are the ones that account for most "why did my script misbehave."

Forgot the transaction

Mutations without startTransaction throw IllegalStateException: not in transaction. If a run-script call mutates and you see this in stderr, that's the cause. Wrap the mutating block — see the contract section above.

monitor.isCancelled() check missing

Tight loops that never check the monitor blow past the timeout silently — the timedOut flag still flips, but your script may have already done damage. Always check the monitor inside any loop touching every function / instruction / address.

Java Iterator vs Python iterable

Most Ghidra iterators are Python-iterable thanks to JPype, but a few (notably SymbolIterator from some accessors and the older ReferenceIterator) only expose hasNext() / next(). If a for x in it: loop silently yields nothing, fall back to while it.hasNext(): x = it.next(). FunctionManager.getFunctions(True) is iterable directly.

Java byte is signed

MemoryBlock.getBytes(addr, byte[] buf) fills you a Java byte[]. Values come back as -128..127. Mask with b & 0xff if you want 0..255. Allocating the buffer: import jpype; buf = jpype.JByte[16].

print() is what shows up in stdout

The script's print() is rerouted to the captured stdout writer — that's how you get output back. Don't use logging frameworks expecting them to flush somewhere visible; print(...) is the contract.

Decompiler is expensive to construct

If you decompile more than a handful of functions in one script, build the DecompInterface once and reuse it. Always dispose() it. With a per-function timeout of 30s, a 100-function pass that re-builds the decompiler each call can easily blow run-script's overall timeout.

from ghidra.app.decompiler import DecompInterface
decomp = DecompInterface()
decomp.openProgram(currentProgram)
try:
    for f in currentProgram.getFunctionManager().getFunctions(True):
        if monitor.isCancelled(): break
        res = decomp.decompileFunction(f, 30, monitor)
        if res.decompileCompleted():
            # process res.getDecompiledFunction().getC()
            pass
finally:
    decomp.dispose()

script paths must live inside a registered scripts dir

run-script(scriptPath=...) refuses paths outside Ghidra's registered script directories (user dir, system dirs, bundle dirs). Same for read-script. For writes, you're additionally limited to the writeable set (user dir, typically ~/ghidra_scripts/). System dirs are read-only.

Inline code and saved scripts both bypass Ghidra's # @category UI metadata

run-script(code=...) writes a temp file with only the # @runtime PyGhidra header. # @category, # @menupath, # @keybinding headers in a saved script matter for the Ghidra GUI but are ignored by run-script — keep them if you want the script to appear in the Script Manager, drop them otherwise.

Where to go next

These reference files are bundled with the skill. Load only the one(s) relevant.

  • references/flat-api.md — Categorised cheat-sheet for the program-inspection surface: FlatProgramAPI one-liners (toAddr, getFunctionAt, createLabel, setEOLComment, references, comments, bookmarks, search), AddressSet arithmetic for restricting iteration, imports/externals/thunks (isThunk, getCallingFunctions, ExternalManager), data types & structs (StructureDataType, DataTypeManager, createData), and FlatDecompilerAPI basics. Load when writing inline code that inspects or mutates program structure.

  • references/decompiler-pcode.md — The decompiler's internal representation: HighFunction, LocalSymbolMap, HighSymbol/HighVariable/HighParam, Varnode def-use chains, PcodeOp opcodes (COPY/LOAD/CALL/MULTIEQUAL/PTRSUB/…), ParallelDecompiler for batch passes, HighFunctionDBUtil for persisting decompiler-derived renames. Load when the question is "trace this value back to its source", "where does this argument come from", "which functions look like the decompiler got confused", "rename this iVar3 to something useful", or any task that needs more than the C source string.

  • references/jpype-interop.md — Python ↔ Java boundary notes: arrays (jpype.JByte[16]), sign extension, varargs, overload resolution, exception types, getter/setter ↔ property, package collisions, type stubs (from ghidra.ghidra_builtins import * under TYPE_CHECKING). Load when JPype errors show up or you're doing anything beyond simple for x in it: iteration.

  • references/recipes.md — Copy-pasteable inline code snippets covering the well-trodden paths: function iteration with predicates, xrefs to a symbol with caller context, batch rename by regex, thunks-aware callers of an import, decompile-and-grep, string dump with referencers, call-graph walk, plate/EOL annotations, raw byte reads — plus the heavier patterns: define a struct and apply it to memory, set a function signature with proper storage, trace argN back through Varnode def-use chains, register touches per function, ParallelDecompiler batch decompilation, EmulatorHelper for string deobfuscation, recursive xref-walking, and persisting a decompiler-derived rename via HighFunctionDBUtil. Load when the user asks "how do I do X" and X is a well-trodden path.

  • references/persistent-scripts.md — Conventions for saved scripts: naming, where files land, GhidraScript headers (# @category, # @runtime), how to discover existing scripts before duplicating, the typical edit cycle. Load when the user wants to save / reuse / iterate on a script rather than one-shot it.

Working style

A few things that keep PyGhidra-via-ReVa pleasant:

  • Prefer dedicated ReVa tools first. run-script is powerful but free-form; the structured tools (decompiler, functions, strings, xrefs, …) have schemas the assistant can reason about more reliably. Reach for run-script only when the structured tools don't cover the question.
  • Print structured output. When a run-script result is going to be re-consumed by an LLM, prefer JSON or fixed columns over prose. The 64K cap is per-stream — design for it.
  • Keep transactions narrow. Open one per logical edit, not one wrapping the whole script. Smaller transactions undo cleanly in Ghidra's history and survive errors better.
  • Iterate with edit-script, not write-script. Once a saved script exists, targeted edits produce reviewable diffs; full overwrites don't.
  • Read before you edit. read-script first to see the current state and pick old_string slices that are unique. Editing blind almost always picks too-short a slice and either misses or hits multiple lines.

FAQ

What does the pyghidra-scripting skill do?

Write and run Python (PyGhidra) code inside the Ghidra session that ReVa's MCP server is already attached to, using the five ReVa scripting tools — run-script, list-scripts, read-script, write-script, edit-script. Use this whenever the user asks to execute Python against the current program, reach for the Ghidra Flat API directly, write a custom analysis pass, automate something the other ReVa tools don't expose, or persist a .py script in Ghidra's scripts directory. Also use when an existing ReVa MCP tool can't do what's needed and the right answer is "drop into PyGhidra for one call." Do NOT use this skill for plain ReVa tool calls that already have a dedicated MCP tool (use that tool instead); do NOT use it to build standalone Python programs that run pyghidra in their own process (the run-script tool runs inside the ReVa-hosted Ghidra).

How do I install pyghidra-scripting?

Run npx skills add https://github.com/cyberkaida/reverse-engineering-assistant --skill pyghidra-scripting in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/pyghidra-scripting/ (personal) or .claude/skills/pyghidra-scripting/ (project).

Which AI agents can use pyghidra-scripting?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is pyghidra-scripting safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains pyghidra-scripting?

It is published by cyberkaida in cyberkaida/reverse-engineering-assistant. It has 86 recorded installs.

More from cyberkaida/reverse-engineering-assistant

View all →

deep-analysis

cyberkaida/reverse-engineering-assistant

Performs focused, depth-first investigation of specific reverse engineering questions through iterative analysis and database improvement. Answers questions like "What does this function do?", "Does this use crypto?", "What's the C2 address?", "Fix types in this function". Makes incremental improvements (renaming, retyping, commenting) to aid understanding. Returns evidence-based answers with new investigation threads. Use after binary-triage for investigating specific suspicious areas or when user asks focused questions about binary behavior.

Data & Databases517 installs

ctf-pwn

cyberkaida/reverse-engineering-assistant

Solve CTF binary exploitation challenges by discovering and exploiting memory corruption vulnerabilities to read flags. Use for buffer overflows, format strings, heap exploits, ROP challenges, or any pwn/exploitation task.

Security333 installs

ctf-rev

cyberkaida/reverse-engineering-assistant

Open the skill to read its SKILL.md instructions.

322 installs

binary-triage

cyberkaida/reverse-engineering-assistant

Performs initial binary triage by surveying memory layout, strings, imports/exports, and functions to quickly understand what a binary does and identify suspicious behavior. Use when first examining a binary, when user asks to triage/survey/analyze a program, or wants an overview before deeper reverse engineering.

Frontend & Design227 installs

ctf-crypto

cyberkaida/reverse-engineering-assistant

Solve CTF cryptography challenges by identifying, analyzing, and exploiting weak crypto implementations in binaries to extract keys or decrypt data. Use for custom ciphers, weak crypto, key extraction, or algorithm identification.

Business & Finance213 installs

Popular ai & agents skills

View category →

google-agents-cli-publish

google/agents-cli

This skill should be used when the user wants to "publish an agent", "publish my ADK agent", "register an agent with Gemini", "publish to Gemini" (formerly Gemini Enterprise), or needs guidance on the agents-cli publish gemini command. Also use when the user wants to "manage agents in Agent Registry", "list/update/delete registered agents", or "register an MCP server". Covers ADK vs A2A registration modes, programmatic and interactive usage, flag reference, auto-detection from deployment metadata, Agent Registry fleet management, and troubleshooting. Part of the agents-cli skills suite. Do NOT use for deployment (use google-agents-cli-deploy).

AI & Agents513.9K installs

google-agents-cli-eval

google/agents-cli

This skill should be used when the user wants to "run an evaluation", "evaluate my agent", "evaluate my ADK agent", "write an eval dataset", "analyze eval failures", "compare eval results", "optimize agent", or needs guidance on the Gemini platform eval methodology and the Quality Flywheel. Covers eval metrics, dataset schema, LLM-as-judge scoring, and common failure causes. Applies to any agents-cli project, whatever framework the agent is written in. Do NOT use for agent API code patterns (ADK: use google-agents-cli-adk-code), deployment (use google-agents-cli-deploy), or project scaffolding (use google-agents-cli-scaffold).

AI & Agents513.5K installs

skill-creator

anthropics/skills

Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.

OfficialAI & Agents416.4K installs

codex-pet

prime-skills/runcomfy-agent-skills

Codex Pet generator on RunComfy. Build a Codex-compatible Codex Pet spritesheet.webp + pet.json from a single reference image, drop it into ${CODEXHOME:-$HOME/.codex}/pets/ / and Codex picks it up as a custom Codex Pet next to the 8 built-ins. This skill produces the exact Codex Pet atlas Codex expects (1536x1872 PNG/WebP, 8 cols x 9 rows, 192x208 cells, 9 animation states — idle, running-right, running-left, waving, jumping, failed, waiting, running, review). Calls OpenAI GPT Image 2 edit ONCE via the local RunComfy CLI as runcomfy run openai/gpt-image-2/edit to produce a canonical Codex Pet pose, then assembles all 9 animation rows programmatically with ImageMagick micro-transforms — no Codex Pro, no $imagegen, no OPENAIAPIKEY required, only RUNCOMFYTOKEN. Triggers on "codex pet", "create codex pet", "make codex pet", "hatch codex pet", "/hatch image", "desktop pet codex", "codex pets", "spritesheet.webp", or any explicit ask to build a custom pet for OpenAI Codex.

AI & Agents402.1K installs

full-output-enforcement

leonxlnx/taste-skill

Overrides default LLM truncation behavior. Enforces complete code generation, bans placeholder patterns, and handles token-limit splits cleanly. Apply to any task requiring exhaustive, unabridged output.

AI & Agents401K installs

executing-plans

obra/superpowers

Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available

AI & Agents245.7K installs