[GH-ISSUE #2158] [Feature] Decode ldstr user strings in inspect_managed_members #707

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

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

Note

🤖 Claude Opus 5.5 responding on behalf of Nick

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

Evidence, artifacts, or reconstruction (managed static analysis, inspect_managed_members)

Current limitation

inspect_managed_members reports every ldstr operand as a bare 0x70 token, and no REA operation reads the #US stream. The layout admits only #Strings, #GUID and #Blob. An analyst can see that a method loads a literal, but not which one.

This blocks the first move in most managed teardowns: finding where a file name, key, URL or format marker is used. Today that needs a second tool such as ILSpy or dnSpy.

Use cases

  • Save-file and config work on Unity Mono games. In PEAK's Assembly-CSharp.dll (Unity Mono, 1.6 MB), Peak.Quicksave has 12 ldstr literals. The useful one is quicksave.peak, the save file name, and REA currently shows it only as 0x7000e875.
  • Tracing hard-coded keys, endpoints, registry paths and log messages back to the method that uses them, without loading the assembly.

Desired outcome and success criteria

  • Each distinct ldstr token loaded by an inspected method body is decoded once, with its token, #US file offset and value.
  • Values keep their stored UTF-16 code units, including unpaired surrogates.
  • Instruction anchors and normalized_il_sha256 stay unchanged, so build-local tokens and cross-build comparison semantics are preserved.
  • Type selection (--type / type_names) restricts the strings like it restricts methods.
  • A missing #US stream or a malformed item is reported against its token as partial coverage. Readable literals remain.
  • Older member Evidence without the field still parses, with the field reading as not examined.

Authority boundaries and non-goals

  • Static only. No assembly loading or execution.
  • No decompilation.
  • No change to the representation budget.
  • No string search across assemblies.
  • No change to the normalized CIL digest contract.

Alternatives considered

  • Resolving inline on each ldstr anchor repeats a literal at every use and changes the anchor shape.
  • A token-keyed inventory, like member_refs, keeps anchors and digests stable and charges each literal once.

Possible direction

Add a top-level user_strings: [{ token, offset, value }] | null to the member inspection result, defaulting to null the way exported_types does.

I have an implementation with focused tests ready and will open a PR referencing this issue.

References

  • ECMA-335 II.24.2.4 (#US and #Blob heaps).
  • docs/managed-code-analysis.md, normalized CIL digest tuple format.
Originally created by @Nelglor on GitHub (Oct 12, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2158 > [!NOTE] > 🤖 **Claude Opus 5.5 responding on behalf of Nick** ### 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 Evidence, artifacts, or reconstruction (managed static analysis, `inspect_managed_members`) ### Current limitation `inspect_managed_members` reports every `ldstr` operand as a bare `0x70` token, and no REA operation reads the `#US` stream. The layout admits only `#Strings`, `#GUID` and `#Blob`. An analyst can see that a method loads a literal, but not which one. This blocks the first move in most managed teardowns: finding where a file name, key, URL or format marker is used. Today that needs a second tool such as ILSpy or dnSpy. ### Use cases - **Save-file and config work on Unity Mono games.** In PEAK's `Assembly-CSharp.dll` (Unity Mono, 1.6 MB), `Peak.Quicksave` has 12 `ldstr` literals. The useful one is `quicksave.peak`, the save file name, and REA currently shows it only as `0x7000e875`. - **Tracing hard-coded keys, endpoints, registry paths and log messages** back to the method that uses them, without loading the assembly. ### Desired outcome and success criteria - Each distinct `ldstr` token loaded by an inspected method body is decoded once, with its token, `#US` file offset and value. - Values keep their stored UTF-16 code units, including unpaired surrogates. - Instruction anchors and `normalized_il_sha256` stay unchanged, so build-local tokens and cross-build comparison semantics are preserved. - Type selection (`--type` / `type_names`) restricts the strings like it restricts methods. - A missing `#US` stream or a malformed item is reported against its token as partial coverage. Readable literals remain. - Older member Evidence without the field still parses, with the field reading as not examined. ### Authority boundaries and non-goals - Static only. No assembly loading or execution. - No decompilation. - No change to the representation budget. - No string search across assemblies. - No change to the normalized CIL digest contract. ### Alternatives considered - Resolving inline on each `ldstr` anchor repeats a literal at every use and changes the anchor shape. - A token-keyed inventory, like `member_refs`, keeps anchors and digests stable and charges each literal once. ### Possible direction Add a top-level `user_strings: [{ token, offset, value }] | null` to the member inspection result, defaulting to `null` the way `exported_types` does. I have an implementation with focused tests ready and will open a PR referencing this issue. ### References - ECMA-335 II.24.2.4 (`#US` and `#Blob` heaps). - docs/managed-code-analysis.md, normalized CIL digest tuple format.
Author
Owner

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

Design & Implementation Review

Decoding ldstr operands via the #US (User String) stream in ECMA-335 CLI metadata is a high-value addition for managed reverse engineering:

  • Stream Format: In the #US stream, each user string begins with a compressed integer byte length, followed by UTF-16LE characters and a trailing byte indicating whether any character has its high byte set.
  • Operand Token: The ldstr CIL opcode takes a 4-byte 0x70xxxxxx token where the lower 24 bits represent the direct byte offset into the #US stream.
  • Safety / Memory Limits: String literals should be bounded (e.g., truncate strings longer than 1024 characters with an explicit truncated: true limitation indicator) to prevent oversized payloads from blowing up MCP JSON responses.
<!-- gh-comment-id:6134434178 --> @matbrgz commented on GitHub (Oct 12, 2026): ### Design & Implementation Review Decoding `ldstr` operands via the `#US` (User String) stream in ECMA-335 CLI metadata is a high-value addition for managed reverse engineering: - **Stream Format:** In the `#US` stream, each user string begins with a compressed integer byte length, followed by UTF-16LE characters and a trailing byte indicating whether any character has its high byte set. - **Operand Token:** The `ldstr` CIL opcode takes a 4-byte `0x70xxxxxx` token where the lower 24 bits represent the direct byte offset into the `#US` stream. - **Safety / Memory Limits:** String literals should be bounded (e.g., truncate strings longer than 1024 characters with an explicit `truncated: true` limitation indicator) to prevent oversized payloads from blowing up MCP JSON responses.
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#707
No description provided.