[GH-ISSUE #3829] [Feature] Expose .NET FieldRVA mappings and statically sized initializer bytes #1479

Open
opened 2026-10-16 21:05:57 +00:00 by bsakaguchi · 1 comment
Owner

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

Current limitation

Static .NET inspection can identify a field used by an array initializer but cannot expose the field's file-backed initial bytes. For the small source-owned fixture below, artifact inspection reports one FieldRVA row and member inspection lists its field, but the result has no declared RVA, mapped file offset or initializer data. This leaves an analyst unable to follow the initialization through REA alone.

This is a request to extend the existing static inspection, not a claim that its current CIL decoding is incorrect.

Observed result

Environment: Windows 11 x64 build 26200, Node 24.18.0, npm 11.16.0, .NET SDK 9.0.304. Observed on published rea-agents@6.5.0 and an unmodified source build at 4692bb33300c7cb77b095b6cc0c33da861c1b860.

rea inspect-managed-artifact <absolute-path>/FieldRvaFixture.dll --format json
rea inspect-managed-members <absolute-path>/FieldRvaFixture.dll --format json
  • Artifact metadata reports FieldRVA: 1.
  • The .cctor has an ldtoken anchor referencing field 0x04000002.
  • Member inspection lists that field under <PrivateImplementationDetails>, with flags 307 (including HasFieldRVA) and its signature. Its returned keys are token, row_offset, declaring_type_token, declaring_type, name, flags and signature.
  • No field RVA, mapped file offset or initial bytes are returned.

An independent System.Reflection.Metadata/PEReader check reads the field's RVA as 10240, maps it to file offset 2560, and reads the fixture's known 32 bytes:

000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f

Data SHA-256: 630dcd2966c4336691125448bbb25b4ff412a49c732db2c8abc1b8581bd710dd.
Tested DLL SHA-256: 8a7a0d905cdefd1048bb84c82b49c22832e14f247e9f540b41b0b3a36d9b9223. Rebuilt DLL hashes and coordinates may differ; these identify this exact build.

The RVA belongs to the compiler-generated backing field, not the public Data array object. Reading these file bytes does not observe the runtime array or execute its initializer.

Desired outcome

For a selected field with a FieldRVA entry:

  1. Expose the declared RVA and verified file mapping, tied to the artifact digest and build-local field token.
  2. When supported signature/layout facts prove the size, support a bounded read of the initial bytes with exact length and digest. Keep unknown or unsupported sizes explicit; do not derive a size from the next RVA or the section boundary.

Honor existing field selection and resource policies. The requested target interaction is static file reading only. The output shape and supported size cases are open for discussion.

Reproduction source

Build this class library with dotnet build -c Release; do not load or run the resulting DLL.

FieldRvaFixture.csproj and Fixture.cs
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <OutputType>Library</OutputType>
    <Optimize>true</Optimize>
    <Deterministic>true</Deterministic>
  </PropertyGroup>
</Project>
public static class FieldRvaFixture
{
    public static readonly byte[] Data = new byte[]
    {
        0, 1, 2, 3, 4, 5, 6, 7,
        8, 9, 10, 11, 12, 13, 14, 15,
        16, 17, 18, 19, 20, 21, 22, 23,
        24, 25, 26, 27, 28, 29, 30, 31
    };

    public static byte Read(int index) => Data[index];
}

The following independent reader was built with dotnet build -c Release and run with the fixture DLL's absolute path as its only argument. Its 32-byte length comes from this fixture; it does not implement general field-size inference.

FieldRvaOracle.csproj and Program.cs
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <OutputType>Exe</OutputType>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
using System.Security.Cryptography;
using System.Text.Json;

var path = args.Single();
using var stream = File.OpenRead(path);
using var pe = new PEReader(stream);
var metadata = pe.GetMetadataReader();
var expected = Enumerable.Range(0, 32).Select(value => (byte)value).ToArray();
var fields = new List<object>();
foreach (var handle in metadata.FieldDefinitions)
{
    var field = metadata.GetFieldDefinition(handle);
    var rva = field.GetRelativeVirtualAddress();
    if (rva == 0) continue;
    // This probe knows the fixture's exact 32-byte initializer. It does not
    // infer arbitrary field lengths from an RVA or the next field.
    var bytes = pe.GetSectionData(rva).GetContent(0, expected.Length).ToArray();
    if (!bytes.SequenceEqual(expected)) throw new Exception("Unexpected fixture initializer");
    var section = pe.PEHeaders.SectionHeaders.Single(section => rva >= section.VirtualAddress && rva < section.VirtualAddress + section.VirtualSize);
    fields.Add(new
    {
        token = $"0x{MetadataTokens.GetToken(handle):x8}",
        name = metadata.GetString(field.Name),
        flags = (int)field.Attributes,
        rva,
        file_offset = section.PointerToRawData + rva - section.VirtualAddress,
        known_fixture_length = expected.Length,
        hex = Convert.ToHexStringLower(bytes),
        data_sha256 = Convert.ToHexStringLower(SHA256.HashData(bytes)),
    });
}
if (fields.Count != 1) throw new Exception($"Expected one FieldRVA row, got {fields.Count}");
Console.WriteLine(JsonSerializer.Serialize(new { path, sha256 = Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(path))), fields }));

