2014-08-29 10 views
4

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

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

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

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

+0

Вместо того, чтобы вызывать 'NtQueryInformationProcess' самостоятельно, вы должны получить эту информацию от windbg, по крайней мере, на этих этапах устранения неполадок. –

+1

Hook DLL вводятся и удаляются, когда целевой процесс передает сообщения. (Так как это единственный раз, когда безопасно вводить код в поток.) ​​Если в процессе нет пользовательского интерфейса, он может прекратить перекачку сообщений, после чего у диспетчера окон нет возможности получить контроль. Включение вызова «Разгрузка» просто создает новую проблему: когда целевой процесс окончательно решает перекачать сообщения снова, у вас будет ошибка с двойным доступом. –

+0

Я подтвердил, что приложение не получает сообщение, которое вызывает декремент счетчика ссылок. Я добавил код, который узнал счетчик, и уменьшил число, и библиотеки DLL сразу разгружаются, но, как вы подозревали, я считаю, что вижу проблему с двойным свободным сообщением, когда сообщение наконец приходит. Я думал, что если проблема связана с отсутствием работающего насоса сообщений, возможно, все, что мне нужно сделать, это создать его? Я пробовал это, однако, это не называется. Любые мысли по этому поводу? – tdemay

ответ

2

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

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

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

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

+0

Другими словами ... это решение: http://stackoverflow.com/a/4076495/785343 – BitsEvolved

6

ОК .. здесь идет .. Существует множество способов получить информацию о модуле из процесса. Недокументированный способ и «документированный» способ.

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

enter image description here

Здесь вы "документально" способ ..

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