Windows

       

Внедрение DLL с помощью удаленных потоков


Третий способ внедрения DLL — самый гибкий. В нем используются многие особенности Windows: процессы, потоки, синхронизация потоков, управление виртуальной памятью, поддержка DLL и Unicode. (Если Вы плаваете в каких-то из этих тем, прочтите сначала соответствующие главы книги.) Большинство Windows-функций позволяет процессу управлять лишь самим собой, исключая тем самым риск повреждения одного процесса другим. Однако есть и такие функции, которые дают возможность управлять чужим процессом Изначально многие из них были рассчитзны на применение в отладчиках и других инструментальных средствах. Но ничто не мешает использовать их и в обычном приложении.

Внедрение DLL этим способом предполагает вызов функции LoadLibrary потоком целевого процесса для загрузки нужной DLL. Так как управление потоками чужого процесса сильно затруднено, Вы должны создать в нсм свой поток. К счастью, Windows-функция CreateRemoteThread делает эту задачу несложной:

HANDLE CreateRemoteThread( HANDLE hProcess, PSECURITY_ATTRIBUTES psa, DWORD dwStackSize, PTHREAD_START_ROUTTNE pfnStartAddr, PVOTD pvParam, DWOHD fdwCreate, PDWORD pdwThreadId);

Она идентична CreateThread, но имеетдополнительный параметр hProcess, идентифицирующий процесс, которому будет принадлежать новый поток. Параметр pfnStartAddr определяет адрес функции потока. Этот адрес, разумеется, относится к удаленному процессу — функция потока не может находиться в адресном пространстве Вашего процесса.

NOTE
В Windowb 2000 чаще используемая функция CreateThread, между прочим, реализована через вызов CreateRemoteThread

HANDLE CreateThread(PSECURITY_ATNRlBUTES psa, DWORD dwStackSize, PTHREAD_START_ROUriNE pfnStartAddr, PVOID pvParam, DWORD fdwCreate, PDWORD pdwThrcadID)
{

return (CreateRemoteThread(GetCurrentProcess(), psa, dwStackSize, pfnStartAddr, pvParam, fdwCreate, pdwThreadID));

}

WINDOWS 98
B Windows 98 функция CreateRemoteThread определена, но не реализована и просто возвращает FALSE, последующий вызов GetLastError даеткод ERROR_CALL_NOT_IMPLEMENTED (Но функция CreateThread, которая создает поток в вызывающем процессе, реализована полностью.) Так что описываемый здесь метод внедрения DLL в Windows 98 не работает




О'кэй, теперь Вы знаете, как создать поток в другом процессе. Но как заставить этот поток загрузить пашу DLL? Ответ прост; нужно, чтобы он вызвал функцию LoadLibrary:

HINSTANCE LoadLibrary(PCTSTR pszlibFile);



Заглянув в заголовочный файл WinBase.h, Вы увидите, что для LoadLibrary там есть такие строки:

HINSTANCE WINAPI LoadLibraryA(LPCSTR pszLibFileName);
HINSTANCE WINAPI LuadLibiatyW(LPCWSTR pszLibFileName);

#ifdef UNICODE

#define LoadLibrary LoadLibraryW #else

#define LoadLibrary LoadLibraryA

#endif // !UNICODE

В действительности существует две функции LoadLibrary LoadLibraryA и LoadLibraryW. Они различаются только типом передаваемого параметра. Если имя фаЙла библиотеки хранится как ANSI-строка, вызывайте LoadLibraryA; если же имя файла представлено Unicode-строкой — LoadLibraryW. Самой функции LoadLibrary нет В большинстве программ макрос LoadLibrary раскрывается в loadLibraryA.

К счастью, прототипы LoadLibrary и функции потока идентичны. Вот как выглядит прототип функции потока:

DWORD WINAPI ThreadFunc(PVOID pvParam);

О'кэй, не идентичны, но очень похожи друг на друга Обе функции принимают единственный параметр и возвращают некое значение. Кроме того, обе используют одни и те же правила вызова — WINAPI. Это крайне удачное стечение обстоятельств, потому что нам как раз и нужно создать новый поток, адрес функции которого является адресом LoadLibraryA или LoadLibraryW По сути, требуется выполнить примерно такую строку кода

HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0, LoadlibraryA, "C.\\MyLibdll", 0, NULL);

Или, если Вы предпочитаете Unicode

HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0, LoadLibraryW, L"C \\MyLib.dll" , 0, NULL);

Новый поток в удаленном процессе немедленно вызывает LoadLibraryA (или LoadLibraryW), передавая ей адрес полного имени DLL Все просто. Однако Вас ждут две проблемы.

Первая в том, что нельзя вот так запросто, как я показал выше, передать LoadLibraryA или LoadLibraryW в четвертом параметре функции CreateRemoteThread Причина этого весьма неочевидна. При сборке программы в конечный двоичный файл помещается раздел импорта (описанный в главе 19). Этот раздел состоит из серии шлю-


зов к импортируемым функциям. Так что, когда Ваш код вызывает функцию вроде LoadLibraryA, в разделе импорта модуля генерируется вызов соответствующего шлюза. А уже от шлюза происходит переход к реальной функции.

Следовательно, прямая ссылка на LoadLibraryA в вызове CreateRemoteThread преобразуется в обращение к шлюзу LoadLibraryA в разделе импорта Вашего модуля. Передача адреса шлюза в качестве стартового адреса удаленного потока заставит этот поток выполнить неизвестно что. И скорее всего это окончится нарушением доступа. Чтобы напрямую вызывать LoadLibraryA, минуя шлюз, Вы должны выяснить ее точный адрес в памяти с помощью GetProcAddress.

