2010-12-31 1 views
2

Как я могу программно анализировать родную DLL, чтобы читать ее импорт?Как программировать чтение встроенных DLL-импорта в C#?

[EDIT: мой первоначальный вопрос выглядел следующим образом, наряду с огромным куском дефектного кода. См. Ответы ниже для более правильного кода.]

Код C#, расположенный по адресу this link, предназначен для печати импорта родной DLL.

Я нахожу, что когда я запускаю образец кода с целевым объектом исходного примера, MSCOREE.DLL, он печатает все импортные штрафы. Но когда я использую другие DLL, такие как GDI32.DLL или WSOCK32.DLL, импорт не печатается. Что отсутствует в этом коде, который позволит ему распечатать все импортные данные, например, DUMPBIN.EXE?

+2

Вы должны шутить. Используйте Dumpbin.exe/exports –

+0

@Hans - этот код в конечном итоге будет использоваться в инструменте анализа зависимостей. Консольная версия здесь просто разминка. У меня фактически есть рабочая версия реального приложения, которое вызывает «DUMPBIN/import», а затем анализирует вывод, но я бы предпочел не делать этого. – Eric

+0

+1 Ханс. Но если вам нужно сделать это программно, разве это не то, что dbghelp.dll? –

ответ

4

Существует одна очень большая проблема в коде (а именно определение THUNK_DATA) и ряд других мелких проблем, в основном касающееся окончание срока таблицы (используя IsBadReadPtr вместо NULL-проверок, а также не добавляя базовый адрес по мере необходимости).

Вот фиксированная версия, которая производит такой же результат, как Dumpbin по крайней мере, для wsock32:

using System; 
using System.Runtime.InteropServices; 
using System.Security; 

namespace PETest2 
{ 
    [StructLayout(LayoutKind.Explicit)] 
    public unsafe struct IMAGE_IMPORT_BY_NAME 
    { 
     [FieldOffset(0)] 
     public ushort Hint; 
     [FieldOffset(2)] 
     public fixed char Name[1]; 
    } 

    [StructLayout(LayoutKind.Explicit)] 
    public struct IMAGE_IMPORT_DESCRIPTOR 
    { 
     #region union 
     /// <summary> 
     /// CSharp doesnt really support unions, but they can be emulated by a field offset 0 
     /// </summary> 

     [FieldOffset(0)] 
     public uint Characteristics;   // 0 for terminating null import descriptor 
     [FieldOffset(0)] 
     public uint OriginalFirstThunk;   // RVA to original unbound IAT (PIMAGE_THUNK_DATA) 
     #endregion 

     [FieldOffset(4)] 
     public uint TimeDateStamp; 
     [FieldOffset(8)] 
     public uint ForwarderChain; 
     [FieldOffset(12)] 
     public uint Name; 
     [FieldOffset(16)] 
     public uint FirstThunk; 
    } 

    [StructLayout(LayoutKind.Explicit)] 
    public struct THUNK_DATA 
    { 
     [FieldOffset(0)] 
     public uint ForwarderString;  // PBYTE 
     [FieldOffset(0)] 
     public uint Function;    // PDWORD 
     [FieldOffset(0)] 
     public uint Ordinal; 
     [FieldOffset(0)] 
     public uint AddressOfData;  // PIMAGE_IMPORT_BY_NAME 
    } 

    public unsafe class Interop 
    { 
     #region Public Constants 
     public static readonly ushort IMAGE_DIRECTORY_ENTRY_IMPORT = 1; 
     #endregion 
     #region Private Constants 
     #region CallingConvention CALLING_CONVENTION 
     /// <summary> 
     ///  Specifies the calling convention. 
     /// </summary> 
     /// <remarks> 
     ///  Specifies <see cref="CallingConvention.Winapi" /> for Windows to 
     ///  indicate that the default should be used. 
     /// </remarks> 
     private const CallingConvention CALLING_CONVENTION = CallingConvention.Winapi; 
     #endregion CallingConvention CALLING_CONVENTION 
     #region IMPORT DLL FUNCTIONS 
     private const string KERNEL_DLL = "kernel32"; 
     private const string DBGHELP_DLL = "Dbghelp"; 
     #endregion 
     #endregion Private Constants 

