[PR #2887] feat(native): decode RT_STRING and RT_VERSION payloads in PE resources #5470

Open
opened 2026-10-31 01:06:53 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/2887
Author: @thefolahan
Created: 10/13/2026
Status: 🔄 Open

Base: main ← Head: feat/pe-string-version-resources


📝 Commits (9)

  • 54fb981 feat(native): decode RT_STRING and RT_VERSION payloads in PE resources
  • 01c1f2d fix(pe): bound resource decoding to declared blocks
  • a889604 fix(pe): reject impossible version text extents
  • 0e5373d docs(pe): describe decoded string and version resources
  • 19d815b docs(pe): wrap resource guidance
  • 25d2f5d fix(pe): validate decoded payload evidence ownership
  • fc350ff perf(pe): reuse identical version payload decodes
  • eea4980 Merge remote-tracking branch 'origin/main' into HEAD
  • 8bcb160 test(pe): share one payload range between repeated version resources

📊 Changes

10 files changed (+918 additions, -9 deletions)

View changed files

📝 .agents/skills/reverse-engineer-anything/SKILL.md (+3 -1)
📝 .agents/skills/reverse-engineer-anything/references/native-and-artifacts.md (+5 -2)
📝 docs/binary-diagnostics.md (+19 -1)
📝 src/application/binaryDiagnostics/PeResourcesService.test.ts (+35 -0)
📝 src/application/binaryDiagnostics/PeResourcesService.ts (+77 -0)
📝 src/contracts/native/binaryDiagnosticsToolContracts.ts (+3 -2)
📝 src/domain/native/peResources.ts (+62 -1)
📝 src/native/pe/PeResourceParser.ts (+18 -2)
➕ src/native/pe/PeResourcePayloads.test.ts (+332 -0)
➕ src/native/pe/PeResourcePayloads.ts (+364 -0)

📄 Description

Summary

Fixes #2812. inspect_pe_resources now decodes RT_STRING and RT_VERSION payloads, which it previously returned as opaque bytes. Both formats are fixed and documented, and they answer two first questions of a Windows investigation: which build is this, and which messages does it contain.

On Microsoft's own dbghelp.dll (shipped inside WebStorm's async-profiler), the result now includes:

version_infos[0]: decoded, file 10.0.19041.685, product 10.0.19041.685, translation 1033/1200
  040904B0: CompanyName="Microsoft Corporation"  FileDescription="Windows Image Helper"
            FileVersion="10.0.19041.685 (WinBuild.160101.0800)"  InternalName="DBGHELP.DLL"
            OriginalFilename="DBGHELP.DLL"  ProductName="Microsoft® Windows® Operating System" ...

and on symsrv.dll, string blocks 63 to 66 expand to IDs such as 1002="Symbol Server" and 1022="Requests/sec".

Change and scope

Two new arrays sit next to icon_groups, both produced by a new src/native/pe/PeResourcePayloads.ts.

string_tables, one per RT_STRING resource:

  • Block N expands into its sixteen counted UTF-16LE strings, with IDs (N - 1) * 16 through (N - 1) * 16 + 15.
  • Empty slots are kept, so every ID in the block is accounted for. Each string carries its file location.
  • trailing_bytes counts padding after the sixteenth string instead of rejecting it.

version_infos, one per RT_VERSION resource:

  • fixed_file_info: the VS_FIXEDFILEINFO signature is checked. File and product versions come as text ("6.6.3.0") and as their original words, plus flags, OS, type, subtype and date.
  • string_file_info: every StringFileInfo table, with its key split into language and code page, and each value with its location.
  • translations: the VarFileInfo\Translation language and code page pairs.
  • A text value is read up to its terminator inside its own block, because some resource compilers count its length in bytes rather than UTF-16 units. There is a test for this, since Delphi output like the sample in the issue is a common source.

As the issue asks, a payload that breaks its documented layout becomes status: "malformed" with a reason and no decoded values, while the resource inventory still succeeds. Decoded text is reserved against the existing 8 MiB metadata budget before allocation, and every row goes through the same account path as icon groups.

Not included: RT_MESSAGETABLE and other payload formats, which stay opaque, and LoadString/VerQueryValue language fallback, which is not observed. Both limitations are stated in the result.