Вызов CreateRemoteThread предполагает, что Kerne32.dll спроецирована в локальном процессе на ту же область памяти, что и в удаленном. Kernel32.dll используется всеми приложениями, и, как показывает опыт, система проецирует эту DLL в каждом процессе по одному и тому же адресу. Так что CreateRemoteThread надо вызвать так:

// получаем истинный адрес LoadLibraryA в Kernel32 dll PTHREAD_START_ROUTIHE pfnThreadRtn = (PTHREAD_START_ROUTINE)

GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");

HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0, pfnThreadRtn, "C.\\MyLib.dll", 0, NULL);

Или, если Вы предпочитаете Unicode:

// получаем истинный адрес LoadLibraryA в Kernel32.dll PTHRFAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START ROUTINE)

GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");

HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0, pfnThreadRtn, L"C:\\HyLib.dll", 0, NULL);

Отлично, одну проблему мы решили. Но я говорил, что их две. Вторая связана со строкой, в которой содержится полное имя файла DLL. Строка «C.\\MyLib.dll» находится в адресном пространстве вызывающего процесса. Ее адрес передается только что созданному потоку, который в свою очередь передает его в LoadLibraryA. Но, когда LoadLibraryA будет проводить разыменование (dereferencing) этого адреса, она не найдет по нему строку с полным именем файла DLL и скорее всего вызовет нарушение доступа в потоке удаленного процесса; пользователь увидит сообщение о необрабатываемом исключении, и удаленный процесс будет закрыт. Все верно: Вы благополучно угробили чужой процесс, сохранив свой в целости и сохранности



Эта проблема решается размещением строки с полным именем файла DLL в адресном пространстве удаленного процесса. Впоследствии, вызывая CreateRemoteThread, мы передадим ее адрес (в удаленном процессе). На этот случай в Windows предусмотрена функция VirtualAllocEx, которая позволяет процессу выделять память в чужом адресном пространстве:

PVOID VirtualAllocEx( HANDLE hProcess, PVOIO pvAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);

А освободить эту память можно с помощью функции VirtualFreeEx.

BOOL VirtualFreeEx( HANDLE hProcess, PVOID pvAddress, SIZE_T dwSize, DWORD dwFreeType);

Обе функции аналогичны своим версиям без суффикса Ex в конце (о них я рассказывал в главе 15). Единственная разница между ними в том, что эти две функции требуют передачи в первом параметре описателя удаленного процесса.

Выделив память, мы должны каким-то образом скопировать строку из локального адресного пространства в удаленное. Для этого в Windows есть две функции

BOOL ReadProcessMemory( HANDLE hProcess, PVOID pvAddressRemote, PVOID pvBufferLocal, DWORD dwSize, PDWORD pdwNumBytesRead);

BOOL WriteProcessMemory( HANDLE hProcess, PVOID pvAddressRemote, PVOTD pvBufferLocal, DWOHD dwSize, PDWORD pdwNumBytesWritten);

Параметр hProcess идентифицирует удаленный процесс, pvAddressRemote и pvBufferLocal определяют адреса в адресных пространствах удаленного и локального процесса, a dwSize — число передаваемых байтов. По адресу, на который указывает параметр pdwNumBytesRead или pdwNumBytesWritten, возвращается число фактически считанных или записанных байтов

Теперь, когда Вы понимаете, что я пьтаюсь сделать, давайте суммируем все сказанное и запишем это в виде последовательности операций, которые Вам надо будет выполнить

  • Выделите блок памяти в адресном пространстве удаленного процесса через VirtualAllocEx.
  • Вызвав WriteProcessMemory, скопируйте строку с полным именем файла DLL в блок памяти, выделенный в п 1
  • Используя GetProcAddress, получите истинный адрес функции LoadLibraryA или LoadLibraryW внутри Kernel32.dll.
  • Вызвав CreateRemoteThread, создайте поток в удаленном процессе, который вызовет соответствующую функцию LoadLibrary, передав ей адрес блока памяти, выделенного в п. 1.




  • На этом этапе DLL внедрена в удаленный процесс, а ее функция DllMam получила уведомление DLL_PROCESS_ATTACH и может приступить к выполнению нужного кода Когда DllMain вернет управление, удаленный поток выйдет из LoadLibrary и вернется в функцию BaseThreadStart (см. главу 6), которая в свою очередь вызовет ExitThread и завершит этот поток

    Теперь в удаленном процессе имеется блок памяти, выделенный в п. 1, и DLL, все еще «сидящая» в его адресном пространстве. Для очистки после завершения удаленного потока потребуется несколько дополнительных операций.

  • Вызовом VirtualFreeEx освободите блок памяти, выделенный в п. 1.
  • С помощью GetProcAddress определите истинный адрес функции FreeLibrary внутри Kernel32.dll.
  • Используя CreateRemoteThtead, создайте в удаленном процессе поток, который вызовет FreeLibrary с передачей HINSTANCE внедренной DLL.


  • Вот, собственно, и все. Единственный недостаток этого метода внедрения DLL (самого универсального из уже рассмотренных) — многие нужные функции в Windows 98 не поддерживаются. Так что данный метод применим только в Windows 2000.


    Содержание раздела