20

What is the best way to check if a DLL file is a Win32 DLL or if it is a CLR assembly. At the moment I use this code

    try
    {
        this.currentWorkingDirectory = Path.GetDirectoryName(assemblyPath);

        //Try to load the assembly.
        assembly = Assembly.LoadFile(assemblyPath);

        return assembly != null;
    }
    catch (FileLoadException ex)
    {
        exception = ex;
    }
    catch (BadImageFormatException ex)
    {
        exception = ex;
    }
    catch (ArgumentException ex)
    {
        exception = ex;
    }
    catch (Exception ex)
    {
        exception = ex;
    }

    if (exception is BadImageFormatException)
    {
        return false;
    }

But I like to check before loading because I do not want those exceptions (time).

Is there a better way?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
schoetbi
  • 12,009
  • 10
  • 54
  • 72
  • Does this answer your question? [How to determine whether a DLL is a managed assembly or native (prevent loading a native dll)?](https://stackoverflow.com/questions/367761/how-to-determine-whether-a-dll-is-a-managed-assembly-or-native-prevent-loading) – MSDN.WhiteKnight Jul 31 '23 at 05:41

8 Answers8

23

Check the PE header:

DOS header starts at 0x0, the DWORD at 0x3c contains a pointer to the PE signature (usually 0x80) which is 4 bytes, the next 20 bytes is the COFF header and then there is the PE header (at 0x9. The PE header is 224 bytes and contains the data directory (at 96 bytes into the PE header = 0xf. The 15th entry (at 0x16 is the CLR header descriptor (sometimes called the COM descriptor, but this does not have anything to do with COM). If this is empty (ie 0 in the 8 bytes from 0x168 to 0x16f) then the file is not a .NET assembly. If you want to check if it is a COM DLL then you should look to see if it exports GetClassObject.

Ref.

UPDATE: there is a more '.NET' way of accomplishing this:

Use Module.GetPEKind method and check the PortableExecutableKinds Enumeration:

NotAPortableExecutableImage The file is not in portable executable (PE) file format.

ILOnly The executable contains only Microsoft intermediate language (MSIL), and is therefore neutral with respect to 32-bit or 64-bit platforms.

Required32Bit The executable can be run on a 32-bit platform, or in the 32-bit Windows on Windows (WOW) environment on a 64-bit platform.

PE32Plus The executable requires a 64-bit platform.

Unmanaged32Bit The executable contains pure unmanaged code.

Mitch Wheat
  • 295,962
  • 43
  • 465
  • 541
  • 12
    I'm trying to do something similar to the OP. This solution sounds great, but how can you get a Module instance without calling Assembly.LoadFile (which throws a BadImageFormatException for non-CLR dlls before you can call GetPEKind)? – Paul A Jungwirth Jan 18 '10 at 19:55
  • 1
    @Simon: of course if it's of interest to you, how about you write and add some code as an answer? – Mitch Wheat Jul 20 '11 at 01:26
  • 3
    @Mitch: Ok perhaps the downvote was a bit hasty. I tried to undo it but the timeout has elapsed. Sorry. feel free to downvote some of my answers :). I did try to get this working. Although it seemed to me that Pauls question was valid. Module.GetPEKind is instance. So to get a Module you need to call assembly Assembly.Load. which will throw an exception. Since the question explicitly states "no exceptions" it would seem this answer is either incomplete or incorrect. – Simon Jul 20 '11 at 10:03
13

If an assembly gets loaded eg Assembly.LoadFile(dotNetDllorExe) and doesn’t throw any exception, it’s a valid .NET assembly. If it’s not then it’ll throw a “BadImageFormatException”.

The idea of checking weather a file is assembly or not by loading it and checking if exception is thrown or not; doesn’t seem to be too clean. After all exceptions are supposed to be used exceptionally.


.NET assemblies are regular Win32 PE files, the operating System doesn’t differentiate between .NET assemblies and Win32 executable binaries, they are the same normal PE files. So how does the System work out if a DLL or EXE is a managed assembly in order to load the CLR?

It validates the file header to check if it’s a managed assembly or not. In the ECMA Specifications Partition II – Metadata which is shipped along with .NET SDK you see there is a separate CLI Header in the PE Format. It is the 15th data directory in the PE Optional Headers. So, in simple terms, if we have value in this data directory, then it means this is a valid .NET assembly, otherwise it's not.

internal static class PortableExecutableHelper
{
    internal static bool IsDotNetAssembly(string peFile)
    {
        uint peHeader;
        uint peHeaderSignature;
        ushort machine;
        ushort sections;
        uint timestamp;
        uint pSymbolTable;
        uint noOfSymbol;
        ushort optionalHeaderSize;
        ushort characteristics;
        ushort dataDictionaryStart;
        uint[] dataDictionaryRVA = new uint[16];
        uint[] dataDictionarySize = new uint[16];


        Stream fs = new FileStream(peFile, FileMode.Open, FileAccess.Read);
        BinaryReader reader = new BinaryReader(fs);

        //PE Header starts @ 0x3C (60). Its a 4 byte header.
        fs.Position = 0x3C;

        peHeader = reader.ReadUInt32();

        //Moving to PE Header start location...
        fs.Position = peHeader;
        peHeaderSignature = reader.ReadUInt32();

        //We can also show all these value, but we will be       
        //limiting to the CLI header test.

        machine = reader.ReadUInt16();
        sections = reader.ReadUInt16();
        timestamp = reader.ReadUInt32();
        pSymbolTable = reader.ReadUInt32();
        noOfSymbol = reader.ReadUInt32();
        optionalHeaderSize = reader.ReadUInt16();
        characteristics = reader.ReadUInt16();

        /*
            Now we are at the end of the PE Header and from here, the
                        PE Optional Headers starts...
                To go directly to the datadictionary, we'll increase the      
                stream’s current position to with 96 (0x60). 96 because,
                        28 for Standard fields
                        68 for NT-specific fields
            From here DataDictionary starts...and its of total 128 bytes. DataDictionay has 16 directories in total,
            doing simple maths 128/16 = 8.
            So each directory is of 8 bytes.
                        In this 8 bytes, 4 bytes is of RVA and 4 bytes of Size.

            btw, the 15th directory consist of CLR header! if its 0, its not a CLR file :)
     */
        dataDictionaryStart = Convert.ToUInt16(Convert.ToUInt16(fs.Position) + 0x60);
        fs.Position = dataDictionaryStart;
        for (int i = 0; i < 15; i++)
        {
            dataDictionaryRVA[i] = reader.ReadUInt32();
            dataDictionarySize[i] = reader.ReadUInt32();
        }
        if (dataDictionaryRVA[14] == 0)
        {
            Console.WriteLine("This is NOT a valid CLR File!!");
            return false;
        }
        else
        {
            Console.WriteLine("This is a valid CLR File..");
            return true;
        }
        fs.Close();
    }
}

ECMA Ref, Blog Ref

Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
  • Unfortunately I still had tons of BadImageFormatExceptions with this seemingly often reporting false positives, like for C++ Boost Libraries. – Jonas Nov 06 '20 at 07:32
  • Check the answer from Calum Heanney to update this solution for 64-bit dlls. https://stackoverflow.com/a/29643803/2151722 – edwabr123 Dec 21 '22 at 03:11
4

My current reputation is insufficient to comment directly on Jeremy's answer which works well for 32-bit DLLs. But I had to refine it for 64-bit DLLs, which appear to have more Windows-specific COFF fields.

As described by Microsoft, the COFF Magic number indicates if the format is PE32 (32-bit) or PE32+ (64-bit). If the latter, then the offset to the Data Directories is 112 bytes instead of 96.

// After the COFF header, the first COFF field is a Magic Number.
UInt16 coffMagic = reader.ReadUInt16();

// Skip remaining fields to reach data directories.  See:
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-image-only
if (coffMagic == 0x010B)
{
    // It's a 32-bit DLL.  96 bytes of COFF fields.
    // Subtract 2 for the magic number we just read.
    fs.Position += (96 - 2);
}
else if (coffMagic == 0x020B)
{
    // It's a 64-bit DLL.  112 bytes of COFF fields.
    // Subtract 2 for the magic number we just read.
    fs.Position += (112 - 2);
}
2

Faced with the same problem in the past, I resorted to using your reflection approach because the alternative is to manually read the PE header like this. Just seemed like overkill for my scenario, but it may be useful to you.

Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393
0

You didn't specify whether you have to do this in code, or if you just personally need to know if a file you're looking at on your system is a .NET assembly (which maybe you think requires you writing your own code to do so). If the latter, you can use Dependency Walker to see if it has a dependency on MSCOREE.dll, which is the .Net runtime engine.

Sam Skuce
  • 1,666
  • 14
  • 20
0

You could use something like:

        AssemblyName assemblyName = null;

        try
        {
            assemblyName = AssemblyName.GetAssemblyName(filename);
        }
        catch (System.IO.FileNotFoundException ex)
        {
            throw new Exception("File not found!", ex);
        }
        catch (System.BadImageFormatException ex)
        {
            throw new Exception("File is not an .Net Assembly.", ex);
        }

Please also check out: https://msdn.microsoft.com/en-us/library/ms173100.aspx

0

Update 2020:

This should be the easiest way to detect if the dll is a .Net library:

public bool IsNetAssembly(string fileName)
{
    try
    {
        AssemblyName.GetAssemblyName(fileName);
    }
    catch (BadImageFormatException)
    {
        // not a .Net Assembly
         return false;
    }
    
    return true;
}
BenHero
  • 323
  • 1
  • 3
  • 9
-2

You can read the first two bytes from the file, if the bytes are "MZ" then try to read the assembly name to determine (microsoft slow way) the validity of the assembly.

    public static bool isValidAssembly (string sFileName)
    {
        try
        {
            using (FileStream fs = File.OpenRead(sFileName))
            {
                if ((fs.ReadByte() != 'M') || (fs.ReadByte() != 'Z'))
                {
                    fs.Close();
                    return false;
                }
                fs.Close();
            }

            // http://msdn.microsoft.com/en-us/library/ms173100.aspx
            object foo = SR.AssemblyName.GetAssemblyName(sFileName);
            return true;
        }
        catch 
        {
            return false;
        }
    }