簡體   English   中英

如何判斷一個DLL是托管程序集還是原生(防止加載原生dll)?

[英]How to determine whether a DLL is a managed assembly or native (prevent loading a native dll)?

原標題:如何防止從 .NET 應用程序加載本機 dll?

背景:

我的 C# 應用程序包括一個插件框架和通用插件加載器。

插件加載器枚舉應用程序目錄以識別插件 dll(此時它基本上搜索 *.dll)。

在同一個應用程序目錄中有一個本機(Windows,非.net)dll,其中一個插件 dll 間接依賴於它。

插件加載器盲目地假設 native.dll 是一個 .NET Assembly dll,僅僅是因為它只檢查文件擴展名。 當它嘗試加載本機 dll 時,拋出異常:

“無法加載文件或程序集‘native.dll’或其依賴項之一。該模塊應包含程序集清單。”

如果插件加載失敗,我基本上會創建一個診斷報告,所以我試圖避免讓這個日志充滿關於無法加載本機 dll 的消息(我什至不想嘗試)。

問題:

是否有一些 .NET API 調用可用於確定二進制文件是否恰好是 .NET 程序集,以便我根本不會嘗試加載本機 dll?

也許從長遠來看,我會將我的插件移動到一個子目錄中,但就目前而言,我只想要一個不涉及在我的插件加載器中硬編碼“native.dll”名稱的解決方法。

我想我正在尋找某種我忽略的靜態 Assembly.IsManaged() API 調用……大概不存在這樣的 API?

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

如何確定文件是否是.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;
}

我擔心這樣做的唯一真正方法是調用System.Reflection.AssemblyName.GetAssemblyName將完整路徑傳遞給要檢查的文件。 這將嘗試從清單中提取名稱而不將完整程序集加載到域中。 如果文件是托管程序集,那么它將返回程序集的名稱作為字符串,否則它將拋出BadImageFormatException ,您可以在跳過程序集並移動到其他插件之前捕獲並忽略它。

正如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

使用BadImageFormatException異常是一種不好的方法,例如。 如果您的應用程序以.NET 3.5為目標,它將無法識別讓我們說針對.NET Core編譯的程序集,盡管程序集是受管理的。

所以我認為解析PE頭要好得多。

根據Kirill的回答,我將其翻譯為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

您總是可以使用try / except塊包裝DLL加載...

使用 System.Reflection.Metadata 的現代方式(包含在 .NET Core 和 .NET 5+ 中,或作為 .NET Framework 上的 NuGet 包安裝):

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;

//...

static bool IsAssembly(string path)
{
    using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

    // Try to read CLI metadata from the PE file.
    using var peReader = new PEReader(fs);

    if (!peReader.HasMetadata)
    {
        return false; // File does not have CLI metadata.
    }

    // Check that file has an assembly manifest.
    MetadataReader reader = peReader.GetMetadataReader();
    return reader.IsAssembly;
}

來源: https ://learn.microsoft.com/en-us/dotnet/standard/assembly/identify#using-the-pereader-class

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM