Как программно читать собственный импорт DLL в С#?

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


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

Код C# находится по этой ссылке предназначен для печати импорта собственной DLL.

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


person Eric    schedule 31.12.2010    source источник
comment
Вы должно быть шутите. Используйте Dumpbin.exe /exports   -  person Hans Passant    schedule 31.12.2010
comment
@Hans - код в конечном итоге будет использоваться в инструменте анализа зависимостей. Консольная версия здесь просто разминка. На самом деле у меня есть рабочая версия настоящего приложения, которое вызывает DUMPBIN /imports, а затем анализирует вывод, но я бы предпочел этого не делать.   -  person Eric    schedule 31.12.2010
comment
+1 Ганс. Но если вам нужно сделать это программно, разве не для этого нужна dbghelp.dll?   -  person David Heffernan    schedule 31.12.2010
comment
@David - функция ImageDirectoryEntryToData взята из DbgHelp. Я не увидел там ничего, чтобы копаться глубже в таблицах импорта и экспорта, вы знаете что-нибудь?   -  person Eric    schedule 31.12.2010


Ответы (6)


В коде есть одна очень большая проблема (а именно определение 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();
        }
    }
}
person Jester    schedule 31.12.2010
comment
Указатели C++ внутри структур должны стать .NET IntPtr, а не UInt32 (C# uint). - person Ben Voigt; 01.01.2011

Основываясь на исправлениях Jester к исходному образцу, вот класс, который считывает как IMPORT, так и EXPORT. Я успешно использовал его на 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
}
person Eric    schedule 14.01.2011
comment
Пожалуйста, смотрите ответ от @PiranhA - он содержит необходимые изменения, чтобы этот код работал. - person Alexander; 16.12.2015


В отладчике видно, что этот цикл while никогда не запускается (для 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

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

Является ли это хорошей практикой или нет, это другой разговор.

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

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

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

person Steven K.    schedule 31.12.2010

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

person secana    schedule 21.11.2016

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

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

  1. RvaToVA аварийно завершает работу с некоторыми файлами (например, тестовый пример Adobe Reader, упомянутый ниже), в строке

    вернуть RvaToVa (loadedImage, (uint) (rva.ToInt32());

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

  2. Похоже, что он создает неполный список импортируемых данных при тестировании на основе Acrobat Reader 10 MUI (версия acrod32.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->Ordinal возвращает 0, что приводит к завершению цикла.

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

person Andy    schedule 05.04.2016