Verification and limits

  • Both projects built and the CLI/PEReader observations above ran successfully. The target DLL's SHA-256 remained unchanged.
  • MCP was not run for this fixture; the shared returned field schema was inspected in source.
  • At review time, fetched upstream/main was 4135c911d3a3414b17fcd8954a15f6c7ec6ae8f4, four commits after the executed source revision. There were no changes in src/dotnet or src/domain/managed between them. The newer complete revision was not executed.
  • Searches of issues and PRs for FieldRVA and field-initialization terms found no matching proposal; please link one if missed.
  • The reproduction, independent check and report were prepared with AI assistance. No REA parser implementation is included in this request.
Originally created by @rancy777 on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3829 ### Current limitation Static .NET inspection can identify a field used by an array initializer but cannot expose the field's file-backed initial bytes. For the small source-owned fixture below, artifact inspection reports one FieldRVA row and member inspection lists its field, but the result has no declared RVA, mapped file offset or initializer data. This leaves an analyst unable to follow the initialization through REA alone. This is a request to extend the existing static inspection, not a claim that its current CIL decoding is incorrect. ### Observed result Environment: Windows 11 x64 build 26200, Node 24.18.0, npm 11.16.0, .NET SDK 9.0.304. Observed on published `rea-agents@6.5.0` and an unmodified source build at `4692bb33300c7cb77b095b6cc0c33da861c1b860`. ```text rea inspect-managed-artifact <absolute-path>/FieldRvaFixture.dll --format json rea inspect-managed-members <absolute-path>/FieldRvaFixture.dll --format json ``` - Artifact metadata reports `FieldRVA: 1`. - The `.cctor` has an `ldtoken` anchor referencing field `0x04000002`. - Member inspection lists that field under `<PrivateImplementationDetails>`, with flags `307` (including HasFieldRVA) and its signature. Its returned keys are token, row_offset, declaring_type_token, declaring_type, name, flags and signature. - No field RVA, mapped file offset or initial bytes are returned. An independent `System.Reflection.Metadata/PEReader` check reads the field's RVA as `10240`, maps it to file offset `2560`, and reads the fixture's known 32 bytes: ```text 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f ``` Data SHA-256: `630dcd2966c4336691125448bbb25b4ff412a49c732db2c8abc1b8581bd710dd`. Tested DLL SHA-256: `8a7a0d905cdefd1048bb84c82b49c22832e14f247e9f540b41b0b3a36d9b9223`. Rebuilt DLL hashes and coordinates may differ; these identify this exact build. The RVA belongs to the compiler-generated backing field, not the public `Data` array object. Reading these file bytes does not observe the runtime array or execute its initializer. ### Desired outcome For a selected field with a FieldRVA entry: 1. Expose the declared RVA and verified file mapping, tied to the artifact digest and build-local field token. 2. When supported signature/layout facts prove the size, support a bounded read of the initial bytes with exact length and digest. Keep unknown or unsupported sizes explicit; do not derive a size from the next RVA or the section boundary. Honor existing field selection and resource policies. The requested target interaction is static file reading only. The output shape and supported size cases are open for discussion. ### Reproduction source Build this class library with `dotnet build -c Release`; do not load or run the resulting DLL. <details> <summary>FieldRvaFixture.csproj and Fixture.cs</summary> ```xml <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net9.0</TargetFramework> <OutputType>Library</OutputType> <Optimize>true</Optimize> <Deterministic>true</Deterministic> </PropertyGroup> </Project> ``` ```csharp public static class FieldRvaFixture { public static readonly byte[] Data = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }; public static byte Read(int index) => Data[index]; } ``` </details> The following independent reader was built with `dotnet build -c Release` and run with the fixture DLL's absolute path as its only argument. Its 32-byte length comes from this fixture; it does not implement general field-size inference. <details> <summary>FieldRvaOracle.csproj and Program.cs</summary> ```xml <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net9.0</TargetFramework> <OutputType>Exe</OutputType> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> </Project> ``` ```csharp using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; using System.Security.Cryptography; using System.Text.Json; var path = args.Single(); using var stream = File.OpenRead(path); using var pe = new PEReader(stream); var metadata = pe.GetMetadataReader(); var expected = Enumerable.Range(0, 32).Select(value => (byte)value).ToArray(); var fields = new List<object>(); foreach (var handle in metadata.FieldDefinitions) { var field = metadata.GetFieldDefinition(handle); var rva = field.GetRelativeVirtualAddress(); if (rva == 0) continue; // This probe knows the fixture's exact 32-byte initializer. It does not // infer arbitrary field lengths from an RVA or the next field. var bytes = pe.GetSectionData(rva).GetContent(0, expected.Length).ToArray(); if (!bytes.SequenceEqual(expected)) throw new Exception("Unexpected fixture initializer"); var section = pe.PEHeaders.SectionHeaders.Single(section => rva >= section.VirtualAddress && rva < section.VirtualAddress + section.VirtualSize); fields.Add(new { token = $"0x{MetadataTokens.GetToken(handle):x8}", name = metadata.GetString(field.Name), flags = (int)field.Attributes, rva, file_offset = section.PointerToRawData + rva - section.VirtualAddress, known_fixture_length = expected.Length, hex = Convert.ToHexStringLower(bytes), data_sha256 = Convert.ToHexStringLower(SHA256.HashData(bytes)), }); } if (fields.Count != 1) throw new Exception($"Expected one FieldRVA row, got {fields.Count}"); Console.WriteLine(JsonSerializer.Serialize(new { path, sha256 = Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(path))), fields })); ``` </details> ### Verification and limits - Both projects built and the CLI/PEReader observations above ran successfully. The target DLL's SHA-256 remained unchanged. - MCP was not run for this fixture; the shared returned field schema was inspected in source. - At review time, fetched upstream/main was `4135c911d3a3414b17fcd8954a15f6c7ec6ae8f4`, four commits after the executed source revision. There were no changes in `src/dotnet` or `src/domain/managed` between them. The newer complete revision was not executed. - Searches of issues and PRs for FieldRVA and field-initialization terms found no matching proposal; please link one if missed. - The reproduction, independent check and report were prepared with AI assistance. No REA parser implementation is included in this request.
Author
Owner

