Search code examples
c#.netvb.netreflectionportable-executable

Good approach to determine the PEFileKind of an assembly?


In C# or VB.NET, I would like to know which would be the best approach to determine the PEFileKinds of an assembly loaded through Reflection. In other words, determine whether an assembly is a WinExe, Console application, or a Dynamic link library.

I found this solution (the other proposed solutions in that question are not effective), but If I'm not wrong I think it implies to assume that the file loaded is a .NET assembly, and, seems a little bit tidy to manually parse the PE header.

I also found this other solution but reading the comments it seem not effective in some circumstances.

For those reasons, I wonder if exists a truly safe, managed way, preferably through Reflection, to determine the PE file kind of a loaded assembly.

I'm sure the System.Reflection.Emit.PEFileKinds enumeration does not exists only for decorative purposes, if that enum exists then its logic to think that there could be a member/function that I missed inside the Reflection namespaces that internally use that enum to return the PE file kind of a Assembly object, however, I managed to look at the private members of the Assembly class through Reflection and other related classes and I found nothing relevant.


Solution

  • According to a search of the reference source, the PEFileKinds enum is only used in AssemblyBuilder and ModuleBuilder (and the non-public helper types for them). This enum and the classes are found in the System.Reflection.Emit namespace - e.g., they're intended for writing assemblies, not reading.

    However, the official System.Reflection.Metadata NuGet package exposes the relevant values of an assembly's PE header in its System.Reflection.PortableExecutable namespace. You can use these headers to reverse engineer the equivalent PEFileKinds value. Here's an example in C#:

    using (var stream = File.OpenRead(filenameAndExtension))
    {
        using (var peFile = new PEReader(stream))
        {
            var headers = peFile.PEHeaders;
            Console.WriteLine($"Reading {filenameAndExtension} with System.Reflection.Metadata");
            Console.WriteLine($"  IsDll: {headers.IsDll}");
            Console.WriteLine($"  IsExe: {headers.IsExe}");
            Console.WriteLine($"  IsConsoleApplication: {headers.IsConsoleApplication}");
    
            PEFileKinds reverseEngineeredKind;
    
            // NOTE: the header values cause IsConsoleApplication to return
            //       true for DLLs, so we need to check IsDll first
            if (headers.IsDll)
            {
                reverseEngineeredKind = PEFileKinds.Dll;
            }
            else if (headers.IsConsoleApplication)
            {
                reverseEngineeredKind = PEFileKinds.ConsoleApplication;
            }
            else
            {
                reverseEngineeredKind = PEFileKinds.WindowApplication;
            }
            Console.WriteLine($"  Reverse-engineered kind: {reverseEngineeredKind}");
        }
    }
    

    I ran this code on assemblies I generated with System.Reflection.Emit to ensure its accuracy. The full program is in this gist.

    You can probably also get this information with third-party libraries, like Mono.Cecil or, as Lex Li mentioned, PeNet.