У меня есть DLL, которую я внедряю в другие процессы, используя SetWindowsHookEx
, Внутри DLL я увеличиваю счетчик ссылок модуля, вызывая GetModuleHandleEx
так что я могу контролировать, когда модуль выгружен.
На этом этапе счетчик ссылок на модули «должен быть» 2 от обоих этих вызовов API. Когда вызывающий процесс завершает работу, он вызывает UnhookWindowsHookEx
, уменьшая количество ссылок до 1. В DLL есть поток, который ожидает несколько вещей, одна из которых является дескриптором процесса, который вызвал SetWindowsHookEx
, Когда процесс завершается, DLL выполняет некоторую очистку, завершает все потоки, очищает память и обрабатывает, а затем вызывает FreeLibraryAndExitThread
, Это уменьшает счетчик и DLL выгружается.
Вот моя проблема. Есть несколько процессов, особенно без UI, где DLL никогда не выгружается. Я уверен, что я все очистил. И я точно знаю, что ни одна из моих тем не запущена.
Прежде всего, если у вас есть какие-либо советы по устранению неполадок, которые помогут выявить причину, это будет полезно. В противном случае, я думал об использовании некоторого API, как NtQueryInformationProcess
чтобы получить адрес модуля и подтвердить, что дескриптор модуля фактически равен нулю, затем вызовите CreateRemoteThread
сделать звонок LdrUnloadDll
выгрузить адрес модуля изнутри процесса. Что вы думаете об этом подходе? У кого-нибудь есть пример кода? У меня возникли трудности с поиском, как узнать количество дескрипторов модуля.
Хорошо … здесь идет .. Есть много способов получить информацию о модуле из процесса. Недокументированный способ и «документированный» способ.
Результаты (задокументировано):
Вот «документированный» способ ..
#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 состоит в том, что я никогда не выяснял, почему подсчет нагрузки всегда равен «6» для динамических модулей и «-1» для статических модулей. По этой причине я не буду публиковать его.
Лучше НЕ использовать недокументированный API, если вы хотите просто подсчитать нагрузку. Единственное преимущество недокументированного API состоит в том, что вы можете использовать его для «снятия связи / скрытия» модуля внутри процесса (как это делают вирусы). Он будет «отсоединять / скрывать» его. НЕ «выгружать» его. Это означает, что в любой момент вы можете «перекомпоновать» его обратно в список модулей процесса.
Поскольку вам нужен только подсчет ссылок на модули, я включил только «документированный» API, который делает именно это.
Я нашел причину проблемы и решение. Честно говоря, я чувствую себя довольно глупо из-за того, что упустил это и боролся с этим так долго.
Как я упоминал в исходной проблеме, процессы, которые являются проблемными, не имеют пользовательского интерфейса. Оказывается, у них работает насос сообщений. Проблема в том, что отправка сообщений этим процессам без пользовательского интерфейса после вызова UnhookWindowsHookEx
это приведет к разгрузке. (На самом деле, я считаю, что MSDN утверждает, что оконные сообщения не отправляются процессам при вызове UnhookWindowsHookEx
.)
Передавая WM_NULL всем процессам после вызовов процесса внедрения UnhookWindowsHookEx
насос сообщений просыпается во внедренных процессах, и счетчик ссылок на DLL уменьшается. DLL выгружается немедленно, когда введенная DLL наконец вызывает FreeLibraryAndExitThread
,
Это только часть решения. Если процесс впрыскивания уничтожен или аварийно завершает работу, сообщение не передается, поэтому библиотека DLL не выгружается из процессов, не имеющих пользовательского интерфейса. Как я уже упоминал ранее, в DLL работает поток, ожидающий дескриптора процесса внедрения. Когда процесс инъекции заканчивается, DLL сигнализируется и затем вызывает PostThreadMessage
отправить WM_NULL каждому потоку в процессе. Затем он ждет, пока счетчик ссылок DLL будет уменьшен, прежде чем продолжить и очистить перед вызовом. FreeLibraryAndExitThread
, В результате библиотека DLL выгружается практически сразу из всех процессов, из пользовательского интерфейса или без него.