@XinwangTao commented on GitHub (Oct 15, 2026):

Thanks for the well-documented proposal. The gap is clear: REA can surface the FieldRVA row and the backing field, but without the file-backed initializer bytes an analyst has to leave REA to follow the initialization. I especially support the constraint to only read bounded bytes when signature/layout facts prove the size, and to keep unknown sizes explicit instead of deriving them from the next RVA or section boundary — that matters for safely inspecting untrusted artifacts. Exposing the declared RVA plus the verified file mapping tied to the artifact digest and the build-local field token sounds like the right granularity. A field-token-scoped JSON shape with rva/fileOffset/size and an optional digest would be easy to consume downstream. Looking forward to seeing this land.

<!-- gh-comment-id:6184172391 --> @XinwangTao commented on GitHub (Oct 15, 2026): Thanks for the well-documented proposal. The gap is clear: REA can surface the FieldRVA row and the backing field, but without the file-backed initializer bytes an analyst has to leave REA to follow the initialization. I especially support the constraint to only read bounded bytes when signature/layout facts prove the size, and to keep unknown sizes explicit instead of deriving them from the next RVA or section boundary — that matters for safely inspecting untrusted artifacts. Exposing the declared RVA plus the verified file mapping tied to the artifact digest and the build-local field token sounds like the right granularity. A field-token-scoped JSON shape with rva/fileOffset/size and an optional digest would be easy to consume downstream. Looking forward to seeing this land.
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#1479
No description provided.