Contract and boundary impact

  • Semantic owner and earliest changed stage: src/native/pe (payload decoding after the resource tree is read). peResourcesSchema owns the contract.
  • CLI and MCP/tool-catalog contract: additive. Two new required arrays (string_tables and version_infos, empty when an image has no such resources), an updated tool description and example title, and updated limitation text.
  • Provider, bridge, target-format, or platform compatibility: portable Node parsing, with no provider or Windows SDK. PE32 and PE32+ as before.
  • Docs: docs/binary-diagnostics.md describes both decoders and the malformed status.

Verification

Node 24.21.0 on macOS (Apple silicon):

  • src/native/pe/PeResourcePayloads.test.ts (new, 10 tests) builds payloads by hand, independent of the decoder. It covers a 16-ID block with empty slots and non-ASCII text checked against its byte range, trailing padding, a truncated block, a string past its block, a named block, full version info with string tables and translations, a byte-counted text value, a bad signature, a wrong root key, and a length past the payload. Each malformed case asserts the inventory still succeeds.
  • src/native/pe (34 tests), tests/acceptance/applications/peResources.test.ts through the compiled CLI and real stdio MCP, tests/boundary/mcp/toolSchemaValidity.test.ts, tests/boundary/mcp/compactInputSchemaMcp.test.ts, src/contracts and tests/conformance/contracts: all pass.
  • npm run check:fast, npm run docs:check and npm run verify:test-discovery: pass.
  • Real binaries: dbghelp.dll and symsrv.dll (version info and string tables as above), and Ghidra's WinHelloCPP.exe training sample, whose block 7 decodes to 103="WinHello" and 109="WINHELLO", the Visual Studio template's IDS_APP_TITLE and IDC_WINHELLO. Other PE files without these resources return empty arrays.

