Привет. Задача Внедрить собственную DLL во все процессы, в которых есть хотя бы kernel32.dll. Т.е. native-процессы типа менеджера сеансов (smss.exe) не интересуют и даже противопоказаны. ОС: Windows XP SP0 и выше (x86). Текущее решение Архитектурно, текущее решение выглядит следующим образом (несущественные мелочи опущены): 1.1. Стартуем службу вместе с системой. Служба при старте перечисляет запущенные на данный момент процессы и в каждый принудительно внедряет DLL. Способ внедрения DLL описан ниже. После этой процедуры служба за процессами больше не следит, т.е. внедрение из службы происходит только один раз. 1.2. Сразу после загрузки в процесс, DLL перехватывает сплайсингом функцию CreateProcessW(). Сам перехват работает, тут проблем нет. В обработчике NewCreateProcessW() в случае успешного создания процесса, сразу же после вызова оригинального кода, имея на руках хендл нового процесса, DLL внедряет себя в него таким же образом, как это делала служба. Способ внедрения сейчас выглядит так: 2.1. Открываем процесс по его ID'у. Если хендл уже есть, переходим сразу к п.2. 2.2. Выделяем память в целевом процессе и записываем туда путь к нашей DLL. 2.3. Получаем адрес функции LoadLibraryW() в целевом процессе. Для этого делаем следующее: 2.3.1. Перечисляем все модули целевого процесса с помощью EnumProcessModules(). 2.3.2. Для каждого модуля получаем его путь с помощью GetModuleFileNameEx(). 2.3.3. Сравниваем полученный путь с путём к kernel32.dll. Если равны, значит мы нашли нужный нам HMODULE. 2.3.4. Имея хендл модуля, получаем адрес функции LoadLibraryW() посредством вызова GetProcAddress(). 2.4. Создаём удалённый поток в целевом процессе. В качестве адреса функции потока указываем адрес LoadLibraryW() в целевом процессе, который мы нашли на шаге 2.3. Некоторые моменты я пропустил, например, включение привилегий отладки и подмену токена текущего потока. Сейчас это несущественно. Проблема реализации текущего решения Проблема здесь только одна и проявляется она только в случае 1.2 на шаге 2.3.1 алгоритма внедрения. EnumProcessModules() возвращает FALSE и устанавливает код ошибки ERROR_PARTIAL_COPY. Решение проблемы Почитал в сети, выяснилось, что данная ошибка возвращается, в основном, на x64-платформах. Там вопрос решается выставлением правильных флагов. Но, к сожалению, это не мой случай. Платформа у меня всегда x86, система Windows XP Pro SP3. Получение списка модулей процесса другими средствами не помогает, ибо кроме Process Helper'а остаётся только 2 варианта: 1. Tool Help API. Не помогает, т.к. первый же вызов Module32First() возвращает FALSE, а код ошибки устанавливается в ERROR_NO_MORE_FILES. 2. Native API. Также не помогает, т.к. функция LdrQueryProcessModuleInformation() не возвращает HMODULE, вместо этого она возвращает базовый адрес каждого модуля. К сожалению, базовый адрес не катит ни в функции GetModuleFileNameEx() ни в функции GetProcAddress(). Но решение я таки нашёл опытным путём, хотя, к сожалению, оно не лишено недостатков. Решение проблемы заключается в установке задержки сразу после вызова оригинальной функции (см. случай 1.2), но до попытки внедрения. Я ставил задержку в 5 секунд, после неё шаг 2.3.1 алгоритма внедрения отрабатывал нормально, как и должен. Если задержку убрать, будет как раз ошибка (см. выше). Недостаток очевиден - пока мы спим эти 5 секунд, запускаемый процесс успеет совершить какие-то действия, которые мы вообще-то должны перехватить. Так что решение хреновое. Вопросы Что делать и кто виноват?
Кстати, только что нашёл официальное подтверждение своей проблеме в описании функции GetModuleFileNameEx(): В таком случае, объясните мне дураку, как же это так: процесс начал исполнятся, а список модулей для него ещё не сформирован. Это просто не логично.
> При создании процесса в него проецируется только ntdll. Загрузчик не выполнил инициализацию, вчастности не выделена память под его списки(PEB.Ldr). > Гарантировано, что адрес загрузки ntdll фиксирован в системе, так как ядро получает некоторые указатели в нём. Так как при инициализации ntdll модуль kernel32 загруждается первым, то и его быза фиксирована в системе, ненужно находить её в чужом процессе. > 'Хэндл модуля' - HMODULE как у тебя это и есть его адрес загрузки в текущем процессе. > LdrQueryProcessModuleInformation() получает список модулей из Ldr в текущем процессе. > Не создавать удалённые потоки, это расценивается как акт вторжения в АП, конечно не везде. Не использовать RtlQueryProcessDebugInformation(), слепки и томуподобное для получения списка модулей, ибо они создают удалённые потоки. > ERROR_PARTIAL_COPY - этот код ошибки возвращают сервисы чтения/записи памяти для некоторых областей системных процессов, в частности для csrss. > Не перехватывать по возможности WinApi, перехватывать системный шлюз, либо заглушки. > Не загружать модуль посредством WinApi, использовать однократно-созданные именованные секции, либо разделяемую память.
Спасибо за информацию! Как же так?! А если программе прямо в WinMain сразу же после старта нужна информация о загруженных модулях, она её не получит?! И второе: как в этом случае гарантированно дождаться загрузки всех модулей, которые были прилинкованы статически? Вариант с циклом и задержками что-то совсем не радует (( Как это выглядит? Достаточно ли написать такой код, чтобы получить адрес LoadLibraryW() в контексте любого процесса (в т.ч. и целевого) Код (Text): FARPROC pfnLoadLibrary = (FARPROC) LoadLibraryW; ? Да, так и есть. Да, действительно, ошибся. В моём случае это не существенно. А можно чуть более подробно? В каких именно случаях может быть возвращена такая ошибка? К сожалению, в моём случае перехватывать нужно именно высокоуровневые Win32 API. Это как?
Если ты имеешь ввиду ModuleEntryPoint то Получит, поток переходит на на эту точку после подгрузки и инициализации всех модулей. Если ты имеешь ввиду DllEntryPoint, то гарантированно что она выполняется при загрузке и инициализации всех модулей в статическом импорте. RVA LoadLibrary как и остальных функций в экспорте фиксирован, адрес загрузки модулей может различаться в разных процессах. Для функций из ntdll и kernel32 достаточно определить адрес загрузки их в текущем процессе и RVA функций в них, тоесть адрес LoadLibrary одинаков во всех процессах. Это довольно редкий случай, у тебя вероятно неверно указан размер читаемого блока памяти, либо они пересекаются, имея различные атрибуты. Какие ? Читай про обьект 'Секция'
Спасибо! Думаю, это не важно. Тем более, что ничего более низкоуровневого в моём случае просто нет. Если не считать конечно фильтрацию девайсов в режиме ядра, но это уже будет как "из пушки по воробьям". Да нет, что такое секции и т.п. я в курсе. Но ты же говоришь, что через секции как-то можно модули загружать минуя системные API. Вот мне и интересно, как это? Или ты имеешь в виду самому выполнить работу загрузчика (замапить образ, настроить IAT/EAT, ...) ?
Можно и вручную замапить, вобщем тут это точно ненужно, наверно будет достаточно просто LoadLibrary().