2

ファイルシステムを介して動作する LoadLibrary API 関数を使用する代わりに、メモリ/ストリームからライブラリをロードできるようにする、このアプリケーションの少しの C# 移植に気付きました。ポインターを少しいじり、結果を一致させた後...最終的に、意図したとおりに動作するようになりました。私が抱えている唯一の問題は、DLLMain への呼び出しが常に失敗することです (Kernel32.dll と User32.dll で試しました)。理由がわかりません。問題をデバッグする方法もわかりません。

これは、ライブラリを読み取り、メモリに割り当て、手動でロードする私のプロジェクト (単純な 32 ビット コンソール アプリケーション) の主な機能です。

public static UInt32 Load(String libraryName)
{
    if (libraries.ContainsKey(libraryName))
        return libraries[libraryName];

    String libraryPath = Path.Combine(Environment.SystemDirectory, libraryName);
    Byte[] libraryBytes = File.ReadAllBytes(libraryPath);

    fixed (Byte* libraryPointer = libraryBytes)
    {
        HeaderDOS* headerDOS = (HeaderDOS*)libraryPointer;

        if ((UInt16)((headerDOS->Magic << 8) | (headerDOS->Magic >> 8)) != IMAGE_DOS_SIGNATURE)
            return 0;

        HeadersNT* headerNT = (HeadersNT*)(libraryPointer + headerDOS->LFANEW);

        UInt32 addressLibrary = VirtualAlloc(headerNT->OptionalHeader.ImageBase, headerNT->OptionalHeader.SizeOfImage, AllocationType.RESERVE, MemoryProtection.READWRITE);

        if (addressLibrary == 0)
            addressLibrary = VirtualAlloc(0, headerNT->OptionalHeader.SizeOfImage, AllocationType.RESERVE, MemoryProtection.READWRITE);

        if (addressLibrary == 0)
            return 0;

        Library* library = (Library*)Marshal.AllocHGlobal(sizeof(Library));
        library->Address = (Byte*)addressLibrary;
        library->ModulesCount = 0;
        library->Modules = null;
        library->Initialized = false;

        VirtualAlloc(addressLibrary, headerNT->OptionalHeader.SizeOfImage, AllocationType.COMMIT, MemoryProtection.READWRITE);

        UInt32 addressHeaders = VirtualAlloc(addressLibrary, headerNT->OptionalHeader.SizeOfHeaders, AllocationType.COMMIT, MemoryProtection.READWRITE);

        MemoryCopy((Byte*)headerDOS, (Byte*)addressHeaders, (headerDOS->LFANEW + headerNT->OptionalHeader.SizeOfHeaders));

        library->Headers = (HeadersNT*)((Byte*)addressHeaders + headerDOS->LFANEW);
        library->Headers->OptionalHeader.ImageBase = addressLibrary;

        CopySections(library, headerNT, libraryPointer);

        UInt32 locationDelta = addressLibrary - headerNT->OptionalHeader.ImageBase;

        if (locationDelta != 0)
            PerformBaseRelocation(library, locationDelta);

        UInt32 libraryHandle = (UInt32)library;

        if (!BuildImportTable(library))
        {
            Free(libraryName);
            return 0;
        }

        FinalizeSections(library);

        if (library->Headers->OptionalHeader.AddressOfEntryPoint == 0)
        {
            Free(libraryName);
            return 0;
        }

        UInt32 libraryEntryPoint = addressLibrary + library->Headers->OptionalHeader.AddressOfEntryPoint;

        if (libraryEntryPoint == 0)
        {
            Free(libraryName);
            return 0;
        }

        LibraryMain main = (LibraryMain)Marshal.GetDelegateForFunctionPointer(new IntPtr(libraryEntryPoint), typeof(LibraryMain));
        UInt32 result = main(addressLibrary, DLL_PROCESS_ATTACH, 0);

        if (result == 0)
        {
            Free(libraryName);
            return 0;
        }

        library->Initialized = true;

        libraries[libraryName] = libraryHandle;

        return libraryHandle;
    }
}

そして、これを使用する方法の例を次に示します。

private const Byte VK_Z_BREAK = 0x5A;

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void KeyboardEventDelegate(Byte key, Byte scan, KeyboardFlags flags, Int32 extra);

[Flags]
private enum KeyboardFlags : uint
{
    EXTENDEDKEY = 0x0001,
    KEYUP = 0x0002,
}

public static void Main()
{
    UInt32 libraryHandle = LibraryLoader.Load("User32.dll");

    if (libraryHandle != 0)
    {
        UInt32 functionHandle = LibraryLoader.GetFunctionAddress("User32.dll", "keybd_event");

        if (functionHandle != 0)
        {
            KeyboardEventDelegate s_KeyboardEvent = (KeyboardEventDelegate)Marshal.GetDelegateForFunctionPointer(new IntPtr(functionHandle), typeof(KeyboardEventDelegate));

            while (true)
            {
                s_KeyboardEvent(VK_Z_BREAK, VK_Z_SCANCODE, 0, 0);
                s_KeyboardEvent(VK_Z_BREAK, VK_Z_SCANCODE, KeyboardFlags.KEYUP, 0);
            }
        }
    }

    Console.ReadLine();
}

すぐに試してみたい場合は、このリンクからプロジェクトをダウンロードできます。

[編集] DllMain を呼び出した直後に Marshal.GetLastWin32Error() を使用して数回試行した後、ERROR_OUTOFMEMORY に対応するエラー コード 14 が生成されていることがわかりました。DllMain の呼び出しが失敗した後に続行し、ライブラリ関数のアドレスを取得した場合、デリゲートを使用してそれを呼び出そうとすると、PInvokeStackImbalance 例外が生成されます。これについての手がかりはありますか?^_^

4

1 に答える 1

9

このコードは、Windows ローダーが DLL をロードするために何を行うかの一次近似にすぎません。最も単純な DLL に対してのみ機能します。C から C# コードへの変換も、対処しているスタックの不均衡の問題のような問題を引き起こす可能性が非常に高くなります。私が見る主な問題:

  • DLL が以前にロードされていないことを確認することは何もしません。これは、kernel32.dll と user32.dll を読み込もうとすると、問題の原因になることがほぼ確実です。これらの DLL は、マネージ コードの実行が開始される前に既に読み込まれています。彼らは再びロードされることを親切に受け入れません。

  • 依存する DLL も読み込まれること、およびそれらの DllMain() エントリポイントが正しい順序で呼び出され、厳密にシリアル化されることを保証するために、明らかなことは何もしません。

  • マネージ コード ローダーのスタブである MSCoree.dll を適切に処理することはありません。そのため、混合モード コードを含む DLL を適切にロードできる可能性はほとんどありません。

  • Windows ローダーがこれらのモジュールを認識していることを確認することは何もしないため、DLL に対する後続の要求が失敗する可能性が非常に高くなります。このような失敗はまったく診断できません。

これらの問題に正しく対処できる可能性は非常に低いです。

于 2013-01-06T16:07:26.757 に答える