Basic I/O
Every .NET binary interaction is done through classes defined by the AsmResolver.DotNet namespace:
using AsmResolver.DotNet;
Assemblies
The root of a .NET application is an assembly, represented by AssemblyDefinition.
Creating a new .NET assembly
To create a new assembly definition, use its constructor:
var assembly = new AssemblyDefinition("MyAssembly", new Version(1, 0, 0, 0));
Modules can be added to an assembly:
var module = new ModuleDefinition("MyAssembly.dll");
assembly.Modules.Add(module);
The first module in Modules is considered the main manifest module (and in most cases this is the only module that is defined in an assembly).
The following two statements are equivalent:
var manifestModule = assembly.ManifestModule;
var manifestModule = assembly.Modules[0];
Note
Creating a new assembly definition does not automatically add a manifest module.
Note
Creating a new assembly definition will not automatically add it to a RuntimeContext.
See Managing Assemblies for adding the assembly to a runtime context.
Opening a .NET assembly
Opening existing .NET assemblies can be done using one of the FromXXX methods:
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);
If you want to read large files (+100MB), consider using memory mapped I/O instead for better performance and memory usage:
using var service = new MemoryMappedFileService();
var assembly = AssemblyDefinition.FromFile(service.OpenFile(@"C:\myfile.exe"));
Note
Each call to any of the FromXXX methods will result in a new RuntimeContext unless explicitly specified otherwise.
For example:
var assembly = AssemblyDefinition.FromBytes(raw, createRuntimeContext: false);
See also Runtime Contexts.
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.exe");
Note that for multi-module assemblies, this may implicitly overwrite other files in the same directory matching the names of sub-modules. For these cases, consider writing into a separate directory, or write the individual modules instead:
assembly.WriteManifest(@"C:\myfile.exe");
Stream stream = ...;
assembly.WriteManifest(stream);
assembly.Modules[1].Write(@"C:\myfile.exe");
For more advanced options to write .NET assemblies, see Advanced PE Image Building.
Modules
Modules are single compilation units within a single assembly, represented using the ModuleDefinition class.
Creating a new .NET module
Creating a new ModuleDefinition can be done by using its constructor:
var module = new ModuleDefinition("MyModule.exe");
By default, the new module will target .NET Framework 4.x. If another version is needed, use one of the overloads of the constructor.
var module = new ModuleDefinition("MyModule.dll", DotNetRuntimeInfo.NetFramework(2, 0));
var module = new ModuleDefinition("MyModule.dll", DotNetRuntimeInfo.Parse(".NETCoreApp,Version=v3.1"));
RuntimeContext context = ...;
var module = new ModuleDefinition("MyModule.dll", context.TargetRuntime);
var module = new ModuleDefinition("MyModule.dll", KnownCorLibs.SystemRuntime_v4_2_2_0);
AssemblyReference customCorLib = ...;
var module = new ModuleDefinition("MyModule.dll", customCorLib);
var module = new ModuleDefinition("MyModule.dll", null); // Create a new corlib module.
Note
Creating a new module definition does not automatically add it to an assembly definition, and thus will also not be automatically added to a RuntimeContext.
See Managing Assemblies for adding the assembly to a runtime context.
Opening a .NET module
Opening an existing .NET module can be done in two ways.
- By opening an
AssemblyDefinitionand accessing itsManifestModuleorModulesproperty. - By explicitly opening individual modules using the
FromXXXmethods from theModuleDefinitionclass.
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 for better performance and memory usage:
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);
Note
Each call to any of the FromXXX methods will result in a new RuntimeContext if the module contains an assembly manifest.
This can be overridden by explicitly specifying not to create a runtime context.
For example:
var module = ModuleDefinition.FromBytes(raw, createRuntimeContext: false);
See also Runtime Contexts.
For more information on customizing the reading process, see Advanced Module Reading.
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.