从 DLL(`atexit` 或 `msvcrt!__dllonexit`)创建退出处理程序

问题描述 投票:0回答:1

我正在尝试创建一种机制来解决加载程序锁,这样我就可以运行任意代码,只需要控制

DllMain()
.

我取得了一些成功,请看这里:

演示应用加载库:

LoadLibraryW(L"test.dll");

动态链接库:

#include <Windows.h>

void func() {
    // Spawn calc.exe (can't do this from DllMain but we can here)
    STARTUPINFO info = { sizeof(info) };
    PROCESS_INFORMATION processInfo;
    wchar_t app[5] = { L'c', L'a', L'l', L'c', 0 };
    CreateProcess(NULL, app, NULL, NULL, TRUE, 0, NULL, NULL, &info, &processInfo);

    // We can even load libraries from here so loader lock does appear to be gone

    // MessageBox... this crashes in msvcrt!__dllonexit (why!?)
    // Pretty deep into the call stack to creating the message box it happens (there's only one thread)
    MessageBoxW(NULL, L"hi", L"hello", 0);
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        atexit(func);
        break;
    }
    return TRUE;
}

拜托,你能帮帮我吗,为什么我在创建消息框时会收到那个错误?似乎有一个竞争

msvcrt!__dllonexit
,但我不确定如何使用它(代码)或我应该做什么。

Microsoft 的文档说在 DLL 中调用

atexit()
应该可以,但根据我的测试,它对某些事情有效,对其他事情无效:

https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/onexit-onexit-m?view=msvc-170

https://learn.microsoft.com/en-us/cpp/c-runtime-library/dllonexit?view=msvc-170

c winapi dll msvcrt dll-injection
1个回答
0
投票

Raymond Chen 是正确的,您不能调用

atexit
来从 DLL 中解决加载程序锁定问题。事实证明,
CreateProcess
仅在加载程序锁定下工作,因为我的进程已经加载了所有必要的 DLL 以调用
CreateProcess
。另外,我不确定
LoadLibrary
是否真的加载过任何东西,因为我可能一直在加载 DLL,而这些 DLL 已经加载到我的目标进程中。

幸运的是,我能够使用我编写的以下代码通过另一种方法解决加载程序锁定问题。

此代码适用于静态或动态加载的 DLL,并且相对简单。不过,它确实将整个 EXE 变成了 NOP sled 作为其操作的一部分(在执行我们想要的操作后保持进程的正常功能不是我的目标之一)。

我验证了加载器锁随着

!critsec ntdll!LdrpLoaderLock
消失了(感谢这篇文章:https://devblogs.microsoft.com/oldnewthing/20140808-00/?p=293)。出于某种原因,我找不到
!locks -v
下的那个锁,它早先会有所帮助(因为它是我在其他地方读到的内核锁?)。

评论非常详细地描述了代码的作用,所以我不会进一步扩展它:

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <shellapi.h>

// If you have any DLL exports to declare then do so here
EXTERN_C __declspec(dllexport) BOOL WINAPI ExampleExport(HINSTANCE hinstDll, DWORD fwdReason, LPVOID lpvReserved) { return TRUE; };

void func(void) {
    // Verify loader lock is gone in WinDbg: !critsec ntdll!LdrpLoaderLock
    //__debugbreak();

    // These work!
    MessageBox(NULL, L"hi", L"hello", 0);
    ShellExecute(NULL, L"open", L"calc", NULL, NULL, SW_SHOW);

    // The program doesn't crash if we don't exit but it does hang forever
    ExitProcess(0);

    // Returning from this function causes: ntdll!RtlExitUserThread -> ntdll!NtTerminateThread
}

BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fwdReason, LPVOID lpvReserved)
{
    switch (fwdReason)
    {
    case DLL_PROCESS_ATTACH:
        HMODULE exeHandle = GetModuleHandle(NULL);

        // This assumes the PE header page is 0x1000 and that the code page is right after it which will be true for 99.9% of executables but there's technically no guarantee
        PBYTE codePage = (PBYTE)exeHandle + 0x1000;

        // Get size of code page
        MEMORY_BASIC_INFORMATION mbi;
        VirtualQuery(codePage, &mbi, sizeof(mbi));

        DWORD dwOldProtect = NULL;
        VirtualProtect(codePage, mbi.RegionSize, PAGE_READWRITE, &dwOldProtect);

        // Fill code page with NOP instructions
        RtlFillMemory(codePage, mbi.RegionSize, 0x90);

        // Assemble these instructions with rasm2
        BYTE jmpAssembly[12] = {
            0x48, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rax, <placeholder address>
            0xff, 0xe0 // jmp rax
        };

        // Place JMP gadget at end of code page
        RtlCopyMemory(codePage + mbi.RegionSize - sizeof(jmpAssembly), jmpAssembly, sizeof(jmpAssembly));
        DWORD_PTR* assemblyJmpDestinationAddr = (DWORD_PTR*)(codePage + mbi.RegionSize - sizeof(jmpAssembly) + 2);
        *assemblyJmpDestinationAddr = (DWORD_PTR)func; // Set JMP destination address

        VirtualProtect(codePage, mbi.RegionSize, dwOldProtect, &dwOldProtect);
    }

    return TRUE;
}

我要补充一点,发生的一件奇怪的事情是,如果您将

DLL_PROCESS_ATTACH
换成
DLL_THREAD_ATTACH
DLL_THREAD_DETACH
那么程序将在
SHELL32!_tailMerge_shcore_dll
换成
ShellExecute
USER32!GetMessageBoxFontForDpi
换成
MessageBox
并且两次都是通过堆栈上的指针进行的无效写入(如
movaps xmmword ptr [rsp+30h], xmm0
)。但是,在检查崩溃现场是否存在装载机锁 (
!critsec ntdll!LdrpLoaderLock
) 时,它会显示
NOT LOCKED
表明在这种情况下还有其他东西在起作用(其他锁?)。

请注意,当 EXE 退出时可以使用

DLL_PROCESS_DETACH
运行此例程(有点模拟
atexit
)这似乎可以工作,除了指令指针永远不会返回到 EXE
DLL_PROCESS_DETACH
运行。您也许能够覆盖调用堆栈中存在的一些 DLL(如果您至少替换它的内容)但我没有测试过这个。

最后,请记住,这是非常 hacky 的,如果你有多个线程从你的 EXE 运行代码,跨 Windows 版本等,它绝对不能保证工作。话虽如此,这是一个非常简单的解决方案,每次在我的在 Windows 10 20H2 上测试。

© www.soinside.com 2019 - 2024. All rights reserved.