Table of Contents

Basic I/O

Every .NET image interaction is done through classes defined by the AsmResolver.DotNet namespace:

using AsmResolver.DotNet;

Creating a new .NET module

Creating a new image can be done by instantiating a ModuleDefinition class:

var module = new ModuleDefinition("MyModule.exe");

The above will create a module that references mscorlib.dll 4.0.0.0 (.NET Framework 4.0). If another version of the Common Object Runtime Library is desired, we can use one of the overloads of the constructor, and use a custom AssemblyReference, or one of the pre-defined assembly references in the KnownCorLibs class to target another version of the library.

var module = new ModuleDefinition("MyModule.dll", KnownCorLibs.SystemRuntime_v4_2_2_0);

If you have a .NET runtime identifier as specified in the TargetFrameworkAttribute of an assembly, you can use the DotNetRuntimeInfo structure to get the corresponding default corlib:

var runtime = DotNetRuntimeInfo.Parse(".NETCoreApp,Version=v3.1");
var module = new ModuleDefinition("MyModule.dll", runtime.GetDefaultCorLib());

Opening a .NET module

Opening a .NET module can be done through one of the FromXXX methods from the ModuleDefinition class:

byte[] raw = ...
var module = ModuleDefinition.FromBytes(raw);
var module = ModuleDefinition.FromFile(@"C:\myfile.exe");
PEFile peFile = ...
var module = ModuleDefinition.FromFile(peFile);
BinaryStreamReader reader = ...
var module = ModuleDefinition.FromReader(reader);
PEImage peImage = ...
var module = ModuleDefinition.FromImage(peImage);

If you want to read large files (+100MB), consider using memory mapped I/O instead:

using var service = new MemoryMappedFileService();
var module = ModuleDefinition.FromFile(service.OpenFile(@"C:\myfile.exe"));

On Windows, if a module is loaded and mapped in memory (e.g. as a dependency defined in Metadata or by the means of System.Reflection), it is possible to load the module from memory by using FromModule, or by transforming the module into a HINSTANCE and then providing it to the FromModuleBaseAddress method:

Module module = ...;
var module = ModuleDefinition.FromModule(module);
Module module = ...;
IntPtr hInstance = Marshal.GetHINSTANCE(module);
var module = ModuleDefinition.FromModuleBaseAddress(hInstance);

For more information on customizing the reading process, see Advanced Module Reading.

Opening multiple .NET modules using Runtime Contexts

By default, AsmResolver auto-detects the runtime the module originally targets, and each module assumes their own set of caches and resolved dependencies.

To force a module to be loaded as a specific runtime, define a RuntimeContext with the provided runtime info:

// Define a context targeting .NET Core 3.1.
var context = new RuntimeContext(new DotNetRuntimeInfo(DotNetRuntimeInfo.NetCoreApp, new Version(3, 1)));

// Load module within the context.
var module = ModuleDefinition.FromFile(@"C:\Path\To\File.exe", new ModuleReaderParameters(context));

Modules can also be loaded explicitly into an existing context from another module:

ModuleDefinition primaryModule = ...;

// Load a module within the same context as the primary module.
var secondaryModule = ModuleDefinition.FromFile("C:\Path\To\Dependency.dll", new ModuleReaderParameters(primaryModule.RuntimeContext));

Reusing an existing context ensures that the same target runtime is assumed, and that equivalent assembly references resolve to the same assembly definition instances. This provides additional caching performance, and avoids many problems when processing multiple files at once.

Writing a .NET module

Writing a .NET module can be done through one of the Write method overloads.

module.Write(@"C:\myfile.patched.exe");
Stream stream = ...;
module.Write(stream);

For more advanced options to write .NET modules, see Advanced PE Image Building.

Creating a new .NET assembly

AsmResolver also supports creating entire (multi-module) .NET assemblies instead.

var assembly = new AssemblyDefinition("MyAssembly", new Version(1, 0, 0, 0));

Opening a .NET assembly

Opening (multi-module) .NET assemblies can be done in a very similar fashion as reading a single module:

byte[] raw = ...
var assembly = AssemblyDefinition.FromBytes(raw);
var assembly = AssemblyDefinition.FromFile(@"C:\myfile.exe");
PEFile peFile = ...
var assembly = AssemblyDefinition.FromFile(peFile);
BinaryStreamReader reader = ...
var assembly = AssemblyDefinition.FromReader(reader);
PEImage peImage = ...
var assembly = AssemblyDefinition.FromImage(peImage);

Similar to reading module definitions, if you want to read large files (+100MB), consider using memory mapped I/O instead:

using var service = new MemoryMappedFileService();
var assembly = AssemblyDefinition.FromFile(service.OpenFile(@"C:\myfile.exe"));

For more information on customizing the reading process, see Advanced Module Reading.

Writing a .NET assembly

Writing a .NET assembly can be done through one of the Write method overloads.

assembly.Write(@"C:\myfile.patched.exe");

For more advanced options to write .NET assemblies, see Advanced PE Image Building.