我有一个DLL,我使用SetWindowsHookEx
注入其他进程。在DLL内部,我通过调用GetModuleHandleEx
来增加模块的引用计数器,这样我就可以控制何时卸载模块。
此时,这两个API调用的模块引用计数“应为”2。当调用进程关闭时,它调用UnhookWindowsHookEx
,将引用计数减少到1. DLL有一个等待一些东西的线程,其中一个是调用SetWindowsHookEx
的进程的句柄。 。当进程消失时,DLL会进行一些清理,终止所有线程,清理内存和句柄,然后调用FreeLibraryAndExitThread
。这会减少计数器并卸载DLL。
这是我的问题。有一些进程,特别是没有UI的进程,DLL永远不会被卸载。我很自信我清理了一切。而且我知道我的线程都没有运行。
首先,如果您有任何疑难解答提示以帮助发现原因,那将会有所帮助。否则,我正在考虑使用像NtQueryInformationProcess
这样的API来获取模块地址并确认模块句柄计数实际上为零,然后调用CreateRemoteThread
来调用LdrUnloadDll
来卸载进程内的模块地址。你对这种方法有什么看法?有没有人有任何示例代码?我很难找到如何获得模块句柄计数。
答案 0 :(得分:6)
好的..这里有..有很多方法可以从流程中获取模块信息。无证的方式和"记录"方式。
结果(记录):
这是"记录的"方式..
#include <windows.h>
#include <TlHelp32.h>
#include <iostream>
#include <sstream>
int strcompare(const char* One, const char* Two, bool CaseSensitive)
{
#if defined _WIN32 || defined _WIN64
return CaseSensitive ? strcmp(One, Two) : _stricmp(One, Two);
#else
return CaseSensitive ? strcmp(One, Two) : strcasecmp(One, Two);
#endif
}
PROCESSENTRY32 GetProcessInfo(const char* ProcessName)
{
void* hSnap = nullptr;
PROCESSENTRY32 Proc32 = {0};
if ((hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)) == INVALID_HANDLE_VALUE)
return Proc32;
Proc32.dwSize = sizeof(PROCESSENTRY32);
while (Process32Next(hSnap, &Proc32))
{
if (!strcompare(ProcessName, Proc32.szExeFile, false))
{
CloseHandle(hSnap);
return Proc32;
}
}
CloseHandle(hSnap);
Proc32 = { 0 };
return Proc32;
}
MODULEENTRY32 GetModuleInfo(std::uint32_t ProcessID, const char* ModuleName)
{
void* hSnap = nullptr;
MODULEENTRY32 Mod32 = {0};
if ((hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, ProcessID)) == INVALID_HANDLE_VALUE)
return Mod32;
Mod32.dwSize = sizeof(MODULEENTRY32);
while (Module32Next(hSnap, &Mod32))
{
if (!strcompare(ModuleName, Mod32.szModule, false))
{
CloseHandle(hSnap);
return Mod32;
}
}
CloseHandle(hSnap);
Mod32 = {0};
return Mod32;
}
std::string ModuleInfoToString(MODULEENTRY32 Mod32)
{
auto to_hex_string = [](std::size_t val, std::ios_base &(*f)(std::ios_base&)) -> std::string
{
std::stringstream oss;
oss << std::hex << std::uppercase << val;
return oss.str();
};
std::string str;
str.append(" =======================================================\r\n");
str.append(" Module Name: ").append(Mod32.szModule).append("\r\n");
str.append(" =======================================================\r\n\r\n");
str.append(" Module Path: ").append(Mod32.szExePath).append("\r\n");
str.append(" Process ID: ").append(std::to_string(Mod32.th32ProcessID).c_str()).append("\r\n");
str.append(" Load Count (Global): ").append(std::to_string(static_cast<int>(Mod32.GlblcntUsage != 0xFFFF ? Mod32.GlblcntUsage : -1)).c_str()).append("\r\n");
str.append(" Load Count (Process): ").append(std::to_string(static_cast<int>(Mod32.ProccntUsage != 0xFFFF ? Mod32.ProccntUsage : -1)).c_str()).append("\r\n");
str.append(" Base Address: 0x").append(to_hex_string(reinterpret_cast<std::size_t>(Mod32.modBaseAddr), std::hex).c_str()).append("\r\n");
str.append(" Base Size: 0x").append(to_hex_string(Mod32.modBaseSize, std::hex).c_str()).append("\r\n\r\n");
str.append(" =======================================================\r\n");
return str;
}
int main()
{
PROCESSENTRY32 ProcessInfo = GetProcessInfo("notepad.exe");
MODULEENTRY32 ME = GetModuleInfo(ProcessInfo.th32ProcessID, "uxtheme.dll");
std::cout<<ModuleInfoToString(ME);
}
未记录的API的问题在于,我从来没有弄清楚为什么负载计数始终是&#34; 6&#34;对于动态模块和&#34; -1&#34;对于静态模块..出于这个原因,我不会发布它..
如果您只想加载计数,最好不要使用未记录的API。未记录的API的唯一优势是您可以使用它来取消链接/隐藏&#34;一个过程中的一个模块(比如病毒一样)。它将&#34;取消链接/隐藏&#34;它..不是&#34;卸载&#34;它。这意味着您可以随时重新链接&#34;它回到流程的模块列表中。
由于您只需要模块引用计数,因此我只包括&#34;记录的&#34; API就是这样做的。
答案 1 :(得分:3)
我找到了问题的原因和解决方案。老实说,我错过了它并且长期挣扎,我觉得很愚蠢。
正如我在原始问题中提到的那样,有问题的流程没有UI。事实证明他们确实有一个消息泵运行。在我们调用UnhookWindowsHookEx
会触发卸载之后,问题就是在没有UI的情况下向这些进程发送消息。 (事实上,我相信MSDN确实声明在调用UnhookWindowsHookEx
时窗口消息不会发送到进程。)
在注入过程调用UnhookWindowsHookEx
之后,通过向所有进程广播WM_NULL,消息泵在注入的进程中唤醒,并且DLL引用计数递减。当注入的DLL最终调用FreeLibraryAndExitThread
时,会立即卸载DLL。
这只是解决方案的一部分。如果注入进程被终止或崩溃,则不会广播任何消息,因此DLL不会从没有UI的进程中卸载。正如我之前提到的,我在DLL中运行了一个等待注入进程句柄的线程。当注入过程结束时,DLL发出信号,然后调用PostThreadMessage
将WM_NULL发送到进程中的每个线程。然后它等待,直到DLL引用计数递减,然后继续并清理,然后再调用FreeLibraryAndExitThread
。因此,几乎可以立即从所有进程,UI或无UI卸载DLL。