2

ネイティブ DLL をプログラムで分析してそのインポートを読み取るにはどうすればよいですか?


[編集: 私の元の質問は、欠陥のあるコードの巨大なチャンクとともに、次のように見えました。より正しいコードについては、以下の回答を参照してください。]

このリンクにある C# コードは、ネイティブ DLL のインポートを出力するためのものです。

元の例のターゲットである MSCOREE.DLL を使用してサンプル コードを実行すると、すべてのインポートが正常に出力されることがわかりました。しかし、GDI32.DLL や WSOCK32.DLL などの他の dll を使用すると、インポートが印刷されません。たとえば DUMPBIN.EXE が行うように、すべてのインポートを出力できるこのコードには何が欠けているのでしょうか?

4

6 に答える 6

4

コードには非常に大きな問題が 1 つあります (つまり、 の定義THUNK_DATA)、その他のさまざまな小さな問題は、主にテーブルの終わりの検出に関するものです ( IsBadReadPtrNULL チェックの代わりに使用し、必要に応じてベース アドレスを追加しない)。

以下は、少なくともwsock32に対してdumpbinと同じ出力を生成する修正バージョンです。

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();
        }
    }
}
于 2010-12-31T18:29:14.113 に答える
2

元のサンプルに対する 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
}
于 2011-01-14T22:58:06.770 に答える
2

エリックの回答を x64 で機能させるには、IMAGE_EXPORT_DIRECTORY で RVA アドレス (AddressOfFunctions、AddressOfNames、および AddressOfNameOrdinals) のデータ型を変更しました。これらは常に 32 ビット長 (UInt32) です。IntPtr は、x86 では 32 ビット、x64 では 64 ビットです。

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
}
于 2015-12-16T09:24:06.523 に答える
1

デバッガーから、この while ループに入らないことがわかります (gdi32.dll & wsock32.dll の場合):

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

IsBadReadPtr の戻り値に常に依存できるとは限らないため、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

于 2010-12-31T17:55:07.290 に答える
1

PeNet ライブラリを使用します。PE ヘッダーを解析でき、C# で記述されています。NuGet パッケージをインストールすることで簡単に使用できます。(免責事項: 私はライブラリの作成者です)

于 2016-11-21T16:19:03.580 に答える
0

エリックの答えを拡張する(そして、ここでコードを提供してくれたすべての人に感謝します) ....NET ホスト プロジェクトが x86 をターゲットにしている場合、x64 の修正とマージすると、そのクラスは正しく動作するように見えます。32 ビットと 64 ビットの両方の実行可能ファイルを正常に読み取ります。

ただし、ホスト プロジェクトがターゲット x64 に設定されている場合、2 つの問題が発生します。

  1. RvaToVA が一部のファイル (たとえば、下記の Adob​​e Reader テスト ケース) でクラッシュします。

    return RvaToVa(loadedImage, (uint)(rva.ToInt32());

    オーバーフロー付き。これを .ToInt64 に変更するとオーバーフローは解決しますが、

  2. Acrobat Reader 10 MUI ベースライン (acrord32.exe バージョン 10.0.0.396) に対してテストすると、インポートの不完全なリストが生成されるようです。これは 32 ビットの実行可能ファイルです。

    Dumpbin は、Shell32.dll の次の 9 つのインポートを報告します。

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

    ただし、x64 モードで実行した場合、コードはこれらのうち 4 つしか検出しません。

    SHGetFolderPathW,
    ShellExecuteW,
    SHGetFileInfoW,
    FindExecutableW
    

    そして、この時点で pThunk->Ordinal は 0 を返し、ループを終了させます

この段階では、何が起こっているのかを把握するためにデバッグを行う機会がありませんでしたが、x64 で何かが変更されたようなサイズになる可能性があります。一方、ホストが x86 をターゲットにしている限り、これは問題なく動作するように見えることに注意してください (これで十分です)。根本的な理由を見つけたら、皆さんにお知らせします。

于 2016-04-05T13:06:25.957 に答える