从内存加载库/模块

本文关键字:模块 加载 内存 | 更新日期: 2023-09-27 17:50:50

我实现了这个应用程序的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();
}

如果你想快速尝试一下,你可以从这个链接下载项目。

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

从内存加载库/模块

这段代码只是Windows加载器加载DLL的一阶近似。它只能用于最简单的dll,从C到c#代码的转换也极有可能导致麻烦,比如您正在处理的堆栈不平衡问题。我看到的主要问题是:

  • 它不做任何事情来确保DLL之前没有被加载。当您尝试加载kernel32.dll和user32.dll时,这几乎肯定是一个麻烦的来源,这些dll在托管代码开始执行之前已经加载了。

  • 它没有做任何明显的事情来确保依赖的dll也被加载,并且它们的dlmain()入口点被以正确的顺序调用并严格序列化。

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

  • 它不做任何事情来确保Windows加载器知道这些模块,这使得后续对DLL的任何请求都很可能失败。

你能正确解决这些问题的可能性是相当低的。