Определение, является ли dll допустимой CLR-библиотекой, читая PE напрямую (64-битная проблема) - PullRequest
6 голосов
/ 21 декабря 2011

Я работаю над переносом 32-битного веб-приложения на 64-битную, и у меня возникли некоторые проблемы с нашим кодом загрузчика плагинов.

В 32-битной версии мы сканируем каталог bin веб-приложений на наличие всех .net-библиотек, затем загружаем их с Assembly.Load, чтобы проверить наличие атрибутов нашего плагина.

Мы сделали это довольно изящно, используя код общественного достояния:

/// <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's 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();
    }
}

Теперь проблема в том, что теперь мы должны обрабатывать 64-битные или независимые от платформы dll, и смещение, похоже, изменилось, и этот код завершился ошибкой. Кто-нибудь знает правильные изменения в приведенном выше коде, чтобы вернуть true для действительных только 64-битных ИЛИ независимых от платформы dll?

Ответы [ 3 ]

7 голосов
/ 22 декабря 2011

Причина, по которой ваш код не работает для 64-разрядных библиотек DLL, заключается в том, что необязательный размер заголовка изображения для 64-разрядных библиотек и 64-разрядных библиотек отличается.Необходимо принять во внимание необязательные размеры заголовков для разных изображений, чтобы определить, является ли данная DLL .NET DLL.

Спецификация формата файла PE описывает в разделе 3.4 (Необязательный заголовок) различные смещения для перехода к каталогам данных:

  1. Для изображений PE32 (x86)смещение составляет 0x60 (как в вашем коде) и
  2. для изображений PE32 + (x64) смещение составляет 0x70.

Чтобы определить, действительно лиданная DLL-библиотека является 64-битной DLL, вам нужно прочитать волшебные байты необязательного заголовка:

  1. Значение 0x20b означает PE32 +,
  2. , значение 0x10bPE32.

Я расширил ваш пример:

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();
 }

В Windows SDK также есть структуры, определенные для дополнительных заголовков PE32 / PE32 +.Описание этих структур можно найти здесь MSDN .

Надеюсь, это поможет.

3 голосов
/ 22 декабря 2011

Для альтернативы, которая не использует отражение и не загружает сборки напрямую, попробуйте API метаданных общей инфраструктуры компилятора . Кажется, что вы можете довольно просто загрузить PE-сборку и определить, есть ли у нее модуль CLR.

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;
}
2 голосов
/ 21 декабря 2011

Есть ли причина, по которой вы не можете использовать методы в рамках?Пример кода ниже:

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

Метод GetPEKind для MSDN и PortableExecutableKinds должен помочь вам начать работу.Последний в основном корфлаг

...