I did not have the issue's DATASUS sample, so it was not run.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/2887 **Author:** [@thefolahan](https://github.com/thefolahan) **Created:** 10/13/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `feat/pe-string-version-resources` --- ### 📝 Commits (9) - [`54fb981`](https://github.com/morluto/rea/commit/54fb9811e0738e7d590d00663002b9c183ea75d5) feat(native): decode RT_STRING and RT_VERSION payloads in PE resources - [`01c1f2d`](https://github.com/morluto/rea/commit/01c1f2d9e206f1441b8e298c5517096044075cf8) fix(pe): bound resource decoding to declared blocks - [`a889604`](https://github.com/morluto/rea/commit/a889604757e0397c759ee87f4f1b9c42ac5e7465) fix(pe): reject impossible version text extents - [`0e5373d`](https://github.com/morluto/rea/commit/0e5373dbfabbfbc918441697de8f877d6e779171) docs(pe): describe decoded string and version resources - [`19d815b`](https://github.com/morluto/rea/commit/19d815bd850acb19c316f2b1fb41f475885296e3) docs(pe): wrap resource guidance - [`25d2f5d`](https://github.com/morluto/rea/commit/25d2f5da80ed898978ef4798417e1eb8278ff6e9) fix(pe): validate decoded payload evidence ownership - [`fc350ff`](https://github.com/morluto/rea/commit/fc350ff5ce8a7afb0f2134309f49ad85cf45b1ca) perf(pe): reuse identical version payload decodes - [`eea4980`](https://github.com/morluto/rea/commit/eea49800079a30ecbea3121c58483aa549e8638a) Merge remote-tracking branch 'origin/main' into HEAD - [`8bcb160`](https://github.com/morluto/rea/commit/8bcb1608432c12cc6441853011a21fdf8c802a57) test(pe): share one payload range between repeated version resources ### 📊 Changes **10 files changed** (+918 additions, -9 deletions) <details> <summary>View changed files</summary> 📝 `.agents/skills/reverse-engineer-anything/SKILL.md` (+3 -1) 📝 `.agents/skills/reverse-engineer-anything/references/native-and-artifacts.md` (+5 -2) 📝 `docs/binary-diagnostics.md` (+19 -1) 📝 `src/application/binaryDiagnostics/PeResourcesService.test.ts` (+35 -0) 📝 `src/application/binaryDiagnostics/PeResourcesService.ts` (+77 -0) 📝 `src/contracts/native/binaryDiagnosticsToolContracts.ts` (+3 -2) 📝 `src/domain/native/peResources.ts` (+62 -1) 📝 `src/native/pe/PeResourceParser.ts` (+18 -2) ➕ `src/native/pe/PeResourcePayloads.test.ts` (+332 -0) ➕ `src/native/pe/PeResourcePayloads.ts` (+364 -0) </details> ### 📄 Description ## Summary Fixes #2812. `inspect_pe_resources` now decodes `RT_STRING` and `RT_VERSION` payloads, which it previously returned as opaque bytes. Both formats are fixed and documented, and they answer two first questions of a Windows investigation: which build is this, and which messages does it contain. On Microsoft's own `dbghelp.dll` (shipped inside WebStorm's async-profiler), the result now includes: ``` version_infos[0]: decoded, file 10.0.19041.685, product 10.0.19041.685, translation 1033/1200 040904B0: CompanyName="Microsoft Corporation" FileDescription="Windows Image Helper" FileVersion="10.0.19041.685 (WinBuild.160101.0800)" InternalName="DBGHELP.DLL" OriginalFilename="DBGHELP.DLL" ProductName="Microsoft® Windows® Operating System" ... ``` and on `symsrv.dll`, string blocks 63 to 66 expand to IDs such as `1002="Symbol Server"` and `1022="Requests/sec"`. ## Change and scope Two new arrays sit next to `icon_groups`, both produced by a new `src/native/pe/PeResourcePayloads.ts`. **`string_tables`**, one per `RT_STRING` resource: * Block N expands into its sixteen counted UTF-16LE strings, with IDs `(N - 1) * 16` through `(N - 1) * 16 + 15`. * Empty slots are kept, so every ID in the block is accounted for. Each string carries its file location. * `trailing_bytes` counts padding after the sixteenth string instead of rejecting it. **`version_infos`**, one per `RT_VERSION` resource: * `fixed_file_info`: the `VS_FIXEDFILEINFO` signature is checked. File and product versions come as text (`"6.6.3.0"`) and as their original words, plus flags, OS, type, subtype and date. * `string_file_info`: every `StringFileInfo` table, with its key split into language and code page, and each value with its location. * `translations`: the `VarFileInfo\Translation` language and code page pairs. * A text value is read up to its terminator inside its own block, because some resource compilers count its length in bytes rather than UTF-16 units. There is a test for this, since Delphi output like the sample in the issue is a common source. As the issue asks, a payload that breaks its documented layout becomes `status: "malformed"` with a `reason` and no decoded values, while the resource inventory still succeeds. Decoded text is reserved against the existing 8 MiB metadata budget before allocation, and every row goes through the same `account` path as icon groups. Not included: `RT_MESSAGETABLE` and other payload formats, which stay opaque, and LoadString/VerQueryValue language fallback, which is not observed. Both limitations are stated in the result. ## Contract and boundary impact - Semantic owner and earliest changed stage: `src/native/pe` (payload decoding after the resource tree is read). `peResourcesSchema` owns the contract. - CLI and MCP/tool-catalog contract: additive. Two new required arrays (`string_tables` and `version_infos`, empty when an image has no such resources), an updated tool description and example title, and updated limitation text. - Provider, bridge, target-format, or platform compatibility: portable Node parsing, with no provider or Windows SDK. PE32 and PE32+ as before. - Docs: `docs/binary-diagnostics.md` describes both decoders and the malformed status. ## Verification Node 24.21.0 on macOS (Apple silicon): - `src/native/pe/PeResourcePayloads.test.ts` (new, 10 tests) builds payloads by hand, independent of the decoder. It covers a 16-ID block with empty slots and non-ASCII text checked against its byte range, trailing padding, a truncated block, a string past its block, a named block, full version info with string tables and translations, a byte-counted text value, a bad signature, a wrong root key, and a length past the payload. Each malformed case asserts the inventory still succeeds. - `src/native/pe` (34 tests), `tests/acceptance/applications/peResources.test.ts` through the compiled CLI and real stdio MCP, `tests/boundary/mcp/toolSchemaValidity.test.ts`, `tests/boundary/mcp/compactInputSchemaMcp.test.ts`, `src/contracts` and `tests/conformance/contracts`: all pass. - `npm run check:fast`, `npm run docs:check` and `npm run verify:test-discovery`: pass. - Real binaries: `dbghelp.dll` and `symsrv.dll` (version info and string tables as above), and Ghidra's `WinHelloCPP.exe` training sample, whose block 7 decodes to `103="WinHello"` and `109="WINHELLO"`, the Visual Studio template's `IDS_APP_TITLE` and `IDC_WINHELLO`. Other PE files without these resources return empty arrays. I did not have the issue's DATASUS sample, so it was not run. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
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#5470
No description provided.