CLI tool
Noelo-Lab/kuna avatar
Noelo-Lab/kuna

Kuna is a Ghidra port that ships its own agent instructions inside the binary

An agent-first decompiler designed to be refined by other agents. Kuna is written in Rust and was originally ported from Ghidra.

530 stars32 forksRustApache-2.0

At a glance

What is it?
Kuna is a Rust decompiler ported from Ghidra and designed around agents reading its output rather than a human clicking through it. The interesting decisions live in the build driver and the option surface: four Cargo packages behind one make target, a release profile by default, a spec corpus that has to decode to 675 out of 675, and a skill file embedded in the binary that installs itself into Claude Code, Codex, or OpenCode.
Who is it for?
Kuna suits reverse engineers who want decompiled text an agent can act on, and who are willing to accept that the Ghidra GUI is a shell rather than a full replacement, since native Ghidra features are still unsupported and the plugin has to be disabled to fall back. Before depending on it, check three things.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The port kept Ghidra's binary names on purpose

The build driver opens with a piece of history that explains most of the layout. Kuna started as a port of Ghidra's C++ decompiler and SLEIGH compiler, and the vendored C++ tree was removed once the Rust port reached parity, with the reasoning pushed into `docs/history.md`. What survived the removal is the naming.

The decompiler binaries keep their upstream names, `decomp_dbg` and `decomp_test_dbg`, and only the SLEIGH compiler is renamed, to `slacomp`. Nothing forces that. It is a choice to keep muscle memory and script compatibility intact for anyone arriving from Ghidra, and it is also a hint about how complete the port is claimed to be.

The output format has the same property. Because Kuna originated as a Ghidra port, the decompiled text is described as largely compatible with Ghidra proper, which is what makes it usable as a drop-in backend inside the existing GUI at all. The compatibility is described as largely rather than fully, and the gap is named later in the same document: native Ghidra features are not yet supported.

One make target builds four Cargo packages in release mode

The `binaries` target is a single cargo invocation across four packages, and the recipe is one line:

bash
cd $(ENGINE) && cargo build --$(PROFILE) -p kuna-console -p kuna-harness -p kuna-slacomp -p kuna-cli

The profile is not hardcoded. A `PROFILE` variable defaults to `release` and can be overridden, and the binary directory is derived from it as `$(ENGINE)/target/$(PROFILE)`, so a debug build lands somewhere different. Defaulting a native decompiler to release is unusual enough to be deliberate: the project states that the bottleneck on large binaries is the decompiler itself, so a debug build of it is not a useful configuration.

Two of the four packages exist for reasons that are not obvious from their names. `kuna-harness` is where `decomp_test_dbg` lives, and the CLI pulls it in as a sibling package at build time rather than the test harness depending on the CLI. That keeps the regression harness out of the dependency graph of the tool a user installs, while still letting one command produce both.

The default target is `all: binaries specs`, so a bare `make` gives you the executables and the compiled language specs, not a test run. Tests are separate targets, and the list of them is unusually fine grained: `test`, `test-stages`, `test-cli`, `test-tools`, and `test-ghidra`.

The spec corpus has to decode to 675 out of 675

The second half of the default build compiles language specifications, and that recipe is one line too:

bash
$(SLACOMP) -a $(SPECS)

`SLACOMP` resolves to the SLEIGH compiler inside the target directory and `SPECS` to the top level `specs` folder, so the effect is to compile every vendored `.slaspec` into a `.sla` written next to its source. Those compiled files are gitignored, which means a fresh clone has no compiled specs at all and the first build has to produce them.

Correctness is not asserted by the specs target itself. The comment attached to it says end to end correctness is `make test`, and gives the number: the built specs decode the corpus to 675 out of 675. That framing is worth noting, because it separates two things that are easy to confuse. Compiling a spec successfully proves the compiler works; decoding a 675 case corpus at full marks is what proves the specs themselves are right.

The test list also contains a target called `test-stages`, which matches the phase based design that the project treats as its central engineering constraint.

Options are the control surface an LLM is expected to drive

The CLI exposes decompiler internals as flags. One example from the usage block flips a feature that is otherwise internal, and the comment beside it says why:

bash
kuna decompile ./a.out main --option compareform canonical

The stated rationale is that this is useful for LLMs. That is the clearest expression of the project agent first stance: the knobs a human would never think to touch are surfaced because the consumer of the output is a model that has no UI to explore them through.

The rationale for having knobs at all is a disagreement between two audiences. Reversers want high level readable code, and people writing exploits want low level code close to the instruction stream, so the design commits to making new features togglable and configurable rather than picking one default for everybody.

The catalog behind those flags is treated as a first class artifact. `docs/options.md` is described as a tiered option catalog whose transforms are the LLM control surface, and it carries a generated symptom index, meaning the index is derived from the option definitions rather than maintained by hand. The runtime counterpart is a stage registry that the console can query with `stage list`, `stage map`, and `stage catalog`, so the phase model is inspectable from a running process rather than only from `docs/phases.md`.

The agent instructions travel inside the executable

When Kuna is used with an agent, the documentation points at a skill file rather than at a prompt to copy. The file lives at `skills/kuna/SKILL.md`, it ships inside the binary, and `kuna install-skill` writes it out where an agent will pick it up, offline, for Claude Code, Codex, and OpenCode. Two flags cover the cases that installer does not: `--dir DIR` for an agent outside those three, and `--print` to dump the skill to standard output and read it yourself.

