[GH-ISSUE #2030] [Feature] Decode Swift String literals in native function results #676

Closed
opened 2026-10-15 18:27:17 +00:00 by bsakaguchi · 1 comment
Owner

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

Before submitting

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

Area

Native analysis or decompilation

Current limitation

On arm64 Swift binaries analyzed through Ghidra, Swift String literals are invisible to referenced_strings, xrefs and string search, and appear in pseudocode only as opaque integer pairs. Swift does not pass a literal as a plain pointer to C string data:

  • Small strings (up to 15 UTF-8 bytes) are packed into the two registers of the String value itself. The text never exists as data in the binary, so search cannot find it at all.
  • Large strings keep their bytes in __TEXT,__cstring, but code refers to them through an encoded object value: flag bits plus address - 0x20. Ghidra creates no reference to the real address, so referenced_strings and xrefs miss it.

Most short user-facing text in a Swift app (labels, status messages, format strings, dictionary keys) is a small string, so an agent working through REA cannot see the text that usually explains what a function does.

Use cases

The README's core workflow is "see a feature you like, understand how it works". For a Swift app, the quickest anchor is usually visible text: find the function that builds a label or error message, then follow its callers. Today that path fails on Swift targets, while the same question works on Objective-C or C targets whose literals are ordinary data references.

Observed evidence

Target: my own open source SwiftUI app Vigil (github.com/thefolahan/vigil), arm64 slice extracted with lipo -thin arm64, analyzed with rea function <slice> <address> --provider ghidra (REA 6.4.0, Ghidra 12.1.4, macOS 27.0.1 on Apple silicon).

VigilKit.LidMode.Failure.errorDescription is, in source:

case .unsafeUserName(let name): return "The user name \(name) cannot be written into a sudoers rule safely."
case .command(let output): return output.isEmpty ? "The power setting could not be changed." : output

REA returns referenced_strings: [] and no outgoing reference to any string, and the pseudocode contains:

SVar2.bridgeObject = (void *)0xee0020656d616e20;
SVar2.str = (char *)0x7265737520656854;          // small: "The user name " (0xe = ASCII, count 14)
...
SVar4.bridgeObject = (void *)0x8000000100070250;
SVar4.str = (char *)0xd00000000000002e;          // large: count 46 at 0x100070250 + 0x20
...
SVar3.str = (char *)0xd000000000000027;          // large: count 39, "The power setting could not be changed."

The large literal is real string data that the code does load (otool disassembly):

000000010004dab4  adrp  x8, 35            ; 0x100070000
000000010004dab8  add   x8, x8, #0x270    ; literal pool for: " cannot be written into a sudoers rule safely."
000000010004dabc  sub   x8, x8, #0x20

yet rea xrefs <slice> 0x100070270 --provider ghidra returns [].

A second function, VigilKit.Format.rate, builds "%.1f KB/s", "%.1f MB/s" and " B/s" entirely from small strings. Its dossier also has referenced_strings: [], and rea search <slice> "SleepDisabled" --provider ghidra (a 13 byte literal compared in LidMode.parseSleepDisabled) matches only symbol names, never the literal.

Desired outcome and success criteria

When a function dossier, referenced_strings or string search covers Swift code, recognize Swift String literal values and decode them, with provenance that marks them as decoded from the Swift ABI layout rather than observed as data references:

  • Small ASCII/UTF-8 strings: a 16 byte (_countAndFlags, _object) pair whose top nibble marks a small string; the count is in bits 56 to 59 of the second word, and the bytes are the first word followed by the low bytes of the second.
  • Large native literals: _countAndFlags with the immortal/ASCII flags and the count in the low 48 bits, and _object = 0x8000000000000000 | (address - 0x20); resolve address to the string data and add the reference.

Success: on the Vigil example, the errorDescription dossier lists all three literals with their decoded text, and search "SleepDisabled" finds the literal in parseSleepDisabled. Possible surfaces, open to maintainer preference: referenced_strings entries with a swift-literal provenance, an annotation on the pseudocode constants, and string search over decoded small literals.

Authority boundaries and non-goals

  • arm64 Mach-O through Ghidra was observed; x86_64 Swift uses the same String layout, so the decoder would likely apply there too, but I have not verified that.
  • Not proposing to rewrite Ghidra pseudocode; the observed constants should stay, with decoded values added alongside.
  • Bridged NSString literals and non literal strings built at runtime are out of scope.

Alternatives considered

Relying on Ghidra alone: Ghidra 12.1.4 neither decodes small strings nor follows the address - 0x20 encoding here. Running strings on the binary finds large literals but cannot connect them to functions, and never finds small ones.

Additional context

Found while checking REA's results against the source of my own apps. Happy to work on a PR once the maintainers agree on where the decoded values should appear.

Originally created by @thefolahan on GitHub (Oct 12, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2030 ### Before submitting - [x] I searched the open and closed issues and pull requests for a related proposal. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Native analysis or decompilation ### Current limitation On arm64 Swift binaries analyzed through Ghidra, Swift `String` literals are invisible to `referenced_strings`, `xrefs` and string `search`, and appear in pseudocode only as opaque integer pairs. Swift does not pass a literal as a plain pointer to C string data: - **Small strings** (up to 15 UTF-8 bytes) are packed into the two registers of the `String` value itself. The text never exists as data in the binary, so `search` cannot find it at all. - **Large strings** keep their bytes in `__TEXT,__cstring`, but code refers to them through an encoded object value: flag bits plus `address - 0x20`. Ghidra creates no reference to the real address, so `referenced_strings` and `xrefs` miss it. Most short user-facing text in a Swift app (labels, status messages, format strings, dictionary keys) is a small string, so an agent working through REA cannot see the text that usually explains what a function does. ### Use cases The README's core workflow is "see a feature you like, understand how it works". For a Swift app, the quickest anchor is usually visible text: find the function that builds a label or error message, then follow its callers. Today that path fails on Swift targets, while the same question works on Objective-C or C targets whose literals are ordinary data references. #### Observed evidence Target: my own open source SwiftUI app Vigil (github.com/thefolahan/vigil), arm64 slice extracted with `lipo -thin arm64`, analyzed with `rea function <slice> <address> --provider ghidra` (REA 6.4.0, Ghidra 12.1.4, macOS 27.0.1 on Apple silicon). `VigilKit.LidMode.Failure.errorDescription` is, in source: ```swift case .unsafeUserName(let name): return "The user name \(name) cannot be written into a sudoers rule safely." case .command(let output): return output.isEmpty ? "The power setting could not be changed." : output ``` REA returns `referenced_strings: []` and no outgoing reference to any string, and the pseudocode contains: ```c SVar2.bridgeObject = (void *)0xee0020656d616e20; SVar2.str = (char *)0x7265737520656854; // small: "The user name " (0xe = ASCII, count 14) ... SVar4.bridgeObject = (void *)0x8000000100070250; SVar4.str = (char *)0xd00000000000002e; // large: count 46 at 0x100070250 + 0x20 ... SVar3.str = (char *)0xd000000000000027; // large: count 39, "The power setting could not be changed." ``` The large literal is real string data that the code does load (`otool` disassembly): ```text 000000010004dab4 adrp x8, 35 ; 0x100070000 000000010004dab8 add x8, x8, #0x270 ; literal pool for: " cannot be written into a sudoers rule safely." 000000010004dabc sub x8, x8, #0x20 ``` yet `rea xrefs <slice> 0x100070270 --provider ghidra` returns `[]`. A second function, `VigilKit.Format.rate`, builds `"%.1f KB/s"`, `"%.1f MB/s"` and `" B/s"` entirely from small strings. Its dossier also has `referenced_strings: []`, and `rea search <slice> "SleepDisabled" --provider ghidra` (a 13 byte literal compared in `LidMode.parseSleepDisabled`) matches only symbol names, never the literal. ### Desired outcome and success criteria When a function dossier, `referenced_strings` or string search covers Swift code, recognize Swift `String` literal values and decode them, with provenance that marks them as decoded from the Swift ABI layout rather than observed as data references: - **Small ASCII/UTF-8 strings:** a 16 byte `(_countAndFlags, _object)` pair whose top nibble marks a small string; the count is in bits 56 to 59 of the second word, and the bytes are the first word followed by the low bytes of the second. - **Large native literals:** `_countAndFlags` with the immortal/ASCII flags and the count in the low 48 bits, and `_object = 0x8000000000000000 | (address - 0x20)`; resolve `address` to the string data and add the reference. Success: on the Vigil example, the `errorDescription` dossier lists all three literals with their decoded text, and `search "SleepDisabled"` finds the literal in `parseSleepDisabled`. Possible surfaces, open to maintainer preference: `referenced_strings` entries with a `swift-literal` provenance, an annotation on the pseudocode constants, and string search over decoded small literals. ### Authority boundaries and non-goals - arm64 Mach-O through Ghidra was observed; x86_64 Swift uses the same `String` layout, so the decoder would likely apply there too, but I have not verified that. - Not proposing to rewrite Ghidra pseudocode; the observed constants should stay, with decoded values added alongside. - Bridged `NSString` literals and non literal strings built at runtime are out of scope. ### Alternatives considered Relying on Ghidra alone: Ghidra 12.1.4 neither decodes small strings nor follows the `address - 0x20` encoding here. Running `strings` on the binary finds large literals but cannot connect them to functions, and never finds small ones. ### Additional context Found while checking REA's results against the source of my own apps. Happy to work on a PR once the maintainers agree on where the decoded values should appear.
Author
Owner

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

Good idea! Feel free to submit the PR :)

<!-- gh-comment-id:6117274187 --> @morluto commented on GitHub (Oct 12, 2026): Good idea! Feel free to submit the PR :)
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#676
No description provided.