[GH-ISSUE #8257] [Bug] Ghidra loader-created ELF entry labels are treated as exact source symbols #6514

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

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

Originally assigned to: @kaoru0822-kitauji on GitHub.

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Native analysis or decompilation

Observed behavior

For stripped ELF files with no symbol table, Ghidra 12.1.4 creates an entry function at each ELF header entry point and reports its symbol source as imported. REA treats that provider-created role label as a stable exact symbol: match_native_functions pairs the entries with basis: exact-symbol, and compare_functions reports function_match: matched, method: symbol. The two selected bodies in this reproduction are deliberately different functions. Matching by shared entry-point role may be useful, but it is not the same observation as a symbol name supplied by the artifact.

Reproduction

  1. Build two small x86-64 ELF executables and strip all symbols. The first entry at 0x201120 calls three local functions. In the second, select a different function as ELF entry at 0x20113b; that body simply returns 42. llvm-readelf -h -S -s confirms both entry addresses and no symbol table; LLVM disassembly confirms the distinct bodies.
  2. In real REA/Ghidra MCP, collect list_procedures for each, export before close, import the two bundles, and run match_native_functions. Both inventories call their entry entry with symbol.source: imported, and the result pairs them as matched / exact-symbol.
  3. Independently collect each analyze_function entry dossier, export/import them, then compare_functions using retained references. It reports a symbol match and identity dimension unchanged while correctly reporting pseudocode and assembly changed.

After supplying exact body ranges and reads, matching retains the same symbol-based entry pair despite different body fingerprints. It also matches one unique equal-byte body and keeps repeated equal-byte bodies as an unresolved group. The latter controls behaved as documented. Neither artifact was executed or modified; hashes remained unchanged.

Expected behavior

Do not promote a loader-generated role name to artifact-supplied symbol identity solely because Ghidra labels its origin imported. Preserve the actual provider label and entry-point observation, and represent any entry-role correspondence distinctly from exact symbol identity or leave correspondence uncertain as appropriate. Avoid a spelling-based entry blacklist: an actual artifact can legitimately contain that name. Verify other loaders, explicit source symbols, and user-renamed/project symbols before broadening the rule.

Impact and scope

A cross-build matching or comparison workflow can assert name-based correspondence for two unrelated bodies because both occupy their respective ELF entry roles. The comparison does expose changed bodies and explicitly does not assert behavioral equivalence; this report concerns the provenance and strength of the identity conclusion. Observed on two constructed ELF pairs and one real Ghidra/host version; frequency elsewhere is unknown.

REA version

53a4ab78451a0654e9c3beb3d43a5e371a288fcc.

Runtime and platform

Node 24.22.1; macOS arm64; Ghidra 12.1.4; JDK 21.

Target and artifact context

Two self-built x86-64 static ELF executables, stripped with llvm-strip --strip-all; no target execution. SHA-256 values 22c95361532a742a8ea1362971e4f439cf9e2ea52700d0533d43cb5ed8fd4172 and 1047dee164ac51bb396e52df6f95aed80d33a4204f32995a2af0ac927ee36fe6, unchanged.

Interface

MCP

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Left: {address:"0x201120", value:"entry", symbol:{source:"imported"}}; right: {address:"0x20113b", value:"entry", symbol:{source:"imported"}}. Matcher: {state:"matched", basis:"exact-symbol"}. Comparison: {function_match:{status:"matched",method:"symbol"}} with pseudocode and assembly changed. Left entry body contains calls; right entry body returns 0x2a.

Typed outcome or recovery expected

Keep source-symbol origin separate from provider/loader-created labels and entry-point role. Any change should preserve observed source facts and uncertainty in inventory, dossier, matcher and comparison Evidence, and preserve available independent byte matches. Unknown origin must not become absence of a symbol.

Analysis or suspected cause

Ghidra 12.1.4's ELF entry-point loader calls createEntryFunction with the constant name entry; its creation path distinguishes an already-created symbol-based function. Source inspection explains the loader-created label; the live Evidence establishes Ghidra reported it as imported here. REA's functionMatch treats stable equal names as a symbol match. Investigate the shared symbol-origin boundary rather than special-casing the comparison consumer.

Additional context

Related #8173 addresses using independently observed symbol facts where Kuna does not expose origin; #8213 concerns an incorrect mismatch after an analyst rename. This report concerns a Ghidra loader-generated imported label presented as exact source-symbol correspondence.

Originally created by @morluto on GitHub (Oct 31, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/8257 Originally assigned to: @kaoru0822-kitauji on GitHub. ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Native analysis or decompilation ### Observed behavior For stripped ELF files with no symbol table, Ghidra 12.1.4 creates an `entry` function at each ELF header entry point and reports its symbol source as `imported`. REA treats that provider-created role label as a stable exact symbol: `match_native_functions` pairs the entries with `basis: exact-symbol`, and `compare_functions` reports `function_match: matched, method: symbol`. The two selected bodies in this reproduction are deliberately different functions. Matching by shared entry-point role may be useful, but it is not the same observation as a symbol name supplied by the artifact. ### Reproduction 1. Build two small x86-64 ELF executables and strip all symbols. The first entry at `0x201120` calls three local functions. In the second, select a different function as ELF entry at `0x20113b`; that body simply returns 42. `llvm-readelf -h -S -s` confirms both entry addresses and no symbol table; LLVM disassembly confirms the distinct bodies. 2. In real REA/Ghidra MCP, collect `list_procedures` for each, export before close, import the two bundles, and run `match_native_functions`. Both inventories call their entry `entry` with `symbol.source: imported`, and the result pairs them as `matched / exact-symbol`. 3. Independently collect each `analyze_function` entry dossier, export/import them, then `compare_functions` using retained references. It reports a symbol match and identity dimension unchanged while correctly reporting pseudocode and assembly changed. After supplying exact body ranges and reads, matching retains the same symbol-based entry pair despite different body fingerprints. It also matches one unique equal-byte body and keeps repeated equal-byte bodies as an unresolved group. The latter controls behaved as documented. Neither artifact was executed or modified; hashes remained unchanged. ### Expected behavior Do not promote a loader-generated role name to artifact-supplied symbol identity solely because Ghidra labels its origin `imported`. Preserve the actual provider label and entry-point observation, and represent any entry-role correspondence distinctly from exact symbol identity or leave correspondence uncertain as appropriate. Avoid a spelling-based `entry` blacklist: an actual artifact can legitimately contain that name. Verify other loaders, explicit source symbols, and user-renamed/project symbols before broadening the rule. ### Impact and scope A cross-build matching or comparison workflow can assert name-based correspondence for two unrelated bodies because both occupy their respective ELF entry roles. The comparison does expose changed bodies and explicitly does not assert behavioral equivalence; this report concerns the provenance and strength of the identity conclusion. Observed on two constructed ELF pairs and one real Ghidra/host version; frequency elsewhere is unknown. ### REA version `53a4ab78451a0654e9c3beb3d43a5e371a288fcc`. ### Runtime and platform Node 24.22.1; macOS arm64; Ghidra 12.1.4; JDK 21. ### Target and artifact context Two self-built x86-64 static ELF executables, stripped with `llvm-strip --strip-all`; no target execution. SHA-256 values `22c95361532a742a8ea1362971e4f439cf9e2ea52700d0533d43cb5ed8fd4172` and `1047dee164ac51bb396e52df6f95aed80d33a4204f32995a2af0ac927ee36fe6`, unchanged. ### Interface MCP ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Left: `{address:"0x201120", value:"entry", symbol:{source:"imported"}}`; right: `{address:"0x20113b", value:"entry", symbol:{source:"imported"}}`. Matcher: `{state:"matched", basis:"exact-symbol"}`. Comparison: `{function_match:{status:"matched",method:"symbol"}}` with pseudocode and assembly changed. Left entry body contains calls; right entry body returns `0x2a`. ### Typed outcome or recovery expected Keep source-symbol origin separate from provider/loader-created labels and entry-point role. Any change should preserve observed source facts and uncertainty in inventory, dossier, matcher and comparison Evidence, and preserve available independent byte matches. Unknown origin must not become absence of a symbol. ### Analysis or suspected cause Ghidra 12.1.4's [ELF entry-point loader](https://github.com/NationalSecurityAgency/ghidra/blob/Ghidra_12.1.4_build/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java#L651-L657) calls `createEntryFunction` with [the constant name `entry`](https://github.com/NationalSecurityAgency/ghidra/blob/Ghidra_12.1.4_build/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfLoader.java); its [creation path](https://github.com/NationalSecurityAgency/ghidra/blob/Ghidra_12.1.4_build/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java#L779-L798) distinguishes an already-created symbol-based function. Source inspection explains the loader-created label; the live Evidence establishes Ghidra reported it as imported here. REA's [`functionMatch`](https://github.com/morluto/rea/blob/53a4ab78451a0654e9c3beb3d43a5e371a288fcc/src/domain/functionComparisonNormalization.ts#L17-L38) treats stable equal names as a symbol match. Investigate the shared symbol-origin boundary rather than special-casing the comparison consumer. ### Additional context Related [#8173](https://github.com/morluto/rea/issues/8173) addresses using independently observed symbol facts where Kuna does not expose origin; [#8213](https://github.com/morluto/rea/issues/8213) concerns an incorrect mismatch after an analyst rename. This report concerns a Ghidra loader-generated imported label presented as exact source-symbol correspondence.
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:6538809933 --> @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.
Author
Owner

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

Three additional source-owned x86-64 ELF controls show why a fix cannot blacklist the spelling entry or treat an empty dynamic export inventory as proof that no artifact symbol exists. All were analyzed with real REA/Ghidra 12.1.4 on macOS arm64 at REA main 245a581818ab13694fa438a06e39898ce03ab206; none were executed or modified.

  1. A PIE's .dynsym and .symtab both contain a real global FUNC named entry (value 0x1324) and an OBJECT named entry_data. Independent llvm-readelf and REA inspect_native_exports both reported the function and object as distinct types. Ghidra list_procedures reported function entry at 0x101324, primary source imported; address_to_file_offset mapped that analysis address to file offset 804. The distinct coordinate systems need an authenticated mapping rather than equality by number.
  2. A non-PIE ELF contains a genuine global FUNC entry in .symtab, verified by llvm-readelf, but has no .dynsym. REA inspect_native_exports correctly returned an empty complete dynamic export inventory with an explicit no-dynamic-table limitation; Ghidra again reported entry as an imported function. The empty export set does not establish absence of source symbols from .symtab.
  3. The original fully stripped ELF has neither symbol table, yet Ghidra again reported its loader-created entry as an imported function. Its dynamic export inventory was likewise empty. Thus the currently projected name, type and provider source fields alone do not distinguish the two controls; the absence of a dynamic export cannot safely distinguish them either.

In the PIE session, I also tried supplying the authentic inspect_native_exports record as a match_native_functions.left_observations input. It was explicitly rejected with invalid_request, identifying that only list_names, procedure_info, read_bytes, and analyze_function Evidence are admitted. This is the existing contract and is related to #8173, not a claim that accepting every export is safe: symbol type, artifact/slice identity, and coordinate mapping still require proof. These small controls do not establish behavior for all ELF loaders, symbols or provider versions.

<!-- gh-comment-id:6540448730 --> @morluto commented on GitHub (Oct 31, 2026): Three additional source-owned x86-64 ELF controls show why a fix cannot blacklist the spelling `entry` or treat an empty dynamic export inventory as proof that no artifact symbol exists. All were analyzed with real REA/Ghidra 12.1.4 on macOS arm64 at REA main `245a581818ab13694fa438a06e39898ce03ab206`; none were executed or modified. 1. A PIE's `.dynsym` and `.symtab` both contain a real global `FUNC` named `entry` (value `0x1324`) and an `OBJECT` named `entry_data`. Independent `llvm-readelf` and REA `inspect_native_exports` both reported the function and object as distinct types. Ghidra `list_procedures` reported function `entry` at `0x101324`, primary source `imported`; `address_to_file_offset` mapped that analysis address to file offset 804. The distinct coordinate systems need an authenticated mapping rather than equality by number. 2. A non-PIE ELF contains a genuine global `FUNC entry` in `.symtab`, verified by `llvm-readelf`, but has no `.dynsym`. REA `inspect_native_exports` correctly returned an empty complete **dynamic export** inventory with an explicit no-dynamic-table limitation; Ghidra again reported `entry` as an imported function. The empty export set does not establish absence of source symbols from `.symtab`. 3. The original fully stripped ELF has neither symbol table, yet Ghidra again reported its loader-created `entry` as an imported function. Its dynamic export inventory was likewise empty. Thus the currently projected `name`, `type` and provider `source` fields alone do not distinguish the two controls; the absence of a dynamic export cannot safely distinguish them either. In the PIE session, I also tried supplying the authentic `inspect_native_exports` record as a `match_native_functions.left_observations` input. It was explicitly rejected with `invalid_request`, identifying that only `list_names`, `procedure_info`, `read_bytes`, and `analyze_function` Evidence are admitted. This is the existing contract and is related to #8173, not a claim that accepting every export is safe: symbol type, artifact/slice identity, and coordinate mapping still require proof. These small controls do not establish behavior for all ELF loaders, symbols or provider versions.
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#6514
No description provided.