在另一个线程中创建线程

CreateThread inside another thread

本文关键字:线程 创建 另一个      更新时间:2023-10-16

在另一个线程中创建线程时遇到问题。通常我可以这样做,但出现这个问题的原因是我有启动这些线程的DLL的Incremented Reference Count。我需要在这个DLL中启动多个线程。我如何绕过这一点,并能够在项目中需要时发出多个CreateThread(),而不会因为DLL中的Incremented Reference Count而出现问题?

这是我在DLL文件中为增量引用计数写的函数:

BOOL IncrementReference( HMODULE hModule )
{
    if ( hModule == NULL )
        return FALSE;
    TCHAR ModulePath[ MAX_PATH + 1 ];
    if ( GetModuleFileName( hModule , ModulePath , MAX_PATH ) == 0 )
        return FALSE;
    if ( LoadLibrary( ModulePath ) == NULL )
        return FALSE;
    return TRUE;
}

根据要求,这里有一个PoC程序来重现我面临的问题。我真的希望这能帮助你们给我一个解决方案。此外,请注意,由于我所针对的应用程序中的条件(该应用程序中已经设置了钩子),DLL正在卸载,因此首先需要增加引用计数才能运行我的线程。

此外,我不能在主线程中运行多个操作,因为它有自己的功能需要处理,并且需要另一个线程来处理其他事情。它们也必须同时运行,因此我需要解决在增量DLL中生成多个线程的问题。

// dllmain.cpp : Defines the entry point for the DLL application.
#pragma comment( linker , "/Entry:DllMain" )
#include <Windows.h>
#include <process.h>
UINT CALLBACK SecondThread( PVOID pParam )
{
    MessageBox( NULL , __FUNCTION__ , "Which Thread?" , 0 );
    return 0;
}
UINT CALLBACK FirstThread( PVOID pParam )
{
  MessageBox( NULL , __FUNCTION__ , "Which Thread?" , 0 );
  _beginthreadex(0, 0, &SecondThread, 0, 0, 0);
  return 0;
}
BOOL IncrementReference( HMODULE hModule )
{
    if ( hModule == NULL )
        return FALSE;
    TCHAR ModulePath[ MAX_PATH + 1 ];
    if ( GetModuleFileName( hModule , ModulePath , MAX_PATH ) == 0 )
        return FALSE;
    if ( LoadLibrary( ModulePath ) == NULL )
        return FALSE;
    return TRUE;
}
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
        {
            if (IncrementReference(0))    
                _beginthreadex(0, 0, &FirstThread, 0, 0, 0);
        }
        break;
    }
    return TRUE;
}

正如您所看到的,该代码从不执行SecondThread函数。问题是,为什么?能做些什么来修复它?

   #pragma comment( linker , "/Entry:DllMain" )

这是一个非常糟糕的想法,DLL的正确入口点是而不是,事实上是DllMain()。您必须记住,WinMain和DllMain只是占位符名称。Microsoft记录可执行文件入口点相关性的一种方法。按照惯例,你在程序中使用相同的名称,每个人都会理解他们的工作

但是在C或C++程序中还有一个非常重要的附加细节,CRT(C运行库)需要首先初始化之前,您可以运行任何可能进行CRT函数调用的代码。像_beginthreadex()

换句话说,默认的/ENTRY链接器选项是而不是DllMain()。DLL的真正入口点是_DllMainCRTStartup()。CRT内部的一个函数,负责所需的初始化,然后调用DllMain()。如果你在程序中写了一个,那么它就是运行的程序。如果你不这样做,那么CRT中的一个伪链接就会被链接起来。

当你调用CRT函数并且CRT没有初始化时,所有的赌注都会被取消。您必须删除#pragma,以便链接器使用正确的入口点。

根据MSDN,您既不能在DllMain中调用LoadLibrary,也不能在其中调用CreateThread——您的代码可以同时调用这两个!

发布的MCVE有三个问题:

  • 第一个是一个简单的错误,您调用的是IncrementReference(0)而不是IncrementReference(hModule)

  • 二是rundll32没有可供使用的入口点;入口点参数是强制性的,否则rundll32将不起作用(我认为它甚至不会加载DLL)。

  • 三是汉斯提出的CCD_ 11。

修复IncrementReference()调用后,删除#pragma并添加一个入口点:

extern "C" __declspec(dllexport) void __stdcall EntryPoint(HWND, HINSTANCE, LPSTR, INT)
{
      MessageBoxA( NULL , __FUNCTION__ , "Which Thread?" , 0 );
}

然后你可以像这样运行DLL:

rundll32 testdll.dll,_EntryPoint@16

这在我的机器上工作;EntryPoint、FirstThread和SecondThread都会生成消息框。请确保您不会过早地从EntryPoint中取消消息框,因为这将导致应用程序退出,并带走其他线程。

对LoadLibrary的调用仍然不正确,但在这种情况下似乎没有任何副作用(可能是因为有问题的库保证已经加载)。


(上一页)答案:

MCVE可以通过简单地将对IncrementReference的调用从DllMain移动到FirstThread来修复。这是解决问题的唯一安全和正确的方法。

附录:正如Hans所指出的,您还需要删除/Entry杂注。


(多余?)评论:

如果加载DLL的应用程序行为不端,以至于在FirstThread运行之前卸载了DLL,并且为了论证起见,假设您无法修复它,那么唯一现实的选择就是解决这个问题——例如,DllMain可以挂起进程中的所有其他线程,这样它们就无法卸载DLL,并在调用IncrementReference之后从FirstThread恢复它们。

或者,你可以尝试挂接FreeLibrary,或者对加载程序进行反向工程,直接干扰引用计数,或者删除应用程序放置的挂接,或者在DllMain中手动加载DLL的单独副本(使用你自己的DLL加载程序,而不是Windows提供的加载程序),或者启动一个单独的进程,从那里开始工作,或者,哦,毫无疑问,还有很多其他的可能性,但在这一点上,我担心这个问题对Stack Overflow来说真的太宽泛了,特别是因为你无法向我们提供应用程序正在做什么的真正细节。