Determining if the dll is CLR.valid DLL by directly reading the PE (64-bit issue)

I am working on porting a 32-bit web application to 64-bit, and I have some problems with our plugin loader code.

In the 32-bit version, we scan the bin webapps directory for all .net DLLs and then load them using Assembly.Load to check for our plugin attributes.

We did this pretty well using the public domain code:

 /// <summary> /// Returns true if the file specified is a real CLR type, /// otherwise false is returned. /// False is also returned in the case of an exception being caught /// </summary> /// <param name="file">A string representing the file to check for /// CLR validity</param> /// <returns>True if the file specified is a real CLR type, /// otherwise false is returned. /// False is also returned in the case of an exception being /// caught</returns> public static bool IsDotNetAssembly(String file) { Stream fs = new FileStream(@file, FileMode.Open, FileAccess.Read); try { BinaryReader reader = new BinaryReader(fs); //PE Header starts @ 0x3C (60). Its a 4 byte header. fs.Position = 0x3C; uint peHeader = reader.ReadUInt32(); //Moving to PE Header start location... fs.Position = peHeader; uint peHeaderSignature = reader.ReadUInt32(); ushort machine = reader.ReadUInt16(); ushort sections = reader.ReadUInt16(); uint timestamp = reader.ReadUInt32(); uint pSymbolTable = reader.ReadUInt32(); uint noOfSymbol = reader.ReadUInt32(); ushort optionalHeaderSize = reader.ReadUInt16(); ushort characteristics = reader.ReadUInt16(); // PE Optional Headers // To go directly to the datadictionary, we'll increase the stream current position to with 96 (0x60). // 28 bytes for Standard fields // 68 bytes for NT-specific fields // 128 bytes DataDictionary // DataDictionay has 16 directories // 8 bytes per directory (4 bytes RVA and 4 bytes of Size.) // 15th directory consist of CLR header! (if its 0, it is not a CLR file ) uint[] dataDictionaryRVA = new uint[16]; uint[] dataDictionarySize = new uint[16]; ushort 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) { fs.Close(); return false; } else { fs.Close(); return true; } } catch (Exception) { return false; } finally { fs.Close(); } } 

Now the problem is that now we have to process 64-bit or platform independent DLLs, and the offset seems to have changed, and this code does not work. Does anyone know the correct changes to the above code to return true for a valid 64-bit OR platform independent DLL?

+6
source share
3 answers

The reason your code does not work for x64-bit DLLs is because the extra image header size of the x64-bit DLL and the x86-bit DLL are different. You must accept different images with extra header sizes to determine whether the given DLL.Net is a DLL.

the PE file format specification describes in section 3.4 (optional header) various offsets for navigating to data directories:

  • For PE32 (x86) images, the offset is 0x60 (as in your code) and
  • for PE32 + (x64) 0x70 offset is 0x70 .

To determine if a given DLL is an x64-bit DLL, you must read the magic bytes of the optional header:

  • A value of 0x20b means PE32 +,
  • value 0x10b PE32.

I expanded your example:

 Stream fs = new FileStream(@file, FileMode.Open, FileAccess.Read); try { BinaryReader reader = new BinaryReader(fs); //PE Header starts @ 0x3C (60). Its a 4 byte header. fs.Position = 0x3C; uint peHeader = reader.ReadUInt32(); //Moving to PE Header start location... fs.Position = peHeader; uint peHeaderSignature = reader.ReadUInt32(); ushort machine = reader.ReadUInt16(); ushort sections = reader.ReadUInt16(); uint timestamp = reader.ReadUInt32(); uint pSymbolTable = reader.ReadUInt32(); uint noOfSymbol = reader.ReadUInt32(); ushort optionalHeaderSize = reader.ReadUInt16(); ushort characteristics = reader.ReadUInt16(); long posEndOfHeader = fs.Position; ushort magic = reader.ReadUInt16(); int off = 0x60; // Offset to data directories for 32Bit PE images // See section 3.4 of the PE format specification. if (magic == 0x20b) //0x20b == PE32+ (64Bit), 0x10b == PE32 (32Bit) { off = 0x70; // Offset to data directories for 64Bit PE images } fs.Position = posEndOfHeader; uint[] dataDictionaryRVA = new uint[16]; uint[] dataDictionarySize = new uint[16]; ushort dataDictionaryStart = Convert.ToUInt16(Convert.ToUInt16(fs.Position) + off); fs.Position = dataDictionaryStart; for (int i = 0; i < 15; i++) { dataDictionaryRVA[i] = reader.ReadUInt32(); dataDictionarySize[i] = reader.ReadUInt32(); } if (dataDictionaryRVA[14] == 0) { fs.Close(); return false; } else { fs.Close(); return true; } } catch (Exception) { return false; } finally { fs.Close(); } 

Structures defined for optional PE32 / PE32 + headers also exist in the Windows SDK. A description of these structures can be found here MSDN .

Hope this helps.

+7
source

For an alternative that does not use reflection and does not load assemblies directly, try the common compiler framework metadata API . It looks like you can pretty easily download the PE assembly and determine if it has a CLR module.

 MetadataReaderHost host = new PeReader.DefaultHost(); var module = host.LoadUnitFrom(args[0]) as IModule; if (module == null) { Console.WriteLine(args[0]+" is not a PE file containing a CLR module or assembly."); return; } 
+3
source

Is there a reason you cannot use methods within the framework? Sample code below:

  var assembly = Assembly.Load("path to assembly"); ImageFileMachine machine; PortableExecutableKinds peKind; assembly.ManifestModule.GetPEKind(out peKind, out machine); 

The GetPEKind method on MSDN and PortableExecutableKinds should start. The latter is basically corflags

+2
source

Source: https://habr.com/ru/post/904259/


All Articles