Embedding the instructions in the binary is the part that matters. The tool that tells an agent how to drive the tool cannot then get out of step with it, because there is only one copy. The alternative, a skill file maintained in the repository and pasted into every agent configuration, drifts the first time the CLI grows a flag.

The same idea appears in the contributor side. `AGENTS.md` at the root is a symlink to `docs/agents.md`, and it holds both the enforced rules for contributing features and a map to everything else. So there are two audiences for written instructions, the agent using the finished tool and the agent modifying the source, and both read from a single file rather than from prose that can diverge from behaviour.

Ghidra remains the shell and the plugin is the backend

The Ghidra integration is additive. You build the extension from `./integrations/ghidra` or download a zip from the releases, then install it through the normal extension dialog: File, Install Extensions, add the zip, restart. Activation is a checkbox under File, Configure, Miscellaneous, named KunaDecompilerPlugin.

Once it is active, the decompilation carries a marker comment reading `/* Kuna v{version} */`, so you can tell which backend produced any given function rather than inferring it from timing. The extension works on every platform Kuna supports.

Two limitations are stated without hedging. Native Ghidra features are not yet supported, and the project asks for reports when you hit them. Falling back is a single action: disable the plugin in the same Configure dialog. That is a real advantage over a replacement, because the fallback is a checkbox rather than a rebuild.

One detail is worth checking before you download anything. The documented extension zip is named for v1.157 and carries a Ghidra version suffix of 12.1.2, while the recent releases are v1.608, v1.624, and v1.631. The URL is a single fixed example rather than a version tracking scheme, so the revision of the plugin you get from that link is not the revision of Kuna you would get from a release page.

The browser build exists so the binary never leaves the machine

Because Kuna is Rust, the same decompiler compiles to WebAssembly and runs at `kuna.noelo.org/decompile`. The privacy claim here is structural rather than a policy statement: all of the work happens on the machine you opened it on, so the binary you paste in does not travel to a server. The site for running your own deployment is in the repository at `./integrations/web`.

That capability is also the clearest test of the agent first priority. The stated ordering is that the quality of the decompilation text outranks the things normally treated as important in a decompiler, such as interfaces and visualization, because the reader is a model and the log is the interface. A browser tab with no graphs is the correct amount of interface for that reader.

The same trade is stated as a balance rather than a hierarchy. Speed is not sacrificed outright; the position is that features and speed have to be balanced, since on large binaries the decompiler is the bottleneck, and a faster result nobody waits for is worth nothing to an agent loop.

Two canaries guard the Ghidra simulation test

One variable and one comment in the build driver describe the failure mode the team worries about most:

bash
GHIDRA_SIM_LOG := $(ENGINE)/target/ghidra-sim.log

`test-ghidra` tees its output to that path so that two canaries can inspect it. The file is written inside the target directory, which is gitignored, for a reason the comment spells out: a failed run leaves no litter in the repository. A test that writes a log next to the sources tends to get committed by accident, and then two runs disagree.

A canary in this context is a check that fails when something looks successful but is not, which is the specific hazard of an integration test against an external application. Ghidra is a large program with its own startup, plugin loading, and headless mode, and a test harness that gets those wrong can report a pass while decompiling nothing.

The other signals in the same file are less dramatic and just as informative. The driver declares a Python interpreter variable defaulting to `python3` for tooling, and the phony target list includes `lint`, `check-spec`, and `version` alongside the test targets, which means the spec files are validated by something other than the compiler that consumes them.

Editorial conclusion

Kuna suits reverse engineers who want decompiled text an agent can act on, and who are willing to accept that the Ghidra GUI is a shell rather than a full replacement, since native Ghidra features are still unsupported and the plugin has to be disabled to fall back. Before depending on it, check three things. Pin the version yourself, because the install commands in the documentation still point at v1.157 while the current release is v1.631. Read the option catalog rather than the defaults, since the design assumes you will select high level output for reading and low level output for exploitation work. And treat the DecBench ranking as a claim about optimized C specifically, not a general ranking against every decompiler. Anyone who needs the full Ghidra feature set should stay in Ghidra and treat Kuna as a second opinion.

Frequently asked questions

How do I install Kuna for use with Claude Code or Codex?

Kuna ships a skill file inside its binary. Run kuna install-skill to set it up offline for Claude Code, Codex, or OpenCode, pass --dir DIR to place it for another agent, or pass --print to read the skill without installing it.

Can Kuna run inside the Ghidra GUI?

Yes. Kuna works as the decompiler backend inside Ghidra, installed as an extension through File, Install Extensions, then enabled by checking KunaDecompilerPlugin under File, Configure, Miscellaneous. Decompiled output then carries a Kuna version marker comment, and native Ghidra features are not yet supported.

What does the make specs target actually verify?

It compiles every vendored .slaspec into a .sla next to the source using slacomp, with the compiled files gitignored. End to end correctness is checked by make test instead, which the build driver notes decodes the corpus to 675 out of 675.

How do I make Kuna emit low level rather than high level code?

Decompiler internals are exposed as command line options, for example the compareform option set to canonical, and the documentation says this is intended to be useful for LLMs. The option catalog in docs/options.md is described as the LLM control surface and carries a generated symptom index.

Does Kuna send my binary to a server?

The web browser build at kuna.noelo.org/decompile runs through WebAssembly and does all of the work on your own machine, so the binary stays local. Code for deploying your own site is in the repository under ./integrations/web.

Official sources

  1. License: Apache-2.0
  2. Noelo-Lab/kuna on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/noelo-lab-kuna.svg)](https://hysenlabs.com/projects/noelo-lab-kuna)