[GH-ISSUE #5424] [Bug] Dispatch-metadata symbol fallback turns Ghidra pointer/string labels into fake Objective-C classes and misses every class the binary defines #3289

Open
opened 2026-10-31 00:38:50 +00:00 by bsakaguchi · 2 comments
Owner

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

Before submitting

  • I searched the open and closed issues for a related report (dispatch metadata Ghidra, objc_classes, OBJC_CLASS symbol fallback). Related: #2564 (fallback rejected Ghidra name records with symbol facts; fixed) and #3343 (UI action traces omit handlers). Neither covers which names the fallback derives classes and methods from.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Native analysis or decompilation

Observed behavior

Off macOS, inspect_native_dispatch_metadata uses the symbol-based fallback over Ghidra's list_names. Run on a public Objective-C app, the result is wrong in two ways.

1. Fake classes from Ghidra labels. objcClassSymbolInventory in src/domain/native/objcSwiftMetadata.ts (unchanged on main 074768901) uses an unanchored /(?:OBJC_CLASS_|OBJC_METACLASS_)\$_([^\s]+)/u. Ghidra also puts that text in its own labels for class-reference pointers and symbol-string data, so the suffix after $_ becomes a separate class:

Ghidra label REA objc_classes[].name
PTR__OBJC_CLASS_$_NSAlert_100028668 NSAlert_100028668
s__OBJC_CLASS_$_NSAlert_10002ed0d NSAlert_10002ed0d
s__OBJC_CLASS_$_NSAppleEventManage_10002ed23 NSAppleEventManage_10002ed23 (name truncated by Ghidra)

With --max-records 20000, 102 of the 153 reported classes are these address-suffixed names. Only 51 are real (the imported _OBJC_CLASS_$_… binds, such as NSAlert).

2. The binary's own classes and methods are absent. Ghidra's Objective-C analysis names every class the binary defines as a namespace, and every method as Class::selector, for example KYAAddDurationViewController::viewDidLoad. The fallback only recognises -[Class selector] names, so none of these match:

Ghidra list_names REA result
Own class namespaces (KYA…::) 28 (= 28 __objc_classlist entries) 0 classes
Own method labels (excluding ::override:: thunks) 506 (arm64) / 430 (x86_64) 0 objc_dispatch_implementations

The coverage facets read objc_class_symbols: partial ("symbol_names_do_not_recover_all_runtime_class_metadata") and objc_dispatch_implementations: unsupported ("provider_name_inventory_has_no_objc_method_symbols"). A reader gets a list of mostly system classes plus artefacts, no hint that the app defines 28 classes, and an explicit claim that the inventory has no method symbols.

With the default --max-records 5000, the arm64 run examines only the first 5,000 of 6,077 names and returns just the 51 imported classes.

Reproduction

KeepingYouAwake 1.6.8 (https://github.com/newmarcel/KeepingYouAwake/releases/tag/1.6.8), KeepingYouAwake-1.6.8.zip sha256 8001a149b4490c008fdac19898bce9902d516c4aa6412a7eb0f9a37443b15c6b. Its main executable is universal; Ghidra refuses universal input, so I extracted the slices from the FAT header:

  • arm64 slice: offset 212992, 213,232 bytes, sha256 2b23abb80a33dfd12bd3b66d4cbdc95a61c200a8a84472187de7770d59fdecc1
  • x86_64 slice: offset 16384, 184,848 bytes, sha256 fe8a4613bcb7c67f0ae01a3115a4e7785c51f13601c2f42bd0aa46db1ed4df9e
rea inspect-native-dispatch-metadata /targets/KeepingYouAwake-arm64 --max-records 20000 --format json

Raw names come from MCP open_binary (provider_id: "ghidra") plus list_names. Ground truth for the own classes is the slice's __DATA_CONST,__objc_classlist (224 bytes = 28 pointers) and the KYA… names in __TEXT,__objc_classname.

Expected behavior

  • Derive class names only from symbols whose name is the class symbol, for example ^_?OBJC_(META)?CLASS_\$_, possibly after a library-path prefix such as …/AppKit::_OBJC_CLASS_$_NSAlert. Ghidra PTR__… and s__… data labels are not class symbols.
  • Recognise Ghidra's Class::selector method labels, or report the class namespaces that Ghidra exposes. At minimum, state that classes defined by the binary are not inventoried, rather than provider_name_inventory_has_no_objc_method_symbols.
  • Separate imported (external) classes from classes the binary defines, since the fallback currently mixes them without distinction.

Impact and scope

This affects every non-macOS host, because that's where the symbol fallback runs (the byte reader needs macOS). For any Objective-C app analysed with Ghidra there, the dispatch inventory is mostly artefacts and omits the app's own classes. That includes trace_native_ui_action, which builds on the same result (see #3343).

Tested on rea-agents@6.7.0 with Ghidra 12.1.4 on Linux; the same pattern holds on both slices.

Originally created by @Rinkia on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5424 ### Before submitting - [x] I searched the open and closed issues for a related report (dispatch metadata Ghidra, objc_classes, OBJC_CLASS symbol fallback). Related: #2564 (fallback rejected Ghidra name records with `symbol` facts; fixed) and #3343 (UI action traces omit handlers). Neither covers which names the fallback derives classes and methods from. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Native analysis or decompilation ### Observed behavior Off macOS, `inspect_native_dispatch_metadata` uses the symbol-based fallback over Ghidra's `list_names`. Run on a public Objective-C app, the result is wrong in two ways. **1. Fake classes from Ghidra labels.** `objcClassSymbolInventory` in `src/domain/native/objcSwiftMetadata.ts` (unchanged on `main` `074768901`) uses an unanchored `/(?:OBJC_CLASS_|OBJC_METACLASS_)\$_([^\s]+)/u`. Ghidra also puts that text in its own labels for class-reference pointers and symbol-string data, so the suffix after `$_` becomes a separate class: | Ghidra label | REA `objc_classes[].name` | |---|---| | `PTR__OBJC_CLASS_$_NSAlert_100028668` | `NSAlert_100028668` | | `s__OBJC_CLASS_$_NSAlert_10002ed0d` | `NSAlert_10002ed0d` | | `s__OBJC_CLASS_$_NSAppleEventManage_10002ed23` | `NSAppleEventManage_10002ed23` (name truncated by Ghidra) | With `--max-records 20000`, 102 of the 153 reported classes are these address-suffixed names. Only 51 are real (the imported `_OBJC_CLASS_$_…` binds, such as `NSAlert`). **2. The binary's own classes and methods are absent.** Ghidra's Objective-C analysis names every class the binary defines as a namespace, and every method as `Class::selector`, for example `KYAAddDurationViewController::viewDidLoad`. The fallback only recognises `-[Class selector]` names, so none of these match: | | Ghidra `list_names` | REA result | |---|---|---| | Own class namespaces (`KYA…::`) | 28 (= 28 `__objc_classlist` entries) | 0 classes | | Own method labels (excluding `::override::` thunks) | 506 (arm64) / 430 (x86_64) | 0 `objc_dispatch_implementations` | The coverage facets read `objc_class_symbols: partial` ("symbol_names_do_not_recover_all_runtime_class_metadata") and `objc_dispatch_implementations: unsupported` ("provider_name_inventory_has_no_objc_method_symbols"). A reader gets a list of mostly system classes plus artefacts, no hint that the app defines 28 classes, and an explicit claim that the inventory has no method symbols. With the default `--max-records 5000`, the arm64 run examines only the first 5,000 of 6,077 names and returns just the 51 imported classes. ### Reproduction KeepingYouAwake 1.6.8 (https://github.com/newmarcel/KeepingYouAwake/releases/tag/1.6.8), `KeepingYouAwake-1.6.8.zip` sha256 `8001a149b4490c008fdac19898bce9902d516c4aa6412a7eb0f9a37443b15c6b`. Its main executable is universal; Ghidra refuses universal input, so I extracted the slices from the FAT header: - arm64 slice: offset 212992, 213,232 bytes, sha256 `2b23abb80a33dfd12bd3b66d4cbdc95a61c200a8a84472187de7770d59fdecc1` - x86_64 slice: offset 16384, 184,848 bytes, sha256 `fe8a4613bcb7c67f0ae01a3115a4e7785c51f13601c2f42bd0aa46db1ed4df9e` ```sh rea inspect-native-dispatch-metadata /targets/KeepingYouAwake-arm64 --max-records 20000 --format json ``` Raw names come from MCP `open_binary` (`provider_id: "ghidra"`) plus `list_names`. Ground truth for the own classes is the slice's `__DATA_CONST,__objc_classlist` (224 bytes = 28 pointers) and the `KYA…` names in `__TEXT,__objc_classname`. ### Expected behavior - Derive class names only from symbols whose name is the class symbol, for example `^_?OBJC_(META)?CLASS_\$_`, possibly after a library-path prefix such as `…/AppKit::_OBJC_CLASS_$_NSAlert`. Ghidra `PTR__…` and `s__…` data labels are not class symbols. - Recognise Ghidra's `Class::selector` method labels, or report the class namespaces that Ghidra exposes. At minimum, state that classes defined by the binary are not inventoried, rather than `provider_name_inventory_has_no_objc_method_symbols`. - Separate imported (external) classes from classes the binary defines, since the fallback currently mixes them without distinction. ### Impact and scope This affects every non-macOS host, because that's where the symbol fallback runs (the byte reader needs macOS). For any Objective-C app analysed with Ghidra there, the dispatch inventory is mostly artefacts and omits the app's own classes. That includes `trace_native_ui_action`, which builds on the same result (see #3343). Tested on `rea-agents@6.7.0` with Ghidra 12.1.4 on Linux; the same pattern holds on both slices.
Author
Owner

@matbrgz commented on GitHub (Oct 20, 2026):

Cross-referencing with canonical issue #4368 ([Bug] Empty Swift conformance names escape entry recovery and invalidate dispatch metadata) and sibling #5529 regarding native dispatch metadata reconstruction and provider symbol fallbacks. Cataloged under Cluster AZ in consolidation tracker #4330.

<!-- gh-comment-id:6293053543 --> @matbrgz commented on GitHub (Oct 20, 2026): Cross-referencing with canonical issue #4368 (`[Bug] Empty Swift conformance names escape entry recovery and invalidate dispatch metadata`) and sibling #5529 regarding native dispatch metadata reconstruction and provider symbol fallbacks. Cataloged under Cluster AZ in consolidation tracker #4330.
Author
Owner

@Rinkia commented on GitHub (Oct 26, 2026):

Re-tested on rea-agents@6.8.0 (Linux, Ghidra 12.1.4) with the KeepingYouAwake 1.6.8 arm64 slice from the report (2b23abb8…cc1), --max-records 20000:

6.7.0 6.8.0
objc_classes 153, of which 102 address-suffixed Ghidra labels 51, none address-suffixed
classes the binary defines (KYA…, 28 in __objc_classlist) 0 0
objc_dispatch_implementations 0 0
coverage objc_dispatch_implementations: provider_name_inventory_has_no_objc_method_symbols provider_name_inventory_has_no_supported_objc_method_symbols, plus a new objc_class_definitions: unsupported ("symbol_inventory_does_not_establish_class_definitions_import_origins_or_namespace_declarations")

So the fake classes are gone, and the result now says it doesn't inventory class definitions. That covers the "at minimum" part of the request. Thanks.

What remains is optional: recognising Ghidra's Class::selector method labels (506 on this slice) and class namespaces. If that's out of scope for the symbol fallback, closing this is fine with me.

<!-- gh-comment-id:6412666673 --> @Rinkia commented on GitHub (Oct 26, 2026): Re-tested on `rea-agents@6.8.0` (Linux, Ghidra 12.1.4) with the KeepingYouAwake 1.6.8 arm64 slice from the report (`2b23abb8…cc1`), `--max-records 20000`: | | 6.7.0 | 6.8.0 | |---|---|---| | `objc_classes` | 153, of which 102 address-suffixed Ghidra labels | **51**, none address-suffixed | | classes the binary defines (`KYA…`, 28 in `__objc_classlist`) | 0 | 0 | | `objc_dispatch_implementations` | 0 | 0 | | coverage | `objc_dispatch_implementations`: `provider_name_inventory_has_no_objc_method_symbols` | `provider_name_inventory_has_no_supported_objc_method_symbols`, plus a new `objc_class_definitions: unsupported` ("symbol_inventory_does_not_establish_class_definitions_import_origins_or_namespace_declarations") | So the fake classes are gone, and the result now says it doesn't inventory class definitions. That covers the "at minimum" part of the request. Thanks. What remains is optional: recognising Ghidra's `Class::selector` method labels (506 on this slice) and class namespaces. If that's out of scope for the symbol fallback, closing this is fine with me.
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#3289
No description provided.