【问题标题】:Determining if a dll is a .valid CLR dll by reading the PE directly (64bit issue)通过直接读取 PE 来确定 dll 是否为 .valid CLR dll(64 位问题)
【发布时间】:2011-12-21 16:44:10
【问题描述】:

我正在将 32 位 Web 应用程序迁移到 64 位,但我们的插件加载器代码存在一些问题。

在 32 位版本中,我们扫描 webapps bin 目录中的所有 .net dll,然后使用 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,并且偏移量似乎已经改变并且这段代码失败了。有谁知道对上述代码的正确修改,以便为有效的 64 位或平台无关的 dll 返回 true?

【问题讨论】:

  • 你真的要使用上面的代码吗?
  • 漂亮吗?更像是实现特定的、假设的和必然会让你陷入困境的……哦等等,它已经做到了。 :)
  • Web 应用程序非常庞大 - 在 bin 目录中有超过 100 个 dll。许多 dll 不是我们应用程序的插件,而且许多不是 clr dll,因此使用 Assembly.Load 不是一个好的选择。

标签: c# parsing dll 64-bit portable-executable


【解决方案1】:

您的代码不适用于 x64 位 DLL 的原因是 因为 x64 位 DLL 的图像可选标头大小和 x86 位 DLL 是不同的。你必须采取不同的 考虑到图像可选标题大小以确定 给定的 DLL 是否为 .Net DLL。

第 3.4 节中的PE file format specification describes(可选标题) 跳转到数据目录的不同偏移量:

  1. 对于 PE32 (x86) 图像,偏移量为 0x60(在您的代码中)和
  2. 对于 PE32+ (x64) 图像,偏移量为 0x70

为了确定给定的 DLL 是否是 x64 位 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

希望,这会有所帮助。

【讨论】:

    【解决方案2】:

    对于不使用反射且不直接加载程序集的替代方案,请尝试Common Compiler Infrastructure Metadata API。看来您可以相当轻松地load a PE assembly 并确定它是否具有 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;
    }
    

    【讨论】:

      【解决方案3】:

      你不能在框架中使用方法有什么原因吗?示例代码如下:

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

      GetPEKind method on MSDNPortableExecutableKinds 应该可以帮助您入门。后者基本上是corflags

      【讨论】:

      • 不是解决方案,如果 Load() 方法不是 .NET 程序集,或者它的目标是错误的 .NET 版本或位数,Load() 方法将会爆炸。
      猜你喜欢
      • 1970-01-01
      • 2013-06-06
      • 2022-01-20
      • 2010-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多