     [DllImport(KERNEL_DLL, CallingConvention = CALLING_CONVENTION, EntryPoint = "GetModuleHandleA"), SuppressUnmanagedCodeSecurity] 
     public static extern void* GetModuleHandleA(/*IN*/ char* lpModuleName); 

     [DllImport(KERNEL_DLL, CallingConvention = CALLING_CONVENTION, EntryPoint = "GetModuleHandleW"), SuppressUnmanagedCodeSecurity] 
     public static extern void* GetModuleHandleW(/*IN*/ char* lpModuleName); 

     [DllImport(KERNEL_DLL, CallingConvention = CALLING_CONVENTION, EntryPoint = "IsBadReadPtr"), SuppressUnmanagedCodeSecurity] 
     public static extern bool IsBadReadPtr(void* lpBase, uint ucb); 

     [DllImport(DBGHELP_DLL, CallingConvention = CALLING_CONVENTION, EntryPoint = "ImageDirectoryEntryToData"), SuppressUnmanagedCodeSecurity] 
     public static extern void* ImageDirectoryEntryToData(void* Base, bool MappedAsImage, ushort DirectoryEntry, out uint Size); 


    } 


    static class Foo 
    { 
     // From winbase.h in the Win32 platform SDK. 
     // 
     const uint DONT_RESOLVE_DLL_REFERENCES = 0x00000001; 
     const uint LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010; 

     [DllImport("kernel32.dll"), SuppressUnmanagedCodeSecurity] 
     static extern uint LoadLibraryEx(string fileName, uint notUsedMustBeZero, uint flags); 

     public static void Main() 
     { 
      //var path = @"c:\windows\system32\mscoree.dll"; 
      //var path = @"c:\windows\system32\gdi32.dll"; 
      var path = @"c:\windows\system32\wsock32.dll"; 
      var hLib = LoadLibraryEx(path, 0, 
            DONT_RESOLVE_DLL_REFERENCES | LOAD_IGNORE_CODE_AUTHZ_LEVEL); 
      TestImports(hLib, true); 

     } 


     // using mscoree.dll as an example as it doesnt export any thing 
     // so nothing shows up if you use your own module. 
     // and the only none delayload in mscoree.dll is the Kernel32.dll 
     private static void TestImports(uint hLib, bool mappedAsImage) 
     { 
      unsafe 
      { 
       //fixed (char* pszModule = "mscoree.dll") 
       { 
        //void* hMod = Interop.GetModuleHandleW(pszModule); 
        void* hMod = (void*)hLib; 

        uint size = 0; 
        uint BaseAddress = (uint)hMod; 

        if (hMod != null) 
        { 
         Console.WriteLine("Got handle"); 

         IMAGE_IMPORT_DESCRIPTOR* pIID = (IMAGE_IMPORT_DESCRIPTOR*)Interop.ImageDirectoryEntryToData((void*)hMod, mappedAsImage, Interop.IMAGE_DIRECTORY_ENTRY_IMPORT, out size); 
         if (pIID != null) 
         { 
          Console.WriteLine("Got Image Import Descriptor"); 
          while (pIID->OriginalFirstThunk != 0) 
          { 
           try 
           { 
            char* szName = (char*)(BaseAddress + pIID->Name); 
            string name = Marshal.PtrToStringAnsi((IntPtr)szName); 
            Console.WriteLine("pIID->Name = {0} BaseAddress - {1}", name, (uint)BaseAddress); 

            THUNK_DATA* pThunkOrg = (THUNK_DATA*)(BaseAddress + pIID->OriginalFirstThunk); 

            while (pThunkOrg->AddressOfData != 0) 
            { 
             char* szImportName; 
             uint Ord; 

             if ((pThunkOrg->Ordinal & 0x80000000) > 0) 
             { 
              Ord = pThunkOrg->Ordinal & 0xffff; 
              Console.WriteLine("imports ({0}).Ordinal{1} - Address: {2}", name, Ord, pThunkOrg->Function); 
             } 
             else 
             { 
              IMAGE_IMPORT_BY_NAME* pIBN = (IMAGE_IMPORT_BY_NAME*)(BaseAddress + pThunkOrg->AddressOfData); 

              if (!Interop.IsBadReadPtr((void*)pIBN, (uint)sizeof(IMAGE_IMPORT_BY_NAME))) 
              { 
               Ord = pIBN->Hint; 
               szImportName = (char*)pIBN->Name; 
               string sImportName = Marshal.PtrToStringAnsi((IntPtr)szImportName); // yes i know i am a lazy ass 
               Console.WriteLine("imports ({0}).{1}@{2} - Address: {3}", name, sImportName, Ord, pThunkOrg->Function); 
              } 
              else 
              { 
               Console.WriteLine("Bad ReadPtr Detected or EOF on Imports"); 
               break; 
              } 
             } 

             pThunkOrg++; 
            } 
           } 
           catch (AccessViolationException e) 
           { 
            Console.WriteLine("An Access violation occured\n" + 
                 "this seems to suggest the end of the imports section\n"); 
            Console.WriteLine(e); 
           } 

           pIID++; 
          } 

         } 

        } 
       } 
      } 

      Console.WriteLine("Press Any Key To Continue......"); 
      Console.ReadKey(); 
     } 
    } 
} 
+0

