deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

ReadyToRun .NET assemblies still decompile to clean C# because the IL is never removed

A dev.to write-up explains that ReadyToRun binaries store native code alongside, not instead of, the original IL and metadata, so decompilers reconstruct them to C# with no extra effort.

ReadyToRun .NET assemblies still decompile to clean C# because the IL is never removed

A write-up on dev.to walks through why ReadyToRun (R2R) .NET assemblies — the precompiled binaries used to speed up application startup — decompile into readable C# just as easily as ordinary managed assemblies, and why teams counting on precompilation as a form of code protection are mistaken.

What ReadyToRun actually does

In a standard .NET deployment, an assembly ships as metadata plus Intermediate Language (IL), and the just-in-time compiler turns each method into native machine code the first time it executes. ReadyToRun moves that compilation earlier: according to the dev.to article, a tool called crossgen2 runs the JIT ahead of time and embeds the resulting native code in the assembly, letting the runtime skip JIT work at startup. The .NET runtime libraries ship this way, and developers can opt in for their own applications and container images by setting PublishReadyToRun=true.

That native code is what gives people the impression precompilation doubles as obfuscation. The article's central point is that it does not.

What is inside the binary

An R2R assembly keeps the native code in addition to the original managed artifacts, not as a replacement for them. The PE file carries three relevant parts: the ECMA-335 metadata tables describing types, methods and signatures, which remain untouched; the IL bodies of every method, which remain present; and a ReadyToRun section holding the precompiled native code plus the lookup tables the runtime needs to use it. A ReadyToRunHeader, reachable through the CLI header's managed-native-header directory, ties the pieces together and marks the image as R2R.

Why decompilers ignore the native code

A decompiler such as Glass works by reconstructing C# from IL. As the article describes it, the tool walks the metadata tables to recover type and signature information, then lifts each method's IL body back into C#. The precompiled native code lives in a separate section with its own tables, and the decompiler never consults it, because the IL is still the complete, portable definition of every method and it remains right there in the file.

The practical consequence: opening an R2R image looks no different from opening a plain, JIT-only assembly. Same metadata, same IL, same C# output. That means the .NET runtime libraries themselves, a PublishReadyToRun application, or a crossgen-compiled container image can all be read without any special handling.

To confirm that a given assembly is R2R rather than plain IL, the article suggests looking for the ReadyToRun header — decompiler tooling labels it and often displays an R2R version number — or simply checking whether the binary came from crossgen2 or a PublishReadyToRun build.

NativeAOT is the real exception

The technology most often confused with R2R is NativeAOT, and the article stresses that it behaves in the opposite way. NativeAOT compiles the entire application into a standalone native executable that contains neither IL nor recoverable managed metadata, so an IL decompiler has nothing to read. The quick test offered: if a tool can load the file as a .NET assembly at all, it is managed — plain IL or R2R — and it will decompile. If it is just a native binary, it is NativeAOT, and reading it becomes a job for a native disassembler rather than a decompiler.

The article also notes that Glass is free, so verifying any of this on a real R2R image costs nothing.

Why it matters

For anyone shipping .NET code and counting on ReadyToRun to hide it, this settles the question: R2R is a startup-performance feature, not a protection mechanism. Any assembly that loads in a decompiler yields readable C#, business logic included. Teams with genuine confidentiality or licensing concerns need to look elsewhere — NativeAOT for a harder target, or dedicated obfuscation tooling applied to the IL itself.

The flip side is good news for everyone else. R2R is common in performance-sensitive deployments — the runtime libraries ship that way — and the write-up shows it costs nothing in transparency. Being handed a precompiled assembly is no barrier to understanding what the code does, auditing a third-party dependency, or recovering source that was lost along the way.

  • #dotnet
  • #decompilation
  • #readytorun
  • #nativeaot
  • #reverse-engineering