Выгрузка вставленной DLL

У меня есть DLL, которую я внедряю в другие процессы, используя SetWindowsHookEx, Внутри DLL я увеличиваю счетчик ссылок модуля, вызывая GetModuleHandleEx так что я могу контролировать, когда модуль выгружен.

На этом этапе счетчик ссылок на модули «должен быть» 2 от обоих этих вызовов API. Когда вызывающий процесс завершает работу, он вызывает UnhookWindowsHookEx, уменьшая количество ссылок до 1. В DLL есть поток, который ожидает несколько вещей, одна из которых является дескриптором процесса, который вызвал SetWindowsHookEx, Когда процесс завершается, DLL выполняет некоторую очистку, завершает все потоки, очищает память и обрабатывает, а затем вызывает FreeLibraryAndExitThread, Это уменьшает счетчик и DLL выгружается.

Вот моя проблема. Есть несколько процессов, особенно без UI, где DLL никогда не выгружается. Я уверен, что я все очистил. И я точно знаю, что ни одна из моих тем не запущена.

Прежде всего, если у вас есть какие-либо советы по устранению неполадок, которые помогут выявить причину, это будет полезно. В противном случае, я думал об использовании некоторого API, как NtQueryInformationProcess чтобы получить адрес модуля и подтвердить, что дескриптор модуля фактически равен нулю, затем вызовите CreateRemoteThread сделать звонок LdrUnloadDll выгрузить адрес модуля изнутри процесса. Что вы думаете об этом подходе? У кого-нибудь есть пример кода? У меня возникли трудности с поиском, как узнать количество дескрипторов модуля.

4

Решение

Хорошо … здесь идет .. Есть много способов получить информацию о модуле из процесса. Недокументированный способ и «документированный» способ.

Результаты (задокументировано):

введите описание изображения здесь

Вот «документированный» способ ..

#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, который делает именно это.

6

Другие решения

Я нашел причину проблемы и решение. Честно говоря, я чувствую себя довольно глупо из-за того, что упустил это и боролся с этим так долго.

Как я упоминал в исходной проблеме, процессы, которые являются проблемными, не имеют пользовательского интерфейса. Оказывается, у них работает насос сообщений. Проблема в том, что отправка сообщений этим процессам без пользовательского интерфейса после вызова UnhookWindowsHookEx это приведет к разгрузке. (На самом деле, я считаю, что MSDN утверждает, что оконные сообщения не отправляются процессам при вызове UnhookWindowsHookEx.)

Передавая WM_NULL всем процессам после вызовов процесса внедрения UnhookWindowsHookEx насос сообщений просыпается во внедренных процессах, и счетчик ссылок на DLL уменьшается. DLL выгружается немедленно, когда введенная DLL наконец вызывает FreeLibraryAndExitThread,

Это только часть решения. Если процесс впрыскивания уничтожен или аварийно завершает работу, сообщение не передается, поэтому библиотека DLL не выгружается из процессов, не имеющих пользовательского интерфейса. Как я уже упоминал ранее, в DLL работает поток, ожидающий дескриптора процесса внедрения. Когда процесс инъекции заканчивается, DLL сигнализируется и затем вызывает PostThreadMessage отправить WM_NULL каждому потоку в процессе. Затем он ждет, пока счетчик ссылок DLL будет уменьшен, прежде чем продолжить и очистить перед вызовом. FreeLibraryAndExitThread, В результате библиотека DLL выгружается практически сразу из всех процессов, из пользовательского интерфейса или без него.

2

По вопросам рекламы [email protected]