Reverse engineer .NET assemblies with no extra tools

Quick facts

DependenciesNone. Static analysis only — no Ghidra, Hopper or IDA.
What REA returnsMetadata, CIL instructions, declared native dependencies and build comparisons
InputA .dll or .exe that actually contains PE/CLI metadata
RequirementNode.js 22 (22.19+), 24 (24.11+) or 26+, then npx rea-agents@latest setup
Official guidedocs/managed-code-analysis.md

Official wording for this target type, verbatim from the project README:

Metadata, CIL instructions, declared native dependencies and build comparisons

and from the analysis doc:

REA inspects .NET PE/CLI artifacts without loading or executing their code. It can identify an assembly, inspect metadata and CIL, compare members across builds, and connect declared native calls to supplied native-analysis evidence.

Why this matters for a .NET page specifically: the managed-code path is the one target family that needs nothing installed. No decompiler licence, no native provider, no engine bridge — you point it at a file you already have.

The seven tools

TaskMCP tool
Identify an artifact and its managed deployment forminspect_managed_artifact
Inspect types, signatures, method bodies, calls, and field accessinspect_managed_members
List declared native calls and native implementation indicatorsinspect_managed_native_boundaries
Compare members across builds and map build-local tokenscompare_managed_members
Check native call declarations against supplied export or function evidenceverify_managed_native_boundaries
Import decompiled code against verified static member identitiesimport_managed_reconstruction
Add managed findings to an application graphproject_managed_application_graph

(Table above is verbatim from the official doc.)

Step 1: Identify the artifact

Start by asking what the file actually is, not by opening it in a decompiler. inspect_managed_artifact answers the questions that decide every later step: is this really a managed assembly, what is its managed deployment form, and are its type tables intact or mostly forwarded elsewhere?

Two fields are worth understanding before you read anyone's output:

  • exported_types — type-forwarding surface. An assembly that looks thin isn't empty; it may be exporting types that live in another assembly. If you skip this, you conclude "this file has nothing in it" when it's actually a facade.
  • CIL fingerprints — il_sha256 and normalized_il_sha256. The first is the bytes; the second is the bytes with build-local noise normalised out. Use the pair when two builds "look the same" but the raw hashes disagree: normalised hashes are what make an unchanged method recognisable between builds.
  • #- pointer tables — metadata streams that carry pointers rather than inline data are handled; don't read a pointer-table stream as if it were inline data.

Step 2: Inspect members

inspect_managed_members returns types, signatures, method bodies, calls and field access.

Three behaviours to know before you scale up:

  1. --type takes an exact full_name as reported by the member inspection — or a declaring type name you already know. Repeat the flag to select several types.
  2. Top-level type names are namespace-qualified (Example.Parser). Nested types currently report bare names, which can match more than one type.
  3. The result keeps your requested names in selection.type_names and reports unmatched names separately — a typo shows up as an unmatched entry, not as a silently empty result.
  4. Selection restricts decoding to those types, but member references stay an assembly-wide inventory. Narrowing your selection does not guarantee a small output.

Step 3: Read the evidence like an analyst

Static reads are cheap; they are also bounded. Two limits worth knowing before a large run:

  • Input files up to 128 MiB are admitted before their bytes are retained. Reads stay bounded if a file grows afterwards.
  • Metadata, CIL decoding and projected records share a conservative 64 MiB representation budget. Overflow reports resource-limit with unavailable or partial coverage.

So: a resource-limit result is a scope answer, not a crash. Read coverage, issues and limitations before drawing a conclusion — the doc says so explicitly, and it's the difference between "this assembly calls into native code" and "this assembly declares a native call that nobody supplied evidence for".

Step 4: The walkthrough — Newtonsoft.Json 13.0.4

Object: Newtonsoft.Json 13.0.4 — MIT-licensed, 11.3k stars, released as a NuGet package whose nuspec carries multiple target frameworks (net6.0, .NETStandard2.0, and the .NETFramework generations). Source commit 4e13299d4b0ec96bd4df9954ef646bd2d1b5bf2a. It's a good first target precisely because the licence is clean and the internals are public — so you can check every conclusion against the source.

Suggested walkthrough sequence:

  1. Identify the artifact — inspect_managed_artifact on the Newtonsoft.Json.dll from the net6.0 target; note its managed deployment form and the exported_types forwarding surface.
  2. Inspect a known type — inspect_managed_members --type Newtonsoft.Json.JsonSerializer (exact full_name); read types, signatures and method bodies.
  3. Fingerprint a method — record both il_sha256 and normalized_il_sha256; repeat on a second build and compare with compare_managed_members.
  4. Check native boundaries — inspect_managed_native_boundaries; for a pure-managed library expect no native calls, and understand that verify_managed_native_boundaries only checks declarations against evidence you supply.
  5. The licence point — every claim above is checkable against source you are entitled to read.

The point of the walkthrough isn't "look what you can read". It's that every claim on this page is checkable against a licence you're entitled to read. A page that demonstrates the workflow on a proprietary binary and a page that demonstrates it on an MIT assembly teach very different things about what's allowed.

Command reference

The official managed-code guide documents two CLI entry points for this family. They are the only CLI spellings the guide gives, so they are quoted verbatim.

Identify the artifact — metadata, CIL, declared native dependencies and managed deployment form:

rea inspect-managed-artifact /absolute/path/Assembly.dll --json

Inspect members — types, signatures, method bodies, calls and field access:

rea inspect-managed-members /absolute/path/Assembly.dll --json

Restrict to one type (exact full_name, e.g. Example.Parser):

rea inspect-managed-members /absolute/path/Assembly.dll --type Example.Parser

(The three commands above are per the official docs/managed-code-analysis.md — the inspect-managed-artifact and inspect-managed-members entry points are exactly what the guide documents. The other five tools in the seven-tool table — native-boundaries, compare, verify, import, and project graph — are invoked through your agent as MCP tools; the guide does not publish separate terminal verbs for them, so none are invented here.)

The rea binary is available after a global install (npm install --global rea-agents); without it, prefix with npx -y rea-agents@latest. All paths are absolute, exactly as in the guide.

What REA will not do here

Four boundaries, all from the official doc:

  • Single-file host packages are not unpacked by this path; the host bundle is a different target.
  • IL2CPP / NativeAOT output isn't managed code any more — that's a native analysis route, not this one.
  • Native implementation evidence has to be supplied by you: verify_managed_native_boundaries checks declarations against evidence you provide, it doesn't produce it.
  • Reconstructed code is not licensed code — see the authorized-use block below.

Last verified: 2026-10-14 · REA 6.4.0 · commands quoted per the official docs/managed-code-analysis.md.