[GH-ISSUE #8177] [Design] Investigate Kuna decompilation inside a Ghidra analysis session #6464

Open
opened 2026-11-01 21:11:20 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @morluto on GitHub (Oct 30, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/8177

Before submitting

  • I searched the open and closed issues and pull requests for a related discussion.
  • I understand the contribution scope must be reviewed before integration; open questions are welcome here.
  • This discussion does not include credentials, private samples, or sensitive target data.

Decision needed

Is an opt-in Ghidra analysis session using Kuna's Ghidra-protocol decompiler backend worth investigating as a distinct analysis profile, and what evidence and authority would be needed before any integration?

Context and evidence

The upstream Kuna 1.986 release ships an extension and kuna_ghidra backend built for Ghidra 12.1.2. Its README describes swapping Ghidra's decompiler process through a private, JVM-wide factory field; it states symbol/type resolution is incomplete and other Ghidra versions are unverified.

On macOS arm64, I used Ghidra 12.1.4 analyzeHeadless with a self-owned Java post-script and a locally built Mach-O. The script opened a stock DecompInterface, decompiled two functions, closed it, switched that factory field in the temporary process to the extension's official 1.986 macOS backend, and opened a fresh interface. Both backends reported the two functions complete. The Kuna output identified itself as 1.986 and used Ghidra's struct type, field names, global and callee names; stock output also had those. The original file was not executed or modified, the import was discarded, and the script restored the factory field. The first script attempt failed to compile due to a mistaken result class name; the corrected attempt produced these results.

This was not an REA provider test, an installed GUI plugin, or proof of compatibility on Ghidra 12.1.4 beyond these two functions. REA's current Kuna provider analyzes directly with the Kuna CLI; its Ghidra provider uses Ghidra's own decompiler. The hybrid could let an analyst use one program's analysis with a different engine, but correctness, high-p-code semantics, operation compatibility, and practical value remain unknown.

Expected interaction with targets

Read an explicitly selected local binary and invoke caller-supplied Ghidra and Kuna executables in an owned headless session. The experiment did not modify the executable or a user-owned project. Exact process, workspace, and analysis-state effects for an integration require review; there is no proposal to control a GUI or change a Ghidra installation.

Boundary details (optional)

Do not silently replace the Ghidra core or equate this hybrid with the independent Kuna provider. Bind both engine identities, versions, backend executable identity, selected target, applied analysis state, protocol compatibility and unknown coverage in the analysis profile and cache. The private static switch affects future decompiler spawns across the JVM, and already-open interfaces retain their process. REA's persistent interface, timeout recovery and decompiler-derived value flow must remain correct, or be reported unavailable. Windows P0 and applicable scope decisions still apply.

Options and tradeoffs

Compare a dedicated exploratory verifier and opt-in profile, direct use of upstream's headless recipe, or deferring until upstream's symbol scope and protocol mature. Do not turn upstream's GUI extension into an implicit dependency; choose an analyst-visible operation and evidence semantics before designing controls.

Verification available so far

I have a prototype or observed results

Success criteria and open questions

Evaluate representative stripped and typed binaries, functions that use globals/calls/complex types, failures and provider timeouts, fresh process restart, cancellation and cleanup. Compare pseudocode, p-code/dossier facets and honest partial-coverage reporting against independent provider evidence, without treating textual agreement as semantic proof. Verify exact Ghidra/Kuna pairs and supported hosts before making any supported-release claim.

References

Kuna 1.986 release: https://github.com/Noelo-Lab/kuna/releases/tag/v1.986
Ghidra integration documentation: https://github.com/Noelo-Lab/kuna/blob/v1.986/docs/ghidra-integration.md
Related type-hypothesis discussion: https://github.com/morluto/rea/issues/8166

A follow-up in the same headless setup inspected HighFunction p-code: for adjust, both backends returned four operations including one return; for evaluate, stock returned 14 operations and Kuna 15, each including one call, two loads and one return. This confirms those facets were present on the fixture, not that operations, def-use graphs, semantics, or REA's dossier projections are equivalent.

Upstream documentation correction

The extension zip's README describes Phase 2 and missing global/type resolution, but the v1.986 integration guide says Phase 3 lazy symbol/type providers and Phase 4 output work have shipped, with specific remaining gaps (including function-definition type prototypes and some injection handling). The release docs therefore disagree; do not treat the extension README's Phase 2 status as a current exhaustive capability statement. The two-function observation above independently confirms named fields and globals on that fixture, but does not verify the upstream claims at scale. The guide describes callbacks into Ghidra and cache flushing after each function; any REA design must verify those semantics with the exact version rather than assuming direct CLI behavior.

Released Kuna 1.989 headless backend control

Using the macOS arm64 kuna_ghidra binary from the official v1.989 extension release, I reran a disposable Ghidra 12.1.4 analyzeHeadless import of an owned arm64 Mach-O. A test-only Java script opened a stock DecompInterface, decompiled audit_leaf and audit_caller, disposed it, temporarily changed DecompileProcessFactory.exepath in that headless JVM, then opened a fresh interface using the Kuna backend and restored the prior field afterward. Both backends reported both functions complete. Kuna's text identified version 1.989 and retained the named audit_global and audit_leaf call in these outputs. Ghidra emitted DWARF import errors for some frame/register expressions, so this is not a clean full-debug-information test. The target was not executed, its SHA-256 510a23fc3c152175b9977130b3b1711d580e47d22a085cdee6a4e0c9dea2c51a stayed unchanged, and the temporary imported project was deleted.

The release labels the extension for Ghidra 12.1.2 and says other 12.x versions have unverified compatibility. This is a direct backend control on two functions, not installation of the GUI extension, an REA provider integration, semantic equivalence, high-p-code validation, timeout recovery, or a general 12.1.4 compatibility claim. The v1.989 integration guide describes the child-process protocol; any future REA work still needs explicit dual-engine identity, cancellation/restart, and project-state semantics.

High-p-code and existing REA workflow control on 1.989

On the same unexecuted Mach-O, a fresh disposable Ghidra 12.1.4 headless import with the released Kuna 1.989 backend returned a non-null HighFunction for each function. The stock and Kuna backends each exposed COPY=2, INT_ADD=1, RETURN=1 for _audit_leaf, and CALL=1, COPY=1, INT_ADD=1, RETURN=1 for _audit_caller. The Ghidra import again reported unrecoverable DWARF frame/register expressions. Equal opcode counts do not establish equal operations, varnodes, def-use, calling conventions, or semantic results; these are two small sampled functions.

Separately, real one-shot REA trace-native-values _audit_caller on the same file using Ghidra 12.1.4 returned a two-procedure graph with 10 nodes, 10 edges, no reported unknowns and truncated: false. REA's independent Kuna 1.989 provider returned no graph nodes or edges and explicitly reported Provider has no native def-use model. These are different provider workflows: this test did not connect the hybrid HighFunction to REA, and no runtime execution or general graph completeness is implied. The target SHA-256 remained 510a23fc3c152175b9977130b3b1711d580e47d22a085cdee6a4e0c9dea2c51a. This is an analyst-facing reason to evaluate whether a separately identified hybrid value-flow operation is useful, not a reason to silently claim that the existing Kuna provider supplies Ghidra's def-use semantics.

Typed dSYM control and input identity

A separate unexecuted arm64 Mach-O used struct Sample { int count; int scale; } and a function computing r->count * r->scale + audit_bias. With its compiler-produced dSYM adjacent, Ghidra 12.1.4 headless imported three DWARF types; both its stock decompiler and Kuna 1.989 backend produced Sample * and the named count and scale fields. Ghidra also reported unrecoverable frame/register DWARF expressions. REA's independent Ghidra and Kuna providers on the same target returned integer-pointer pseudocode without those field names.

To distinguish engine behavior from selected input, I copied the executable unchanged into a separate directory without its dSYM and reimported it into a fresh disposable headless project. Both direct Ghidra decompilers then produced integer-pointer pseudocode without those field names, matching the REA result at that level. Both executable copies had SHA-256 c244f7d7d9fa9b7f4cfcb1f69d69cc1f83ad98fe38b0293b223b3ca7d9076363; neither ran. The first headless no-sidecar script used only the DWARF-derived unprefixed function name and failed to locate it; the corrected script also accepted the Mach-O _audit_record symbol.

This supports an input-selection explanation for this fixture, not a general engine quality comparison. #1193 separately tracks explicit matching dSYM selection and its provenance. Any hybrid design must bind the chosen debug companion and analysis state as well as both engine identities; automatically using ambient neighboring debug files would change the analyzed inputs. No REA hybrid integration was tested.

Originally created by @morluto on GitHub (Oct 30, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/8177 ### Before submitting - [x] I searched the open and closed issues and pull requests for a related discussion. - [x] I understand the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope) must be reviewed before integration; open questions are welcome here. - [x] This discussion does not include credentials, private samples, or sensitive target data. ### Decision needed Is an opt-in Ghidra analysis session using Kuna's Ghidra-protocol decompiler backend worth investigating as a distinct analysis profile, and what evidence and authority would be needed before any integration? ### Context and evidence The upstream Kuna 1.986 release ships an extension and `kuna_ghidra` backend built for Ghidra 12.1.2. Its README describes swapping Ghidra's decompiler process through a private, JVM-wide factory field; it states symbol/type resolution is incomplete and other Ghidra versions are unverified. On macOS arm64, I used Ghidra 12.1.4 `analyzeHeadless` with a self-owned Java post-script and a locally built Mach-O. The script opened a stock `DecompInterface`, decompiled two functions, closed it, switched that factory field in the temporary process to the extension's official 1.986 macOS backend, and opened a fresh interface. Both backends reported the two functions complete. The Kuna output identified itself as 1.986 and used Ghidra's struct type, field names, global and callee names; stock output also had those. The original file was not executed or modified, the import was discarded, and the script restored the factory field. The first script attempt failed to compile due to a mistaken result class name; the corrected attempt produced these results. This was not an REA provider test, an installed GUI plugin, or proof of compatibility on Ghidra 12.1.4 beyond these two functions. REA's current Kuna provider analyzes directly with the Kuna CLI; its Ghidra provider uses Ghidra's own decompiler. The hybrid could let an analyst use one program's analysis with a different engine, but correctness, high-p-code semantics, operation compatibility, and practical value remain unknown. ### Expected interaction with targets Read an explicitly selected local binary and invoke caller-supplied Ghidra and Kuna executables in an owned headless session. The experiment did not modify the executable or a user-owned project. Exact process, workspace, and analysis-state effects for an integration require review; there is no proposal to control a GUI or change a Ghidra installation. ### Boundary details (optional) Do not silently replace the Ghidra core or equate this hybrid with the independent Kuna provider. Bind both engine identities, versions, backend executable identity, selected target, applied analysis state, protocol compatibility and unknown coverage in the analysis profile and cache. The private static switch affects future decompiler spawns across the JVM, and already-open interfaces retain their process. REA's persistent interface, timeout recovery and decompiler-derived value flow must remain correct, or be reported unavailable. Windows P0 and applicable scope decisions still apply. ### Options and tradeoffs Compare a dedicated exploratory verifier and opt-in profile, direct use of upstream's headless recipe, or deferring until upstream's symbol scope and protocol mature. Do not turn upstream's GUI extension into an implicit dependency; choose an analyst-visible operation and evidence semantics before designing controls. ### Verification available so far I have a prototype or observed results ### Success criteria and open questions Evaluate representative stripped and typed binaries, functions that use globals/calls/complex types, failures and provider timeouts, fresh process restart, cancellation and cleanup. Compare pseudocode, p-code/dossier facets and honest partial-coverage reporting against independent provider evidence, without treating textual agreement as semantic proof. Verify exact Ghidra/Kuna pairs and supported hosts before making any supported-release claim. ### References Kuna 1.986 release: https://github.com/Noelo-Lab/kuna/releases/tag/v1.986 Ghidra integration documentation: https://github.com/Noelo-Lab/kuna/blob/v1.986/docs/ghidra-integration.md Related type-hypothesis discussion: https://github.com/morluto/rea/issues/8166 A follow-up in the same headless setup inspected `HighFunction` p-code: for `adjust`, both backends returned four operations including one return; for `evaluate`, stock returned 14 operations and Kuna 15, each including one call, two loads and one return. This confirms those facets were present on the fixture, not that operations, def-use graphs, semantics, or REA's dossier projections are equivalent. ### Upstream documentation correction The extension zip's README describes Phase 2 and missing global/type resolution, but the [v1.986 integration guide](https://github.com/Noelo-Lab/kuna/blob/v1.986/docs/ghidra-integration.md) says Phase 3 lazy symbol/type providers and Phase 4 output work have shipped, with specific remaining gaps (including function-definition type prototypes and some injection handling). The release docs therefore disagree; do not treat the extension README's Phase 2 status as a current exhaustive capability statement. The two-function observation above independently confirms named fields and globals on that fixture, but does not verify the upstream claims at scale. The guide describes callbacks into Ghidra and cache flushing after each function; any REA design must verify those semantics with the exact version rather than assuming direct CLI behavior. ### Released Kuna 1.989 headless backend control Using the macOS arm64 `kuna_ghidra` binary from the official [v1.989 extension release](https://github.com/Noelo-Lab/kuna/releases/tag/v1.989), I reran a disposable Ghidra 12.1.4 `analyzeHeadless` import of an owned arm64 Mach-O. A test-only Java script opened a stock `DecompInterface`, decompiled `audit_leaf` and `audit_caller`, disposed it, temporarily changed `DecompileProcessFactory.exepath` in that headless JVM, then opened a fresh interface using the Kuna backend and restored the prior field afterward. Both backends reported both functions complete. Kuna's text identified version 1.989 and retained the named `audit_global` and `audit_leaf` call in these outputs. Ghidra emitted DWARF import errors for some frame/register expressions, so this is not a clean full-debug-information test. The target was not executed, its SHA-256 `510a23fc3c152175b9977130b3b1711d580e47d22a085cdee6a4e0c9dea2c51a` stayed unchanged, and the temporary imported project was deleted. The release labels the extension for Ghidra 12.1.2 and says other 12.x versions have unverified compatibility. This is a direct backend control on two functions, not installation of the GUI extension, an REA provider integration, semantic equivalence, high-p-code validation, timeout recovery, or a general 12.1.4 compatibility claim. The [v1.989 integration guide](https://github.com/Noelo-Lab/kuna/blob/v1.989/docs/ghidra-integration.md) describes the child-process protocol; any future REA work still needs explicit dual-engine identity, cancellation/restart, and project-state semantics. ### High-p-code and existing REA workflow control on 1.989 On the same unexecuted Mach-O, a fresh disposable Ghidra 12.1.4 headless import with the released Kuna 1.989 backend returned a non-null `HighFunction` for each function. The stock and Kuna backends each exposed `COPY=2, INT_ADD=1, RETURN=1` for `_audit_leaf`, and `CALL=1, COPY=1, INT_ADD=1, RETURN=1` for `_audit_caller`. The Ghidra import again reported unrecoverable DWARF frame/register expressions. Equal opcode counts do not establish equal operations, varnodes, def-use, calling conventions, or semantic results; these are two small sampled functions. Separately, real one-shot REA `trace-native-values _audit_caller` on the same file using Ghidra 12.1.4 returned a two-procedure graph with 10 nodes, 10 edges, no reported unknowns and `truncated: false`. REA's independent Kuna 1.989 provider returned no graph nodes or edges and explicitly reported `Provider has no native def-use model`. These are different provider workflows: this test did not connect the hybrid HighFunction to REA, and no runtime execution or general graph completeness is implied. The target SHA-256 remained `510a23fc3c152175b9977130b3b1711d580e47d22a085cdee6a4e0c9dea2c51a`. This is an analyst-facing reason to evaluate whether a separately identified hybrid value-flow operation is useful, not a reason to silently claim that the existing Kuna provider supplies Ghidra's def-use semantics. ### Typed dSYM control and input identity A separate unexecuted arm64 Mach-O used `struct Sample { int count; int scale; }` and a function computing `r->count * r->scale + audit_bias`. With its compiler-produced dSYM adjacent, Ghidra 12.1.4 headless imported three DWARF types; both its stock decompiler and Kuna 1.989 backend produced `Sample *` and the named `count` and `scale` fields. Ghidra also reported unrecoverable frame/register DWARF expressions. REA's independent Ghidra and Kuna providers on the same target returned integer-pointer pseudocode without those field names. To distinguish engine behavior from selected input, I copied the executable unchanged into a separate directory without its dSYM and reimported it into a fresh disposable headless project. Both direct Ghidra decompilers then produced integer-pointer pseudocode without those field names, matching the REA result at that level. Both executable copies had SHA-256 `c244f7d7d9fa9b7f4cfcb1f69d69cc1f83ad98fe38b0293b223b3ca7d9076363`; neither ran. The first headless no-sidecar script used only the DWARF-derived unprefixed function name and failed to locate it; the corrected script also accepted the Mach-O `_audit_record` symbol. This supports an input-selection explanation for this fixture, not a general engine quality comparison. #1193 separately tracks explicit matching dSYM selection and its provenance. Any hybrid design must bind the chosen debug companion and analysis state as well as both engine identities; automatically using ambient neighboring debug files would change the analyzed inputs. No REA hybrid integration was tested.
Author
Owner

@morluto commented on GitHub (Oct 30, 2026):

A related direct-CLI comparison on the owned arm64 _evaluate fixture: Kuna 1.986 decompile --raw --regions printed pseudocode followed by p-code-like block/operation text and a nested region report. It rejected either --raw --json or --regions --json on the tested release. In a real REA/Ghidra MCP session, trace_native_values for the same function returned 19 structured nodes and 17 edges under the chosen limits, including one argument binding and one return binding; REA/Kuna returned no nodes with an explicit Provider has no native def-use model unknown.

This is a potential analyst question—recovering value dependencies—not evidence that the textual upstream output has stable, complete or equivalent semantics. It does not justify parsing console lines as interchangeable with Ghidra HighFunction, or prove that the hybrid backend yields correct REA value traces. If a structured source or hybrid is evaluated, require operation/variable identity, block membership, deadness, call/argument binding, coverage and timeout/cleanup checks for exact version pairs.

<!-- gh-comment-id:6533488075 --> @morluto commented on GitHub (Oct 30, 2026): A related direct-CLI comparison on the owned arm64 `_evaluate` fixture: Kuna 1.986 `decompile --raw --regions` printed pseudocode followed by p-code-like block/operation text and a nested region report. It rejected either `--raw --json` or `--regions --json` on the tested release. In a real REA/Ghidra MCP session, `trace_native_values` for the same function returned 19 structured nodes and 17 edges under the chosen limits, including one argument binding and one return binding; REA/Kuna returned no nodes with an explicit `Provider has no native def-use model` unknown. This is a potential analyst question—recovering value dependencies—not evidence that the textual upstream output has stable, complete or equivalent semantics. It does not justify parsing console lines as interchangeable with Ghidra HighFunction, or prove that the hybrid backend yields correct REA value traces. If a structured source or hybrid is evaluated, require operation/variable identity, block membership, deadness, call/argument binding, coverage and timeout/cleanup checks for exact version pairs.
Author
Owner

@morluto commented on GitHub (Oct 31, 2026):

Looping in @gensokyo-dev @kaoru0822-kitauji @N0zoM1z0 for visibility on the Ghidra, Kuna, and REA tool-design investigation.

<!-- gh-comment-id:6538786958 --> @morluto commented on GitHub (Oct 31, 2026): Looping in @gensokyo-dev @kaoru0822-kitauji @N0zoM1z0 for visibility on the Ghidra, Kuna, and REA tool-design investigation.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#6464
No description provided.