Search
Close this search box.

How to determine whether a file is a .NET Assembly or not?

 There are some scenarios where need to check programmatically if the given file is a .NET assembly or not. How do we do that? One way is to use reflection and try to load that file (assembly). If the assembly gets loaded, and doesn’t throw any exception, then yes, it’s a valid .NET assembly. If it’s not a valid file, 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 way at least to me. So I was trying some other way with which I can get to know if the file is an assembly or not. Here is the solution I got;

.NET assemblies are regular Win32 PE files. Operating System doesn’t differentiate between .NET assemblies and Win32 executable binaries; for it, they are same normal PE files. So how does system get to know if this file is a managed assembly and if yes, how to load CLR? How OS loads CLR is a separate discussion, I’ll just talk about how to check this header for validating the file if it’s a managed assembly or not. Well if we go through the ECMA Specifications Partition II – Metadata which is shipped along with .NET SDK, we see that we have 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 that this is a valid .NET assembly, otherwise it is not.

Here is the code sample that does the same:

private void GetHeaders()

{

    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(@"D:\Test.exe", 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!!");

            }

            else

            {

                Console.WriteLine("This is a valid CLR File..");

            }

            fs.Close();

}

To simplify more, you can directly move your stream position to the 15th Data Directory location and check the first 8 bytes (4 bytes for RVA and other 4 bytes for Size).

This article is part of the GWB Archives. Original Author: Rupreet’s Weblog

Related Posts