谨慎使用DLL_THREAD_ATTACH,以及利用DLL_THREAD_ATTACH来阻止远程线程的创建执行

来源:互联网 发布:hammer.js教程 编辑:程序博客网 时间:2024/05/16 01:58

DLL_THREAD_ATTACH的调用时机

当进程创建一个线程的的时候,系统会检查当前映射到该进程的地址空间中的所有DLL文件映像,并用DLL_THREAD_ATTACH来调用每个DLL的DllMain函数,新创建的线程负责执行所有DLL的DllMain函数中的代码。系统不会让进程的主线程调用DLL_THREAD_ATTACH的值来调用DllMain函数。

此时新线程已经被创建但尚未执行,更精确的说已经创建了线程内核对象、线程堆栈等资源,正处于初始化阶段。只有在每个DLL都正常处理DLL_THREAD_ATTACH通知,新线程的线程函数才能开始执行!

DllMain的顺序调用

用《windows核心编程》中的一个例子来说明最好不过了,一个进程有两个线程(线程A和线程B),同时在进程中有一个已加载的SomeDLL.dll,当线程A调用CreateThread来创建线程C时,系统调用带有DLL_THREAD_ATTACH值的SomeDLL.dll的DllMain函数。当线程C执行DllMain函数中的代码时,线程B调用CreateThread函数来创建线程D。这时系统必须再次调用带有DLL_THREAD_ATTACH值的DllMain函数,这次是让线程D 执行代码。但是,系统是顺序调用DllMain函数的,因此系统会暂停线程D的运行,直到线程C完成对DllMain函数中的代码的处理并且返回为止。

当线程C完成DllMain的处理后,它就开始执行它的线程函数。这时系统唤醒线程D,让它处理DllMain中的代码。当它返回时,线程D开始处理它的线程函数。

至于为什么要注意DllMain的顺寻调用这个问题,windows核心编程中作者给出了类似下面的一个典型例子:

BOOLEAN WINAPI DllMain(    HINSTANCE hDllHandle,     DWORD     nReason,        LPVOID    Reserved    ){HANDLE hThread;DWORD dwThreadId;switch(nReason){case DLL_PROCESS_ATTACH://创建一个新线程hThread = CreateThread(NULL, 0, ThreadProc,NULL, 0, &dwThreadId);WaitForSingleObject(hThread,INFINITE);CloseHandle(hThread);break;case DLL_THREAD_ATTACH:break;}return TRUE;}

当DllMain收到DLL_PROCESS_ATTACH通知时, 一个新线程就创建了。系统必须用DLL_THREAD_ATTACH的值再次调用DllMain函数。但是,新线程被暂停运行,因为导致DLL_PROCESS_ATTACH被发送给DllMain函数的线程尚未完成处理操作。问题是调用WaitForSingleObject函数而产生的。这个函数使当前正在运行的线程暂停运行,直到新线程终止运行。但是新线程从未得到机会运行,更不要说终止运行,因为它处于暂停状态,等待当前线程退出DllMain函数。这里我们得到的是个死锁条件。两个线程将永远处于暂停状态。因此,我们应该重新去设计代码,而不应该在任何DLL的DllMain函数中使用WaitForSingleObject。

通过上面的叙述之后,下面我们来看看如何利用DLL_THREAD_ATTACH阻止远程线程的执行。远程线程本质与本地线程一样,区别只是由其他进程创建。我们就是要写个DLL,在收到DLL_THREAD_ATTACH时通知结束线程,那么远程线程就无法执行了。

// 创建线程之前设置nFlag为1,表示需要创建一个合法的线程// 只有合法线程才能正常创建int nFlag = 0;// 设置线程创建标识VOID  SetFlag ( int Flag ){nFlag = Flag;}BOOL APIENTRY DllMain( HMODULE hModule,                       DWORD  ul_reason_for_call,                       LPVOID lpReserved ){switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:break;case DLL_THREAD_ATTACH:// 检测线程创建标识if ( nFlag == 1){// 如果线程创建标识为FALSE,就直接结束线程TerminateThread (GetCurrentThread(),0 ) ;}else{// 每次创建合法线程后,将nFlag标识置为0// 每次设置合法线程标识只能使用一次SetFlag ( 0 ) ;}break;case DLL_THREAD_DETACH:break ;case DLL_PROCESS_DETACH:break;}return TRUE;}

上面的SetFlag函数要export,供外部程序调用,外部程序在调用线程时要先SetFlag(1),再调用CreateThread(),否则,将视为非法创建线程(这里特指远程线程)。像下面的方式调用:

SetFlag(1);CreateThread(NULL,0,NewThread,NULL,0,NULL);