- Status: Accepted
- Date: 2026-07-16
- Implementation status: Read-only PE/CLI triage and exact identity are shipped
through
inspect_managed_artifact/rea inspect-managed-artifact. Bounded metadata, signatures, method bodies, raw CIL hashes, limited decoded-instruction-tuple v1 hashes, exception regions, call edges, and field-access anchors are shipped throughinspect_managed_members;rea inspect-managed-membersprovides CLI parity. Static member comparison and build-local token remapping are shipped throughcompare_managed_members/rea compare-managed-members. Declaration-only managed/native boundary inventory for ModuleRef, ImplMap/PInvoke, ReadyToRun indicators, and non-IL method flags is shipped throughinspect_managed_native_boundaries/rea inspect-managed-native-boundaries. Managed/native export or function Evidence matching is shipped throughverify_managed_native_boundaries/rea verify-managed-native-boundaries; it verifies only supplied P/Invoke export/function observations and does not map managed tokens to native addresses. Decompiler reconstruction import is shipped throughimport_managed_reconstruction/rea import-managed-reconstruction; it records user-supplied C#/IL/pseudocode as analyst inference only after exact static member locks match. Default-disabled runtime-correlation admission planning is shipped throughplan_managed_runtime_correlation/rea plan-managed-runtime-correlation; it does not attach, load, debug, reflect, instrument, invoke, or execute target code. Managed static graph projection is shipped throughproject_managed_application_graph/rea project-managed-application-graph; it projects authenticated managed Evidence into the application graph without changing managed tokens into native addresses. Native-body bridge mapping and an actual runtime executor remain future contracts.
Professional C# reverse engineering is not one decompiler operation. A file described as “.NET” can be ordinary Common Intermediate Language (CIL), ReadyToRun code containing both CIL and native implementations, a single-file deployment, C++/CLI mixed mode, Unity Mono, Unity IL2CPP, or NativeAOT. Those forms have different sources of truth and different native-analysis needs. Choosing a decompiler before classifying the artifact can therefore produce a plausible but false model.
The durable static facts in an ordinary managed PE are its PE/CLI headers, metadata tables, signatures, method bodies, exception regions, and CIL. A C# decompiler reconstructs source-like control flow and language constructs from those facts. That reconstruction is valuable, but it is not the original source and cannot replace the underlying metadata and CIL evidence. Obfuscation makes names especially weak while tokens remain coordinates in only one build.
REA already separates one selected deep native provider from disjoint auxiliary providers under ADR-0001. It also separates static evidence, passive observation, process capture, and controlled replay authorities. Managed analysis must fit those boundaries without requiring a CLR to inspect an artifact, loading target code into the REA process, or treating a managed token as a native address.
The design is informed by exact-build, reflection-free metadata extraction, hash-locked method manifests, independent behavior models, and managed/native boundary analysis performed against a local C# application. The application, its decompiler output, and its runtime observations are research inputs only; they are not distributable REA fixtures.
- Classify the deployment before selecting an analysis route.
- Make metadata and CIL independently inspectable without a CLR or target execution.
- Preserve exact-build provenance while enabling structural comparison across builds whose MVIDs, tokens, names, and layout differ.
- Compose managed and native analysis without introducing another ambiguous deep-provider candidate.
- Keep decompiled C# useful but epistemically subordinate to canonical bytes.
- Analyze Linux, macOS, and Windows managed artifacts through the same local CLI/MCP contracts on REA's supported hosts.
- Keep setup additive and prevent managed analysis from installing .NET, ILSpy, an SDK, or another unrelated toolchain.
- Prove semantics with source-built conformance artifacts and compact, operator-local real-application checks.
- Managed artifact: a PE, bundle component, or related metadata artifact that carries a CLI header, CLI metadata, CIL, or another managed-runtime commitment.
- Classification vector: independently observed container, runtime family, implementation form, and architecture. It is not one guessed label.
- Canonical static observation: a bounded value decoded directly from the target bytes under a versioned parser contract.
- Reconstruction: source-like output, including decompiled C#, derived from canonical observations.
- Structural inference: a relationship or cross-version match derived from signatures, APIs, constants, control/data-flow shape, or other observations.
- Validation: an independently checked proposition, for example a source-built oracle assertion or a behavior-model corpus result.
- Runtime observation: a separately authorized fact obtained by attaching, loading, debugging, instrumenting, or otherwise executing code.
Managed triage returns a classification vector and the observations supporting
each dimension. Absence of a marker produces unknown or not_applicable, not
an invented runtime family.
| Dimension | Initial values |
|---|---|
| Container | PE file, single-file bundle, bundle component, Unity metadata pair, native image, malformed, unknown |
| Runtime family | .NET Framework, modern .NET, Unity Mono, Unity IL2CPP, NativeAOT, mixed CLR/native, unknown |
| Implementation form | CIL, CIL plus ReadyToRun, native only, C++/CLI mixed, metadata only, unsupported, unknown |
| Architecture | PE machine plus CLI flags; component-specific architecture for bundles; conflicting or unknown when required |
Target-framework attributes, assembly references, metadata stream versions, bundle manifests, ReadyToRun directories, and native headers are observations. They may support a runtime-family inference, but no single string is treated as authoritative when the other bytes conflict.
Classification selects these routes:
| Observed form | Managed route | Native route and limitation |
|---|---|---|
| Ordinary Framework or modern CIL | Full metadata, signatures, method bodies, and CIL | Optional only for declared interop boundaries |
| Obfuscated CIL | Same canonical route; names receive no special trust | Optional for declared interop boundaries |
| Unity Mono assembly | Same canonical route with Unity context retained | Optional for engine/native boundaries |
| ReadyToRun | Metadata/CIL where present; implementation availability is per method | Selected Hopper/Ghidra analyzes native sections; CIL absence is reported per method |
| C++/CLI mixed mode | Metadata/CIL for managed members and explicit mixed-mode boundary observations | Selected Hopper/Ghidra analyzes native bodies; REA never converts tokens directly to native addresses |
| Single-file deployment | Inventory the bundle, then analyze each authenticated managed component | Selected Hopper/Ghidra analyzes the host/native components; unsupported compression remains explicit |
| Unity IL2CPP | Pair and authenticate available Unity metadata; do not claim canonical CIL | Selected Hopper/Ghidra analyzes the generated native binary; semantic recovery is degraded until supported |
| NativeAOT | Report any independently present managed metadata, otherwise no CIL claim | Treat as a native target with NativeAOT provenance; managed source reconstruction is unavailable |
| Malformed or unsupported artifact | Return bounded partial observations and typed unavailable/unknown scopes | Do not silently retry by loading or executing the file |
Classification is byte-driven and bounded. File extensions, product names, process names, and an operator's description are hints only.
The initial producer is a REA-owned auxiliary provider with ID
rea-dotnet-static. It owns a disjoint managed-artifact operation family. It is
not registered as a Hopper/Ghidra deep-provider candidate because it does not
compete for the same operation names and must be able to coexist with the one
selected native provider.
one opened artifact/session
|
SessionProviderRouter
/ \
rea-dotnet-static auxiliary selected deep binding
PE/CLI metadata and CIL Hopper or Ghidra
\ /
explicit managed/native links
|
Evidence and workflows
An ordinary managed artifact can therefore remain unbound to a deep native provider while managed operations work. ReadyToRun, C++/CLI, IL2CPP, NativeAOT, and interop workflows may additionally require a caller-selected native binding. Native-provider failure never causes managed evidence to be relabeled, and managed-parser failure never causes an implicit native fallback.
Provider mechanics live under a provider directory such as src/dotnet/.
Provider-neutral identities, evidence states, and bounded values may live in
the domain/contracts layers. Application code composes results but does not
parse PE headers, metadata tables, signatures, CIL, ReadyToRun headers, or
provider-specific output.
rea-dotnet-static reads approved local bytes and implements the required
PE/CLI metadata and CIL subset in TypeScript. It does not call any reflection
API and does not require the .NET runtime. In particular, static inspection
must never:
- call
Assembly.Load,Assembly.LoadFrom,ReflectionOnlyLoad, or an equivalent runtime loader; - resolve target dependencies by executing target-controlled resolver code;
- run module initializers, constructors, entry points, build tasks, plugins, or generated helpers;
- start the target, attach to a process, contact a service, or inherit target configuration and credentials; or
- interpret a decompiler, CLR, or native-provider diagnostic as an observation from the artifact bytes.
The parser uses checked offsets and additions, explicit endianness, bounded table/heap/body counts, cycle-safe traversal, and typed partial results. A bad row, signature, body, resource, or stream does not authorize an unbounded retry or a CLR load. Complete-coverage claims require every applicable bounded region to have been admitted; otherwise coverage is partial, unknown, or unavailable with exact offsets and reasons.
The input path remains under REA's approved investigation roots. Local artifact paths, digests, MVIDs, tokens, malformed offsets, and analysis metadata are actionable diagnostics and are not secrets. Credentials, authorization data, and genuine secrets remain redacted.
Every managed result carries a commitment with at least:
artifact: canonical path + byte length + SHA-256
component: container identity + component path/name + component SHA-256, when applicable
classification: container + runtime family + implementation form + architecture
assembly/module: simple name + version + culture + public-key identity + module name + MVID
producer: provider ID + exact version + parser/profile schema + profile digest
An assembly-qualified name does not replace the artifact digest or MVID. Neither an MVID nor an Authenticode signature proves who authored the behavior. Bundle components retain both their container and component commitments so that bytes extracted from another host cannot be silently substituted.
Method observations add:
declaring type + normalized CLI signature
build-local metadata token
RVA/file extent when present
raw CIL byte length + SHA-256 when the body is admitted
member-result schema version + decoded-instruction-tuple SHA-256 when CIL is valid
separate max-stack, locals-token, and exception-region observations
The shipped il_sha256 commits only the raw CIL byte stream identified by the
method header's il_size. It does not commit the method header, local-signature
blob, alignment padding, or extra sections such as exception clauses. Those
facts remain separate observations and the enclosing artifact SHA-256 binds
them to the exact file. A digest over the complete method body is not shipped.
The caller-visible field remains named normalized_il_sha256, but its shipped
v1 meaning is deliberately narrower than a canonical semantic CIL identity.
For a completely decoded method, REA serializes the instruction-order array of
[opcode, operand_kind, operand] tuples with JavaScript JSON.stringify,
encodes that text as UTF-8, and reports its lowercase SHA-256. The member
result's schema_version: 1 scopes this convention; there is no independent
normalized-CIL algorithm-version field in v1.
Operands retain the current decoder projection: metadata and user-string
operands are build-local lowercase hexadecimal tokens, scalar branch operands
are decimal target CIL byte offsets, and switch retains only its decimal case
count. Constants and variable indexes are decimal strings; an operand-free
instruction uses kind none and value null. Opcode names preserve distinct
short forms and prefix instructions, but instruction offsets, branch target
instruction identity, switch targets, resolved token identities, method
headers, locals, and exception regions are not part of this digest. A partial
or malformed decode has no normalized_il_sha256.
Consequently v1 is a deterministic decoded-tuple fingerprint, not proof of semantic equivalence. In particular, methods with the same opcodes and switch case counts can share it despite different switch targets, and token movement can change it without a semantic change. Cross-build comparison may use it as one unique-only structural tier while retaining artifact/MVID commitments and ambiguity; it must not describe the digest itself as token-remapped canonical CIL. Canonical operand resolution, complete control-flow operands, exception/local commitments, and an explicit algorithm version require a new normalized-CIL schema.
Metadata tokens are reported because they are precise coordinates inside one module. Cross-version identity never consists of a token alone. A structural match cites both build commitments, both local tokens, the v1 tuple inputs, the member-result schema version, and all competing candidates. V1 cannot cite a separate matching-algorithm version because none is exposed.
Managed workflows retain these layers independently:
| Layer | Examples | Permitted claim |
|---|---|---|
| Static observation | Metadata row, signature, CIL opcode, literal, exception region | “These bounded bytes decode to this value.” |
| Reconstruction | Decompiled C#, async/iterator source shape, expression simplification | “This is one source-like representation of cited observations.” |
| Structural inference | Likely caller, field flow, behavior slice, cross-build method match | “These cited structures support this relationship.” |
| Independent validation | Synthetic oracle, corpus result, second parser agreement | “This declared proposition passed the named independent check.” |
| Runtime observation | Exact-build attach/load/instrumentation event | “This separately authorized experiment observed this event.” |
Each layer has its own producer, authority, state, confidence, coverage, and limitations. A readable C# method is never emitted as original source or exact semantic proof. A validator does not upgrade unrelated claims. Runtime agreement does not retroactively turn a static inference into an observation.
Negative results are scope-qualified. For example, “no declared P/Invoke rows were found in the admitted metadata tables” does not prove the application has no native, dynamically resolved, generated, server-side, or protected path.
REA's canonical static operations do not depend on ILSpy, a .NET SDK/runtime, dnlib, or Mono.Cecil. The following review was current on the decision date:
| Tool | Reviewed coordinate | License | Runtime/host observation | Decision |
|---|---|---|---|---|
System.Reflection.Metadata |
10.0.10 | MIT | Library targets .NET Framework 4.6.2, .NET 8/9/10, and .NET Standard 2.0 | Pinned independent metadata oracle for conformance; not shipped or invoked by production parsing |
ICSharpCode.Decompiler |
10.1.1.8388 | MIT | Library targets .NET Standard 2.0; output is reconstruction | Pinned reconstruction/differential oracle candidate |
ilspycmd |
9.1.0.7988 | MIT | Cross-platform .NET 8 tool; newer 10.1.1.8388 requires .NET 10 | Initial optional BYO reconstruction coordinate because it matches the source research baseline |
| dnlib | 4.5.0 | MIT | Cross-platform library; also exposes assembly-writing APIs | Differential test candidate only; its writable model is outside the production trust boundary |
| Mono.Cecil | 0.11.6 | MIT | Cross-platform .NET Framework/.NET Standard library with read/write support | Differential test candidate only; not a production dependency |
Coordinates and package hashes used by real verification are checked into a source-owned lock manifest when that verifier is implemented. “Latest” is never a reproducibility coordinate. A version change requires conformance and an explicit profile/version change.
Artifact platform and REA host support are separate. The byte-only parser must inspect PE/CLI artifacts produced for Windows on a supported Linux or macOS REA host; that is not a claim that REA itself currently runs on Windows. Managed library/tool candidates above are cross-platform at their reviewed targets, but native composition remains limited by the selected Hopper/Ghidra host and format support. Real verification runs the canonical path on Linux and macOS and may run pinned oracle checks on Windows without turning that development check into a public Windows-host claim.
If a caller-visible C# reconstruction operation is admitted, it is a distinct
optional capability. It accepts only an exact supported BYO ilspycmd, records
the tool and decompiler version, runs in an owned bounded process without
loading target code into REA, and links output to the canonical method
observation. Setup and installers do not download or install ILSpy, .NET, an
SDK, or a runtime. Doctor may inspect an explicitly configured executable and
report exact actionable incompatibility. Missing reconstruction never removes
canonical metadata/CIL capabilities.
This decision does not make a decompiler process a security sandbox. The initial public path can defer C# reconstruction while canonical contracts and process isolation are proven.
The repository stores source, build descriptions, expected semantic facts, and tool lock manifests, but not compiled managed binaries. An isolated build step creates fixtures outside source directories and records their SHA-256 and MVID before verification. At minimum, the corpus covers:
- Framework-compatible and modern .NET metadata;
- AnyCPU, x86, and x64 PE/CLI flags;
- generics, nested types, overloaded signatures, properties, events, resources, attributes, and constants;
- exceptions, filters, prefixes, switch branches, P/Invoke, async and iterator state machines;
- obfuscated and Unicode names without assigning semantics from names;
- malformed headers, tables, heaps, signatures, bodies, and exception regions; and
- two semantically related builds with different MVIDs, tokens, layout, and selected names.
Expected facts are checked directly and against pinned
System.Reflection.Metadata; ILSpy, dnlib, or Mono.Cecil may add differential
coverage but do not vote by majority. Disagreement is a failing or explicitly
unsupported case, not silently normalized output.
The real-application benchmark is an operator-local, opt-in osu! check derived from the research workflow. It accepts an explicit path plus a compact expectation manifest; verifies target SHA-256, MVID, architecture, and expected assembly identity before analysis; and emits only bounded identities, tokens, signatures, IL lengths/hashes, match states, and assertion results. It does not emit full IL, decompiled source, runtime logs, user data, credentials, or service/account material. No proprietary binary, hash-locked application manifest, or derived dump is committed to REA.
Static managed support grants no permission to attach, load, debug, reflect, instrument, invoke, or execute. The shipped runtime-correlation planning capability distinguishes those effects, remains disabled by default, requires explicit administrator and per-call approval, and binds to the exact artifact SHA-256, MVID, method signature, and CIL/body shape observed by the static path. It records an admission plan only; a future executor must pass a separate threat model before any effect can occur.
Runtime admission must additionally validate host OS, CLR family, architecture, and supported build/tool versions; bound time, threads, outputs, UI and network effects; avoid real services and accounts; and own all created processes and artifacts through complete cleanup. Reflection-only loading is still a CLR load and is not a substitute for the static byte parser.
Runtime execution is intentionally outside the current managed-code track and requires its own threat model and admission decision.
- Managed static operations will have CLI and MCP parity and produce the same Evidence commitments.
- Adding managed operations changes the caller-visible tool inventory and generated product catalog only in the implementation PR that admits them.
- Partial parsing includes admitted/dropped counts, exact limitations, and coverage; it never returns a partial list labeled complete.
- Managed addresses distinguish file offsets, RVAs, native virtual addresses, metadata tokens, and CIL offsets. They are not interchangeable.
- Cache compatibility is exact on artifact/component commitment, member-result
schema, parser profile, and producer version. For shipped v1,
normalized_il_sha256has no independent algorithm-version field. - Provider-neutral workflows may compose canonical managed findings with the one native binding, but every source operation retains its actual producer.
This would make canonical metadata/IL inventory depend on a separately installed CLR and conflate source reconstruction with byte observations. It would also make tool-version drift part of ordinary inventory. ILSpy remains a valuable optional reconstruction and conformance tool instead.
Both delegate parsing and dependency behavior to a CLR and make static support runtime-dependent. Ordinary loading can execute target code; reflection-only loading still establishes a CLR load boundary and is unavailable across modern runtimes in a uniform way. Neither is required to decode ECMA-335 bytes.
That would force callers to choose managed analysis instead of Hopper/Ghidra for ReadyToRun and mixed-mode artifacts even though both are needed. Disjoint auxiliary composition represents the real ownership boundary.
Tokens and obfuscated names change across builds, while decompiler output changes across tools and versions. They remain useful build-local coordinates or reconstruction labels but are insufficient for exact or structural cross-version identity.
This would expand installer effects, package size, patching obligations, and the target-parsing attack surface. The initial byte parser keeps canonical support within REA's existing Node runtime; external managed tools remain BYO or development-only oracles.
- Add read-only triage and identity with malformed-input bounds. Shipped.
- Add metadata, signatures, method bodies, deterministic decoded-CIL tuple fingerprints, and exact artifact Evidence. Shipped with the v1 limitations above; full normalized-CIL semantics remain planned.
- Add obfuscation-resistant slices and cross-build match states. Shipped.
- Compose explicit managed/native boundaries with Hopper/Ghidra. Declaration inventory and native export/function Evidence matching shipped; native-body bridge mapping remains planned.
- Add source-built, pinned real-tool, package, CLI, and MCP conformance. Source-owned conformance is shipped; pinned external real-tool checks remain planned.
- Add separately authorized runtime-correlation admission planning. Shipped; runtime execution remains planned.
- Project authenticated managed static Evidence into the application graph. Shipped.
Every gate must keep unsupported ReadyToRun, NativeAOT, IL2CPP, mixed-mode, or bundle cases explicit rather than broadening claims to make a fixture pass.