30

原标题:如何防止从 .NET 应用程序加载本机 dll?

背景:

我的 C# 应用程序包括一个插件框架和通用插件加载器。

插件加载器枚举应用程序目录以识别插件 dll(本质上它此时搜索 *.dll)。

在同一个应用程序目录中是一个本地(Windows,非 .net)dll,它间接依赖于插件 dll 之一。

插件加载器盲目地假设 native.dll 是一个 .NET 程序集 dll,仅仅是因为它只检查文件扩展名。当它尝试加载本机 dll 时,会引发异常:

“无法加载文件或程序集‘native.dll’或其依赖项之一。该模块应包含程序集清单。”

如果插件加载失败,我基本上会创建一个诊断报告,所以我试图避免让这个日志充满关于无法加载本机 dll 的消息(我什至不想尝试)。

问题:

是否有一些 .NET API 调用可用于确定二进制文件是否恰好是 .NET 程序集,以便我根本不尝试加载本机 dll?

也许从长远来看,我会将我的插件移动到一个子目录,但现在,我只想要一个不涉及在我的插件加载器中硬编码“native.dll”名称的解决方法。

我想我正在寻找某种我忽略的静态 Assembly.IsManaged() API 调用......大概不存在这样的 API?

4

7 回答 7

30

lubos hasko 引用的答案很好,但它不适用于 64 位程序集。这是一个更正的版本(灵感来自http://apichange.codeplex.com/SourceControl/changeset/view/76c98b8c7311#ApiChange.Api/src/Introspection/CorFlagsReader.cs

public static bool IsManagedAssembly(string fileName)
{
    using (Stream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
    using (BinaryReader binaryReader = new BinaryReader(fileStream))
    {
        if (fileStream.Length < 64)
        {
            return false;
        }

        //PE Header starts @ 0x3C (60). Its a 4 byte header.
        fileStream.Position = 0x3C;
        uint peHeaderPointer = binaryReader.ReadUInt32();
        if (peHeaderPointer == 0)
        {
            peHeaderPointer = 0x80;
        }

        // Ensure there is at least enough room for the following structures:
        //     24 byte PE Signature & Header
        //     28 byte Standard Fields         (24 bytes for PE32+)
        //     68 byte NT Fields               (88 bytes for PE32+)
        // >= 128 byte Data Dictionary Table
        if (peHeaderPointer > fileStream.Length - 256)
        {
            return false;
        }

        // Check the PE signature.  Should equal 'PE\0\0'.
        fileStream.Position = peHeaderPointer;
        uint peHeaderSignature = binaryReader.ReadUInt32();
        if (peHeaderSignature != 0x00004550)
        {
            return false;
        }

        // skip over the PEHeader fields
        fileStream.Position += 20;

        const ushort PE32 = 0x10b;
        const ushort PE32Plus = 0x20b;

        // Read PE magic number from Standard Fields to determine format.
        var peFormat = binaryReader.ReadUInt16();
        if (peFormat != PE32 && peFormat != PE32Plus)
        {
            return false;
        }

        // Read the 15th Data Dictionary RVA field which contains the CLI header RVA.
        // When this is non-zero then the file contains CLI data otherwise not.
        ushort dataDictionaryStart = (ushort)(peHeaderPointer + (peFormat == PE32 ? 232 : 248));
        fileStream.Position = dataDictionaryStart;

        uint cliHeaderRva = binaryReader.ReadUInt32();
        if (cliHeaderRva == 0)
        {
            return false;
        }

        return true;
    }
}

缺少的部分是根据我们是 PE32 还是 PE32Plus 以不同的方式偏移到数据字典开始:

    // Read PE magic number from Standard Fields to determine format.
    var peFormat = binaryReader.ReadUInt16();
    if (peFormat != PE32 && peFormat != PE32Plus)
    {
        return false;
    }

    // Read the 15th Data Dictionary RVA field which contains the CLI header RVA.
    // When this is non-zero then the file contains CLI data otherwise not.
    ushort dataDictionaryStart = (ushort)(peHeaderPointer + (peFormat == PE32 ? 232 : 248));
于 2013-03-25T05:09:07.310 回答
18

如何确定文件是否为 .NET 程序集?

public static bool IsManagedAssembly(string fileName)
{
    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(fileName, 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();
    }
    fs.Close();

    if (dataDictionaryRVA[14] == 0) return false;
    else return true;
}
于 2008-12-15T08:50:30.553 回答
6

恐怕这样做的唯一真正方法是调用System.Reflection.AssemblyName.GetAssemblyName将完整路径传递给您要检查的文件。这将尝试从清单中提取名称,而不将完整的程序集加载到域中。如果文件是托管程序集,则它将程序集的名称作为字符串返回,否则它将抛出一个BadImageFormatException您可以捕获并忽略的,然后再跳过程序集并移至其他插件。

于 2008-12-15T09:09:37.690 回答
4

正如 orip 建议的那样,您需要将其包装在 try {} catch {} 块中 - 特别是,您要注意BadImageFormatException

foreach (string aDll in dllCollection) 
{
  try 
  {
     Assembly anAssembly = Assembly.LoadFrom(aDll);
  }
  catch (BadImageFormatException ex)
  {
    //Handle this here
  }
  catch (Exception ex)
  {
    //Other exceptions (i/o, security etc.)
   }
}

在此处查看其他例外情况http://msdn.microsoft.com/en-us/library/1009fa28.aspx

于 2008-12-15T08:42:17.513 回答
1

例如,使用 BadImageFormatException 异常是一种不好的方法。如果您的应用程序以 .NET 3.5 为目标,它将无法识别比方说针对 .NET Core 编译的程序集,尽管程序集是托管的。

所以我认为解析PE头要好得多。

于 2013-10-03T10:05:51.303 回答
0

您总是可以使用 try/except 块来包装 DLL 加载...

于 2008-12-15T08:30:33.933 回答
0

扩展基里尔的答案,我已将其翻译为 VB,Boolean稍微调整逻辑以提高可读性,并将其转换为System.IO.FileInfo. 希望它可以帮助某人。

Public Module FileSystem
  <Extension>
  Public Function IsManagedAssembly(File As FileInfo) As Boolean
    Dim _
      uHeaderSignature,
      uHeaderPointer As UInteger

    Dim _
      uFormat,
      u64,
      u32 As UShort

    u64 = &H20B
    u32 = &H10B

    IsManagedAssembly = False

    If File.Exists AndAlso File.Length.IsAtLeast(64) Then
      Using oStream As New FileStream(File.FullName, FileMode.Open, FileAccess.Read)
        Using oReader As New BinaryReader(oStream)
          'PE Header starts @ 0x3C (60). Its a 4 byte header.
          oStream.Position = &H3C
          uHeaderPointer = oReader.ReadUInt32

          If uHeaderPointer = 0 Then
            uHeaderPointer = &H80
          End If

          ' Ensure there is at least enough room for the following structures:
          '     24 byte PE Signature & Header
          '     28 byte Standard Fields         (24 bytes for PE32+)
          '     68 byte NT Fields               (88 bytes for PE32+)
          ' >= 128 byte Data Dictionary Table
          If uHeaderPointer < oStream.Length - 257 Then
            ' Check the PE signature.  Should equal 'PE\0\0'.
            oStream.Position = uHeaderPointer
            uHeaderSignature = oReader.ReadUInt32

            If uHeaderSignature = &H4550 Then
              ' skip over the PEHeader fields
              oStream.Position += 20

              ' Read PE magic number from Standard Fields to determine format.
              uFormat = oReader.ReadUInt16

              If uFormat = u32 OrElse uFormat = u64 Then
                ' Read the 15th Data Dictionary RVA field which contains the CLI header RVA.
                ' When this is non-zero then the file contains CLI data, otherwise not.
                Select Case uFormat
                  Case u32 : oStream.Position = uHeaderPointer + &HE8
                  Case u64 : oStream.Position = uHeaderPointer + &HF8
                End Select

                IsManagedAssembly = oReader.ReadUInt32 > 0
              End If
            End If
          End If
        End Using
      End Using
    End If
  End Function
End Module
于 2015-09-20T06:54:37.007 回答