Определение, является ли dll допустимой CLR-библиотекой, читая PE напрямую (64-битная проблема)
Я работаю над переносом 32-битного веб-приложения в 64-битное и у меня возникли некоторые проблемы с нашим кодом загрузчика плагинов.
В 32-битной версии мы сканируем каталог bin веб-приложений для всех DLL-файлов.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 ответа
Причина, по которой ваш код не работает для 64-разрядных библиотек DLL, заключается в том, что необязательный размер заголовка изображения для 64-разрядных библиотек DLL и 32-разрядных библиотек отличается. Необходимо принять во внимание необязательные размеры заголовков различных изображений, чтобы определить, является ли данная DLL.NET DLL.
Спецификация формата файла PE описывает в разделе 3.4 (Необязательный заголовок) различные смещения для перехода к каталогам данных:
- Для изображений PE32 (x86) смещение
0x60
(как это в вашем коде) и - для изображений PE32+ (x64) смещение
0x70
,
Чтобы определить, является ли данная DLL-библиотека 64-битной DLL, нужно прочитать магические байты необязательного заголовка:
- Значение
0x20b
означает PE32+, - значение
0x10b
PE32.
Я расширил ваш пример:
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.
Надеюсь это поможет.
Для альтернативы, которая не использует отражение и не загружает сборки напрямую, попробуйте 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;
}
Есть ли причина, по которой вы не можете использовать методы в рамках? Пример кода ниже:
var assembly = Assembly.Load("path to assembly");
ImageFileMachine machine;
PortableExecutableKinds peKind;
assembly.ManifestModule.GetPEKind(out peKind, out machine);
Метод GetPEKind в MSDN и PortableExecutableKinds должен помочь вам начать работу. Последний в основном корфляги