Внедрение 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 не работает
При сборке программы в конечный двоичный файл помещается раздел импорта (описанный в главе 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, возвращается число фактически считанных или записанных байтов
Теперь, когда Вы понимаете, что я пьтаюсь сделать, давайте суммируем все сказанное и запишем это в виде последовательности операций, которые Вам надо будет выполнить
На этом этапе DLL внедрена в удаленный процесс, а ее функция DllMam получила уведомление DLL_PROCESS_ATTACH и может приступить к выполнению нужного кода Когда DllMain вернет управление, удаленный поток выйдет из LoadLibrary и вернется в функцию BaseThreadStart (см. главу 6), которая в свою очередь вызовет ExitThread и завершит этот поток
Теперь в удаленном процессе имеется блок памяти, выделенный в п. 1, и DLL, все еще "сидящая" в его адресном пространстве. Для очистки после завершения удаленного потока потребуется несколько дополнительных операций.
Вот, собственно, и все. Единственный недостаток этого метода внедрения DLL (самого универсального из уже рассмотренных) — многие нужные функции в Windows 98 не поддерживаются. Так что данный метод применим только в Windows 2000.