большое спасибо! – Eric

+0

Указатели C++ внутри структур должны стать .NET 'IntPtr', а не UInt32 (C#' uint'). –

1

Из отладчика вы можете увидеть, что этот цикл, пока никогда не вводится (для gdi32.dll & wsock32.dll):

while (!Interop.IsBadReadPtr((void*)pIID->OriginalFirstThunk, (uint)size)) 

настоятельно советовали не использовать IsBadReadPtr, так как вы не можете всегда полагаться на это возвращаемое значение. см.: http://msdn.microsoft.com/en-us/library/aa366713.aspx или http://blogs.msdn.com/b/oldnewthing/archive/2006/09/27/773741.aspx

Другой подход для обработки validaty указателя - использование структурированной обработки исключений. Попытайтесь получить доступ к адресу памяти, обработайте все Исключения Нарушения доступа.

Wether это хорошая практика или нет, это другое обсуждение.

может быть полезно:

http://www.codeproject.com/Messages/2626152/Replacement-for-IsBadReadPtr-in-Windows-Vista.aspx

http://www.softwareverify.com/software-verify-blog/?p=319

2

Опираясь на поправки шута к первоначальному образцу, вот это класс, который читает как Импортирует и ЭКСПоРТ. Я успешно использовал его в 32-разрядных библиотеках DLL, но пока не знаю о 64-битных.

using System; 
using System.Collections.Generic; 
using System.Runtime.InteropServices; 
using System.Security; 

/* 
Inspirations: 
* http://www.bearcanyon.com/dotnet/#AssemblyParser (Mike Woodring's "Parsing PE File Headers to Determine if a DLL or EXE is an Assembly") 
* http://stackoverflow.com/questions/1563134/how-do-i-read-the-pe-header-of-a-module-loaded-in-memory ("How do I read the PE header of a module loaded in memory?") 
* http://stackoverflow.com/questions/2975639/resolving-rvas-for-import-and-export-tables-within-a-pe-file ("Resolving RVA's for Import and Export tables within a PE file.") 
* http://www.lenholgate.com/blog/2006/04/i-love-it-when-a-plan-comes-together.html 
* http://www.gamedev.net/community/forums/topic.asp?topic_id=409936 
* http://stackoverflow.com/questions/4571088/how-to-programatically-read-native-dll-imports-in-c 
*/ 

namespace PE 
{  
    public unsafe class PortableExecutableParser 
    { 
     public delegate void DLog(string fmt, params object[] args); 
     private readonly DLog _fnLog; 
     private void Log(string fmt, params object[] args) 
     { 
      if (_fnLog != null) 
       _fnLog(fmt, args); 
     } 

     private readonly List<string> _exports = new List<string>(); 
     public IEnumerable<string> Exports { get { return _exports; } } 

     private readonly List<Tuple<string, List<string>>> _imports = new List<Tuple<string, List<string>>>(); 
     public IEnumerable<Tuple<string, List<string>>> Imports { get { return _imports; } } 


     public PortableExecutableParser(string path, DLog fnLog=null) 
     { 
      _fnLog = fnLog; 
      LOADED_IMAGE loadedImage; 

      if (MapAndLoad(path, null, out loadedImage, true, true)) 
      { 
       LoadExports(loadedImage); 
       LoadImports(loadedImage); 
      } 
     } 

     private void LoadExports(LOADED_IMAGE loadedImage) 
     { 
      var hMod = (void*)loadedImage.MappedAddress; 

      if (hMod != null) 
      { 
       Log("Got handle"); 

       uint size; 
       var pExportDir = (IMAGE_EXPORT_DIRECTORY*)ImageDirectoryEntryToData(
        (void*)loadedImage.MappedAddress, 
        false, 
        IMAGE_DIRECTORY_ENTRY_EXPORT, 
        out size); 

       if (pExportDir != null) 
       { 
        Log("Got Image Export Descriptor"); 

        var pFuncNames = (uint*)RvaToVa(loadedImage, pExportDir->AddressOfNames); 

        for (uint i = 0; i < pExportDir->NumberOfNames; i++) 
        { 
         uint funcNameRva = pFuncNames[i]; 
         if (funcNameRva != 0) 
         { 
          var funcName = 
           (char*)RvaToVa(loadedImage, funcNameRva); 
          var name = Marshal.PtrToStringAnsi((IntPtr)funcName); 
          Log(" funcName: {0}", name); 
          _exports.Add(name); 
         } 

        } 

       } 

      } 

     } 

     private static IntPtr RvaToVa(LOADED_IMAGE loadedImage, uint rva) 
     { 
      return ImageRvaToVa(loadedImage.FileHeader, loadedImage.MappedAddress, rva, IntPtr.Zero); 
     } 
     private static IntPtr RvaToVa(LOADED_IMAGE loadedImage, IntPtr rva) 
     { 
      return RvaToVa(loadedImage, (uint)(rva.ToInt32())); 
     } 



     private void LoadImports(LOADED_IMAGE loadedImage) 
     { 
      var hMod = (void*)loadedImage.MappedAddress; 

      if (hMod != null) 
      { 
       Console.WriteLine("Got handle"); 

       uint size; 
       var pImportDir = 
        (IMAGE_IMPORT_DESCRIPTOR*) 
        ImageDirectoryEntryToData(hMod, false, 
                 IMAGE_DIRECTORY_ENTRY_IMPORT, out size); 
       if (pImportDir != null) 
       { 
        Log("Got Image Import Descriptor"); 
        while (pImportDir->OriginalFirstThunk != 0) 
        { 
         try 
         { 
          var szName = (char*) RvaToVa(loadedImage, pImportDir->Name); 
          string name = Marshal.PtrToStringAnsi((IntPtr) szName); 

          var pr = new Tuple<string, List<string>>(name, new List<string>()); 
          _imports.Add(pr); 


          var pThunkOrg = (THUNK_DATA*)RvaToVa(loadedImage, pImportDir->OriginalFirstThunk); 

          while (pThunkOrg->AddressOfData != IntPtr.Zero) 
          { 
           uint ord; 

           if ((pThunkOrg->Ordinal & 0x80000000) > 0) 
           { 
            ord = pThunkOrg->Ordinal & 0xffff; 
            Log("imports ({0}).Ordinal{1} - Address: {2}", name, ord, 
                 pThunkOrg->Function); 
           } 
           else 
           { 
            var pImageByName = 
             (IMAGE_IMPORT_BY_NAME*) RvaToVa(loadedImage, pThunkOrg->AddressOfData); 

            if (
             !IsBadReadPtr(pImageByName, (uint) sizeof (IMAGE_IMPORT_BY_NAME))) 
            { 
             ord = pImageByName->Hint; 
             var szImportName = pImageByName->Name; 
             string sImportName = Marshal.PtrToStringAnsi((IntPtr) szImportName); 
             Log("imports ({0}).{1}@{2} - Address: {3}", name, 
                  sImportName, ord, pThunkOrg->Function); 

             pr.Item2.Add(sImportName); 
            } 
            else 
            { 
             Log("Bad ReadPtr Detected or EOF on Imports"); 
             break; 
            } 
           } 

           pThunkOrg++; 
          } 
         } 
         catch (AccessViolationException e) 
         { 
          Log("An Access violation occured\n" + 
               "this seems to suggest the end of the imports section\n"); 
          Log(e.ToString()); 
         } 

         pImportDir++; 
        } 

       } 

      } 
     } 


// ReSharper disable InconsistentNaming 
     private const ushort IMAGE_DIRECTORY_ENTRY_IMPORT = 1; 
     private const ushort IMAGE_DIRECTORY_ENTRY_EXPORT = 0; 

     private const CallingConvention WINAPI = CallingConvention.Winapi; 

     private const string KERNEL_DLL = "kernel32"; 
     private const string DBGHELP_DLL = "Dbghelp"; 
     private const string IMAGEHLP_DLL = "ImageHlp"; 
// ReSharper restore InconsistentNaming 

     [DllImport(KERNEL_DLL, CallingConvention = WINAPI, EntryPoint = "GetModuleHandleA"), SuppressUnmanagedCodeSecurity] 
     public static extern void* GetModuleHandleA(/*IN*/ char* lpModuleName); 

     [DllImport(KERNEL_DLL, CallingConvention = WINAPI, EntryPoint = "GetModuleHandleW"), SuppressUnmanagedCodeSecurity] 
     public static extern void* GetModuleHandleW(/*IN*/ char* lpModuleName); 

     [DllImport(KERNEL_DLL, CallingConvention = WINAPI, EntryPoint = "IsBadReadPtr"), SuppressUnmanagedCodeSecurity] 
     public static extern bool IsBadReadPtr(void* lpBase, uint ucb); 

     [DllImport(DBGHELP_DLL, CallingConvention = WINAPI, EntryPoint = "ImageDirectoryEntryToData"), SuppressUnmanagedCodeSecurity] 
     public static extern void* ImageDirectoryEntryToData(void* pBase, bool mappedAsImage, ushort directoryEntry, out uint size); 

     [DllImport(DBGHELP_DLL, CallingConvention = WINAPI), SuppressUnmanagedCodeSecurity] 
     public static extern IntPtr ImageRvaToVa(
      IntPtr pNtHeaders, 
      IntPtr pBase, 
      uint rva, 
      IntPtr pLastRvaSection); 

     [DllImport(DBGHELP_DLL, CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurity] 
     public static extern IntPtr ImageNtHeader(IntPtr pImageBase); 

     [DllImport(IMAGEHLP_DLL, CallingConvention = CallingConvention.Winapi), SuppressUnmanagedCodeSecurity] 
     public static extern bool MapAndLoad(string imageName, string dllPath, out LOADED_IMAGE loadedImage, bool dotDll, bool readOnly); 

    } 


// ReSharper disable InconsistentNaming 
    [StructLayout(LayoutKind.Sequential)] 
    public struct LOADED_IMAGE 
    { 
     public IntPtr moduleName; 
     public IntPtr hFile; 
     public IntPtr MappedAddress; 
     public IntPtr FileHeader; 
     public IntPtr lastRvaSection; 
     public UInt32 numbOfSections; 
     public IntPtr firstRvaSection; 
     public UInt32 charachteristics; 
     public ushort systemImage; 
     public ushort dosImage; 
     public ushort readOnly; 
     public ushort version; 
     public IntPtr links_1; // these two comprise the LIST_ENTRY 
     public IntPtr links_2; 
     public UInt32 sizeOfImage; 
    } 



    [StructLayout(LayoutKind.Explicit)] 
    public unsafe struct IMAGE_IMPORT_BY_NAME 
    { 
     [FieldOffset(0)] 
     public ushort Hint; 
     [FieldOffset(2)] 
     public fixed char Name[1]; 
    } 

    [StructLayout(LayoutKind.Explicit)] 
    public struct IMAGE_IMPORT_DESCRIPTOR 
    { 
     #region union 
     /// <summary> 
     /// CSharp doesnt really support unions, but they can be emulated by a field offset 0 
     /// </summary> 

     [FieldOffset(0)] 
     public uint Characteristics;   // 0 for terminating null import descriptor 
     [FieldOffset(0)] 
     public uint OriginalFirstThunk;   // RVA to original unbound IAT (PIMAGE_THUNK_DATA) 
     #endregion 

     [FieldOffset(4)] 
     public uint TimeDateStamp; 
     [FieldOffset(8)] 
     public uint ForwarderChain; 
     [FieldOffset(12)] 
     public uint Name; 
     [FieldOffset(16)] 
     public uint FirstThunk; 
    } 

    [StructLayout(LayoutKind.Sequential)] 
    public struct IMAGE_EXPORT_DIRECTORY 
    { 
     public UInt32 Characteristics; 
     public UInt32 TimeDateStamp; 
     public UInt16 MajorVersion; 
     public UInt16 MinorVersion; 
     public UInt32 Name; 
     public UInt32 Base; 
     public UInt32 NumberOfFunctions; 
     public UInt32 NumberOfNames; 
     public IntPtr AddressOfFunctions;  // RVA from base of image 
     public IntPtr AddressOfNames;  // RVA from base of image 
     public IntPtr AddressOfNameOrdinals; // RVA from base of image 
    } 

    [StructLayout(LayoutKind.Explicit)] 
    public struct THUNK_DATA 
    { 
     [FieldOffset(0)] 
     public uint ForwarderString;  // PBYTE 
     [FieldOffset(0)] 
     public uint Function;    // PDWORD 
     [FieldOffset(0)] 
     public uint Ordinal; 
     [FieldOffset(0)] 
     public IntPtr AddressOfData;  // PIMAGE_IMPORT_BY_NAME 
    } 
// ReSharper restore InconsistentNaming 
} 
+1

Не работает на x64. – Maxence

+2

См. Ответ от @PiranhA - он содержит необходимые изменения, чтобы заставить этот код работать – Alexander

2

Чтобы Eric's answer работу на x64 вы должны изменить тип данных RVA адреса (AddressOfFunctions, AddressOfNames и AddressOfNameOrdinals) в IMAGE_EXPORT_DIRECTORY. Это всегда 32 бит (UInt32). IntPtr 32 бит на x86 и 64 бит на x64.

См http://pinvoke.net/default.aspx/Structures/IMAGE_EXPORT_DIRECTORY.html

[StructLayout(LayoutKind.Sequential)] 
public struct IMAGE_EXPORT_DIRECTORY 
{ 
    public UInt32 Characteristics; 
    public UInt32 TimeDateStamp; 
    public UInt16 MajorVersion; 
    public UInt16 MinorVersion; 
    public UInt32 Name; 
    public UInt32 Base; 
    public UInt32 NumberOfFunctions; 
    public UInt32 NumberOfNames; 
    public UInt32 AddressOfFunctions;  // RVA from base of image 
    public UInt32 AddressOfNames;  // RVA from base of image 
    public UInt32 AddressOfNameOrdinals; // RVA from base of image 
} 
0

Расширение на Eric's answer (и спасибо всем, кто способствовал код здесь) ... Этот класс, когда объединилась с 64 исправлений, по-видимому, правильно работать, если целями проекта .NET хозяевах x86.Он успешно считывает как 32, так и 64-разрядные исполняемые файлы.

Однако возникают две проблемы, если для хост-проекта задано значение x64.

  1. RvaToVA падает на несколько файлов (например, тест-ридер случае Adobe, указанных ниже), на линии

    возвращение RvaToVa (loadedImage (UINT) (rva.ToInt32());

    С переполнением. Изменение этого параметра на .ToInt64 разрешает переполнение, но затем

  2. Он, как представляется, создает неполный список импорта при тестировании на основе базовой линии Acrobat Reader 10 MUI (версия acrord32.exe 10.0.0.396). 32-разрядный исполняемый файл.

    Dumpbin сообщает следующие 9 импорта для Shell32.dll

    SHGetFolderPathW, 
    ShellExecuteExW, 
    CommandLineToArgvW, 
    ShellExecuteW, 
    SHCreateDirectoryExW, 
    SHGetFileInfoW, 
    SHGetPathFromIDListW, 
    FindExecutableW 
    SHBrowseForFolderW 
    

    Однако код при запуске в режиме x64 находит только 4 из этих

    SHGetFolderPathW, 
    ShellExecuteW, 
    SHGetFileInfoW, 
    FindExecutableW 
    

    И в этой точке pThunk-> порядковых возвращается 0, в результате чего петля заканчивается

У меня на этом этапе не было возможности выполнить некоторую отладку, чтобы попытаться выучите, что происходит, хотя это может быть размер чего-то, изменяющегося в x64. Между тем просто знайте, что это работает нормально, пока хост нацеливается на x86 (что достаточно для моих нужд). Если я нахожу основную причину, я дам вам знать.

0

Используйте PeNet Library. Он может анализировать PE-заголовок и записываться на C#. Вы можете легко использовать его, установив пакет NuGet. (Отказ от ответственности: я, автор библиотеки)