【问题标题】:Load Library/Module from Memory从内存中加载库/模块
【发布时间】:2012-12-18 23:48:42
【问题描述】:

我实现了this application 的一点 C# 移植,它允许从内存/流加载库,而不是使用通过文件系统工作的 LoadLibrary API 函数。 在用指针和不匹配的结果弄乱了一点之后......最后我得到了一些按预期工作的东西。我唯一的问题是对 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();
}

如果你想快速尝试,可以从this link下载项目。

[编辑] 几次尝试后,在调用 DllMain 后使用 Marshal.GetLastWin32Error(),我发现正在生成错误代码 14,它对应于 ERROR_OUTOFMEMORY。如果在 DllMain 调用失败后继续,并且我获得了库函数的地址,则尝试使用委托调用它会产生 PInvokeStackImbalance 异常。有什么线索吗? ^_^

【问题讨论】:

  • 不完全移植它而是在原始项目之上提供一个小的 C++/CLI 层不是更容易(也用于维护)吗?
  • 不要在此处发布所有代码。如果您将所有逻辑都放在一个方法中,请仅发布相关部分。然后我建议重构它。我没有时间如果超过 10-15 行,请查看您的任何代码..
  • 你的 DLLMain 做了什么,它返回了什么结果?它是否被调用(例如,你可以在 DLLMain 中执行 OutoutDebugString 并查看输出)吗?
  • 根据我的线程编辑,使用委托“main(addressLibrary, DLL_PROCESS_ATTACH, 0)”调用 DllMain,结果为 0、NULL 或您想要的任何值。使用 DLL_PROCESS_ATTACH 的调用失败,该代码行之后的最后一个 Win32 错误是 14 (ERROR_OUTOFMEMORY)。
  • C++/CLI 是你想要的。另外,你为什么要从内存中加载kernel32?太疯狂了。它被加载到每个进程中。我敢打赌,你的进程也有加载器加载的user32。尝试使用您自己制作的简单 DLL。

标签: c# memory dll dllimport unmanaged


【解决方案1】:

此代码只是 Windows 加载程序加载 DLL 的一阶近似值。它只适用于最简单的 DLL,从 C 代码到 C# 代码的转换也很可能导致您正在处理的堆栈不平衡问题等问题。我看到的主要问题:

  • 它不会做任何事情来确保之前没有加载过 DLL。当您尝试加载 kernel32.dll 和 user32.dll 时,这几乎肯定会成为麻烦的根源,这些 DLL 在托管代码开始执行之前已经加载。他们不会乐意再次加载。

  • 确保相关的 DLL 也被加载并且它们的 DllMain() 入口点以正确的顺序被调用并严格序列化并没有做任何明显的事情。

  • 它没有做任何事情来正确处理托管代码加载器存根 MSCoree.dll,这使得您几乎不可能正确加载任何包含混合模式代码的 DLL。

    李>
  • 它不会做任何事情来确保 Windows 加载程序知道这些模块,这使得任何后续对 DLL 的请求很可能会失败。这种故障是完全无法诊断的。

您能够正确解决这些问题的可能性非常低。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-10-03
    • 2017-01-01
    • 2013-07-09
    • 2019-04-25
    • 2013-09-07
    • 2012-08-03
    相关资源
    最近更新 更多