The best way to analyze .Net assemblies without Assembly.LoadFile(), beyond ReflectionOnlyLoad

Zack Yang
3 min readFeb 8, 2022

When writing .Net programs, if we need to analyze an assembly file, we can use Assembly.LoadFile() to load the assembly for further analysis. However, the Assembly.LoadFile() method loads an Assembly into the program for execution purposes, so it has strict requirements on the loaded assembly file. For example, LoadFile() throws an exception if the referenced assembly does not exist. If we only want to analyze assemblies, but do not need to execute assemblies, we need a way to simply analyze assembly files.

Net Framework provides the Assembly.ReflectionOnlyLoad () to achieve a similar way, but because this method depends on the AppDomain, so it’s not supported in NET Core. Microsoft has proposed one in the lab project System. Reflection. TypeLoader to implement the functions on .NET Core, but it’s not included in official NET Core.

As we know, the .Net assemblies are PE file format, NET provides the class PEReader (located in the System. Reflection. Metadata the NuGet package) to analysis PE file, so we can use PEReader to analyze assembly files.

Using PEReader, we can get all classes in an assembly through TypeDefinitions, and we can get all methods defined in a class using GetMethods(). For efficiency purposes, members like TypeDefinitions and GetMethods() get TypeDefinitionHandle, MethodDefinitionHandle, etc. These objects only contain address information. It does not contain details such as the name of the type, the name of the method, the parameters of the method, etc. To get detailed information, we need to call the GetTypeDefinition(), GetMethodDefinition() methods of MetadataReader.

The following code shows a demo to load an assembly and prints all the types in an assembly and the methods of each type:

//Install-Package System.Reflection.Metadatausing System.Reflection.Metadata;using System.Reflection.PortableExecutable;string file = @"E:\Microsoft.AspNetCore.Components.Web.dll";using FileStream fileStream = File.OpenRead(file);using PEReader peReader = new PEReader(fileStream);if(!peReader.HasMetadata){Console.WriteLine($"{file} doesn't contain CLI metadata.");return;}var mdReader = peReader.GetMetadataReader();if (!mdReader.IsAssembly){Console.WriteLine($"{file} is not an assembly.");return;}foreach (var typeHandler in mdReader.TypeDefinitions){var typeDef = mdReader.GetTypeDefinition(typeHandler);string name = mdReader.GetString(typeDef.Name);string nameSpace = mdReader.GetString(typeDef.Namespace);Console.WriteLine($"***********{nameSpace}.{name}***********");foreach (var methodHandler in typeDef.GetMethods()){var methodDef = mdReader.GetMethodDefinition(methodHandler);Console.WriteLine(mdReader.GetString(methodDef.Name));}}

With PEReader, we need to get XXXHandler first and then call MetadataReader to get the details of the handle. Although this is high performance, the code is verbose and it is difficult to implement some advanced operations. For example, if we want to get the CustomAttribute information of an assembly, PEReader does not provide an easy way, requiring a thorough knowledge of the PE format.
We can simplify the analysis of assembly files by using AsmResolver.DotNet, a third-party Nuget package that is a high level wrapper of PEReader. The following code shows a demo which loads an assembly, prints the assembly’s company information, and prints all the assembly’s type information and the methods defined in the type:

string file = @"E:\Microsoft.AspNetCore.Components.Web.dll";var moduleDef = AsmResolver.DotNet.ModuleDefinition.FromFile(file);// ModuleDefinition here is under  AsmResolver.DotNet instead of System.Reflection.Metadatavar asmCompanyAttr = moduleDef.Assembly.CustomAttributes.FirstOrDefault(c => c.Constructor.DeclaringType.FullName == "System.Reflection.AssemblyCompanyAttribute");var utf8Value = (Utf8String?)asmCompanyAttr.Signature.FixedArguments[0].Element;var strValue = (string?)utf8Value;Console.WriteLine($"company name:{strValue}");foreach(var typeDef in moduleDef.GetAllTypes()){string name = typeDef.Name;string nameSpace = typeDef.Namespace;Console.WriteLine($"***********{nameSpace}.{name}***********");foreach (var methodDef in typeDef.Methods){Console.WriteLine(methodDef.Name);}}

In conclusion, if we need to analyze an assembly and run its code, we can use Assembly.LoadFile(); If we don’t need to run an assembly and just want to analyze it, PEReader is a better choice, or we can choose the NuGet package AsmResolver.DotNet. I used AsmResolver.DotNet for Zack.Commons, an open source project, to determine whether an assembly was developed by Microsoft. Check out the GitHub repository of Zack.Commons for the source code.

--

--